Skip to content

Commit 75b3c76

Browse files
committed
Add InternalDangerousSettings.forceSignatureFailures
Similar to #2486. This will be used for SDK-3181.
1 parent 5ec9d36 commit 75b3c76

7 files changed

Lines changed: 131 additions & 14 deletions

File tree

RevenueCat.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@
226226
4F6BEE3B2A27B45300CD9322 /* StoreKitTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 571E7AD3279F2D0C003B3606 /* StoreKitTestHelpers.swift */; };
227227
4F6BEE3C2A27B45900CD9322 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DE61A83264190830021CEA0 /* Constants.swift */; };
228228
4F6BEE882A27E16B00CD9322 /* TestLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57057FF728B0048900995F21 /* TestLogHandler.swift */; };
229+
4F6EEBD92A38ED76007FD783 /* FakeSigning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6EEBD82A38ED76007FD783 /* FakeSigning.swift */; };
229230
4F7C37B22A27E2E8001E17D3 /* AsyncTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575A8EE02922C56300936709 /* AsyncTestHelpers.swift */; };
230231
4F7C37E42A27EFE1001E17D3 /* BaseBackendIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 579234E127F777EE00B39C68 /* BaseBackendIntegrationTests.swift */; };
231232
4F7C37E52A27EFF7001E17D3 /* BaseStoreKitIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5753EE0F294B93CC00CBAB54 /* BaseStoreKitIntegrationTests.swift */; };
@@ -927,6 +928,7 @@
927928
4F6BEDE12A26B69500CD9322 /* DebugContentViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugContentViews.swift; sourceTree = "<group>"; };
928929
4F6BEE022A27ADF900CD9322 /* CustomEntitlementsComputationIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEntitlementsComputationIntegrationTests.swift; sourceTree = "<group>"; };
929930
4F6BEE312A27B02400CD9322 /* BackendCustomEntitlementsIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BackendCustomEntitlementsIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
931+
4F6EEBD82A38ED76007FD783 /* FakeSigning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeSigning.swift; sourceTree = "<group>"; };
930932
4F7DBFBC2A1E986C00A2F511 /* StoreKit2TransactionFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKit2TransactionFetcher.swift; sourceTree = "<group>"; };
931933
4F8038322A1EA7C300D21039 /* TransactionPoster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionPoster.swift; sourceTree = "<group>"; };
932934
4F8A58162A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockOfflineCustomerInfoCreator.swift; sourceTree = "<group>"; };
@@ -2343,6 +2345,7 @@
23432345
57E6C27B29723A94001AFE98 /* Signing.swift */,
23442346
5791FE492994453500F1FEDA /* Signing+ResponseVerification.swift */,
23452347
5740FCD22996CE5E00E049F9 /* VerificationResult.swift */,
2348+
4F6EEBD82A38ED76007FD783 /* FakeSigning.swift */,
23462349
);
23472350
path = Security;
23482351
sourceTree = "<group>";
@@ -3172,6 +3175,7 @@
31723175
35D832F4262E606500E60AC5 /* HTTPResponse.swift in Sources */,
31733176
352B7D7927BD919B002A47DD /* DangerousSettings.swift in Sources */,
31743177
A56F9AB126990E9200AFC48F /* CustomerInfo.swift in Sources */,
3178+
4F6EEBD92A38ED76007FD783 /* FakeSigning.swift in Sources */,
31753179
2DDF41AE24F6F37C005BC22D /* InAppPurchase.swift in Sources */,
31763180
B32B750126868C1D005647BF /* EntitlementInfo.swift in Sources */,
31773181
57ABA76D28F08DDA003D9181 /* Either.swift in Sources */,

Sources/Logging/Strings/NetworkStrings.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ enum NetworkStrings {
3838

3939
#if DEBUG
4040
case api_request_forcing_server_error(HTTPRequest)
41+
case api_request_forcing_signature_failure(HTTPRequest)
4142
#endif
4243

4344
}
@@ -110,6 +111,9 @@ extension NetworkStrings: CustomStringConvertible {
110111
#if DEBUG
111112
case let .api_request_forcing_server_error(request):
112113
return "Returning fake HTTP 500 error for '\(request.description)'"
114+
115+
case let .api_request_forcing_signature_failure(request):
116+
return "Returning fake signature verification failure for '\(request.description)'"
113117
#endif
114118
}
115119
}

Sources/Misc/DangerousSettings.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,16 @@ import Foundation
1919

2020
#if DEBUG
2121
let forceServerErrors: Bool
22+
let forceSignatureFailures: Bool
2223

23-
init(enableReceiptFetchRetry: Bool = false, forceServerErrors: Bool = false) {
24+
init(
25+
enableReceiptFetchRetry: Bool = false,
26+
forceServerErrors: Bool = false,
27+
forceSignatureFailures: Bool = false
28+
) {
2429
self.enableReceiptFetchRetry = enableReceiptFetchRetry
2530
self.forceServerErrors = forceServerErrors
31+
self.forceSignatureFailures = forceSignatureFailures
2632
}
2733
#else
2834
init(enableReceiptFetchRetry: Bool = false) {
@@ -107,6 +113,9 @@ internal protocol InternalDangerousSettingsType: Sendable {
107113
#if DEBUG
108114
/// Whether `HTTPClient` will fake server errors
109115
var forceServerErrors: Bool { get }
116+
117+
/// Whether `HTTPClient` will fake invalid signatures.
118+
var forceSignatureFailures: Bool { get }
110119
#endif
111120

112121
}

Sources/Networking/HTTPClient/HTTPClient.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ private extension HTTPClient {
276276
.success(dataIfAvailable(statusCode))
277277
.mapToResponse(response: httpURLResponse,
278278
request: request.httpRequest,
279-
signing: self.signing,
279+
signing: self.signing(for: request.httpRequest),
280280
verificationMode: request.verificationMode)
281281
.map { (response) -> HTTPResponse<Data>? in
282282
guard let cachedResponse = self.eTagManager.httpResultFromCacheOrBackend(
@@ -408,6 +408,17 @@ private extension HTTPClient {
408408
}
409409
}
410410

411+
private func signing(for request: HTTPRequest) -> SigningType.Type {
412+
#if DEBUG
413+
if self.systemInfo.dangerousSettings.internalSettings.forceSignatureFailures {
414+
Logger.warn(Strings.network.api_request_forcing_signature_failure(request))
415+
return FakeSigning.self
416+
}
417+
#endif
418+
419+
return self.signing
420+
}
421+
411422
}
412423

413424
// MARK: - Extensions

Sources/Security/FakeSigning.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//
2+
// Copyright RevenueCat Inc. All Rights Reserved.
3+
//
4+
// Licensed under the MIT License (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// https://opensource.org/licenses/MIT
9+
//
10+
// FakeSigning.swift
11+
//
12+
// Created by Nacho Soto on 6/13/23.
13+
14+
import Foundation
15+
16+
#if DEBUG
17+
18+
/// A `SigningType` implementation that always fails, used for testing.
19+
/// - Seealso: `InternalDangerousSettingsType.forceSignatureFailures`
20+
final class FakeSigning: SigningType {
21+
22+
static func verify(
23+
signature: String,
24+
with parameters: Signing.SignatureParameters,
25+
publicKey: Signing.PublicKey
26+
) -> Bool {
27+
return false
28+
}
29+
30+
}
31+
32+
#endif

Tests/BackendIntegrationTests/BaseBackendIntegrationTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,5 +177,6 @@ extension BaseBackendIntegrationTests: InternalDangerousSettingsType {
177177

178178
var enableReceiptFetchRetry: Bool { return true }
179179
var forceServerErrors: Bool { return false }
180+
var forceSignatureFailures: Bool { return false }
180181

181182
}

Tests/UnitTests/Networking/SignatureVerificationHTTPClientTests.swift

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -565,25 +565,81 @@ final class EnforcedSignatureVerificationHTTPClientTests: BaseSignatureVerificat
565565
expect(response).to(beSuccess())
566566
}
567567

568-
}
568+
func testFakeSignatureFailuresInEnforcedMode() throws {
569+
self.mockResponse(signature: Self.sampleSignature, requestDate: Self.date1)
570+
MockSigning.stubbedVerificationResult = true
569571

570-
// MARK: - Private
572+
try self.changeClientToEnforced(forceSignatureFailures: true)
571573

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

575-
final func changeClient(_ verificationMode: Configuration.EntitlementVerificationMode) throws {
576-
try self.createClient(Signing.verificationMode(with: verificationMode))
578+
expect(response).to(beFailure())
579+
expect(response?.error) == NetworkError.signatureVerificationFailed(path: Self.path)
577580
}
578581

579-
final func changeClientToEnforced() throws {
580-
try self.createClient(Signing.enforcedVerificationMode())
582+
func testFakeSignatureFailuresInInformationalMode() throws {
583+
self.mockResponse(signature: Self.sampleSignature, requestDate: Self.date1)
584+
MockSigning.stubbedVerificationResult = true
585+
586+
try self.changeClient(.informational, forceSignatureFailures: true)
587+
588+
let response: HTTPResponse<HTTPEmptyResponseBody>.Result? = waitUntilValue { completion in
589+
self.client.perform(.init(method: .get, path: Self.path), completionHandler: completion)
590+
}
591+
592+
expect(response).to(beSuccess())
593+
expect(response?.value?.verificationResult) == .failed
581594
}
582595

583-
private final func createClient(_ mode: Signing.ResponseVerificationMode) throws {
584-
self.systemInfo = try MockSystemInfo(platformInfo: nil,
585-
finishTransactions: false,
586-
responseVerificationMode: mode)
596+
func testFakeSignatureFailuresWithDisabledVerification() throws {
597+
self.mockResponse(signature: Self.sampleSignature, requestDate: Self.date1)
598+
MockSigning.stubbedVerificationResult = true
599+
600+
try self.changeClient(.disabled, forceSignatureFailures: true)
601+
602+
let response: HTTPResponse<HTTPEmptyResponseBody>.Result? = waitUntilValue { completion in
603+
self.client.perform(.init(method: .get, path: Self.path), completionHandler: completion)
604+
}
605+
606+
expect(response).to(beSuccess())
607+
expect(response?.value?.verificationResult) == .notRequested
608+
}
609+
610+
}
611+
612+
// MARK: - Private
613+
614+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *)
615+
private extension BaseSignatureVerificationHTTPClientTests {
616+
617+
final func changeClient(
618+
_ verificationMode: Configuration.EntitlementVerificationMode,
619+
forceSignatureFailures: Bool = false
620+
) throws {
621+
try self.createClient(Signing.verificationMode(with: verificationMode),
622+
forceSignatureFailures: forceSignatureFailures)
623+
}
624+
625+
final func changeClientToEnforced(forceSignatureFailures: Bool = false) throws {
626+
try self.createClient(Signing.enforcedVerificationMode(),
627+
forceSignatureFailures: forceSignatureFailures)
628+
}
629+
630+
private final func createClient(
631+
_ mode: Signing.ResponseVerificationMode,
632+
forceSignatureFailures: Bool = false
633+
) throws {
634+
self.systemInfo = try MockSystemInfo(
635+
platformInfo: nil,
636+
finishTransactions: false,
637+
responseVerificationMode: mode,
638+
dangerousSettings: .init(
639+
autoSyncPurchases: true,
640+
internalSettings: DangerousSettings.Internal(forceSignatureFailures: forceSignatureFailures)
641+
)
642+
)
587643
self.client = self.createClient()
588644
}
589645

0 commit comments

Comments
 (0)