From 24a86a5ad54283aa06d691da6d08a68af3caecb0 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Mon, 26 Jun 2023 15:24:39 -0700 Subject: [PATCH 1/3] `Signing`: extract and verify intermediate key --- Sources/Logging/Strings/SigningStrings.swift | 22 ++++ Sources/Security/Signing.swift | 82 +++++++++++--- Tests/UnitTests/Security/SigningTests.swift | 109 +++++++++++++++++-- 3 files changed, 187 insertions(+), 26 deletions(-) diff --git a/Sources/Logging/Strings/SigningStrings.swift b/Sources/Logging/Strings/SigningStrings.swift index 068109eb2d..5b46c91f05 100644 --- a/Sources/Logging/Strings/SigningStrings.swift +++ b/Sources/Logging/Strings/SigningStrings.swift @@ -24,12 +24,18 @@ enum SigningStrings { case signature_failed_verification + case intermediate_key_failed_verification(signature: Data) + case intermediate_key_failed_creation(Error) + case intermediate_key_expired(Date, Data) + case intermediate_key_creating(expiration: Date, data: Data) + case signature_was_requested_but_not_provided(HTTPRequest) case request_date_missing_from_headers(HTTPRequest) #if DEBUG case verifying_signature(signature: Data, + publicKey: Data, parameters: Signing.SignatureParameters, salt: Data, payload: Data, @@ -56,6 +62,20 @@ extension SigningStrings: LogMessage { case .signature_failed_verification: return "Signature failed verification" + case let .intermediate_key_failed_verification(signature): + return "Intermediate key failed verification: \(signature)" + + case let .intermediate_key_failed_creation(error): + return "Failed initializing intermediate key: \(error.localizedDescription)\n" + + "This will be reported as a verification failure." + + case let .intermediate_key_expired(date, data): + return "Intermediate key expired at '\(date)' (parsed from '\(data.asString)'). " + + "This will be reported as a verification failure." + + case let .intermediate_key_creating(expiration, data): + return "Creating intermediate key with expiration '\(expiration)': \(data.asString)" + case let .request_date_missing_from_headers(request): return "Request to '\(request.path)' required a request date but none was provided. " + "This will be reported as a verification failure." @@ -76,6 +96,7 @@ extension SigningStrings: LogMessage { case let .verifying_signature( signature, + publicKey, parameters, salt, payload, @@ -83,6 +104,7 @@ extension SigningStrings: LogMessage { ): return """ Verifying signature '\(signature.base64EncodedString())' + Public key: '\(publicKey.asString)' Parameters: \(parameters), Salt: \(salt.base64EncodedString()), Payload: \(payload.base64EncodedString()), diff --git a/Sources/Security/Signing.swift b/Sources/Security/Signing.swift index 8c6278c049..3760bd748e 100644 --- a/Sources/Security/Signing.swift +++ b/Sources/Security/Signing.swift @@ -55,7 +55,7 @@ enum Signing: SigningType { } do { - return try Curve25519.Signing.PublicKey(rawRepresentation: key) + return try Self.createPublicKey(with: key) } catch { fail(Strings.signing.invalid_public_key(error.localizedDescription)) } @@ -76,7 +76,12 @@ enum Signing: SigningType { return false } - // Fixme: verify public key + guard let intermediatePublicKey = Self.extractAndVerifyIntermediateKey( + from: signature, + publicKey: publicKey + ) else { + return false + } let salt = signature.component(.salt) let payload = signature.component(.payload) @@ -85,6 +90,7 @@ enum Signing: SigningType { #if DEBUG Logger.verbose(Strings.signing.verifying_signature( signature: signature, + publicKey: intermediatePublicKey.rawRepresentation, parameters: parameters, salt: salt, payload: payload, @@ -92,7 +98,7 @@ enum Signing: SigningType { )) #endif - let isValid = publicKey.isValidSignature(payload, for: messageToVerify) + let isValid = intermediatePublicKey.isValidSignature(payload, for: messageToVerify) if !isValid { Logger.warn(Strings.signing.signature_failed_verification) @@ -121,7 +127,11 @@ enum Signing: SigningType { // MARK: - - private static let publicKey = "drCCA+6YAKOAjT7b2RosYNTrRexVWnu+dR5fw/JuKeA=" + /// The actual algorithm used to verify signatures. + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + fileprivate typealias Algorithm = Curve25519.Signing.PublicKey + + private static let publicKey = "UC1upXWg5QVmyOSwozp755xLqquBKjjU+di6U8QhMlM=" } @@ -168,11 +178,12 @@ extension Signing { protocol SigningPublicKey { func isValidSignature(_ signature: Data, for data: Data) -> Bool + var rawRepresentation: Data { get } } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) -extension CryptoKit.Curve25519.Signing.PublicKey: SigningPublicKey {} +extension Signing.Algorithm: SigningPublicKey {} // MARK: - Internal implementation (visible for tests) @@ -196,14 +207,6 @@ extension Signing { } } - static let signedPublicKeySize: Int = [Self]([ - .intermediatePublicKey, - .intermediateKeyExpiration, - .intermediateKeySignature - ]) - .map(\.size) - .sum() - static let totalSize: Int = Self.allCases.map(\.size).sum() /// Number of bytes where the component begins @@ -239,7 +242,50 @@ extension Signing.SignatureParameters { private final class BundleToken: NSObject {} -// MARK: - Data extensions +private extension Signing { + + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + static func createPublicKey(with data: Data) throws -> PublicKey { + return try Algorithm(rawRepresentation: data) + } + + static func extractAndVerifyIntermediateKey( + from signature: Data, + publicKey: Signing.PublicKey + ) -> Signing.PublicKey? { + guard #available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) else { return nil } + + let intermediatePublicKey = signature.component(.intermediatePublicKey) + let intermediateKeyExpiration = signature.component(.intermediateKeyExpiration) + let intermediateKeySignature = signature.component(.intermediateKeySignature) + + guard publicKey.isValidSignature(intermediateKeySignature, + for: intermediateKeyExpiration + intermediatePublicKey) else { + Logger.warn(Strings.signing.intermediate_key_failed_verification(signature: intermediateKeySignature)) + return nil + } + + let expirationDate = Date(daysSince1970: UInt32(littleEndian32Bits: intermediateKeyExpiration)) + + guard expirationDate.timeIntervalSince(Date()) >= 0 else { + Logger.warn(Strings.signing.intermediate_key_expired(expirationDate, intermediateKeyExpiration)) + return nil + } + + Logger.verbose(Strings.signing.intermediate_key_creating(expiration: expirationDate, + data: intermediatePublicKey)) + + do { + return try Self.createPublicKey(with: intermediatePublicKey) + } catch { + Logger.error(Strings.signing.intermediate_key_failed_creation(error)) + return nil + } + } + +} + +// MARK: - Extensions private extension Data { @@ -252,3 +298,11 @@ private extension Data { } } + +private extension Date { + + init(daysSince1970: UInt32) { + self.init(timeIntervalSince1970: DispatchTimeInterval.days(Int(daysSince1970)).seconds) + } + +} diff --git a/Tests/UnitTests/Security/SigningTests.swift b/Tests/UnitTests/Security/SigningTests.swift index c6d99f7978..ac6844732d 100644 --- a/Tests/UnitTests/Security/SigningTests.swift +++ b/Tests/UnitTests/Security/SigningTests.swift @@ -24,6 +24,7 @@ class SigningTests: TestCase { fileprivate typealias PublicKey = Curve25519.Signing.PublicKey private let (privateKey, publicKey) = SigningTests.createRandomKey() + private let (privateIntermediateKey, publicIntermediateKey) = SigningTests.createRandomKey() override func setUpWithError() throws { try super.setUpWithError() @@ -59,6 +60,38 @@ class SigningTests: TestCase { logger.verifyMessageWasLogged("Signature is not base64: \(signature)") } + func testVerifySignatureWithExpiredIntermediateSignatureReturnsFalseAndLogsError() throws { + let message = "Hello World" + let nonce = "0123456789ab" + let requestDate = Date().millisecondsSince1970 + let intermediateKey = try self.createIntermediatePublicKeyData(expiration: Self.intermediateKeyPastExpiration) + let salt = Self.createSalt() + + let signature = try self.sign(parameters: .init(message: message.asData, + nonce: nonce.asData, + requestDate: requestDate), + salt: salt.asData) + let fullSignature = Self.fullSignature( + intermediateKey: intermediateKey, + salt: salt, + signature: signature + ) + + let logger = TestLogHandler() + + expect(Signing.verify( + signature: fullSignature.base64EncodedString(), + with: .init( + message: message.asData, + nonce: nonce.asData, + requestDate: requestDate + ), + publicKey: self.publicKey + )) == false + + logger.verifyMessageWasLogged("Intermediate key expired", level: .warn) + } + func testVerifySignatureWithInvalidSignature() throws { expect(Signing.verify( signature: "invalid signature".asData.base64EncodedString(), @@ -72,7 +105,7 @@ class SigningTests: TestCase { )) == false } - func testVerifySignatureLogsWarningWhenFail() throws { + func testVerifySignatureLogsWarningWhenIntermediateSignatureIsInvalid() throws { let logger = TestLogHandler() let signature = String(repeating: "x", count: Signing.SignatureComponent.totalSize) @@ -87,6 +120,37 @@ class SigningTests: TestCase { ), publicKey: Signing.loadPublicKey()) + logger.verifyMessageWasLogged("Intermediate key failed verification", + level: .warn) + } + + func testVerifySignatureLogsWarningFail() throws { + let logger = TestLogHandler() + + let message = "Hello World" + let nonce = "nonce" + let requestDate: UInt64 = 1677005916012 + let intermediateKey = try self.createIntermediatePublicKeyData(expiration: Self.intermediateKeyFutureExpiration) + let salt = Self.createSalt() + + let fullSignature = Self.fullSignature( + intermediateKey: intermediateKey, + salt: salt, + // Invalid signature + signature: String(repeating: "x", count: Signing.SignatureComponent.payload.size).asData + ) + expect( + Signing.verify( + signature: fullSignature.base64EncodedString(), + with: .init( + message: message.asData, + nonce: nonce.asData, + requestDate: requestDate + ), + publicKey: self.publicKey + ) + ) == false + logger.verifyMessageWasLogged(Strings.signing.signature_failed_verification, level: .warn) } @@ -113,7 +177,7 @@ class SigningTests: TestCase { let message = "Hello World" let nonce = "nonce" let requestDate: UInt64 = 1677005916012 - let publicKey = Self.createSignedPublicKey() + let intermediateKey = try self.createIntermediatePublicKeyData(expiration: Self.intermediateKeyFutureExpiration) let salt = Self.createSalt() let signature = try self.sign( @@ -126,7 +190,7 @@ class SigningTests: TestCase { salt: salt.asData ) let fullSignature = Self.fullSignature( - publicKey: publicKey, + intermediateKey: intermediateKey, salt: salt, signature: signature ) @@ -346,7 +410,7 @@ class SigningTests: TestCase { let message = "Hello World" let nonce = "0123456789ab" let requestDate = Date().millisecondsSince1970 - let publicKey = Self.createSignedPublicKey() + let intermediateKey = try self.createIntermediatePublicKeyData(expiration: Self.intermediateKeyFutureExpiration) let salt = Self.createSalt() let signature = try self.sign(parameters: .init(message: message.asData, @@ -355,7 +419,7 @@ class SigningTests: TestCase { requestDate: requestDate), salt: salt.asData) let fullSignature = Self.fullSignature( - publicKey: publicKey, + intermediateKey: intermediateKey, salt: salt, signature: signature ) @@ -410,7 +474,7 @@ class SigningTests: TestCase { func testResponseVerificationWithoutNonceWithValidSignature() throws { let message = "Hello World" let requestDate = Date().millisecondsSince1970 - let publicKey = Self.createSignedPublicKey() + let intermediateKey = try self.createIntermediatePublicKeyData(expiration: Self.intermediateKeyFutureExpiration) let salt = Self.createSalt() let signature = try self.sign(parameters: .init(message: message.asData, @@ -419,7 +483,7 @@ class SigningTests: TestCase { requestDate: requestDate), salt: salt.asData) let fullSignature = Self.fullSignature( - publicKey: publicKey, + intermediateKey: intermediateKey, salt: salt, signature: signature ) @@ -472,23 +536,44 @@ private extension SigningTests { } func sign(parameters: Signing.SignatureParameters, salt: Data) throws -> Data { - return try self.sign(key: self.privateKey, parameters: parameters, salt: salt) + return try self.sign(key: self.privateIntermediateKey, parameters: parameters, salt: salt) } func sign(key: PrivateKey, parameters: Signing.SignatureParameters, salt: Data) throws -> Data { return try key.signature(for: salt + parameters.asData) } - static func fullSignature(publicKey: String, salt: String, signature: Data) -> Data { - return publicKey.asData + salt.asData + signature + static func fullSignature(intermediateKey: Data, salt: String, signature: Data) -> Data { + return intermediateKey + salt.asData + signature } static func createSalt() -> String { return Array(repeating: "a", count: Signing.SignatureComponent.salt.size).joined() } - static func createSignedPublicKey() -> String { - return Array(repeating: "b", count: Signing.SignatureComponent.signedPublicKeySize).joined() + func createIntermediatePublicKeyData(expiration: Date) throws -> Data { + let intermediateKey = self.publicIntermediateKey.rawRepresentation + let expiration = expiration.dataRepresentation + let signature = try self.privateKey.signature(for: expiration + intermediateKey) + + precondition(intermediateKey.count == Signing.SignatureComponent.intermediatePublicKey.size) + precondition(expiration.count == Signing.SignatureComponent.intermediateKeyExpiration.size) + precondition(signature.count == Signing.SignatureComponent.intermediateKeySignature.size) + + return intermediateKey + expiration + signature + } + + static let intermediateKeyFutureExpiration = Date().addingTimeInterval(DispatchTimeInterval.days(5).seconds) + static let intermediateKeyPastExpiration = Date().addingTimeInterval(DispatchTimeInterval.days(5).seconds * -1) + +} + +private extension Date { + + /// Khepri encodes expiration as UInt32 little-endian of the number of days since 1970 + var dataRepresentation: Data { + let days = DispatchTimeInterval(self.timeIntervalSince1970).days + return UInt32(days).littleEndianData } } From ad2edc54a21f8ad462ca3fed09aa042282fff9ba Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Mon, 26 Jun 2023 17:05:28 -0700 Subject: [PATCH 2/3] `Signing`: verify expiration date is not 0 --- Sources/Logging/Strings/SigningStrings.swift | 7 +++- Sources/Security/Signing.swift | 23 +++++++++-- Tests/UnitTests/Security/SigningTests.swift | 42 +++++++++++++++++++- 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/Sources/Logging/Strings/SigningStrings.swift b/Sources/Logging/Strings/SigningStrings.swift index 5b46c91f05..ddc39b38c0 100644 --- a/Sources/Logging/Strings/SigningStrings.swift +++ b/Sources/Logging/Strings/SigningStrings.swift @@ -27,6 +27,7 @@ enum SigningStrings { case intermediate_key_failed_verification(signature: Data) case intermediate_key_failed_creation(Error) case intermediate_key_expired(Date, Data) + case intermediate_key_invalid(Data) case intermediate_key_creating(expiration: Date, data: Data) case signature_was_requested_but_not_provided(HTTPRequest) @@ -63,7 +64,7 @@ extension SigningStrings: LogMessage { return "Signature failed verification" case let .intermediate_key_failed_verification(signature): - return "Intermediate key failed verification: \(signature)" + return "Intermediate key failed verification: \(signature.asString)" case let .intermediate_key_failed_creation(error): return "Failed initializing intermediate key: \(error.localizedDescription)\n" + @@ -73,6 +74,10 @@ extension SigningStrings: LogMessage { return "Intermediate key expired at '\(date)' (parsed from '\(data.asString)'). " + "This will be reported as a verification failure." + case let .intermediate_key_invalid(expirationDate): + return "Found invalid intermediate key expiration date: \(expirationDate.asString). " + + "This will be reported as a verification failure." + case let .intermediate_key_creating(expiration, data): return "Creating intermediate key with expiration '\(expiration)': \(data.asString)" diff --git a/Sources/Security/Signing.swift b/Sources/Security/Signing.swift index 3760bd748e..55af5e583a 100644 --- a/Sources/Security/Signing.swift +++ b/Sources/Security/Signing.swift @@ -265,10 +265,7 @@ private extension Signing { return nil } - let expirationDate = Date(daysSince1970: UInt32(littleEndian32Bits: intermediateKeyExpiration)) - - guard expirationDate.timeIntervalSince(Date()) >= 0 else { - Logger.warn(Strings.signing.intermediate_key_expired(expirationDate, intermediateKeyExpiration)) + guard let expirationDate = Self.extractAndVerifyIntermediateKeyExpiration(intermediateKeyExpiration) else { return nil } @@ -283,6 +280,24 @@ private extension Signing { } } + /// - Returns: `nil` if the key is expired or has an invalid expiration date. + private static func extractAndVerifyIntermediateKeyExpiration(_ expirationData: Data) -> Date? { + let daysSince1970 = UInt32(littleEndian32Bits: expirationData) + + guard daysSince1970 > 0 else { + Logger.warn(Strings.signing.intermediate_key_invalid(expirationData)) + return nil + } + + let expirationDate = Date(daysSince1970: daysSince1970) + guard expirationDate.timeIntervalSince(Date()) >= 0 else { + Logger.warn(Strings.signing.intermediate_key_expired(expirationDate, expirationData)) + return nil + } + + return expirationDate + } + } // MARK: - Extensions diff --git a/Tests/UnitTests/Security/SigningTests.swift b/Tests/UnitTests/Security/SigningTests.swift index ac6844732d..2f46c3609c 100644 --- a/Tests/UnitTests/Security/SigningTests.swift +++ b/Tests/UnitTests/Security/SigningTests.swift @@ -92,6 +92,39 @@ class SigningTests: TestCase { logger.verifyMessageWasLogged("Intermediate key expired", level: .warn) } + func testVerifySignatureWithInvalidIntermediateSignatureExpirationReturnsFalseAndLogsError() throws { + let message = "Hello World" + let nonce = "0123456789ab" + let requestDate = Date().millisecondsSince1970 + let intermediateKey = try self.createIntermediatePublicKeyData(expiration: nil) + let salt = Self.createSalt() + + let signature = try self.sign(parameters: .init(message: message.asData, + nonce: nonce.asData, + requestDate: requestDate), + salt: salt.asData) + let fullSignature = Self.fullSignature( + intermediateKey: intermediateKey, + salt: salt, + signature: signature + ) + + let logger = TestLogHandler() + + expect(Signing.verify( + signature: fullSignature.base64EncodedString(), + with: .init( + message: message.asData, + nonce: nonce.asData, + requestDate: requestDate + ), + publicKey: self.publicKey + )) == false + + logger.verifyMessageWasLogged(Strings.signing.intermediate_key_invalid(Self.invalidIntermediateKeyExpiration), + level: .warn) + } + func testVerifySignatureWithInvalidSignature() throws { expect(Signing.verify( signature: "invalid signature".asData.base64EncodedString(), @@ -551,9 +584,10 @@ private extension SigningTests { return Array(repeating: "a", count: Signing.SignatureComponent.salt.size).joined() } - func createIntermediatePublicKeyData(expiration: Date) throws -> Data { + /// - Parameter expiration: pass `nil` to create a "0" expiration date. + func createIntermediatePublicKeyData(expiration: Date?) throws -> Data { let intermediateKey = self.publicIntermediateKey.rawRepresentation - let expiration = expiration.dataRepresentation + let expiration = expiration.map(\.dataRepresentation) ?? Self.invalidIntermediateKeyExpiration let signature = try self.privateKey.signature(for: expiration + intermediateKey) precondition(intermediateKey.count == Signing.SignatureComponent.intermediatePublicKey.size) @@ -565,6 +599,10 @@ private extension SigningTests { static let intermediateKeyFutureExpiration = Date().addingTimeInterval(DispatchTimeInterval.days(5).seconds) static let intermediateKeyPastExpiration = Date().addingTimeInterval(DispatchTimeInterval.days(5).seconds * -1) + static let invalidIntermediateKeyExpiration = Data( + repeating: 0, + count: Signing.SignatureComponent.intermediateKeyExpiration.size + ) } From bef3bef6f0bc837eac4826c9ddef11579444f44e Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Thu, 29 Jun 2023 12:29:38 -0700 Subject: [PATCH 3/3] Updated tests --- Tests/UnitTests/Security/SigningTests.swift | 74 +++++++++++---------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/Tests/UnitTests/Security/SigningTests.swift b/Tests/UnitTests/Security/SigningTests.swift index 2f46c3609c..87fd247801 100644 --- a/Tests/UnitTests/Security/SigningTests.swift +++ b/Tests/UnitTests/Security/SigningTests.swift @@ -63,14 +63,18 @@ class SigningTests: TestCase { func testVerifySignatureWithExpiredIntermediateSignatureReturnsFalseAndLogsError() throws { let message = "Hello World" let nonce = "0123456789ab" + let etag: String? = nil let requestDate = Date().millisecondsSince1970 let intermediateKey = try self.createIntermediatePublicKeyData(expiration: Self.intermediateKeyPastExpiration) let salt = Self.createSalt() + let parameters: Signing.SignatureParameters = .init( + message: message.asData, + nonce: nonce.asData, + etag: etag, + requestDate: requestDate + ) - let signature = try self.sign(parameters: .init(message: message.asData, - nonce: nonce.asData, - requestDate: requestDate), - salt: salt.asData) + let signature = try self.sign(parameters: parameters, salt: salt.asData) let fullSignature = Self.fullSignature( intermediateKey: intermediateKey, salt: salt, @@ -81,11 +85,7 @@ class SigningTests: TestCase { expect(Signing.verify( signature: fullSignature.base64EncodedString(), - with: .init( - message: message.asData, - nonce: nonce.asData, - requestDate: requestDate - ), + with: parameters, publicKey: self.publicKey )) == false @@ -95,14 +95,18 @@ class SigningTests: TestCase { func testVerifySignatureWithInvalidIntermediateSignatureExpirationReturnsFalseAndLogsError() throws { let message = "Hello World" let nonce = "0123456789ab" + let etag = "etag" let requestDate = Date().millisecondsSince1970 let intermediateKey = try self.createIntermediatePublicKeyData(expiration: nil) let salt = Self.createSalt() + let parameters: Signing.SignatureParameters = .init( + message: message.asData, + nonce: nonce.asData, + etag: etag, + requestDate: requestDate + ) - let signature = try self.sign(parameters: .init(message: message.asData, - nonce: nonce.asData, - requestDate: requestDate), - salt: salt.asData) + let signature = try self.sign(parameters: parameters, salt: salt.asData) let fullSignature = Self.fullSignature( intermediateKey: intermediateKey, salt: salt, @@ -113,11 +117,7 @@ class SigningTests: TestCase { expect(Signing.verify( signature: fullSignature.base64EncodedString(), - with: .init( - message: message.asData, - nonce: nonce.asData, - requestDate: requestDate - ), + with: parameters, publicKey: self.publicKey )) == false @@ -157,7 +157,7 @@ class SigningTests: TestCase { level: .warn) } - func testVerifySignatureLogsWarningFail() throws { + func testVerifySignatureLogsWarningWhenFail() throws { let logger = TestLogHandler() let message = "Hello World" @@ -178,6 +178,7 @@ class SigningTests: TestCase { with: .init( message: message.asData, nonce: nonce.asData, + etag: nil, requestDate: requestDate ), publicKey: self.publicKey @@ -244,8 +245,8 @@ class SigningTests: TestCase { let message = "Hello World" let nonce = "nonce" let requestDate: UInt64 = 1677005916012 + let intermediateKey = try self.createIntermediatePublicKeyData(expiration: Self.intermediateKeyFutureExpiration) let etag = "97d4f0d2353d784a" - let publicKey = Self.createSignedPublicKey() let salt = Self.createSalt() let signature = try self.sign( @@ -258,7 +259,7 @@ class SigningTests: TestCase { salt: salt.asData ) let fullSignature = Self.fullSignature( - publicKey: publicKey, + intermediateKey: intermediateKey, salt: salt, signature: signature ) @@ -288,14 +289,14 @@ class SigningTests: TestCase { // swiftlint:disable line_length let response = """ - {"request_date":"2023-06-28T22:13:01Z","request_date_ms":1687990381493,"subscriber":{"entitlements":{},"first_seen":"2023-06-22T19:28:22Z","last_seen":"2023-06-22T19:28:22Z","management_url":null,"non_subscriptions":{},"original_app_user_id":"login","original_application_version":null,"original_purchase_date":null,"other_purchases":{},"subscriptions":{}}}\n + {"request_date":"2023-06-29T19:23:42Z","request_date_ms":1688066622298,"subscriber":{"entitlements":{},"first_seen":"2023-06-29T19:23:42Z","last_seen":"2023-06-29T19:23:42Z","management_url":null,"non_subscriptions":{},"original_app_user_id":"login","original_application_version":null,"original_purchase_date":null,"other_purchases":{},"subscriptions":{}}}\n """ - let expectedSignature = "drCCA+6YAKOAjT7b2RosYNTrRexVWnu+dR5fw/JuKeAAAAAA0FnsHKjqgSrOj+YkdU2TZfLfpMfx8w9miUkqxyWMI0h2z0weWLNlF1MPG7ZrL+vOEQi+LvYkcffxprzcn1uSAVfQSkHeWl4NJ4IDusH1iegbiDI+RFt7hpFD70vgXYNE0GZEVLL5wezXhzoTI1ob3Q5ccYJHZ9oBEgqysz8dvNgYrrWCtezaHu5pgvHfBc8E" + let expectedSignature = "XX8Mh8DTcqPC5A48nncRU3hDkL/v3baxxqLIWnWJzg1tTAAA7ok0iXupT2bjju/BSHVmgxc0XiwTZXBmsGuWEXa9lsyoFi9HMF4aAIOs4Y+lYE2i4USJCP7ev07QZk7D2b6ZBSkFSDzefa+cDeSEtlG+AB3lQ9F7qXf7kg2GqVQR3D7ayNFwey4c2p/WMZYfx5tJaKVzOPWQPtM3jmfByfOZd6rLkE+SYycExStyDpUACWcA" // swiftlint:enable line_length let nonce = try XCTUnwrap(Data(base64Encoded: "MTIzNDU2Nzg5MGFi")) - let requestDate: UInt64 = 1687455094309 - let etag = "97d4f0d2353d784a" + let requestDate: UInt64 = 1688066622299 + let etag = "9d74782403a43274" expect( Signing.verify( @@ -316,9 +317,12 @@ class SigningTests: TestCase { Signature retrieved with: curl -v 'https://api.revenuecat.com/v1/subscribers/test/offerings' \ -X GET \ - -H 'Authorization: Bearer {api_key}' + -H 'Authorization: Bearer {api_key}' \ + -H 'X-Platform: iOS' */ + XCTExpectFailure("Waiting on backend to generate a valid signature for this test") + // swiftlint:disable line_length let response = """ {"current_offering_id":"default","offerings":[{"description":"Default","identifier":"default","packages":[]}]}\n @@ -345,18 +349,18 @@ class SigningTests: TestCase { func testVerifyKnownSignatureOfEmptyResponseWithNonceAndNoEtag() throws { /* Signature retrieved with: - curl -v 'https://api.revenuecat.com/v1/health/static' \ + curl -v 'https://api.revenuecat.com/v1/health' \ -X GET \ -H 'X-Nonce: MTIzNDU2Nzg5MGFi' */ // swiftlint:disable line_length let response = "\"\"\n" - let expectedSignature = "drCCA+6YAKOAjT7b2RosYNTrRexVWnu+dR5fw/JuKeAAAAAA0FnsHKjqgSrOj+YkdU2TZfLfpMfx8w9miUkqxyWMI0h2z0weWLNlF1MPG7ZrL+vOEQi+LvYkcffxprzcn1uSAVfQSkHeWl4NJ4IDusH1ieiOuhvZ8lHxC5ntXQ+U3wzInufFUkwNChuNwXQ4eEw5tifm45bUv4S0DsKwnBYaHWQzVcVbJ7NUQZKD7I8k23MI" + let expectedSignature = "XX8Mh8DTcqPC5A48nncRU3hDkL/v3baxxqLIWnWJzg1tTAAA7ok0iXupT2bjju/BSHVmgxc0XiwTZXBmsGuWEXa9lsyoFi9HMF4aAIOs4Y+lYE2i4USJCP7ev07QZk7D2b6ZBVIcfv+kOk0mmfI22o3ZId31m88mVG2BqPPQpNfyQYjmwjymg00WqlSHY2Yqgq20fK0wEdG8RDJEqsMOPOo93kO+wGvlkOvlEqMF39vXtOMI" // swiftlint:enable line_length let nonce = try XCTUnwrap(Data(base64Encoded: "MTIzNDU2Nzg5MGFi")) - let requestDate: UInt64 = 1687455094309 + let requestDate: UInt64 = 1688066733210 expect( Signing.verify( @@ -378,17 +382,17 @@ class SigningTests: TestCase { curl -v 'https://api.revenuecat.com/v1/subscribers/login' \ -X GET \ -H 'X-Nonce: MTIzNDU2Nzg5MGFi' - -H 'Authorization: Bearer {apo_key}' + -H 'Authorization: Bearer {api_key}' -H 'X-RevenueCat-ETag: 97d4f0d2353d784a' */ // swiftlint:disable line_length - let expectedSignature = "drCCA+6YAKOAjT7b2RosYNTrRexVWnu+dR5fw/JuKeAAAAAA0FnsHKjqgSrOj+YkdU2TZfLfpMfx8w9miUkqxyWMI0h2z0weWLNlF1MPG7ZrL+vOEQi+LvYkcffxprzcn1uSAVfQSkHeWl4NJ4IDusH1ieiX91GsXy90APKsUAnLepcvRnhQSawwj+7Cm7936jAMoaRinYxd0utkyhZXdLlkXZJ/EU5UDAfdGzMaNpYX9aYO" + let expectedSignature = "XX8Mh8DTcqPC5A48nncRU3hDkL/v3baxxqLIWnWJzg1tTAAA7ok0iXupT2bjju/BSHVmgxc0XiwTZXBmsGuWEXa9lsyoFi9HMF4aAIOs4Y+lYE2i4USJCP7ev07QZk7D2b6ZBT0H1sSsBkbLM0LwwTSwTceDJXijNlz0tStn0Qi0dPRwFL+LN7vcsNqhJFq0+zqm2St/cKHJKxK+1HB+1S0lr0isIHY2G7PVmR2s3Zynx90M" // swiftlint:enable line_length let nonce = try XCTUnwrap(Data(base64Encoded: "MTIzNDU2Nzg5MGFi")) - let requestDate: UInt64 = 1687455094309 - let etag = "97d4f0d2353d784a" + let requestDate: UInt64 = 1688066798532 + let etag = "9d74782403a43274" expect( Signing.verify( @@ -475,7 +479,7 @@ class SigningTests: TestCase { let nonce = "0123456789ab" let etag = "97d4f0d2353d784a" let requestDate = Date().millisecondsSince1970 - let publicKey = Self.createSignedPublicKey() + let intermediateKey = try self.createIntermediatePublicKeyData(expiration: Self.intermediateKeyFutureExpiration) let salt = Self.createSalt() let signature = try self.sign(parameters: .init(message: nil, @@ -484,7 +488,7 @@ class SigningTests: TestCase { requestDate: requestDate), salt: salt.asData) let fullSignature = Self.fullSignature( - publicKey: publicKey, + intermediateKey: intermediateKey, salt: salt, signature: signature )