Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add InternalDangerousSettings.forceSignatureFailures #2635

Merged
merged 1 commit into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions RevenueCat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@
4F6BEE3B2A27B45300CD9322 /* StoreKitTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 571E7AD3279F2D0C003B3606 /* StoreKitTestHelpers.swift */; };
4F6BEE3C2A27B45900CD9322 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DE61A83264190830021CEA0 /* Constants.swift */; };
4F6BEE882A27E16B00CD9322 /* TestLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57057FF728B0048900995F21 /* TestLogHandler.swift */; };
4F6EEBD92A38ED76007FD783 /* FakeSigning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6EEBD82A38ED76007FD783 /* FakeSigning.swift */; };
4F7C37B22A27E2E8001E17D3 /* AsyncTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575A8EE02922C56300936709 /* AsyncTestHelpers.swift */; };
4F7C37E42A27EFE1001E17D3 /* BaseBackendIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 579234E127F777EE00B39C68 /* BaseBackendIntegrationTests.swift */; };
4F7C37E52A27EFF7001E17D3 /* BaseStoreKitIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5753EE0F294B93CC00CBAB54 /* BaseStoreKitIntegrationTests.swift */; };
Expand Down Expand Up @@ -927,6 +928,7 @@
4F6BEDE12A26B69500CD9322 /* DebugContentViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugContentViews.swift; sourceTree = "<group>"; };
4F6BEE022A27ADF900CD9322 /* CustomEntitlementsComputationIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEntitlementsComputationIntegrationTests.swift; sourceTree = "<group>"; };
4F6BEE312A27B02400CD9322 /* BackendCustomEntitlementsIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BackendCustomEntitlementsIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4F6EEBD82A38ED76007FD783 /* FakeSigning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeSigning.swift; sourceTree = "<group>"; };
4F7DBFBC2A1E986C00A2F511 /* StoreKit2TransactionFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKit2TransactionFetcher.swift; sourceTree = "<group>"; };
4F8038322A1EA7C300D21039 /* TransactionPoster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionPoster.swift; sourceTree = "<group>"; };
4F8A58162A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockOfflineCustomerInfoCreator.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2343,6 +2345,7 @@
57E6C27B29723A94001AFE98 /* Signing.swift */,
5791FE492994453500F1FEDA /* Signing+ResponseVerification.swift */,
5740FCD22996CE5E00E049F9 /* VerificationResult.swift */,
4F6EEBD82A38ED76007FD783 /* FakeSigning.swift */,
);
path = Security;
sourceTree = "<group>";
Expand Down Expand Up @@ -3172,6 +3175,7 @@
35D832F4262E606500E60AC5 /* HTTPResponse.swift in Sources */,
352B7D7927BD919B002A47DD /* DangerousSettings.swift in Sources */,
A56F9AB126990E9200AFC48F /* CustomerInfo.swift in Sources */,
4F6EEBD92A38ED76007FD783 /* FakeSigning.swift in Sources */,
2DDF41AE24F6F37C005BC22D /* InAppPurchase.swift in Sources */,
B32B750126868C1D005647BF /* EntitlementInfo.swift in Sources */,
57ABA76D28F08DDA003D9181 /* Either.swift in Sources */,
Expand Down
4 changes: 4 additions & 0 deletions Sources/Logging/Strings/NetworkStrings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ enum NetworkStrings {

#if DEBUG
case api_request_forcing_server_error(HTTPRequest)
case api_request_forcing_signature_failure(HTTPRequest)
#endif

}
Expand Down Expand Up @@ -110,6 +111,9 @@ extension NetworkStrings: CustomStringConvertible {
#if DEBUG
case let .api_request_forcing_server_error(request):
return "Returning fake HTTP 500 error for '\(request.description)'"

case let .api_request_forcing_signature_failure(request):
return "Returning fake signature verification failure for '\(request.description)'"
#endif
}
}
Expand Down
11 changes: 10 additions & 1 deletion Sources/Misc/DangerousSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@ import Foundation

#if DEBUG
let forceServerErrors: Bool
let forceSignatureFailures: Bool

init(enableReceiptFetchRetry: Bool = false, forceServerErrors: Bool = false) {
init(
enableReceiptFetchRetry: Bool = false,
forceServerErrors: Bool = false,
forceSignatureFailures: Bool = false
) {
self.enableReceiptFetchRetry = enableReceiptFetchRetry
self.forceServerErrors = forceServerErrors
self.forceSignatureFailures = forceSignatureFailures
}
#else
init(enableReceiptFetchRetry: Bool = false) {
Expand Down Expand Up @@ -107,6 +113,9 @@ internal protocol InternalDangerousSettingsType: Sendable {
#if DEBUG
/// Whether `HTTPClient` will fake server errors
var forceServerErrors: Bool { get }

/// Whether `HTTPClient` will fake invalid signatures.
var forceSignatureFailures: Bool { get }
#endif

}
13 changes: 12 additions & 1 deletion Sources/Networking/HTTPClient/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ private extension HTTPClient {
.success(dataIfAvailable(statusCode))
.mapToResponse(response: httpURLResponse,
request: request.httpRequest,
signing: self.signing,
signing: self.signing(for: request.httpRequest),
verificationMode: request.verificationMode)
.map { (response) -> HTTPResponse<Data>? in
guard let cachedResponse = self.eTagManager.httpResultFromCacheOrBackend(
Expand Down Expand Up @@ -408,6 +408,17 @@ private extension HTTPClient {
}
}

private func signing(for request: HTTPRequest) -> SigningType.Type {
#if DEBUG
if self.systemInfo.dangerousSettings.internalSettings.forceSignatureFailures {
Logger.warn(Strings.network.api_request_forcing_signature_failure(request))
return FakeSigning.self
}
#endif

return self.signing
}

}

// MARK: - Extensions
Expand Down
32 changes: 32 additions & 0 deletions Sources/Security/FakeSigning.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// Copyright RevenueCat Inc. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// FakeSigning.swift
//
// Created by Nacho Soto on 6/13/23.

import Foundation

#if DEBUG

/// A `SigningType` implementation that always fails, used for testing.
/// - Seealso: `InternalDangerousSettingsType.forceSignatureFailures`
final class FakeSigning: SigningType {

static func verify(
signature: String,
with parameters: Signing.SignatureParameters,
publicKey: Signing.PublicKey
) -> Bool {
return false
}

}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -177,5 +177,6 @@ extension BaseBackendIntegrationTests: InternalDangerousSettingsType {

var enableReceiptFetchRetry: Bool { return true }
var forceServerErrors: Bool { return false }
var forceSignatureFailures: Bool { return false }

}
Original file line number Diff line number Diff line change
Expand Up @@ -565,25 +565,81 @@ final class EnforcedSignatureVerificationHTTPClientTests: BaseSignatureVerificat
expect(response).to(beSuccess())
}

}
func testFakeSignatureFailuresInEnforcedMode() throws {
self.mockResponse(signature: Self.sampleSignature, requestDate: Self.date1)
MockSigning.stubbedVerificationResult = true

// MARK: - Private
try self.changeClientToEnforced(forceSignatureFailures: true)

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *)
private extension BaseSignatureVerificationHTTPClientTests {
let response: HTTPResponse<HTTPEmptyResponseBody>.Result? = waitUntilValue { completion in
self.client.perform(.init(method: .get, path: Self.path), completionHandler: completion)
}

final func changeClient(_ verificationMode: Configuration.EntitlementVerificationMode) throws {
try self.createClient(Signing.verificationMode(with: verificationMode))
expect(response).to(beFailure())
expect(response?.error) == NetworkError.signatureVerificationFailed(path: Self.path)
}

final func changeClientToEnforced() throws {
try self.createClient(Signing.enforcedVerificationMode())
func testFakeSignatureFailuresInInformationalMode() throws {
self.mockResponse(signature: Self.sampleSignature, requestDate: Self.date1)
MockSigning.stubbedVerificationResult = true

try self.changeClient(.informational, forceSignatureFailures: true)

let response: HTTPResponse<HTTPEmptyResponseBody>.Result? = waitUntilValue { completion in
self.client.perform(.init(method: .get, path: Self.path), completionHandler: completion)
}

expect(response).to(beSuccess())
expect(response?.value?.verificationResult) == .failed
}

private final func createClient(_ mode: Signing.ResponseVerificationMode) throws {
self.systemInfo = try MockSystemInfo(platformInfo: nil,
finishTransactions: false,
responseVerificationMode: mode)
func testFakeSignatureFailuresWithDisabledVerification() throws {
self.mockResponse(signature: Self.sampleSignature, requestDate: Self.date1)
MockSigning.stubbedVerificationResult = true

try self.changeClient(.disabled, forceSignatureFailures: true)

let response: HTTPResponse<HTTPEmptyResponseBody>.Result? = waitUntilValue { completion in
self.client.perform(.init(method: .get, path: Self.path), completionHandler: completion)
}

expect(response).to(beSuccess())
expect(response?.value?.verificationResult) == .notRequested
}

}

// MARK: - Private

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *)
private extension BaseSignatureVerificationHTTPClientTests {

final func changeClient(
_ verificationMode: Configuration.EntitlementVerificationMode,
forceSignatureFailures: Bool = false
) throws {
try self.createClient(Signing.verificationMode(with: verificationMode),
forceSignatureFailures: forceSignatureFailures)
}

final func changeClientToEnforced(forceSignatureFailures: Bool = false) throws {
try self.createClient(Signing.enforcedVerificationMode(),
forceSignatureFailures: forceSignatureFailures)
}

private final func createClient(
_ mode: Signing.ResponseVerificationMode,
forceSignatureFailures: Bool = false
) throws {
self.systemInfo = try MockSystemInfo(
platformInfo: nil,
finishTransactions: false,
responseVerificationMode: mode,
dangerousSettings: .init(
autoSyncPurchases: true,
internalSettings: DangerousSettings.Internal(forceSignatureFailures: forceSignatureFailures)
)
)
self.client = self.createClient()
}

Expand Down