Skip to content

Commit

Permalink
Add InternalDangerousSettings.forceSignatureFailures (#2635)
Browse files Browse the repository at this point in the history
Similar to #2486. This will be used for SDK-3181.

```
- WARN: ⚠️ Returning fake signature verification failure for 'GET /v1/subscribers/$RCAnonymousID:fc0508bea4a74a41bfc9a69db30d925f'
- ERROR: 😿‼️ Request failed signature verification. Request to /v1/subscribers/$RCAnonymousID%3Afc0508bea4a74a41bfc9a69db30d925f failed verification
```
  • Loading branch information
NachoSoto authored Jun 13, 2023
1 parent 1dd5e76 commit 06b0f3d
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 14 deletions.
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 @@ -926,6 +927,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 @@ -2332,6 +2334,7 @@
57E6C27B29723A94001AFE98 /* Signing.swift */,
5791FE492994453500F1FEDA /* Signing+ResponseVerification.swift */,
5740FCD22996CE5E00E049F9 /* VerificationResult.swift */,
4F6EEBD82A38ED76007FD783 /* FakeSigning.swift */,
);
path = Security;
sourceTree = "<group>";
Expand Down Expand Up @@ -3160,6 +3163,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

0 comments on commit 06b0f3d

Please sign in to comment.