From 6bd05a3401675db1224d419f8609856d1cc7e652 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 19 Jul 2024 20:10:33 +0200 Subject: [PATCH 01/26] add VerifyServerPubKeyManager, remove verify .com fallback --- .../RPC/Methods/Subscription.swift | 13 +++-- .../WalletConnectVerify/OriginVerifier.swift | 14 +---- .../PublicKeyFetcher.swift | 35 +++++++++++++ .../VerifyServerPubKeyManager.swift | 39 ++++++++++++++ .../VerifyServerPubKeyManagerTests.swift | 52 +++++++++++++++++++ 5 files changed, 136 insertions(+), 17 deletions(-) create mode 100644 Sources/WalletConnectVerify/PublicKeyFetcher.swift create mode 100644 Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift create mode 100644 Tests/VerifyTests/VerifyServerPubKeyManagerTests.swift diff --git a/Sources/WalletConnectRelay/RPC/Methods/Subscription.swift b/Sources/WalletConnectRelay/RPC/Methods/Subscription.swift index c4e4b1831..be2b25259 100644 --- a/Sources/WalletConnectRelay/RPC/Methods/Subscription.swift +++ b/Sources/WalletConnectRelay/RPC/Methods/Subscription.swift @@ -8,21 +8,24 @@ struct Subscription: RelayRPC { let topic: String let message: String let publishedAt: Date + let attestation: String? enum CodingKeys: String, CodingKey { - case topic, message, publishedAt + case topic, message, publishedAt, attestation } - internal init(topic: String, message: String, publishedAt: Date) { + internal init(topic: String, message: String, publishedAt: Date, attestation: String?) { self.topic = topic self.message = message self.publishedAt = publishedAt + self.attestation = attestation } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) topic = try container.decode(String.self, forKey: .topic) message = try container.decode(String.self, forKey: .message) + attestation = try? container.decode(String.self, forKey: .attestation) let publishedAtMiliseconds = try container.decode(UInt64.self, forKey: .publishedAt) publishedAt = Date(milliseconds: publishedAtMiliseconds) } @@ -45,7 +48,9 @@ struct Subscription: RelayRPC { "irn_subscription" } - init(id: String, topic: String, message: String) { - self.params = Params(id: id, data: Params.Contents(topic: topic, message: message, publishedAt: Date())) + #if DEBUG + init(id: String, topic: String, message: String, attestation: String? = nil) { + self.params = Params(id: id, data: Params.Contents(topic: topic, message: message, publishedAt: Date(), attestation: attestation)) } + #endif } diff --git a/Sources/WalletConnectVerify/OriginVerifier.swift b/Sources/WalletConnectVerify/OriginVerifier.swift index 0689088d2..54d4452f0 100644 --- a/Sources/WalletConnectVerify/OriginVerifier.swift +++ b/Sources/WalletConnectVerify/OriginVerifier.swift @@ -5,10 +5,7 @@ public final class OriginVerifier { case registrationFailed } - private var verifyHost = "verify.walletconnect.com" - /// The property is used to determine whether verify.walletconnect.org will be used - /// in case verify.walletconnect.com doesn't respond for some reason (most likely due to being blocked in the user's location). - private var fallback = false + private var verifyHost = "verify.walletconnect.org" func verifyOrigin(assertionId: String) async throws -> VerifyResponse { let sessionConfiguration = URLSessionConfiguration.default @@ -28,17 +25,8 @@ public final class OriginVerifier { } return response } catch { - if (error as? HTTPError) == .couldNotConnect && !fallback { - fallback = true - verifyHostFallback() - return try await verifyOrigin(assertionId: assertionId) - } throw error } } - - func verifyHostFallback() { - verifyHost = "verify.walletconnect.org" - } } diff --git a/Sources/WalletConnectVerify/PublicKeyFetcher.swift b/Sources/WalletConnectVerify/PublicKeyFetcher.swift new file mode 100644 index 000000000..849037f05 --- /dev/null +++ b/Sources/WalletConnectVerify/PublicKeyFetcher.swift @@ -0,0 +1,35 @@ +import Foundation + +// PublicKeyFetcher class +class PublicKeyFetcher { + struct VerifyServerPublicKey: Codable { + let publicKey: String + let expiresAt: TimeInterval + } + + private let urlString = "https://verify.walletconnect.org/v2/public-key" + + func fetchPublicKey() async throws -> VerifyServerPublicKey { + guard let url = URL(string: urlString) else { + throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"]) + } + + let (data, _) = try await URLSession.shared.data(from: url) + let publicKeyResponse = try JSONDecoder().decode(VerifyServerPublicKey.self, from: data) + return publicKeyResponse + } +} + +#if DEBUG +class MockPublicKeyFetcher: PublicKeyFetcher { + var publicKey: VerifyServerPublicKey? + var error: Error? + + override func fetchPublicKey() async throws -> VerifyServerPublicKey { + if let error = error { + throw error + } + return publicKey! + } +} +#endif diff --git a/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift b/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift new file mode 100644 index 000000000..b0f8c1457 --- /dev/null +++ b/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift @@ -0,0 +1,39 @@ +import Foundation + +class VerifyServerPubKeyManager { + static let publicKeyStorageKey = "com.walletconnect.verify.pubKey" + private let store: CodableStore + private let fetcher: PublicKeyFetcher + + init(store: CodableStore, fetcher: PublicKeyFetcher) { + self.store = store + self.fetcher = fetcher + } + + // Public async function to get the public key + func getPublicKey() async throws -> String { + if let localKey = try getPublicKeyFromLocalStorage(), !isKeyExpired(localKey) { + return localKey.publicKey + } else { + let serverKey = try await fetcher.fetchPublicKey() + savePublicKeyToLocalStorage(publicKey: serverKey) + return serverKey.publicKey + } + } + + // Private function to get the public key from local storage + private func getPublicKeyFromLocalStorage() throws -> PublicKeyFetcher.VerifyServerPublicKey? { + return try store.get(key: Self.publicKeyStorageKey) + } + + // Private function to check if the key is expired + private func isKeyExpired(_ key: PublicKeyFetcher.VerifyServerPublicKey) -> Bool { + let currentTime = Date().timeIntervalSince1970 + return currentTime >= key.expiresAt + } + + // Private function to save the public key to local storage + private func savePublicKeyToLocalStorage(publicKey: PublicKeyFetcher.VerifyServerPublicKey) { + store.set(publicKey, forKey: Self.publicKeyStorageKey) + } +} diff --git a/Tests/VerifyTests/VerifyServerPubKeyManagerTests.swift b/Tests/VerifyTests/VerifyServerPubKeyManagerTests.swift new file mode 100644 index 000000000..8fd843fae --- /dev/null +++ b/Tests/VerifyTests/VerifyServerPubKeyManagerTests.swift @@ -0,0 +1,52 @@ +import Foundation +import XCTest +@testable import WalletConnectVerify + +class VerifyServerPubKeyManagerTests: XCTestCase { + var manager: VerifyServerPubKeyManager! + var store: CodableStore! + var fetcher: MockPublicKeyFetcher! + + override func setUp() { + super.setUp() + let storage = RuntimeKeyValueStorage() + store = CodableStore(defaults: storage, identifier: "test") + fetcher = MockPublicKeyFetcher() + manager = VerifyServerPubKeyManager(store: store, fetcher: fetcher) + } + + func testGetPublicKeyFromServer() async throws { + let expectedPublicKey = "test_public_key" + let expiresAt = Date().timeIntervalSince1970 + 3600 // 1 hour from now + fetcher.publicKey = PublicKeyFetcher.VerifyServerPublicKey(publicKey: expectedPublicKey, expiresAt: expiresAt) + + let publicKey = try await manager.getPublicKey() + + XCTAssertEqual(publicKey, expectedPublicKey) + } + + func testGetPublicKeyFromLocalStorage() async throws { + let expectedPublicKey = "test_public_key" + let expiresAt = Date().timeIntervalSince1970 + 3600 // 1 hour from now + let storedKey = PublicKeyFetcher.VerifyServerPublicKey(publicKey: expectedPublicKey, expiresAt: expiresAt) + store.set(storedKey, forKey: VerifyServerPubKeyManager.publicKeyStorageKey) + + let publicKey = try await manager.getPublicKey() + + XCTAssertEqual(publicKey, expectedPublicKey) + } + + func testGetExpiredPublicKeyFromLocalStorage() async throws { + let expectedPublicKey = "test_public_key" + let newTestPubKey = "new_test_public_key" + let expiresAt = Date().timeIntervalSince1970 - 3600 // 1 hour ago + let storedKey = PublicKeyFetcher.VerifyServerPublicKey(publicKey: expectedPublicKey, expiresAt: expiresAt) + store.set(storedKey, forKey: VerifyServerPubKeyManager.publicKeyStorageKey) + + fetcher.publicKey = PublicKeyFetcher.VerifyServerPublicKey(publicKey: newTestPubKey, expiresAt: Date().timeIntervalSince1970 + 3600) + + let publicKey = try await manager.getPublicKey() + + XCTAssertEqual(publicKey, newTestPubKey) + } +} From d3b80b369f70f090cb0baf0b70a26dfc499e214f Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Jul 2024 14:34:27 +0900 Subject: [PATCH 02/26] savepoint --- Package.swift | 2 +- Sources/WalletConnectJWT/JWTValidator.swift | 8 ++- .../WalletConnectVerify/VerifyClient.swift | 72 ++++++++++++++++++- .../WalletConnectVerify/VerifyImports.swift | 2 + .../VerifyServerPubKeyManager.swift | 10 +-- 5 files changed, 86 insertions(+), 8 deletions(-) diff --git a/Package.swift b/Package.swift index 7e5c3133c..797d5622c 100644 --- a/Package.swift +++ b/Package.swift @@ -121,7 +121,7 @@ let package = Package( path: "Sources/WalletConnectRouter/Router"), .target( name: "WalletConnectVerify", - dependencies: ["WalletConnectUtils", "WalletConnectNetworking"], + dependencies: ["WalletConnectUtils", "WalletConnectNetworking", "WalletConnectJWT"], resources: [.process("Resources/PrivacyInfo.xcprivacy")]), .target( name: "Database", diff --git a/Sources/WalletConnectJWT/JWTValidator.swift b/Sources/WalletConnectJWT/JWTValidator.swift index e94a72bdf..110309a8d 100644 --- a/Sources/WalletConnectJWT/JWTValidator.swift +++ b/Sources/WalletConnectJWT/JWTValidator.swift @@ -1,10 +1,14 @@ import Foundation -struct JWTValidator { +public struct JWTValidator { let jwtString: String - func isValid(publicKey: SigningPublicKey) throws -> Bool { + public init(jwtString: String) { + self.jwtString = jwtString + } + + public func isValid(publicKey: SigningPublicKey) throws -> Bool { var components = jwtString.components(separatedBy: ".") guard components.count == 3 else { throw JWTError.undefinedFormat } diff --git a/Sources/WalletConnectVerify/VerifyClient.swift b/Sources/WalletConnectVerify/VerifyClient.swift index 9e834d7ed..b4090be24 100644 --- a/Sources/WalletConnectVerify/VerifyClient.swift +++ b/Sources/WalletConnectVerify/VerifyClient.swift @@ -36,7 +36,11 @@ public actor VerifyClient: VerifyClientProtocol { public func verifyOrigin(assertionId: String) async throws -> VerifyResponse { return try await originVerifier.verifyOrigin(assertionId: assertionId) } - + + public func verifyOrigin(attestationJWT: String, messageId: String) async throws -> VerifyResponse { + + } + nonisolated public func createVerifyContext(origin: String?, domain: String, isScam: Bool?) -> VerifyContext { verifyContextFactory.createVerifyContext(origin: origin, domain: domain, isScam: isScam) } @@ -70,3 +74,69 @@ public struct VerifyClientMock: VerifyClientProtocol { } #endif + +class AttestationJWTVerifier { + + enum Errors: Error { + case issuerDoesNotMatchVerifyServerPubKey + case messageIdMismatch + case invalidJWT + } + + let verifyServerPubKeyManager: VerifyServerPubKeyManager + + init(verifyServerPubKeyManager: VerifyServerPubKeyManager) { + self.verifyServerPubKeyManager = verifyServerPubKeyManager + } + + // messageId - hash of the encrypted message supplied in the request + func verify(attestationJWT: String, messageId: String) async throws { + do { + let verifyServerPubKey = try await verifyServerPubKeyManager.getPublicKey() + try verifyJWTAgainstPubKey(attestationJWT, pubKey: verifyServerPubKey) + } catch { + let refreshedVerifyServerPubKey = try await verifyServerPubKeyManager.refreshKey() + try verifyJWTAgainstPubKey(attestationJWT, pubKey: refreshedVerifyServerPubKey) + } + + let claims = try decodeJWTClaims(jwtString: attestationJWT) + guard messageId == claims.id else { + throw Errors.messageIdMismatch + } + } + + func verifyJWTAgainstPubKey(_ jwtString: String, pubKey: String) throws { + let signingPubKey = try SigningPublicKey(hex: pubKey) + + let validator = JWTValidator(jwtString: jwtString) + guard try validator.isValid(publicKey: signingPubKey) else { + throw Errors.invalidJWT + } + } + + private func decodeJWTClaims(jwtString: String) throws -> AttestationJWTClaims { + let components = jwtString.components(separatedBy: ".") + + guard components.count == 3 else { throw Errors.invalidJWT } + + let payload = components[1] + guard let payloadData = Data(base64urlEncoded: payload) else { + throw Errors.invalidJWT + } + + let claims = try JSONDecoder().decode(AttestationJWTClaims.self, from: payloadData) + return claims + } +} + +struct AttestationJWTClaims: Codable { + + var exp: UInt64 + + var isScam: Bool? + + var id: String + + var origin: String +} + diff --git a/Sources/WalletConnectVerify/VerifyImports.swift b/Sources/WalletConnectVerify/VerifyImports.swift index 065c96db2..c47a6a9ba 100644 --- a/Sources/WalletConnectVerify/VerifyImports.swift +++ b/Sources/WalletConnectVerify/VerifyImports.swift @@ -1,4 +1,6 @@ #if !CocoaPods @_exported import WalletConnectUtils @_exported import WalletConnectNetworking +@_exported import WalletConnectJWT +@_exported import WalletConnectKMS #endif diff --git a/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift b/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift index b0f8c1457..c8acc994c 100644 --- a/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift +++ b/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift @@ -10,7 +10,6 @@ class VerifyServerPubKeyManager { self.fetcher = fetcher } - // Public async function to get the public key func getPublicKey() async throws -> String { if let localKey = try getPublicKeyFromLocalStorage(), !isKeyExpired(localKey) { return localKey.publicKey @@ -21,18 +20,21 @@ class VerifyServerPubKeyManager { } } - // Private function to get the public key from local storage + func refreshKey() async throws -> String { + let serverKey = try await fetcher.fetchPublicKey() + savePublicKeyToLocalStorage(publicKey: serverKey) + return serverKey.publicKey + } + private func getPublicKeyFromLocalStorage() throws -> PublicKeyFetcher.VerifyServerPublicKey? { return try store.get(key: Self.publicKeyStorageKey) } - // Private function to check if the key is expired private func isKeyExpired(_ key: PublicKeyFetcher.VerifyServerPublicKey) -> Bool { let currentTime = Date().timeIntervalSince1970 return currentTime >= key.expiresAt } - // Private function to save the public key to local storage private func savePublicKeyToLocalStorage(publicKey: PublicKeyFetcher.VerifyServerPublicKey) { store.set(publicKey, forKey: Self.publicKeyStorageKey) } From 94dd25821c2ffdc1e09b7e48be6d0b50bbc3d33d Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Jul 2024 15:59:44 +0900 Subject: [PATCH 03/26] fix build --- .../AttestationJWTVerifier.swift | 69 +++++++++++++++ .../PublicKeyFetcher.swift | 11 ++- .../WalletConnectVerify/VerifyClient.swift | 88 ++++--------------- .../VerifyClientFactory.swift | 7 +- .../VerifyServerPubKeyManager.swift | 6 +- 5 files changed, 104 insertions(+), 77 deletions(-) create mode 100644 Sources/WalletConnectVerify/AttestationJWTVerifier.swift diff --git a/Sources/WalletConnectVerify/AttestationJWTVerifier.swift b/Sources/WalletConnectVerify/AttestationJWTVerifier.swift new file mode 100644 index 000000000..3d7ede265 --- /dev/null +++ b/Sources/WalletConnectVerify/AttestationJWTVerifier.swift @@ -0,0 +1,69 @@ + +import Foundation + +struct AttestationJWTClaims: Codable { + + var exp: UInt64 + + var isScam: Bool? + + var id: String + + var origin: String +} + +class AttestationJWTVerifier { + + enum Errors: Error { + case issuerDoesNotMatchVerifyServerPubKey + case messageIdMismatch + case invalidJWT + } + + let verifyServerPubKeyManager: VerifyServerPubKeyManager + + init(verifyServerPubKeyManager: VerifyServerPubKeyManager) { + self.verifyServerPubKeyManager = verifyServerPubKeyManager + } + + // messageId - hash of the encrypted message supplied in the request + func verify(attestationJWT: String, messageId: String) async throws -> VerifyResponse { + do { + let verifyServerPubKey = try await verifyServerPubKeyManager.getPublicKey() + try verifyJWTAgainstPubKey(attestationJWT, pubKey: verifyServerPubKey) + } catch { + let refreshedVerifyServerPubKey = try await verifyServerPubKeyManager.refreshKey() + try verifyJWTAgainstPubKey(attestationJWT, pubKey: refreshedVerifyServerPubKey) + } + + let claims = try decodeJWTClaims(jwtString: attestationJWT) + guard messageId == claims.id else { + throw Errors.messageIdMismatch + } + + return VerifyResponse(origin: claims.origin, isScam: claims.isScam) + } + + func verifyJWTAgainstPubKey(_ jwtString: String, pubKey: String) throws { + let signingPubKey = try SigningPublicKey(hex: pubKey) + + let validator = JWTValidator(jwtString: jwtString) + guard try validator.isValid(publicKey: signingPubKey) else { + throw Errors.invalidJWT + } + } + + private func decodeJWTClaims(jwtString: String) throws -> AttestationJWTClaims { + let components = jwtString.components(separatedBy: ".") + + guard components.count == 3 else { throw Errors.invalidJWT } + + let payload = components[1] + guard let payloadData = Data(base64urlEncoded: payload) else { + throw Errors.invalidJWT + } + + let claims = try JSONDecoder().decode(AttestationJWTClaims.self, from: payloadData) + return claims + } +} diff --git a/Sources/WalletConnectVerify/PublicKeyFetcher.swift b/Sources/WalletConnectVerify/PublicKeyFetcher.swift index 849037f05..517761f15 100644 --- a/Sources/WalletConnectVerify/PublicKeyFetcher.swift +++ b/Sources/WalletConnectVerify/PublicKeyFetcher.swift @@ -1,7 +1,10 @@ import Foundation // PublicKeyFetcher class -class PublicKeyFetcher { +protocol PublicKeyFetching { + func fetchPublicKey() async throws -> PublicKeyFetcher.VerifyServerPublicKey +} +class PublicKeyFetcher: PublicKeyFetching { struct VerifyServerPublicKey: Codable { let publicKey: String let expiresAt: TimeInterval @@ -21,11 +24,11 @@ class PublicKeyFetcher { } #if DEBUG -class MockPublicKeyFetcher: PublicKeyFetcher { - var publicKey: VerifyServerPublicKey? +class MockPublicKeyFetcher: PublicKeyFetching { + var publicKey: PublicKeyFetcher.VerifyServerPublicKey? var error: Error? - override func fetchPublicKey() async throws -> VerifyServerPublicKey { + func fetchPublicKey() async throws -> PublicKeyFetcher.VerifyServerPublicKey { if let error = error { throw error } diff --git a/Sources/WalletConnectVerify/VerifyClient.swift b/Sources/WalletConnectVerify/VerifyClient.swift index b4090be24..4905f1fc2 100644 --- a/Sources/WalletConnectVerify/VerifyClient.swift +++ b/Sources/WalletConnectVerify/VerifyClient.swift @@ -7,6 +7,11 @@ public protocol VerifyClientProtocol { func createVerifyContextForLinkMode(redirectUniversalLink: String, domain: String) -> VerifyContext } +public enum VerificationType { + case v1(assertionId: String) + case v2(attestationJWT: String, messageId: String) +} + public actor VerifyClient: VerifyClientProtocol { enum Errors: Error { case attestationNotSupported @@ -16,17 +21,20 @@ public actor VerifyClient: VerifyClientProtocol { let assertionRegistrer: AssertionRegistrer let appAttestationRegistrer: AppAttestationRegistrer let verifyContextFactory: VerifyContextFactory + let attestationVerifier: AttestationJWTVerifier init( originVerifier: OriginVerifier, assertionRegistrer: AssertionRegistrer, appAttestationRegistrer: AppAttestationRegistrer, - verifyContextFactory: VerifyContextFactory = VerifyContextFactory() + verifyContextFactory: VerifyContextFactory = VerifyContextFactory(), + attestationVerifier: AttestationJWTVerifier ) { self.originVerifier = originVerifier self.assertionRegistrer = assertionRegistrer self.appAttestationRegistrer = appAttestationRegistrer self.verifyContextFactory = verifyContextFactory + self.attestationVerifier = attestationVerifier } public func registerAttestationIfNeeded() async throws { @@ -37,8 +45,16 @@ public actor VerifyClient: VerifyClientProtocol { return try await originVerifier.verifyOrigin(assertionId: assertionId) } - public func verifyOrigin(attestationJWT: String, messageId: String) async throws -> VerifyResponse { - + /// Verify V2 attestation JWT + /// messageId - hash of the encrypted message supplied in the request + /// assertionId - hash of decrytped message + public func verify(_ verificationType: VerificationType) async throws -> VerifyResponse { + switch verificationType { + case .v1(let assertionId): + return try await verifyOrigin(assertionId: assertionId) + case .v2(let attestationJWT, let messageId): + return try await attestationVerifier.verify(attestationJWT: attestationJWT, messageId: messageId) + } } nonisolated public func createVerifyContext(origin: String?, domain: String, isScam: Bool?) -> VerifyContext { @@ -74,69 +90,3 @@ public struct VerifyClientMock: VerifyClientProtocol { } #endif - -class AttestationJWTVerifier { - - enum Errors: Error { - case issuerDoesNotMatchVerifyServerPubKey - case messageIdMismatch - case invalidJWT - } - - let verifyServerPubKeyManager: VerifyServerPubKeyManager - - init(verifyServerPubKeyManager: VerifyServerPubKeyManager) { - self.verifyServerPubKeyManager = verifyServerPubKeyManager - } - - // messageId - hash of the encrypted message supplied in the request - func verify(attestationJWT: String, messageId: String) async throws { - do { - let verifyServerPubKey = try await verifyServerPubKeyManager.getPublicKey() - try verifyJWTAgainstPubKey(attestationJWT, pubKey: verifyServerPubKey) - } catch { - let refreshedVerifyServerPubKey = try await verifyServerPubKeyManager.refreshKey() - try verifyJWTAgainstPubKey(attestationJWT, pubKey: refreshedVerifyServerPubKey) - } - - let claims = try decodeJWTClaims(jwtString: attestationJWT) - guard messageId == claims.id else { - throw Errors.messageIdMismatch - } - } - - func verifyJWTAgainstPubKey(_ jwtString: String, pubKey: String) throws { - let signingPubKey = try SigningPublicKey(hex: pubKey) - - let validator = JWTValidator(jwtString: jwtString) - guard try validator.isValid(publicKey: signingPubKey) else { - throw Errors.invalidJWT - } - } - - private func decodeJWTClaims(jwtString: String) throws -> AttestationJWTClaims { - let components = jwtString.components(separatedBy: ".") - - guard components.count == 3 else { throw Errors.invalidJWT } - - let payload = components[1] - guard let payloadData = Data(base64urlEncoded: payload) else { - throw Errors.invalidJWT - } - - let claims = try JSONDecoder().decode(AttestationJWTClaims.self, from: payloadData) - return claims - } -} - -struct AttestationJWTClaims: Codable { - - var exp: UInt64 - - var isScam: Bool? - - var id: String - - var origin: String -} - diff --git a/Sources/WalletConnectVerify/VerifyClientFactory.swift b/Sources/WalletConnectVerify/VerifyClientFactory.swift index 8b230fc5f..632173aa7 100644 --- a/Sources/WalletConnectVerify/VerifyClientFactory.swift +++ b/Sources/WalletConnectVerify/VerifyClientFactory.swift @@ -17,10 +17,15 @@ public class VerifyClientFactory { attestChallengeProvider: attestChallengeProvider, keyAttestationService: keyAttestationService ) + let verifyServerPubKeyManagerStore: CodableStore = CodableStore(defaults: keyValueStorage, identifier: "com.walletconnect.verify") + + let verifyServerPubKeyManager = VerifyServerPubKeyManager(store: verifyServerPubKeyManagerStore) + let attestationVerifier = AttestationJWTVerifier(verifyServerPubKeyManager: verifyServerPubKeyManager) return VerifyClient( originVerifier: originVerifier, assertionRegistrer: assertionRegistrer, - appAttestationRegistrer: appAttestationRegistrer + appAttestationRegistrer: appAttestationRegistrer, + attestationVerifier: attestationVerifier ) } } diff --git a/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift b/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift index c8acc994c..dac227ed4 100644 --- a/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift +++ b/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift @@ -1,11 +1,11 @@ import Foundation class VerifyServerPubKeyManager { - static let publicKeyStorageKey = "com.walletconnect.verify.pubKey" + static let publicKeyStorageKey = "verify_server_pub_key" private let store: CodableStore - private let fetcher: PublicKeyFetcher + private let fetcher: PublicKeyFetching - init(store: CodableStore, fetcher: PublicKeyFetcher) { + init(store: CodableStore, fetcher: PublicKeyFetching = PublicKeyFetcher()) { self.store = store self.fetcher = fetcher } From 601a01bd0dbc21a1ffe5641018a8bf5cdf45e3a1 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Jul 2024 18:05:58 +0900 Subject: [PATCH 04/26] savepoint --- .../NetworkInteracting.swift | 2 +- .../NetworkingInteractor.swift | 34 ++++++++++++------- .../RequestSubscriptionPayload.swift | 6 +++- .../PairingRequestsSubscriber.swift | 2 +- Sources/WalletConnectRelay/RelayClient.swift | 6 ++-- .../WalletConnectVerify/VerifyClient.swift | 14 +++----- .../VerifyServerPubKeyManager.swift | 15 ++++++++ 7 files changed, 52 insertions(+), 27 deletions(-) diff --git a/Sources/WalletConnectNetworking/NetworkInteracting.swift b/Sources/WalletConnectNetworking/NetworkInteracting.swift index e916c3f62..97d85b47c 100644 --- a/Sources/WalletConnectNetworking/NetworkInteracting.swift +++ b/Sources/WalletConnectNetworking/NetworkInteracting.swift @@ -5,7 +5,7 @@ public protocol NetworkInteracting { var isSocketConnected: Bool { get } var socketConnectionStatusPublisher: AnyPublisher { get } var networkConnectionStatusPublisher: AnyPublisher { get } - var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?), Never> { get } + var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?, encryptedMessage: String?, attestation: String?), Never> { get } func subscribe(topic: String) async throws func unsubscribe(topic: String) func batchSubscribe(topics: [String]) async throws diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index e7c8eb90d..3089b9e43 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -9,10 +9,10 @@ public class NetworkingInteractor: NetworkInteracting { private let rpcHistory: RPCHistory private let logger: ConsoleLogging - private let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?), Never>() + private let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?, encryptedMessage: String?, attestation: String?), Never>() private let responsePublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, response: RPCResponse, publishedAt: Date, derivedTopic: String?), Never>() - public var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?), Never> { + public var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?, encryptedMessage: String?, attestation: String?), Never> { requestPublisherSubject.eraseToAnyPublisher() } @@ -51,8 +51,8 @@ public class NetworkingInteractor: NetworkInteracting { private func setupRelaySubscribtion() { relayClient.messagePublisher - .sink { [unowned self] (topic, message, publishedAt) in - manageSubscription(topic, message, publishedAt) + .sink { [unowned self] (topic, message, publishedAt, attestation) in + manageSubscription(topic, message, publishedAt, attestation) }.store(in: &publishers) } @@ -123,19 +123,29 @@ public class NetworkingInteractor: NetworkInteracting { }.store(in: &publishers) } + public func requestSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { return requestPublisher .filter { rpcRequest in return rpcRequest.request.method == request.method } - .compactMap { [weak self] topic, rpcRequest, decryptedPayload, publishedAt, derivedTopic in + .compactMap { [weak self] topic, rpcRequest, decryptedPayload, publishedAt, derivedTopic, encryptedMessage, attestation in do { guard let id = rpcRequest.id, let request = try rpcRequest.params?.get(RequestParams.self) else { return nil } - return RequestSubscriptionPayload(id: id, topic: topic, request: request, decryptedPayload: decryptedPayload, publishedAt: publishedAt, derivedTopic: derivedTopic) + return RequestSubscriptionPayload( + id: id, + topic: topic, + request: request, + decryptedPayload: decryptedPayload, + publishedAt: publishedAt, + derivedTopic: derivedTopic, + encryptedMessage: encryptedMessage, + attestation: attestation + ) } catch { self?.logger.debug("Networking Interactor - \(error)") + return nil } - return nil } .eraseToAnyPublisher() } @@ -245,11 +255,11 @@ public class NetworkingInteractor: NetworkInteracting { try await respond(topic: topic, response: response, protocolMethod: protocolMethod, envelopeType: envelopeType) } - private func manageSubscription(_ topic: String, _ encodedEnvelope: String, _ publishedAt: Date) { + private func manageSubscription(_ topic: String, _ encodedEnvelope: String, _ publishedAt: Date, _ attestation: String?) { if let result = serializer.tryDeserializeRequestOrResponse(topic: topic, codingType: .base64Encoded, envelopeString: encodedEnvelope) { switch result { case .left(let result): - handleRequest(topic: topic, request: result.request, decryptedPayload: result.decryptedPayload, publishedAt: publishedAt, derivedTopic: result.derivedTopic) + handleRequest(topic: topic, request: result.request, decryptedPayload: result.decryptedPayload, publishedAt: publishedAt, derivedTopic: result.derivedTopic, encryptedMessage: encodedEnvelope, attestation: attestation) case .right(let result): handleResponse(topic: topic, response: result.response, publishedAt: publishedAt, derivedTopic: result.derivedTopic) } @@ -259,13 +269,13 @@ public class NetworkingInteractor: NetworkInteracting { } public func handleHistoryRequest(topic: String, request: RPCRequest) { - requestPublisherSubject.send((topic, request, Data(), Date(), nil)) + requestPublisherSubject.send((topic, request, Data(), Date(), nil, nil, nil )) } - private func handleRequest(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?) { + private func handleRequest(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?, encryptedMessage: String?, attestation: String?) { do { try rpcHistory.set(request, forTopic: topic, emmitedBy: .remote, transportType: .relay) - requestPublisherSubject.send((topic, request, decryptedPayload, publishedAt, derivedTopic)) + requestPublisherSubject.send((topic, request, decryptedPayload, publishedAt, derivedTopic, encryptedMessage, attestation)) } catch { logger.debug(error) } diff --git a/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift b/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift index 135378a99..9ec16a154 100644 --- a/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift +++ b/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift @@ -2,18 +2,22 @@ import Foundation public struct RequestSubscriptionPayload: Codable, SubscriptionPayload { public let id: RPCID + public let encryptedMessage: String? + public let attestation: String? public let topic: String public let request: Request public let decryptedPayload: Data public let publishedAt: Date public let derivedTopic: String? - public init(id: RPCID, topic: String, request: Request, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?) { + public init(id: RPCID, topic: String, request: Request, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?, encryptedMessage: String?, attestation: String?) { self.id = id self.topic = topic self.request = request self.decryptedPayload = decryptedPayload self.publishedAt = publishedAt self.derivedTopic = derivedTopic + self.encryptedMessage = encryptedMessage + self.attestation = attestation } } diff --git a/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift b/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift index c3327e482..089eed549 100644 --- a/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift +++ b/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift @@ -28,7 +28,7 @@ public class PairingRequestsSubscriber { .filter { [unowned self] in !pairingProtocolMethods.contains($0.request.method)} .filter { [unowned self] in pairingStorage.hasPairing(forTopic: $0.topic)} .filter { [unowned self] in !registeredProtocolMethods.contains($0.request.method)} - .sink { [unowned self] topic, request, _, _, _ in + .sink { [unowned self] topic, request, _, _, _, _, _ in Task(priority: .high) { let protocolMethod = UnsupportedProtocolMethod(method: request.method) logger.debug("PairingRequestsSubscriber: responding unregistered request method") diff --git a/Sources/WalletConnectRelay/RelayClient.swift b/Sources/WalletConnectRelay/RelayClient.swift index 93b33f1c7..f51f69c84 100644 --- a/Sources/WalletConnectRelay/RelayClient.swift +++ b/Sources/WalletConnectRelay/RelayClient.swift @@ -26,7 +26,7 @@ public final class RelayClient { return dispatcher.isSocketConnected } - public var messagePublisher: AnyPublisher<(topic: String, message: String, publishedAt: Date), Never> { + public var messagePublisher: AnyPublisher<(topic: String, message: String, publishedAt: Date, attestation: String?), Never> { messagePublisherSubject.eraseToAnyPublisher() } @@ -38,7 +38,7 @@ public final class RelayClient { dispatcher.networkConnectionStatusPublisher } - private let messagePublisherSubject = PassthroughSubject<(topic: String, message: String, publishedAt: Date), Never>() + private let messagePublisherSubject = PassthroughSubject<(topic: String, message: String, publishedAt: Date, attestation: String?), Never>() private let subscriptionResponsePublisherSubject = PassthroughSubject<(RPCID?, [String]), Never>() private var subscriptionResponsePublisher: AnyPublisher<(RPCID?, [String]), Never> { @@ -238,7 +238,7 @@ public final class RelayClient { try acknowledgeRequest(request) try rpcHistory.set(request, forTopic: params.data.topic, emmitedBy: .remote, transportType: .relay) logger.debug("received message: \(params.data.message) on topic: \(params.data.topic)") - messagePublisherSubject.send((params.data.topic, params.data.message, params.data.publishedAt)) + messagePublisherSubject.send((params.data.topic, params.data.message, params.data.publishedAt, params.data.attestation)) } catch { logger.error("RPC History 'set()' error: \(error)") } diff --git a/Sources/WalletConnectVerify/VerifyClient.swift b/Sources/WalletConnectVerify/VerifyClient.swift index 4905f1fc2..0d56ad089 100644 --- a/Sources/WalletConnectVerify/VerifyClient.swift +++ b/Sources/WalletConnectVerify/VerifyClient.swift @@ -2,7 +2,7 @@ import DeviceCheck import Foundation public protocol VerifyClientProtocol { - func verifyOrigin(assertionId: String) async throws -> VerifyResponse + func verify(_ verificationType: VerificationType) async throws -> VerifyResponse func createVerifyContext(origin: String?, domain: String, isScam: Bool?) -> VerifyContext func createVerifyContextForLinkMode(redirectUniversalLink: String, domain: String) -> VerifyContext } @@ -41,17 +41,13 @@ public actor VerifyClient: VerifyClientProtocol { try await appAttestationRegistrer.registerAttestationIfNeeded() } - public func verifyOrigin(assertionId: String) async throws -> VerifyResponse { - return try await originVerifier.verifyOrigin(assertionId: assertionId) - } - /// Verify V2 attestation JWT /// messageId - hash of the encrypted message supplied in the request /// assertionId - hash of decrytped message public func verify(_ verificationType: VerificationType) async throws -> VerifyResponse { switch verificationType { case .v1(let assertionId): - return try await verifyOrigin(assertionId: assertionId) + return try await originVerifier.verifyOrigin(assertionId: assertionId) case .v2(let attestationJWT, let messageId): return try await attestationVerifier.verify(attestationJWT: attestationJWT, messageId: messageId) } @@ -75,11 +71,11 @@ public actor VerifyClient: VerifyClientProtocol { public struct VerifyClientMock: VerifyClientProtocol { public init() {} - - public func verifyOrigin(assertionId: String) async throws -> VerifyResponse { + + public func verify(_ verificationType: VerificationType) async throws -> VerifyResponse { return VerifyResponse(origin: "domain.com", isScam: nil) } - + public func createVerifyContext(origin: String?, domain: String, isScam: Bool?) -> VerifyContext { return VerifyContext(origin: "domain.com", validation: .valid) } diff --git a/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift b/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift index dac227ed4..e91217b48 100644 --- a/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift +++ b/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift @@ -8,6 +8,21 @@ class VerifyServerPubKeyManager { init(store: CodableStore, fetcher: PublicKeyFetching = PublicKeyFetcher()) { self.store = store self.fetcher = fetcher + + // Check if there is a cached, non-expired key on initialization + Task { + do { + if let localKey = try getPublicKeyFromLocalStorage(), !isKeyExpired(localKey) { + // Key is valid, no action needed + } else { + // No valid key, fetch and store a new one + let serverKey = try await fetcher.fetchPublicKey() + savePublicKeyToLocalStorage(publicKey: serverKey) + } + } catch { + print("Failed to initialize public key: \(error)") + } + } } func getPublicKey() async throws -> String { From e98962bba214f38fe866e98463f3eeaace075543 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Jul 2024 18:32:44 +0900 Subject: [PATCH 05/26] savepoint --- .../WalletConnectNetworking/NetworkInteracting.swift | 2 +- .../WalletConnectNetworking/NetworkingInteractor.swift | 8 ++++---- .../RequestSubscriptionPayload.swift | 4 ++-- .../Auth/Link/LinkEnvelopesDispatcher.swift | 2 +- .../Engine/Common/SessionEngine.swift | 10 ++++++++-- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Sources/WalletConnectNetworking/NetworkInteracting.swift b/Sources/WalletConnectNetworking/NetworkInteracting.swift index 97d85b47c..bfef71546 100644 --- a/Sources/WalletConnectNetworking/NetworkInteracting.swift +++ b/Sources/WalletConnectNetworking/NetworkInteracting.swift @@ -5,7 +5,7 @@ public protocol NetworkInteracting { var isSocketConnected: Bool { get } var socketConnectionStatusPublisher: AnyPublisher { get } var networkConnectionStatusPublisher: AnyPublisher { get } - var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?, encryptedMessage: String?, attestation: String?), Never> { get } + var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?, encryptedMessage: String, attestation: String?), Never> { get } func subscribe(topic: String) async throws func unsubscribe(topic: String) func batchSubscribe(topics: [String]) async throws diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index 3089b9e43..5b106cdb8 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -9,10 +9,10 @@ public class NetworkingInteractor: NetworkInteracting { private let rpcHistory: RPCHistory private let logger: ConsoleLogging - private let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?, encryptedMessage: String?, attestation: String?), Never>() + private let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?, encryptedMessage: String, attestation: String?), Never>() private let responsePublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, response: RPCResponse, publishedAt: Date, derivedTopic: String?), Never>() - public var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?, encryptedMessage: String?, attestation: String?), Never> { + public var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?, encryptedMessage: String, attestation: String?), Never> { requestPublisherSubject.eraseToAnyPublisher() } @@ -269,10 +269,10 @@ public class NetworkingInteractor: NetworkInteracting { } public func handleHistoryRequest(topic: String, request: RPCRequest) { - requestPublisherSubject.send((topic, request, Data(), Date(), nil, nil, nil )) + requestPublisherSubject.send((topic, request, Data(), Date(), nil, "", nil )) } - private func handleRequest(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?, encryptedMessage: String?, attestation: String?) { + private func handleRequest(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?, encryptedMessage: String, attestation: String?) { do { try rpcHistory.set(request, forTopic: topic, emmitedBy: .remote, transportType: .relay) requestPublisherSubject.send((topic, request, decryptedPayload, publishedAt, derivedTopic, encryptedMessage, attestation)) diff --git a/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift b/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift index 9ec16a154..9a532441b 100644 --- a/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift +++ b/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift @@ -2,7 +2,7 @@ import Foundation public struct RequestSubscriptionPayload: Codable, SubscriptionPayload { public let id: RPCID - public let encryptedMessage: String? + public let encryptedMessage: String public let attestation: String? public let topic: String public let request: Request @@ -10,7 +10,7 @@ public struct RequestSubscriptionPayload: Codable, Subscriptio public let publishedAt: Date public let derivedTopic: String? - public init(id: RPCID, topic: String, request: Request, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?, encryptedMessage: String?, attestation: String?) { + public init(id: RPCID, topic: String, request: Request, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?, encryptedMessage: String, attestation: String?) { self.id = id self.topic = topic self.request = request diff --git a/Sources/WalletConnectSign/Auth/Link/LinkEnvelopesDispatcher.swift b/Sources/WalletConnectSign/Auth/Link/LinkEnvelopesDispatcher.swift index 2ac026b5a..987a8aae8 100644 --- a/Sources/WalletConnectSign/Auth/Link/LinkEnvelopesDispatcher.swift +++ b/Sources/WalletConnectSign/Auth/Link/LinkEnvelopesDispatcher.swift @@ -152,7 +152,7 @@ final class LinkEnvelopesDispatcher { guard let id = rpcRequest.id, let request = try rpcRequest.params?.get(RequestParams.self) else { return nil } - return RequestSubscriptionPayload(id: id, topic: topic, request: request, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil) + return RequestSubscriptionPayload(id: id, topic: topic, request: request, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil, encryptedMessage: "", attestation: nil) } catch { self?.logger.debug(error) } diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index 2933da364..97902c4ee 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -215,9 +215,15 @@ private extension SessionEngine { return respondError(payload: payload, reason: .sessionRequestExpired, protocolMethod: protocolMethod) } Task(priority: .high) { - let assertionId = payload.decryptedPayload.sha256().toHexString() do { - let response = try await verifyClient.verifyOrigin(assertionId: assertionId) + let response: VerifyResponse + if let attestation = payload.attestation, + let messageId = payload.encryptedMessage.data(using: .utf8)?.sha256().toHexString() { + response = try await verifyClient.verify(.v2(attestationJWT: attestation, messageId: messageId)) + } else { + let assertionId = payload.decryptedPayload.sha256().toHexString() + response = try await verifyClient.verify(.v1(assertionId: assertionId)) + } let verifyContext = verifyClient.createVerifyContext(origin: response.origin, domain: session.peerParticipant.metadata.url, isScam: response.isScam) verifyContextStore.set(verifyContext, forKey: request.id.string) From dffbf647e4788be06da029a39ed7f9bc976dac84 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Jul 2024 18:36:19 +0900 Subject: [PATCH 06/26] update verify on auth request --- .../Auth/Services/Wallet/AuthRequestSubscriber.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Sources/WalletConnectSign/Auth/Services/Wallet/AuthRequestSubscriber.swift b/Sources/WalletConnectSign/Auth/Services/Wallet/AuthRequestSubscriber.swift index a4389ee9f..53ccbe790 100644 --- a/Sources/WalletConnectSign/Auth/Services/Wallet/AuthRequestSubscriber.swift +++ b/Sources/WalletConnectSign/Auth/Services/Wallet/AuthRequestSubscriber.swift @@ -56,7 +56,14 @@ class AuthRequestSubscriber { Task(priority: .high) { let assertionId = payload.decryptedPayload.sha256().toHexString() do { - let response = try await verifyClient.verifyOrigin(assertionId: assertionId) + let response: VerifyResponse + if let attestation = payload.attestation, + let messageId = payload.encryptedMessage.data(using: .utf8)?.sha256().toHexString() { + response = try await verifyClient.verify(.v2(attestationJWT: attestation, messageId: messageId)) + } else { + let assertionId = payload.decryptedPayload.sha256().toHexString() + response = try await verifyClient.verify(.v1(assertionId: assertionId)) + } let verifyContext = verifyClient.createVerifyContext(origin: response.origin, domain: payload.request.authPayload.domain, isScam: response.isScam) verifyContextStore.set(verifyContext, forKey: request.id.string) onRequest?((request, verifyContext)) From 66104b1a060afa5ecb8d9577435d6248a8e8073c Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 Jul 2024 18:40:50 +0900 Subject: [PATCH 07/26] savepoint --- .../WalletConnectSign/Engine/Common/ApproveEngine.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index cd127d81a..def3ed993 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -397,7 +397,14 @@ private extension ApproveEngine { Task(priority: .high) { let assertionId = payload.decryptedPayload.sha256().toHexString() do { - let response = try await verifyClient.verifyOrigin(assertionId: assertionId) + let response: VerifyResponse + if let attestation = payload.attestation, + let messageId = payload.encryptedMessage.data(using: .utf8)?.sha256().toHexString() { + response = try await verifyClient.verify(.v2(attestationJWT: attestation, messageId: messageId)) + } else { + let assertionId = payload.decryptedPayload.sha256().toHexString() + response = try await verifyClient.verify(.v1(assertionId: assertionId)) + } let verifyContext = verifyClient.createVerifyContext( origin: response.origin, domain: payload.request.proposer.metadata.url, From f23fc74f0b0aecb4eb2b89fb7ffcb8ab0665a06b Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 24 Jul 2024 14:31:21 +0900 Subject: [PATCH 08/26] store verify context in session object --- .../Link/ApproveSessionAuthenticateUtil.swift | 6 +++-- .../LinkSessionAuthenticateResponder.swift | 14 ++++++++--- .../Services/App/AuthResponseSubscriber.swift | 3 ++- .../Wallet/SessionAuthenticateResponder.swift | 4 +++- .../Engine/Common/ApproveEngine.swift | 8 +++++-- .../Engine/Common/SessionEngine.swift | 23 +++---------------- .../Sign/SignClientFactory.swift | 2 +- .../Types/Session/WCSession.swift | 13 ++++++++--- 8 files changed, 40 insertions(+), 33 deletions(-) diff --git a/Sources/WalletConnectSign/Auth/Link/ApproveSessionAuthenticateUtil.swift b/Sources/WalletConnectSign/Auth/Link/ApproveSessionAuthenticateUtil.swift index c69461c8a..78e4e9b06 100644 --- a/Sources/WalletConnectSign/Auth/Link/ApproveSessionAuthenticateUtil.swift +++ b/Sources/WalletConnectSign/Auth/Link/ApproveSessionAuthenticateUtil.swift @@ -66,7 +66,8 @@ class ApproveSessionAuthenticateUtil { pairingTopic: String, request: SessionAuthenticateRequestParams, sessionTopic: String, - transportType: WCSession.TransportType + transportType: WCSession.TransportType, + verifyContext: VerifyContext ) throws -> Session? { @@ -101,7 +102,8 @@ class ApproveSessionAuthenticateUtil { settleParams: settleParams, requiredNamespaces: [:], acknowledged: true, - transportType: transportType + transportType: transportType, + verifyContext: verifyContext ) logger.debug("created a session with topic: \(sessionTopic)") diff --git a/Sources/WalletConnectSign/Auth/Link/LinkSessionAuthenticateResponder.swift b/Sources/WalletConnectSign/Auth/Link/LinkSessionAuthenticateResponder.swift index e6116d4ef..b176dd1aa 100644 --- a/Sources/WalletConnectSign/Auth/Link/LinkSessionAuthenticateResponder.swift +++ b/Sources/WalletConnectSign/Auth/Link/LinkSessionAuthenticateResponder.swift @@ -11,6 +11,7 @@ actor LinkSessionAuthenticateResponder { private let metadata: AppMetadata private let util: ApproveSessionAuthenticateUtil private let walletErrorResponder: WalletErrorResponder + private let verifyContextStore: CodableStore init( linkEnvelopesDispatcher: LinkEnvelopesDispatcher, @@ -18,10 +19,12 @@ actor LinkSessionAuthenticateResponder { kms: KeyManagementService, metadata: AppMetadata, approveSessionAuthenticateUtil: ApproveSessionAuthenticateUtil, - walletErrorResponder: WalletErrorResponder + walletErrorResponder: WalletErrorResponder, + verifyContextStore: CodableStore ) { self.linkEnvelopesDispatcher = linkEnvelopesDispatcher self.logger = logger + self.verifyContextStore = verifyContextStore self.kms = kms self.metadata = metadata self.util = approveSessionAuthenticateUtil @@ -57,20 +60,25 @@ actor LinkSessionAuthenticateResponder { let url = try await linkEnvelopesDispatcher.respond(topic: responseTopic, response: response, peerUniversalLink: peerUniversalLink, envelopeType: .type1(pubKey: responseKeys.publicKey.rawRepresentation)) + let verifyContext = (try? verifyContextStore.get(key: requestId.string)) ?? VerifyContext(origin: nil, validation: .unknown) let session = try util.createSession( response: responseParams, pairingTopic: pairingTopic, request: sessionAuthenticateRequestParams, sessionTopic: sessionTopic, - transportType: .linkMode + transportType: .linkMode, + verifyContext: verifyContext ) + verifyContextStore.delete(forKey: requestId.string) + return (session, url) } func respondError(requestId: RPCID) async throws { - try await walletErrorResponder.respondError(AuthError.userRejeted, requestId: requestId) + _ = try await walletErrorResponder.respondError(AuthError.userRejeted, requestId: requestId) + verifyContextStore.delete(forKey: requestId.string) } } diff --git a/Sources/WalletConnectSign/Auth/Services/App/AuthResponseSubscriber.swift b/Sources/WalletConnectSign/Auth/Services/App/AuthResponseSubscriber.swift index 03f0456be..602827661 100644 --- a/Sources/WalletConnectSign/Auth/Services/App/AuthResponseSubscriber.swift +++ b/Sources/WalletConnectSign/Auth/Services/App/AuthResponseSubscriber.swift @@ -200,7 +200,8 @@ class AuthResponseSubscriber { settleParams: settleParams, requiredNamespaces: [:], acknowledged: true, - transportType: transportType + transportType: transportType, + verifyContext: nil ) sessionStore.setSession(session) diff --git a/Sources/WalletConnectSign/Auth/Services/Wallet/SessionAuthenticateResponder.swift b/Sources/WalletConnectSign/Auth/Services/Wallet/SessionAuthenticateResponder.swift index dea7e0e9e..825225c0e 100644 --- a/Sources/WalletConnectSign/Auth/Services/Wallet/SessionAuthenticateResponder.swift +++ b/Sources/WalletConnectSign/Auth/Services/Wallet/SessionAuthenticateResponder.swift @@ -120,12 +120,14 @@ actor SessionAuthenticateResponder { } do { + let verifyContext = (try? verifyContextStore.get(key: requestId.string)) ?? VerifyContext(origin: nil, validation: .unknown) let session = try util.createSession( response: responseParams, pairingTopic: pairingTopic, request: sessionAuthenticateRequestParams, sessionTopic: sessionTopic, - transportType: .relay + transportType: .relay, + verifyContext: verifyContext ) pairingRegisterer.activate( pairingTopic: pairingTopic, diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index def3ed993..a94cae925 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -226,6 +226,8 @@ final class ApproveEngine { expiry: Int64(expiry) ) + let verifyContext = (try? verifyContextStore.get(key: proposal.proposer.publicKey)) ?? VerifyContext(origin: nil, validation: .unknown) + let session = WCSession( topic: topic, pairingTopic: pairingTopic, @@ -235,7 +237,8 @@ final class ApproveEngine { settleParams: settleParams, requiredNamespaces: proposal.requiredNamespaces, acknowledged: false, - transportType: .relay + transportType: .relay, + verifyContext: verifyContext ) logger.debug("Sending session settle request") @@ -467,7 +470,8 @@ private extension ApproveEngine { settleParams: params, requiredNamespaces: proposedNamespaces, acknowledged: true, - transportType: .relay + transportType: .relay, + verifyContext: nil ) sessionStore.setSession(session) diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index 97902c4ee..40997cde9 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -214,26 +214,9 @@ private extension SessionEngine { guard !request.isExpired() else { return respondError(payload: payload, reason: .sessionRequestExpired, protocolMethod: protocolMethod) } - Task(priority: .high) { - do { - let response: VerifyResponse - if let attestation = payload.attestation, - let messageId = payload.encryptedMessage.data(using: .utf8)?.sha256().toHexString() { - response = try await verifyClient.verify(.v2(attestationJWT: attestation, messageId: messageId)) - } else { - let assertionId = payload.decryptedPayload.sha256().toHexString() - response = try await verifyClient.verify(.v1(assertionId: assertionId)) - } - let verifyContext = verifyClient.createVerifyContext(origin: response.origin, domain: session.peerParticipant.metadata.url, isScam: response.isScam) - verifyContextStore.set(verifyContext, forKey: request.id.string) - - sessionRequestsProvider.emitRequestIfPending() - } catch { - let verifyContext = verifyClient.createVerifyContext(origin: nil, domain: session.peerParticipant.metadata.url, isScam: nil) - verifyContextStore.set(verifyContext, forKey: request.id.string) - sessionRequestsProvider.emitRequestIfPending() - } - } + let verifyContext = session.verifyContext ?? VerifyContext(origin: nil, validation: .unknown) + verifyContextStore.set(verifyContext, forKey: request.id.string) + sessionRequestsProvider.emitRequestIfPending() } func onSessionPing(payload: SubscriptionPayload) { diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index 6a9989112..caebc0840 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -131,7 +131,7 @@ public struct SignClientFactory { let relaySessionAuthenticateResponder = SessionAuthenticateResponder(networkingInteractor: networkingClient, logger: logger, kms: kms, verifyContextStore: verifyContextStore, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingClient, metadata: metadata, approveSessionAuthenticateUtil: approveSessionAuthenticateUtil, eventsClient: eventsClient) - let linkSessionAuthenticateResponder = LinkSessionAuthenticateResponder(linkEnvelopesDispatcher: linkEnvelopesDispatcher, logger: logger, kms: kms, metadata: metadata, approveSessionAuthenticateUtil: approveSessionAuthenticateUtil, walletErrorResponder: walletErrorResponder) + let linkSessionAuthenticateResponder = LinkSessionAuthenticateResponder(linkEnvelopesDispatcher: linkEnvelopesDispatcher, logger: logger, kms: kms, metadata: metadata, approveSessionAuthenticateUtil: approveSessionAuthenticateUtil, walletErrorResponder: walletErrorResponder, verifyContextStore: verifyContextStore) let approveSessionAuthenticateDispatcher = ApproveSessionAuthenticateDispatcher(relaySessionAuthenticateResponder: relaySessionAuthenticateResponder, logger: logger, rpcHistory: rpcHistory, approveSessionAuthenticateUtil: approveSessionAuthenticateUtil, linkSessionAuthenticateResponder: linkSessionAuthenticateResponder) diff --git a/Sources/WalletConnectSign/Types/Session/WCSession.swift b/Sources/WalletConnectSign/Types/Session/WCSession.swift index c5224bb00..3e40af9b2 100644 --- a/Sources/WalletConnectSign/Types/Session/WCSession.swift +++ b/Sources/WalletConnectSign/Types/Session/WCSession.swift @@ -17,6 +17,7 @@ struct WCSession: SequenceObject, Equatable { let peerParticipant: Participant let controller: AgreementPeer var transportType: TransportType + var verifyContext: VerifyContext? private(set) var acknowledged: Bool private(set) var expiryDate: Date @@ -41,7 +42,8 @@ struct WCSession: SequenceObject, Equatable { settleParams: SessionType.SettleParams, requiredNamespaces: [String: ProposalNamespace], acknowledged: Bool, - transportType: TransportType + transportType: TransportType, + verifyContext: VerifyContext? ) { self.topic = topic self.pairingTopic = pairingTopic @@ -56,6 +58,7 @@ struct WCSession: SequenceObject, Equatable { self.acknowledged = acknowledged self.expiryDate = Date(timeIntervalSince1970: TimeInterval(settleParams.expiry)) self.transportType = transportType + self.verifyContext = verifyContext } #if DEBUG @@ -74,7 +77,8 @@ struct WCSession: SequenceObject, Equatable { accounts: Set, acknowledged: Bool, expiryTimestamp: Int64, - transportType: TransportType + transportType: TransportType, + verifyContext: VerifyContext ) { self.topic = topic self.pairingTopic = pairingTopic @@ -89,6 +93,7 @@ struct WCSession: SequenceObject, Equatable { self.acknowledged = acknowledged self.expiryDate = Date(timeIntervalSince1970: TimeInterval(expiryTimestamp)) self.transportType = transportType + self.verifyContext = verifyContext } #endif @@ -192,7 +197,7 @@ struct WCSession: SequenceObject, Equatable { extension WCSession { enum CodingKeys: String, CodingKey { - case topic, pairingTopic, relay, selfParticipant, peerParticipant, expiryDate, acknowledged, controller, namespaces, timestamp, requiredNamespaces, sessionProperties, transportType + case topic, pairingTopic, relay, selfParticipant, peerParticipant, expiryDate, acknowledged, controller, namespaces, timestamp, requiredNamespaces, sessionProperties, transportType, verifyContext } init(from decoder: Decoder) throws { @@ -210,6 +215,7 @@ extension WCSession { self.requiredNamespaces = try container.decode([String: ProposalNamespace].self, forKey: .requiredNamespaces) self.pairingTopic = try container.decode(String.self, forKey: .pairingTopic) self.transportType = (try? container.decode(TransportType.self, forKey: .transportType)) ?? .relay + self.verifyContext = try? container.decode(VerifyContext.self, forKey: .verifyContext) } func encode(to encoder: Encoder) throws { @@ -227,5 +233,6 @@ extension WCSession { try container.encode(timestamp, forKey: .timestamp) try container.encode(requiredNamespaces, forKey: .requiredNamespaces) try container.encode(transportType, forKey: .transportType) + try container.encode(verifyContext, forKey: .verifyContext) } } From 9814f9551de3d0e9d9fce437b594284bef8dac38 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 24 Jul 2024 15:06:34 +0900 Subject: [PATCH 09/26] fix tests --- .../Wallet/WalletRequestSubscriber.swift | 2 +- .../WalletConnectVerify/PublicKeyFetcher.swift | 14 +++++++------- .../VerifyClientFactory.swift | 2 +- .../VerifyServerPubKeyManager.swift | 10 +++++----- Tests/RelayerTests/RelayClientTests.swift | 6 +++--- .../TestingUtils/NetworkingInteractorMock.swift | 8 ++++---- .../VerifyServerPubKeyManagerTests.swift | 16 +++++++++------- .../ApproveEngineTests.swift | 16 ++++++++-------- .../NonControllerSessionStateMachineTests.swift | 12 ++++++------ .../SessionEngineTests.swift | 2 +- .../Stub/Session+Stub.swift | 3 ++- 11 files changed, 47 insertions(+), 44 deletions(-) diff --git a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift index 7270871d7..f460252ce 100644 --- a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift +++ b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift @@ -44,7 +44,7 @@ class WalletRequestSubscriber { Task(priority: .high) { let assertionId = payload.decryptedPayload.sha256().toHexString() do { - let response = try await verifyClient.verifyOrigin(assertionId: assertionId) + let response = try await verifyClient.verify(.v1(assertionId: assertionId)) let verifyContext = verifyClient.createVerifyContext(origin: response.origin, domain: payload.request.payloadParams.domain, isScam: response.isScam) verifyContextStore.set(verifyContext, forKey: request.id.string) onRequest?((request, verifyContext)) diff --git a/Sources/WalletConnectVerify/PublicKeyFetcher.swift b/Sources/WalletConnectVerify/PublicKeyFetcher.swift index 517761f15..fdd5caa72 100644 --- a/Sources/WalletConnectVerify/PublicKeyFetcher.swift +++ b/Sources/WalletConnectVerify/PublicKeyFetcher.swift @@ -2,13 +2,13 @@ import Foundation // PublicKeyFetcher class protocol PublicKeyFetching { - func fetchPublicKey() async throws -> PublicKeyFetcher.VerifyServerPublicKey + func fetchPublicKey() async throws -> VerifyServerPublicKey +} +struct VerifyServerPublicKey: Codable { + let publicKey: String + let expiresAt: TimeInterval } class PublicKeyFetcher: PublicKeyFetching { - struct VerifyServerPublicKey: Codable { - let publicKey: String - let expiresAt: TimeInterval - } private let urlString = "https://verify.walletconnect.org/v2/public-key" @@ -25,10 +25,10 @@ class PublicKeyFetcher: PublicKeyFetching { #if DEBUG class MockPublicKeyFetcher: PublicKeyFetching { - var publicKey: PublicKeyFetcher.VerifyServerPublicKey? + var publicKey: VerifyServerPublicKey? var error: Error? - func fetchPublicKey() async throws -> PublicKeyFetcher.VerifyServerPublicKey { + func fetchPublicKey() async throws -> VerifyServerPublicKey { if let error = error { throw error } diff --git a/Sources/WalletConnectVerify/VerifyClientFactory.swift b/Sources/WalletConnectVerify/VerifyClientFactory.swift index 632173aa7..a0fe4fbb6 100644 --- a/Sources/WalletConnectVerify/VerifyClientFactory.swift +++ b/Sources/WalletConnectVerify/VerifyClientFactory.swift @@ -17,7 +17,7 @@ public class VerifyClientFactory { attestChallengeProvider: attestChallengeProvider, keyAttestationService: keyAttestationService ) - let verifyServerPubKeyManagerStore: CodableStore = CodableStore(defaults: keyValueStorage, identifier: "com.walletconnect.verify") + let verifyServerPubKeyManagerStore: CodableStore = CodableStore(defaults: keyValueStorage, identifier: "com.walletconnect.verify") let verifyServerPubKeyManager = VerifyServerPubKeyManager(store: verifyServerPubKeyManagerStore) let attestationVerifier = AttestationJWTVerifier(verifyServerPubKeyManager: verifyServerPubKeyManager) diff --git a/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift b/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift index e91217b48..f72cd5419 100644 --- a/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift +++ b/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift @@ -2,10 +2,10 @@ import Foundation class VerifyServerPubKeyManager { static let publicKeyStorageKey = "verify_server_pub_key" - private let store: CodableStore + private let store: CodableStore private let fetcher: PublicKeyFetching - init(store: CodableStore, fetcher: PublicKeyFetching = PublicKeyFetcher()) { + init(store: CodableStore, fetcher: PublicKeyFetching = PublicKeyFetcher()) { self.store = store self.fetcher = fetcher @@ -41,16 +41,16 @@ class VerifyServerPubKeyManager { return serverKey.publicKey } - private func getPublicKeyFromLocalStorage() throws -> PublicKeyFetcher.VerifyServerPublicKey? { + private func getPublicKeyFromLocalStorage() throws -> VerifyServerPublicKey? { return try store.get(key: Self.publicKeyStorageKey) } - private func isKeyExpired(_ key: PublicKeyFetcher.VerifyServerPublicKey) -> Bool { + private func isKeyExpired(_ key: VerifyServerPublicKey) -> Bool { let currentTime = Date().timeIntervalSince1970 return currentTime >= key.expiresAt } - private func savePublicKeyToLocalStorage(publicKey: PublicKeyFetcher.VerifyServerPublicKey) { + private func savePublicKeyToLocalStorage(publicKey: VerifyServerPublicKey) { store.set(publicKey, forKey: Self.publicKeyStorageKey) } } diff --git a/Tests/RelayerTests/RelayClientTests.swift b/Tests/RelayerTests/RelayClientTests.swift index 6442757cb..d767623e4 100644 --- a/Tests/RelayerTests/RelayClientTests.swift +++ b/Tests/RelayerTests/RelayClientTests.swift @@ -29,10 +29,10 @@ final class RelayClientTests: XCTestCase { let topic = "0987" let message = "qwerty" let subscriptionId = "sub-id" - let subscription = Subscription(id: subscriptionId, topic: topic, message: message) + let subscription = Subscription(id: subscriptionId, topic: topic, message: message, attestation: nil) let request = subscription.asRPCRequest() - sut.messagePublisher.sink { (subscriptionTopic, subscriptionMessage, _) in + sut.messagePublisher.sink { (subscriptionTopic, subscriptionMessage, _, _) in XCTAssertEqual(subscriptionMessage, message) XCTAssertEqual(subscriptionTopic, topic) expectation.fulfill() @@ -62,7 +62,7 @@ final class RelayClientTests: XCTestCase { let expectation = expectation(description: "Duplicate Subscription requests must notify only the first time") let request = Subscription.init(id: "sub_id", topic: "topic", message: "message").asRPCRequest() - sut.messagePublisher.sink { (_, _, _) in + sut.messagePublisher.sink { (_, _, _, _) in expectation.fulfill() }.store(in: &publishers) diff --git a/Tests/TestingUtils/NetworkingInteractorMock.swift b/Tests/TestingUtils/NetworkingInteractorMock.swift index 65b73ab0c..c08cde49c 100644 --- a/Tests/TestingUtils/NetworkingInteractorMock.swift +++ b/Tests/TestingUtils/NetworkingInteractorMock.swift @@ -40,10 +40,10 @@ public class NetworkingInteractorMock: NetworkInteracting { networkConnectionStatusPublisherSubject.eraseToAnyPublisher() } - public let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?), Never>() + public let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?, encryptedMessage: String, attestation: String?), Never>() public let responsePublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, response: RPCResponse, publishedAt: Date, derivedTopic: String?), Never>() - public var requestPublisher: AnyPublisher<(topic: String, request: JSONRPC.RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?), Never> { + public var requestPublisher: AnyPublisher<(topic: String, request: JSONRPC.RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?, encryptedMessage: String, attestation: String?), Never> { requestPublisherSubject.eraseToAnyPublisher() } @@ -63,9 +63,9 @@ public class NetworkingInteractorMock: NetworkInteracting { .filter { rpcRequest in return rpcRequest.request.method == request.method } - .compactMap { topic, rpcRequest, decryptedPayload, publishedAt, derivedTopic in + .compactMap { topic, rpcRequest, decryptedPayload, publishedAt, derivedTopic, encryptedMessage, attestation in guard let id = rpcRequest.id, let request = try? rpcRequest.params?.get(Request.self) else { return nil } - return RequestSubscriptionPayload(id: id, topic: topic, request: request, decryptedPayload: decryptedPayload, publishedAt: publishedAt, derivedTopic: derivedTopic) + return RequestSubscriptionPayload(id: id, topic: topic, request: request, decryptedPayload: decryptedPayload, publishedAt: publishedAt, derivedTopic: derivedTopic, encryptedMessage: encryptedMessage, attestation: attestation) } .eraseToAnyPublisher() } diff --git a/Tests/VerifyTests/VerifyServerPubKeyManagerTests.swift b/Tests/VerifyTests/VerifyServerPubKeyManagerTests.swift index 8fd843fae..913f23994 100644 --- a/Tests/VerifyTests/VerifyServerPubKeyManagerTests.swift +++ b/Tests/VerifyTests/VerifyServerPubKeyManagerTests.swift @@ -4,21 +4,21 @@ import XCTest class VerifyServerPubKeyManagerTests: XCTestCase { var manager: VerifyServerPubKeyManager! - var store: CodableStore! + var store: CodableStore! var fetcher: MockPublicKeyFetcher! override func setUp() { super.setUp() let storage = RuntimeKeyValueStorage() - store = CodableStore(defaults: storage, identifier: "test") + store = CodableStore(defaults: storage, identifier: "test") fetcher = MockPublicKeyFetcher() - manager = VerifyServerPubKeyManager(store: store, fetcher: fetcher) } func testGetPublicKeyFromServer() async throws { let expectedPublicKey = "test_public_key" let expiresAt = Date().timeIntervalSince1970 + 3600 // 1 hour from now - fetcher.publicKey = PublicKeyFetcher.VerifyServerPublicKey(publicKey: expectedPublicKey, expiresAt: expiresAt) + fetcher.publicKey = VerifyServerPublicKey(publicKey: expectedPublicKey, expiresAt: expiresAt) + manager = VerifyServerPubKeyManager(store: store, fetcher: fetcher) let publicKey = try await manager.getPublicKey() @@ -28,8 +28,9 @@ class VerifyServerPubKeyManagerTests: XCTestCase { func testGetPublicKeyFromLocalStorage() async throws { let expectedPublicKey = "test_public_key" let expiresAt = Date().timeIntervalSince1970 + 3600 // 1 hour from now - let storedKey = PublicKeyFetcher.VerifyServerPublicKey(publicKey: expectedPublicKey, expiresAt: expiresAt) + let storedKey = VerifyServerPublicKey(publicKey: expectedPublicKey, expiresAt: expiresAt) store.set(storedKey, forKey: VerifyServerPubKeyManager.publicKeyStorageKey) + manager = VerifyServerPubKeyManager(store: store, fetcher: fetcher) let publicKey = try await manager.getPublicKey() @@ -40,10 +41,11 @@ class VerifyServerPubKeyManagerTests: XCTestCase { let expectedPublicKey = "test_public_key" let newTestPubKey = "new_test_public_key" let expiresAt = Date().timeIntervalSince1970 - 3600 // 1 hour ago - let storedKey = PublicKeyFetcher.VerifyServerPublicKey(publicKey: expectedPublicKey, expiresAt: expiresAt) + let storedKey = VerifyServerPublicKey(publicKey: expectedPublicKey, expiresAt: expiresAt) store.set(storedKey, forKey: VerifyServerPubKeyManager.publicKeyStorageKey) - fetcher.publicKey = PublicKeyFetcher.VerifyServerPublicKey(publicKey: newTestPubKey, expiresAt: Date().timeIntervalSince1970 + 3600) + fetcher.publicKey = VerifyServerPublicKey(publicKey: newTestPubKey, expiresAt: Date().timeIntervalSince1970 + 3600) + manager = VerifyServerPubKeyManager(store: store, fetcher: fetcher) let publicKey = try await manager.getPublicKey() diff --git a/Tests/WalletConnectSignTests/ApproveEngineTests.swift b/Tests/WalletConnectSignTests/ApproveEngineTests.swift index 79b2f7996..1d36d7902 100644 --- a/Tests/WalletConnectSignTests/ApproveEngineTests.swift +++ b/Tests/WalletConnectSignTests/ApproveEngineTests.swift @@ -73,8 +73,8 @@ final class ApproveEngineTests: XCTestCase { pairingStorageMock.setPairing(pairing) let proposerPubKey = AgreementPrivateKey().publicKey.hexRepresentation let proposal = SessionProposal.stub(proposerPubKey: proposerPubKey) - pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil)) - + pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil, encryptedMessage: "", attestation: nil)) + _ = try await engine.approveProposal(proposerPubKey: proposal.proposer.publicKey, validating: SessionNamespace.stubDictionary()) let topicB = networkingInteractor.subscriptions.last! @@ -98,7 +98,7 @@ final class ApproveEngineTests: XCTestCase { sessionProposed = true } - pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil)) + pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil, encryptedMessage: "", attestation: nil)) XCTAssertNotNil(try! proposalPayloadsStore.get(key: proposal.proposer.publicKey), "Proposer must store proposal payload") XCTAssertTrue(sessionProposed) } @@ -121,7 +121,7 @@ final class ApproveEngineTests: XCTestCase { didCallBackOnSessionApproved = true } sessionTopicToProposal.set(SessionProposal.stub().publicRepresentation(pairingTopic: ""), forKey: sessionTopic) - networkingInteractor.requestPublisherSubject.send((sessionTopic, RPCRequest.stubSettle(), Data(), Date(), "")) + networkingInteractor.requestPublisherSubject.send((sessionTopic, RPCRequest.stubSettle(), Data(), Date(), "", "", nil)) usleep(100) @@ -172,7 +172,7 @@ final class ApproveEngineTests: XCTestCase { engine.onSessionProposal = { _, _ in proposalReceivedExpectation.fulfill() } - pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil)) + pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil, encryptedMessage: "", attestation: nil)) wait(for: [proposalReceivedExpectation], timeout: 0.1) @@ -191,8 +191,8 @@ final class ApproveEngineTests: XCTestCase { engine.onSessionProposal = { _, _ in proposalReceivedExpectation.fulfill() } - pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil)) - + pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil, encryptedMessage: "", attestation: nil)) + wait(for: [proposalReceivedExpectation], timeout: 0.1) XCTAssertTrue(verifyContextStore.getAll().count == 1) @@ -214,7 +214,7 @@ final class ApproveEngineTests: XCTestCase { engine.onSessionProposal = { _, _ in proposalReceivedExpectation.fulfill() } - pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil)) + pairingRegisterer.subject.send(RequestSubscriptionPayload(id: RPCID("id"), topic: topicA, request: proposal, decryptedPayload: Data(), publishedAt: Date(), derivedTopic: nil, encryptedMessage: "", attestation: nil)) wait(for: [proposalReceivedExpectation], timeout: 0.1) diff --git a/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift b/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift index 7304ea2c8..5f9007b37 100644 --- a/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift +++ b/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift @@ -35,7 +35,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { didCallbackUpdatMethods = true XCTAssertEqual(topic, session.topic) } - networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateNamespaces(), Data(), Date(), nil)) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateNamespaces(), Data(), Date(), nil, "", nil)) XCTAssertTrue(didCallbackUpdatMethods) usleep(100) XCTAssertTrue(networkingInteractor.didRespondSuccess) @@ -51,7 +51,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { // } func testUpdateMethodPeerErrorSessionNotFound() { - networkingInteractor.requestPublisherSubject.send(("", RPCRequest.stubUpdateNamespaces(), Data(), Date(), nil)) + networkingInteractor.requestPublisherSubject.send(("", RPCRequest.stubUpdateNamespaces(), Data(), Date(), nil, "", nil)) usleep(100) XCTAssertFalse(networkingInteractor.didRespondSuccess) XCTAssertEqual(networkingInteractor.lastErrorCode, 7001) @@ -60,7 +60,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { func testUpdateMethodPeerErrorUnauthorized() { let session = WCSession.stub(isSelfController: true) // Peer is not a controller storageMock.setSession(session) - networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateNamespaces(), Data(), Date(), nil)) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateNamespaces(), Data(), Date(), nil, "", nil)) usleep(100) XCTAssertFalse(networkingInteractor.didRespondSuccess) XCTAssertEqual(networkingInteractor.lastErrorCode, 3003) @@ -73,7 +73,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { storageMock.setSession(session) let twoDaysFromNowTimestamp = Int64(TimeTraveler.dateByAdding(days: 2).timeIntervalSince1970) - networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: twoDaysFromNowTimestamp), Data(), Date(), nil)) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: twoDaysFromNowTimestamp), Data(), Date(), nil, "", nil)) let potentiallyExtendedSession = storageMock.getAll().first {$0.topic == session.topic}! XCTAssertEqual(potentiallyExtendedSession.expiryDate.timeIntervalSinceReferenceDate, tomorrow.timeIntervalSinceReferenceDate, accuracy: 1, "expiry date has been extended for peer non controller request ") @@ -84,7 +84,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { let session = WCSession.stub(isSelfController: false, expiryDate: tomorrow) storageMock.setSession(session) let tenDaysFromNowTimestamp = Int64(TimeTraveler.dateByAdding(days: 10).timeIntervalSince1970) - networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: tenDaysFromNowTimestamp), Data(), Date(), nil)) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: tenDaysFromNowTimestamp), Data(), Date(), nil, "", nil)) let potentaillyExtendedSession = storageMock.getAll().first {$0.topic == session.topic}! XCTAssertEqual(potentaillyExtendedSession.expiryDate.timeIntervalSinceReferenceDate, tomorrow.timeIntervalSinceReferenceDate, accuracy: 1, "expiry date has been extended despite ttl to high") @@ -96,7 +96,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { storageMock.setSession(session) let oneDayFromNowTimestamp = Int64(TimeTraveler.dateByAdding(days: 10).timeIntervalSince1970) - networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: oneDayFromNowTimestamp), Data(), Date(), nil)) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: oneDayFromNowTimestamp), Data(), Date(), nil, "", nil)) let potentaillyExtendedSession = storageMock.getAll().first {$0.topic == session.topic}! XCTAssertEqual(potentaillyExtendedSession.expiryDate.timeIntervalSinceReferenceDate, tomorrow.timeIntervalSinceReferenceDate, accuracy: 1, "expiry date has been extended despite ttl to low") } diff --git a/Tests/WalletConnectSignTests/SessionEngineTests.swift b/Tests/WalletConnectSignTests/SessionEngineTests.swift index 0f2828805..a27cf8807 100644 --- a/Tests/WalletConnectSignTests/SessionEngineTests.swift +++ b/Tests/WalletConnectSignTests/SessionEngineTests.swift @@ -59,7 +59,7 @@ final class SessionEngineTests: XCTestCase { expiry: UInt64(Date().timeIntervalSince1970) ) - networkingInteractor.requestPublisherSubject.send(("topic", request, Data(), Date(), "")) + networkingInteractor.requestPublisherSubject.send(("topic", request, Data(), Date(), "", "", nil)) wait(for: [expectation], timeout: 0.5) } diff --git a/Tests/WalletConnectSignTests/Stub/Session+Stub.swift b/Tests/WalletConnectSignTests/Stub/Session+Stub.swift index 4e332022d..b023226ce 100644 --- a/Tests/WalletConnectSignTests/Stub/Session+Stub.swift +++ b/Tests/WalletConnectSignTests/Stub/Session+Stub.swift @@ -32,7 +32,8 @@ extension WCSession { accounts: Account.stubSet(), acknowledged: acknowledged, expiryTimestamp: Int64(expiryDate.timeIntervalSince1970), - transportType: .relay) + transportType: .relay, + verifyContext: VerifyContext(origin: nil, validation: .unknown)) } } From 6e0167ce36285a3e6d3db476b402332c11047ea5 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 24 Jul 2024 17:56:34 +0900 Subject: [PATCH 10/26] fix relay tests --- Example/RelayIntegrationTests/RelayClientEndToEndTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift b/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift index e3888e786..e52094049 100644 --- a/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift +++ b/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift @@ -115,12 +115,12 @@ final class RelayClientEndToEndTests: XCTestCase { expectationA.assertForOverFulfill = false expectationB.assertForOverFulfill = false - relayA.messagePublisher.sink { topic, payload, _ in + relayA.messagePublisher.sink { topic, payload, _, _ in (subscriptionATopic, subscriptionAPayload) = (topic, payload) expectationA.fulfill() }.store(in: &publishers) - relayB.messagePublisher.sink { topic, payload, _ in + relayB.messagePublisher.sink { topic, payload, _, _ in (subscriptionBTopic, subscriptionBPayload) = (topic, payload) Task(priority: .high) { sleep(1) From 85ffdde8482493090179d02389803a0430947370 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 24 Jul 2024 18:12:00 +0900 Subject: [PATCH 11/26] print more descriptive error --- .../WalletConnectVerify/PublicKeyFetcher.swift | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Sources/WalletConnectVerify/PublicKeyFetcher.swift b/Sources/WalletConnectVerify/PublicKeyFetcher.swift index fdd5caa72..ec080e8a5 100644 --- a/Sources/WalletConnectVerify/PublicKeyFetcher.swift +++ b/Sources/WalletConnectVerify/PublicKeyFetcher.swift @@ -17,9 +17,19 @@ class PublicKeyFetcher: PublicKeyFetching { throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"]) } - let (data, _) = try await URLSession.shared.data(from: url) - let publicKeyResponse = try JSONDecoder().decode(VerifyServerPublicKey.self, from: data) - return publicKeyResponse + let (data, response) = try await URLSession.shared.data(from: url) + if let httpResponse = response as? HTTPURLResponse, !(200...299).contains(httpResponse.statusCode) { + let errorMessage = String(data: data, encoding: .utf8) ?? "Unknown error" + throw NSError(domain: "", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: errorMessage]) + } + + do { + let publicKeyResponse = try JSONDecoder().decode(VerifyServerPublicKey.self, from: data) + return publicKeyResponse + } catch { + print("Decoding error: \(error)") + throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to decode JSON"]) + } } } From 7d8c2f419e4bc54206c7037dc86e678418dd159e Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 24 Jul 2024 18:43:53 +0900 Subject: [PATCH 12/26] remove ack check --- .../LinkAndRelayDispatchers/LinkSessionRequester.swift | 2 +- .../LinkAndRelayDispatchers/SessionResponderDispatcher.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/WalletConnectSign/LinkAndRelayDispatchers/LinkSessionRequester.swift b/Sources/WalletConnectSign/LinkAndRelayDispatchers/LinkSessionRequester.swift index 015f261ee..684e5522a 100644 --- a/Sources/WalletConnectSign/LinkAndRelayDispatchers/LinkSessionRequester.swift +++ b/Sources/WalletConnectSign/LinkAndRelayDispatchers/LinkSessionRequester.swift @@ -18,7 +18,7 @@ final class LinkSessionRequester { func request(_ request: Request) async throws -> String? { logger.debug("will request on session topic: \(request.topic)") - guard let session = sessionStore.getSession(forTopic: request.topic), session.acknowledged else { + guard let session = sessionStore.getSession(forTopic: request.topic) else { logger.debug("Could not find session for topic \(request.topic)") throw WalletConnectError.noSessionMatchingTopic(request.topic) } diff --git a/Sources/WalletConnectSign/LinkAndRelayDispatchers/SessionResponderDispatcher.swift b/Sources/WalletConnectSign/LinkAndRelayDispatchers/SessionResponderDispatcher.swift index fa61d3971..de13ac389 100644 --- a/Sources/WalletConnectSign/LinkAndRelayDispatchers/SessionResponderDispatcher.swift +++ b/Sources/WalletConnectSign/LinkAndRelayDispatchers/SessionResponderDispatcher.swift @@ -21,7 +21,7 @@ class SessionResponderDispatcher { func respondSessionRequest(topic: String, requestId: RPCID, response: RPCResult) async throws -> String? { - guard let session = sessionStore.getSession(forTopic: topic), session.acknowledged else { + guard let session = sessionStore.getSession(forTopic: topic) else { logger.debug("Could not find session for topic \(topic)") throw WalletConnectError.noSessionMatchingTopic(topic) } From 572591dfa49bfb47b0e91c0165fccf4d1e57c2ca Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 24 Jul 2024 19:38:55 +0900 Subject: [PATCH 13/26] update verify context creation --- .../Link/ApproveSessionAuthenticateUtil.swift | 15 +++++++++++++-- .../Link/LinkSessionAuthenticateResponder.swift | 4 +--- .../Wallet/SessionAuthenticateResponder.swift | 3 +-- .../Engine/Common/ApproveEngine.swift | 3 ++- .../Sign/SignClientFactory.swift | 2 +- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Sources/WalletConnectSign/Auth/Link/ApproveSessionAuthenticateUtil.swift b/Sources/WalletConnectSign/Auth/Link/ApproveSessionAuthenticateUtil.swift index 78e4e9b06..c60273ff9 100644 --- a/Sources/WalletConnectSign/Auth/Link/ApproveSessionAuthenticateUtil.swift +++ b/Sources/WalletConnectSign/Auth/Link/ApproveSessionAuthenticateUtil.swift @@ -14,6 +14,8 @@ class ApproveSessionAuthenticateUtil { private let logger: ConsoleLogging private let sessionStore: WCSessionStorage private let sessionNamespaceBuilder: SessionNamespaceBuilder + private let verifyContextStore: CodableStore + private let verifyClient: VerifyClientProtocol init( logger: ConsoleLogging, @@ -23,7 +25,9 @@ class ApproveSessionAuthenticateUtil { messageFormatter: SIWEFromCacaoFormatting, sessionStore: WCSessionStorage, sessionNamespaceBuilder: SessionNamespaceBuilder, - networkingInteractor: NetworkInteracting + networkingInteractor: NetworkInteracting, + verifyContextStore: CodableStore, + verifyClient: VerifyClientProtocol ) { self.logger = logger self.kms = kms @@ -33,6 +37,8 @@ class ApproveSessionAuthenticateUtil { self.signatureVerifier = signatureVerifier self.messageFormatter = messageFormatter self.networkingInteractor = networkingInteractor + self.verifyContextStore = verifyContextStore + self.verifyClient = verifyClient } func getsessionAuthenticateRequestParams(requestId: RPCID) throws -> (request: SessionAuthenticateRequestParams, topic: String) { @@ -60,7 +66,6 @@ class ApproveSessionAuthenticateUtil { return (topic, keys) } - func createSession( response: SessionAuthenticateResponseParams, pairingTopic: String, @@ -116,6 +121,12 @@ class ApproveSessionAuthenticateUtil { return session.publicRepresentation() } + + func getVerifyContext(requestId: RPCID, domain: String) -> VerifyContext { + (try? verifyContextStore.get(key: requestId.string)) ?? verifyClient.createVerifyContext(origin: nil, domain: domain, isScam: false) + } + + func recoverAndVerifySignature(cacaos: [Cacao]) async throws { try await cacaos.asyncForEach { [unowned self] cacao in guard diff --git a/Sources/WalletConnectSign/Auth/Link/LinkSessionAuthenticateResponder.swift b/Sources/WalletConnectSign/Auth/Link/LinkSessionAuthenticateResponder.swift index b176dd1aa..31d6ba9fe 100644 --- a/Sources/WalletConnectSign/Auth/Link/LinkSessionAuthenticateResponder.swift +++ b/Sources/WalletConnectSign/Auth/Link/LinkSessionAuthenticateResponder.swift @@ -60,15 +60,13 @@ actor LinkSessionAuthenticateResponder { let url = try await linkEnvelopesDispatcher.respond(topic: responseTopic, response: response, peerUniversalLink: peerUniversalLink, envelopeType: .type1(pubKey: responseKeys.publicKey.rawRepresentation)) - let verifyContext = (try? verifyContextStore.get(key: requestId.string)) ?? VerifyContext(origin: nil, validation: .unknown) - let session = try util.createSession( response: responseParams, pairingTopic: pairingTopic, request: sessionAuthenticateRequestParams, sessionTopic: sessionTopic, transportType: .linkMode, - verifyContext: verifyContext + verifyContext: util.getVerifyContext(requestId: requestId, domain: sessionAuthenticateRequestParams.requester.metadata.url) ) verifyContextStore.delete(forKey: requestId.string) diff --git a/Sources/WalletConnectSign/Auth/Services/Wallet/SessionAuthenticateResponder.swift b/Sources/WalletConnectSign/Auth/Services/Wallet/SessionAuthenticateResponder.swift index 825225c0e..37e89a07d 100644 --- a/Sources/WalletConnectSign/Auth/Services/Wallet/SessionAuthenticateResponder.swift +++ b/Sources/WalletConnectSign/Auth/Services/Wallet/SessionAuthenticateResponder.swift @@ -120,14 +120,13 @@ actor SessionAuthenticateResponder { } do { - let verifyContext = (try? verifyContextStore.get(key: requestId.string)) ?? VerifyContext(origin: nil, validation: .unknown) let session = try util.createSession( response: responseParams, pairingTopic: pairingTopic, request: sessionAuthenticateRequestParams, sessionTopic: sessionTopic, transportType: .relay, - verifyContext: verifyContext + verifyContext: util.getVerifyContext(requestId: requestId, domain: sessionAuthenticateRequestParams.requester.metadata.url) ) pairingRegisterer.activate( pairingTopic: pairingTopic, diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index a94cae925..e0c762dd5 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -226,7 +226,8 @@ final class ApproveEngine { expiry: Int64(expiry) ) - let verifyContext = (try? verifyContextStore.get(key: proposal.proposer.publicKey)) ?? VerifyContext(origin: nil, validation: .unknown) + let verifyContext = (try? verifyContextStore.get(key: proposal.proposer.publicKey)) ?? verifyClient.createVerifyContext(origin: nil, domain: proposal.proposer.metadata.url, isScam: false) + let session = WCSession( topic: topic, diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index caebc0840..8ad3d214b 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -121,7 +121,7 @@ public struct SignClientFactory { let walletErrorResponder = WalletErrorResponder(networkingInteractor: networkingClient, logger: logger, kms: kms, rpcHistory: rpcHistory, linkEnvelopesDispatcher: linkEnvelopesDispatcher) let authRequestSubscriber = AuthRequestSubscriber(networkingInteractor: networkingClient, logger: logger, kms: kms, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingClient, verifyClient: verifyClient, verifyContextStore: verifyContextStore, pairingStore: pairingStore) - let approveSessionAuthenticateUtil = ApproveSessionAuthenticateUtil(logger: logger, kms: kms, rpcHistory: rpcHistory, signatureVerifier: signatureVerifier, messageFormatter: messageFormatter, sessionStore: sessionStore, sessionNamespaceBuilder: sessionNameSpaceBuilder, networkingInteractor: networkingClient) + let approveSessionAuthenticateUtil = ApproveSessionAuthenticateUtil(logger: logger, kms: kms, rpcHistory: rpcHistory, signatureVerifier: signatureVerifier, messageFormatter: messageFormatter, sessionStore: sessionStore, sessionNamespaceBuilder: sessionNameSpaceBuilder, networkingInteractor: networkingClient, verifyContextStore: verifyContextStore, verifyClient: verifyClient) let pendingRequestsProvider = PendingRequestsProvider(rpcHistory: rpcHistory, verifyContextStore: verifyContextStore) let authResponseTopicResubscriptionService = AuthResponseTopicResubscriptionService(networkingInteractor: networkingClient, logger: logger, authResponseTopicRecordsStore: authResponseTopicRecordsStore) From d563f758422340d774ba1133873a862558d9f24e Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 29 Jul 2024 12:32:50 +0900 Subject: [PATCH 14/26] pubkey as jwk --- Sources/WalletConnectJWT/JWTValidator.swift | 30 +++++++++++ .../AttestationJWTVerifier.swift | 12 ++--- .../PublicKeyFetcher.swift | 51 ++++++++++++++++++- .../VerifyServerPubKeyManager.swift | 12 +++-- .../VerifyServerPubKeyManagerTests.swift | 51 ++++++++++++++----- 5 files changed, 131 insertions(+), 25 deletions(-) diff --git a/Sources/WalletConnectJWT/JWTValidator.swift b/Sources/WalletConnectJWT/JWTValidator.swift index 110309a8d..4b82018fa 100644 --- a/Sources/WalletConnectJWT/JWTValidator.swift +++ b/Sources/WalletConnectJWT/JWTValidator.swift @@ -1,4 +1,5 @@ import Foundation +import CryptoKit public struct JWTValidator { @@ -24,3 +25,32 @@ public struct JWTValidator { return publicKey.isValid(signature: signatureData, for: unsignedData) } } + + +public struct P256JWTValidator { + + let jwtString: String + + public init(jwtString: String) { + self.jwtString = jwtString + } + + public func isValid(publicKey: P256.Signing.PublicKey) throws -> Bool { + var components = jwtString.components(separatedBy: ".") + + guard components.count == 3 else { throw JWTError.undefinedFormat } + + let signature = components.removeLast() + + guard let unsignedData = components + .joined(separator: ".") + .data(using: .utf8) + else { throw JWTError.invalidJWTString } + + let signatureData = try JWTEncoder.base64urlDecodedData(string: signature) + + let P256Signature = try P256.Signing.ECDSASignature(rawRepresentation: signatureData) + + return publicKey.isValidSignature(P256Signature, for: unsignedData) + } +} diff --git a/Sources/WalletConnectVerify/AttestationJWTVerifier.swift b/Sources/WalletConnectVerify/AttestationJWTVerifier.swift index 3d7ede265..844995f5c 100644 --- a/Sources/WalletConnectVerify/AttestationJWTVerifier.swift +++ b/Sources/WalletConnectVerify/AttestationJWTVerifier.swift @@ -1,5 +1,6 @@ import Foundation +import CryptoKit struct AttestationJWTClaims: Codable { @@ -30,10 +31,10 @@ class AttestationJWTVerifier { func verify(attestationJWT: String, messageId: String) async throws -> VerifyResponse { do { let verifyServerPubKey = try await verifyServerPubKeyManager.getPublicKey() - try verifyJWTAgainstPubKey(attestationJWT, pubKey: verifyServerPubKey) + try verifyJWTAgainstPubKey(attestationJWT, signingPubKey: verifyServerPubKey) } catch { let refreshedVerifyServerPubKey = try await verifyServerPubKeyManager.refreshKey() - try verifyJWTAgainstPubKey(attestationJWT, pubKey: refreshedVerifyServerPubKey) + try verifyJWTAgainstPubKey(attestationJWT, signingPubKey: refreshedVerifyServerPubKey) } let claims = try decodeJWTClaims(jwtString: attestationJWT) @@ -44,10 +45,8 @@ class AttestationJWTVerifier { return VerifyResponse(origin: claims.origin, isScam: claims.isScam) } - func verifyJWTAgainstPubKey(_ jwtString: String, pubKey: String) throws { - let signingPubKey = try SigningPublicKey(hex: pubKey) - - let validator = JWTValidator(jwtString: jwtString) + func verifyJWTAgainstPubKey(_ jwtString: String, signingPubKey: P256.Signing.PublicKey) throws { + let validator = P256JWTValidator(jwtString: jwtString) guard try validator.isValid(publicKey: signingPubKey) else { throw Errors.invalidJWT } @@ -67,3 +66,4 @@ class AttestationJWTVerifier { return claims } } + diff --git a/Sources/WalletConnectVerify/PublicKeyFetcher.swift b/Sources/WalletConnectVerify/PublicKeyFetcher.swift index ec080e8a5..bbacc7e6f 100644 --- a/Sources/WalletConnectVerify/PublicKeyFetcher.swift +++ b/Sources/WalletConnectVerify/PublicKeyFetcher.swift @@ -1,13 +1,62 @@ import Foundation +import CryptoKit // PublicKeyFetcher class protocol PublicKeyFetching { func fetchPublicKey() async throws -> VerifyServerPublicKey } struct VerifyServerPublicKey: Codable { - let publicKey: String + enum Errors: Error { + case invalisXCoordinateData + case invalidYCoordinateData + } + let publicKey: JWK let expiresAt: TimeInterval + + enum CodingKeys: String, CodingKey { + case publicKey = "publicKey" + case expiresAt = "expiresAt" + } + + struct JWK: Codable { + let crv: String + let ext: Bool + let keyOps: [String] + let kty: String + let x: String + let y: String + + enum CodingKeys: String, CodingKey { + case crv + case ext + case keyOps = "key_ops" + case kty + case x + case y + } + + func P256SigningPublicKey() throws -> P256.Signing.PublicKey { + let jwk = self + // Convert the x and y values from base64url to Data + guard let xData = Data(base64urlEncoded: jwk.x), xData.count == 32 else { + print("Invalid x-coordinate data.") + throw Errors.invalisXCoordinateData + } + guard let yData = Data(base64urlEncoded: jwk.y), yData.count == 32 else { + print("Invalid y-coordinate data.") + throw Errors.invalidYCoordinateData + } + + // Concatenate the coordinates with the uncompressed point prefix 0x04 + let rawKeyData = xData + yData + + return try P256.Signing.PublicKey(rawRepresentation: rawKeyData) + } + } + } + + class PublicKeyFetcher: PublicKeyFetching { private let urlString = "https://verify.walletconnect.org/v2/public-key" diff --git a/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift b/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift index f72cd5419..3810f5dbe 100644 --- a/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift +++ b/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift @@ -1,6 +1,8 @@ import Foundation +import CryptoKit class VerifyServerPubKeyManager { + static let publicKeyStorageKey = "verify_server_pub_key" private let store: CodableStore private let fetcher: PublicKeyFetching @@ -25,20 +27,20 @@ class VerifyServerPubKeyManager { } } - func getPublicKey() async throws -> String { + func getPublicKey() async throws -> P256.Signing.PublicKey { if let localKey = try getPublicKeyFromLocalStorage(), !isKeyExpired(localKey) { - return localKey.publicKey + return try localKey.publicKey.P256SigningPublicKey() } else { let serverKey = try await fetcher.fetchPublicKey() savePublicKeyToLocalStorage(publicKey: serverKey) - return serverKey.publicKey + return try serverKey.publicKey.P256SigningPublicKey() } } - func refreshKey() async throws -> String { + func refreshKey() async throws -> P256.Signing.PublicKey { let serverKey = try await fetcher.fetchPublicKey() savePublicKeyToLocalStorage(publicKey: serverKey) - return serverKey.publicKey + return try serverKey.publicKey.P256SigningPublicKey() } private func getPublicKeyFromLocalStorage() throws -> VerifyServerPublicKey? { diff --git a/Tests/VerifyTests/VerifyServerPubKeyManagerTests.swift b/Tests/VerifyTests/VerifyServerPubKeyManagerTests.swift index 913f23994..1a997465a 100644 --- a/Tests/VerifyTests/VerifyServerPubKeyManagerTests.swift +++ b/Tests/VerifyTests/VerifyServerPubKeyManagerTests.swift @@ -1,5 +1,5 @@ -import Foundation import XCTest +import CryptoKit @testable import WalletConnectVerify class VerifyServerPubKeyManagerTests: XCTestCase { @@ -15,40 +15,65 @@ class VerifyServerPubKeyManagerTests: XCTestCase { } func testGetPublicKeyFromServer() async throws { - let expectedPublicKey = "test_public_key" + let expectedJWK = VerifyServerPublicKey.JWK.stub() + let expectedPublicKey = try expectedJWK.P256SigningPublicKey() let expiresAt = Date().timeIntervalSince1970 + 3600 // 1 hour from now - fetcher.publicKey = VerifyServerPublicKey(publicKey: expectedPublicKey, expiresAt: expiresAt) + fetcher.publicKey = VerifyServerPublicKey(publicKey: expectedJWK, expiresAt: expiresAt) manager = VerifyServerPubKeyManager(store: store, fetcher: fetcher) let publicKey = try await manager.getPublicKey() - XCTAssertEqual(publicKey, expectedPublicKey) + XCTAssertEqual(publicKey.rawRepresentation, expectedPublicKey.rawRepresentation) } func testGetPublicKeyFromLocalStorage() async throws { - let expectedPublicKey = "test_public_key" + let expectedJWK = VerifyServerPublicKey.JWK.stub() + let expectedPublicKey = try expectedJWK.P256SigningPublicKey() let expiresAt = Date().timeIntervalSince1970 + 3600 // 1 hour from now - let storedKey = VerifyServerPublicKey(publicKey: expectedPublicKey, expiresAt: expiresAt) + let storedKey = VerifyServerPublicKey(publicKey: expectedJWK, expiresAt: expiresAt) store.set(storedKey, forKey: VerifyServerPubKeyManager.publicKeyStorageKey) manager = VerifyServerPubKeyManager(store: store, fetcher: fetcher) let publicKey = try await manager.getPublicKey() - XCTAssertEqual(publicKey, expectedPublicKey) + XCTAssertEqual(publicKey.rawRepresentation, expectedPublicKey.rawRepresentation) } func testGetExpiredPublicKeyFromLocalStorage() async throws { - let expectedPublicKey = "test_public_key" - let newTestPubKey = "new_test_public_key" - let expiresAt = Date().timeIntervalSince1970 - 3600 // 1 hour ago - let storedKey = VerifyServerPublicKey(publicKey: expectedPublicKey, expiresAt: expiresAt) + let oldJWK = VerifyServerPublicKey.JWK.stub() + let newJWK = VerifyServerPublicKey.JWK( + crv: "P-256", + ext: true, + keyOps: ["verify"], + kty: "EC", + x: "MKl2ZQXTZsL10tK3nDXJZUJTTkGaxgPtg42lC5VxW9c", + y: "IcIsyFf6M5XzUjxwK9ujYB69TUMzIYGTkUyrvjoB3UM" + ) + let oldPublicKey = try oldJWK.P256SigningPublicKey() + let newPublicKey = try newJWK.P256SigningPublicKey() + let expiredTime = Date().timeIntervalSince1970 - 3600 // 1 hour ago + let validTime = Date().timeIntervalSince1970 + 3600 // 1 hour from now + let storedKey = VerifyServerPublicKey(publicKey: oldJWK, expiresAt: expiredTime) store.set(storedKey, forKey: VerifyServerPubKeyManager.publicKeyStorageKey) - fetcher.publicKey = VerifyServerPublicKey(publicKey: newTestPubKey, expiresAt: Date().timeIntervalSince1970 + 3600) + fetcher.publicKey = VerifyServerPublicKey(publicKey: newJWK, expiresAt: validTime) manager = VerifyServerPubKeyManager(store: store, fetcher: fetcher) let publicKey = try await manager.getPublicKey() - XCTAssertEqual(publicKey, newTestPubKey) + XCTAssertEqual(publicKey.rawRepresentation, newPublicKey.rawRepresentation) + } +} + +extension VerifyServerPublicKey.JWK { + static func stub() -> VerifyServerPublicKey.JWK { + return VerifyServerPublicKey.JWK( + crv: "P-256", + ext: true, + keyOps: ["verify"], + kty: "EC", + x: "CbL4DOYOb1ntd-8OmExO-oS0DWCMC00DntrymJoB8tk", + y: "KTFwjHtQxGTDR91VsOypcdBfvbo6sAMj5p4Wb-9hRA0" + ) } } From 1a5bba4b9f7c14054bb0033dbff7653b426e7616 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 29 Jul 2024 14:24:02 +0900 Subject: [PATCH 15/26] add AttestationJWTVerifierTests --- .../AttestationJWTVerifier.swift | 4 +- .../VerifyServerPubKeyManager.swift | 40 ++++++++++++++++++- .../VerifyTests/AttestationJWTVerifier.swift | 37 +++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 Tests/VerifyTests/AttestationJWTVerifier.swift diff --git a/Sources/WalletConnectVerify/AttestationJWTVerifier.swift b/Sources/WalletConnectVerify/AttestationJWTVerifier.swift index 844995f5c..fb9ea9c7a 100644 --- a/Sources/WalletConnectVerify/AttestationJWTVerifier.swift +++ b/Sources/WalletConnectVerify/AttestationJWTVerifier.swift @@ -21,9 +21,9 @@ class AttestationJWTVerifier { case invalidJWT } - let verifyServerPubKeyManager: VerifyServerPubKeyManager + let verifyServerPubKeyManager: VerifyServerPubKeyManagerProtocol - init(verifyServerPubKeyManager: VerifyServerPubKeyManager) { + init(verifyServerPubKeyManager: VerifyServerPubKeyManagerProtocol) { self.verifyServerPubKeyManager = verifyServerPubKeyManager } diff --git a/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift b/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift index 3810f5dbe..b48b01d1a 100644 --- a/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift +++ b/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift @@ -1,7 +1,12 @@ import Foundation import CryptoKit -class VerifyServerPubKeyManager { +protocol VerifyServerPubKeyManagerProtocol { + func getPublicKey() async throws -> P256.Signing.PublicKey + func refreshKey() async throws -> P256.Signing.PublicKey +} + +class VerifyServerPubKeyManager: VerifyServerPubKeyManagerProtocol { static let publicKeyStorageKey = "verify_server_pub_key" private let store: CodableStore @@ -56,3 +61,36 @@ class VerifyServerPubKeyManager { store.set(publicKey, forKey: Self.publicKeyStorageKey) } } + +#if DEBUG +class VerifyServerPubKeyManagerMock: VerifyServerPubKeyManagerProtocol { + var mockPublicKey: P256.Signing.PublicKey? + var error: Error? + + init() { + let jwk = VerifyServerPublicKey.JWK( + crv: "P-256", + ext: true, + keyOps: ["verify"], + kty: "EC", + x: "NzUWD0z_Sr3corYWb2bfEF68_UvYcxFzslHKRJ_xCHk", + y: "gWkzUBt_VKro6rydyWdm-ii9-DHjpxdxVQTyX_ZZohc" + ) + mockPublicKey = try? jwk.P256SigningPublicKey() + } + + func getPublicKey() async throws -> P256.Signing.PublicKey { + if let error = error { + throw error + } + return mockPublicKey! + } + + func refreshKey() async throws -> P256.Signing.PublicKey { + if let error = error { + throw error + } + return mockPublicKey! + } +} +#endif diff --git a/Tests/VerifyTests/AttestationJWTVerifier.swift b/Tests/VerifyTests/AttestationJWTVerifier.swift new file mode 100644 index 000000000..8c32352e1 --- /dev/null +++ b/Tests/VerifyTests/AttestationJWTVerifier.swift @@ -0,0 +1,37 @@ +import XCTest +import CryptoKit +@testable import WalletConnectVerify + +class AttestationJWTVerifierTests: XCTestCase { + var verifier: AttestationJWTVerifier! + var mockManager: VerifyServerPubKeyManagerMock! + + override func setUp() { + super.setUp() + mockManager = VerifyServerPubKeyManagerMock() + verifier = AttestationJWTVerifier(verifyServerPubKeyManager: mockManager) + } + + func testVerifyValidJWT() async throws { + let jwt = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjIyMjMyOTYsImlkIjoiQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQSIsIm9yaWdpbiI6Imh0dHBzOi8vd2ViM21vZGFsLmNvbSIsImlzU2NhbSI6ZmFsc2V9.IyIDRId-8Yv6ZkrnHh4BdL7AClNM5brOyGpYbUw9V_SHJqxgEd9UzMlwcOsVoFHxIqgyoYA-ulvANHW0kv_KdA" + let messageId = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + + let response = try await verifier.verify(attestationJWT: jwt, messageId: messageId) + XCTAssertEqual(response.origin, "https://web3modal.com") + XCTAssertEqual(response.isScam, false) + } + + func testVerifyJWTWithInvalidMessageId() async throws { + let jwt = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjIyMjMyOTYsImlkIjoiQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQSIsIm9yaWdpbiI6Imh0dHBzOi8vd2ViM21vZGFsLmNvbSIsImlzU2NhbSI6ZmFsc2V9.IyIDRId-8Yv6ZkrnHh4BdL7AClNM5brOyGpYbUw9V_SHJqxgEd9UzMlwcOsVoFHxIqgyoYA-ulvANHW0kv_KdA" + let invalidMessageId = "InvalidMessageId" + + do { + _ = try await verifier.verify(attestationJWT: jwt, messageId: invalidMessageId) + XCTFail("Expected to throw messageIdMismatch error") + } catch AttestationJWTVerifier.Errors.messageIdMismatch { + // Expected error + } catch { + XCTFail("Unexpected error: \(error)") + } + } +} From df4475629f7e8039eac0a3cefb5ea5b4ed92cd44 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 12 Aug 2024 13:42:16 +0200 Subject: [PATCH 16/26] savepoint --- Example/ExampleApp.xcodeproj/project.pbxproj | 42 ++++++------------- .../Engine/Common/ApproveEngine.swift | 1 - .../Engine/Common/SessionEngine.swift | 3 -- .../Sign/SignClientFactory.swift | 2 +- .../AttestationJWTVerifier.swift | 5 ++- .../Register/VerifyService.swift | 2 +- 6 files changed, 18 insertions(+), 37 deletions(-) diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index cf70d6ce0..498b0aa9a 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -2783,11 +2783,9 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = DApp/DApp.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 173; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + DEVELOPMENT_TEAM = W5R8AG9K22; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = DApp/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = dApp; @@ -2807,7 +2805,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.dapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.dapp"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -2822,11 +2819,9 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = DApp/DAppRelease.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 173; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + DEVELOPMENT_TEAM = W5R8AG9K22; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = DApp/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = dApp; @@ -2846,7 +2841,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.dapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.dapp"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -2859,11 +2853,9 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = PNDecryptionService/PNDecryptionService.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 173; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + DEVELOPMENT_TEAM = W5R8AG9K22; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = PNDecryptionService/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = PNDecryptionService; @@ -2878,7 +2870,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.walletapp.PNDecryptionService; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.walletapp.PNDecryptionService"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -2892,11 +2883,9 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = PNDecryptionService/PNDecryptionServiceRelease.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 173; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + DEVELOPMENT_TEAM = W5R8AG9K22; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = PNDecryptionService/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = PNDecryptionService; @@ -2911,7 +2900,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.walletapp.PNDecryptionService; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.walletapp.PNDecryptionService"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -3075,11 +3063,9 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = WalletApp/WalletApp.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 173; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + DEVELOPMENT_TEAM = W5R8AG9K22; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = WalletApp/Other/Info.plist; @@ -3098,7 +3084,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.walletapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.walletapp"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -3114,11 +3099,9 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = WalletApp/WalletAppRelease.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 173; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + DEVELOPMENT_TEAM = W5R8AG9K22; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = WalletApp/Other/Info.plist; @@ -3137,7 +3120,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.walletapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.walletconnect.walletapp"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index e0c762dd5..79a0cd447 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -399,7 +399,6 @@ private extension ApproveEngine { } Task(priority: .high) { - let assertionId = payload.decryptedPayload.sha256().toHexString() do { let response: VerifyResponse if let attestation = payload.attestation, diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index 40997cde9..a18c922c8 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -20,7 +20,6 @@ final class SessionEngine { private let networkingInteractor: NetworkInteracting private let historyService: HistoryServiceProtocol private let verifyContextStore: CodableStore - private let verifyClient: VerifyClientProtocol private let kms: KeyManagementServiceProtocol private var publishers = [AnyCancellable]() private let logger: ConsoleLogging @@ -31,7 +30,6 @@ final class SessionEngine { networkingInteractor: NetworkInteracting, historyService: HistoryServiceProtocol, verifyContextStore: CodableStore, - verifyClient: VerifyClientProtocol, kms: KeyManagementServiceProtocol, sessionStore: WCSessionStorage, logger: ConsoleLogging, @@ -41,7 +39,6 @@ final class SessionEngine { self.networkingInteractor = networkingInteractor self.historyService = historyService self.verifyContextStore = verifyContextStore - self.verifyClient = verifyClient self.kms = kms self.sessionStore = sessionStore self.logger = logger diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index 8ad3d214b..98bdb3239 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -65,7 +65,7 @@ public struct SignClientFactory { let verifyClient = VerifyClientFactory.create() let sessionRequestsProvider = SessionRequestsProvider(historyService: historyService) let invalidRequestsSanitiser = InvalidRequestsSanitiser(historyService: historyService, history: rpcHistory) - let sessionEngine = SessionEngine(networkingInteractor: networkingClient, historyService: historyService, verifyContextStore: verifyContextStore, verifyClient: verifyClient, kms: kms, sessionStore: sessionStore, logger: logger, sessionRequestsProvider: sessionRequestsProvider, invalidRequestsSanitiser: invalidRequestsSanitiser) + let sessionEngine = SessionEngine(networkingInteractor: networkingClient, historyService: historyService, verifyContextStore: verifyContextStore, kms: kms, sessionStore: sessionStore, logger: logger, sessionRequestsProvider: sessionRequestsProvider, invalidRequestsSanitiser: invalidRequestsSanitiser) let nonControllerSessionStateMachine = NonControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger) let controllerSessionStateMachine = ControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger) let sessionExtendRequester = SessionExtendRequester(sessionStore: sessionStore, networkingInteractor: networkingClient) diff --git a/Sources/WalletConnectVerify/AttestationJWTVerifier.swift b/Sources/WalletConnectVerify/AttestationJWTVerifier.swift index fb9ea9c7a..cea4f9a72 100644 --- a/Sources/WalletConnectVerify/AttestationJWTVerifier.swift +++ b/Sources/WalletConnectVerify/AttestationJWTVerifier.swift @@ -11,6 +11,8 @@ struct AttestationJWTClaims: Codable { var id: String var origin: String + + var isVerified: Bool } class AttestationJWTVerifier { @@ -47,7 +49,8 @@ class AttestationJWTVerifier { func verifyJWTAgainstPubKey(_ jwtString: String, signingPubKey: P256.Signing.PublicKey) throws { let validator = P256JWTValidator(jwtString: jwtString) - guard try validator.isValid(publicKey: signingPubKey) else { + guard let isValid = try? validator.isValid(publicKey: signingPubKey), + isValid else { throw Errors.invalidJWT } } diff --git a/Sources/WalletConnectVerify/Register/VerifyService.swift b/Sources/WalletConnectVerify/Register/VerifyService.swift index 5cf720118..dec10faee 100644 --- a/Sources/WalletConnectVerify/Register/VerifyService.swift +++ b/Sources/WalletConnectVerify/Register/VerifyService.swift @@ -20,7 +20,7 @@ enum VerifyAPI: HTTPService { } var queryParameters: [String: String]? { - return nil + return ["v2Supported": "true"] } var scheme: String { From c54013ae69fd729c2ba724dbff4c37ce4e712b6c Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 12 Aug 2024 14:57:51 +0200 Subject: [PATCH 17/26] savepoint --- .../WalletConnectVerify/VerifyContextFactory.swift | 10 +++++++++- Tests/VerifyTests/VerifyContextFactoryTests.swift | 13 +++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Sources/WalletConnectVerify/VerifyContextFactory.swift b/Sources/WalletConnectVerify/VerifyContextFactory.swift index 0cdec63fb..004743a44 100644 --- a/Sources/WalletConnectVerify/VerifyContextFactory.swift +++ b/Sources/WalletConnectVerify/VerifyContextFactory.swift @@ -2,13 +2,21 @@ import Foundation class VerifyContextFactory { - public func createVerifyContext(origin: String?, domain: String, isScam: Bool?) -> VerifyContext { + public func createVerifyContext(origin: String?, domain: String, isScam: Bool?, isVerified: Bool? = nil) -> VerifyContext { + if let isVerified = isVerified, !isVerified { + return VerifyContext( + origin: origin, + validation: .unknown + ) + } + guard isScam != true else { return VerifyContext( origin: origin, validation: .scam ) } + if let origin, let originUrl = URL(string: origin), let domainUrl = URL(string: domain) { return VerifyContext( origin: origin, diff --git a/Tests/VerifyTests/VerifyContextFactoryTests.swift b/Tests/VerifyTests/VerifyContextFactoryTests.swift index 2806b10c3..f89526d91 100644 --- a/Tests/VerifyTests/VerifyContextFactoryTests.swift +++ b/Tests/VerifyTests/VerifyContextFactoryTests.swift @@ -36,6 +36,19 @@ class VerifyContextFactoryTests: XCTestCase { XCTAssertEqual(context.validation, .unknown) } + func testVerifyContextIsMarkedAsUnknownWhenIsVerifiedIsFalse() { + let context = factory.createVerifyContext(origin: "http://example.com", domain: "http://example.com", isScam: false, isVerified: false) + XCTAssertEqual(context.validation, .unknown) + } + + func testVerifyContextIsMarkedAsScamWhenIsScamIsTrueRegardlessOfIsVerified() { + let context = factory.createVerifyContext(origin: "http://example.com", domain: "http://example.com", isScam: true, isVerified: true) + XCTAssertEqual(context.validation, .scam) + + let contextWithFalseVerification = factory.createVerifyContext(origin: "http://example.com", domain: "http://example.com", isScam: true, isVerified: false) + XCTAssertEqual(contextWithFalseVerification.validation, .scam) + } + // tests for createVerifyContextForLinkMode func testValidUniversalLink() { From 41afb9e9a9e555c7818cdfce2fa4260f1b4e48a7 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 12 Aug 2024 15:05:10 +0200 Subject: [PATCH 18/26] add isVerified property --- .../WalletConnectVerify/VerifyContextFactory.swift | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Sources/WalletConnectVerify/VerifyContextFactory.swift b/Sources/WalletConnectVerify/VerifyContextFactory.swift index 004743a44..f30b5ba65 100644 --- a/Sources/WalletConnectVerify/VerifyContextFactory.swift +++ b/Sources/WalletConnectVerify/VerifyContextFactory.swift @@ -3,20 +3,23 @@ import Foundation class VerifyContextFactory { public func createVerifyContext(origin: String?, domain: String, isScam: Bool?, isVerified: Bool? = nil) -> VerifyContext { - if let isVerified = isVerified, !isVerified { + + guard isScam != true else { return VerifyContext( origin: origin, - validation: .unknown + validation: .scam ) } - guard isScam != true else { + // If isVerified is provided and is false, return unknown + if let isVerified = isVerified, !isVerified { return VerifyContext( origin: origin, - validation: .scam + validation: .unknown ) } + // Existing behavior for origin and domain validation if let origin, let originUrl = URL(string: origin), let domainUrl = URL(string: domain) { return VerifyContext( origin: origin, From bd983cb2069b61062d727a4a3ce40c7a1a138c58 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 12 Aug 2024 15:15:45 +0200 Subject: [PATCH 19/26] fix AttestationJWTVerifierTests --- .../WalletConnectVerify/VerifyServerPubKeyManager.swift | 4 ++-- Tests/VerifyTests/AttestationJWTVerifier.swift | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift b/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift index b48b01d1a..49e2d3051 100644 --- a/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift +++ b/Sources/WalletConnectVerify/VerifyServerPubKeyManager.swift @@ -73,8 +73,8 @@ class VerifyServerPubKeyManagerMock: VerifyServerPubKeyManagerProtocol { ext: true, keyOps: ["verify"], kty: "EC", - x: "NzUWD0z_Sr3corYWb2bfEF68_UvYcxFzslHKRJ_xCHk", - y: "gWkzUBt_VKro6rydyWdm-ii9-DHjpxdxVQTyX_ZZohc" + x: "CbL4DOYOb1ntd-8OmExO-oS0DWCMC00DntrymJoB8tk", + y: "KTFwjHtQxGTDR91VsOypcdBfvbo6sAMj5p4Wb-9hRA0" ) mockPublicKey = try? jwk.P256SigningPublicKey() } diff --git a/Tests/VerifyTests/AttestationJWTVerifier.swift b/Tests/VerifyTests/AttestationJWTVerifier.swift index 8c32352e1..2f1a0e7ab 100644 --- a/Tests/VerifyTests/AttestationJWTVerifier.swift +++ b/Tests/VerifyTests/AttestationJWTVerifier.swift @@ -13,16 +13,15 @@ class AttestationJWTVerifierTests: XCTestCase { } func testVerifyValidJWT() async throws { - let jwt = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjIyMjMyOTYsImlkIjoiQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQSIsIm9yaWdpbiI6Imh0dHBzOi8vd2ViM21vZGFsLmNvbSIsImlzU2NhbSI6ZmFsc2V9.IyIDRId-8Yv6ZkrnHh4BdL7AClNM5brOyGpYbUw9V_SHJqxgEd9UzMlwcOsVoFHxIqgyoYA-ulvANHW0kv_KdA" - let messageId = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + let jwt = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjM0NzE3MjMsImlkIjoiYjNmYmZhMDUxZDJhNjdkNGRmNTYzM2IyMjc0NDAyNTUxMTg1NzQwZGQwMjA3YWM0OWI1M2RiYTcxOTc0YTgzNCIsIm9yaWdpbiI6Imh0dHBzOi8vcmVhY3QtZGFwcC12Mi1naXQtY2hvcmUtdmVyaWZ5LXYyLXNhbXBsZXMtd2FsbGV0Y29ubmVjdDEudmVyY2VsLmFwcCIsImlzU2NhbSI6bnVsbCwiaXNWZXJpZmllZCI6dHJ1ZX0.8RQwiEEfTGn8p3INRdHpi88dpzetKCp3nscfLtWG2cVE2dU0dWgV2ncqnh_RWmygqEnWCPUlH1RMwS1nWbZzrQ" + let messageId = "b3fbfa051d2a67d4df5633b2274402551185740dd0207ac49b53dba71974a834" let response = try await verifier.verify(attestationJWT: jwt, messageId: messageId) - XCTAssertEqual(response.origin, "https://web3modal.com") - XCTAssertEqual(response.isScam, false) + XCTAssertEqual(response.origin, "https://react-dapp-v2-git-chore-verify-v2-samples-walletconnect1.vercel.app") } func testVerifyJWTWithInvalidMessageId() async throws { - let jwt = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjIyMjMyOTYsImlkIjoiQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQSIsIm9yaWdpbiI6Imh0dHBzOi8vd2ViM21vZGFsLmNvbSIsImlzU2NhbSI6ZmFsc2V9.IyIDRId-8Yv6ZkrnHh4BdL7AClNM5brOyGpYbUw9V_SHJqxgEd9UzMlwcOsVoFHxIqgyoYA-ulvANHW0kv_KdA" + let jwt = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjM0NzE3MjMsImlkIjoiYjNmYmZhMDUxZDJhNjdkNGRmNTYzM2IyMjc0NDAyNTUxMTg1NzQwZGQwMjA3YWM0OWI1M2RiYTcxOTc0YTgzNCIsIm9yaWdpbiI6Imh0dHBzOi8vcmVhY3QtZGFwcC12Mi1naXQtY2hvcmUtdmVyaWZ5LXYyLXNhbXBsZXMtd2FsbGV0Y29ubmVjdDEudmVyY2VsLmFwcCIsImlzU2NhbSI6bnVsbCwiaXNWZXJpZmllZCI6dHJ1ZX0.8RQwiEEfTGn8p3INRdHpi88dpzetKCp3nscfLtWG2cVE2dU0dWgV2ncqnh_RWmygqEnWCPUlH1RMwS1nWbZzrQ" let invalidMessageId = "InvalidMessageId" do { From a00908828001f89587918697cd09eea272a728e1 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 12 Aug 2024 15:28:56 +0200 Subject: [PATCH 20/26] savepoint --- .../Auth/Link/ApproveSessionAuthenticateUtil.swift | 2 +- .../Auth/Services/Wallet/AuthRequestSubscriber.swift | 4 ++-- .../Engine/Common/ApproveEngine.swift | 7 ++++--- .../WalletConnectVerify/AttestationJWTVerifier.swift | 2 +- .../WalletConnectVerify/Register/VerifyResponse.swift | 1 + Sources/WalletConnectVerify/VerifyClient.swift | 11 +++++------ .../WalletConnectVerify/VerifyContextFactory.swift | 2 +- Tests/VerifyTests/VerifyContextFactoryTests.swift | 8 ++++---- 8 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Sources/WalletConnectSign/Auth/Link/ApproveSessionAuthenticateUtil.swift b/Sources/WalletConnectSign/Auth/Link/ApproveSessionAuthenticateUtil.swift index c60273ff9..cb439a4ff 100644 --- a/Sources/WalletConnectSign/Auth/Link/ApproveSessionAuthenticateUtil.swift +++ b/Sources/WalletConnectSign/Auth/Link/ApproveSessionAuthenticateUtil.swift @@ -123,7 +123,7 @@ class ApproveSessionAuthenticateUtil { func getVerifyContext(requestId: RPCID, domain: String) -> VerifyContext { - (try? verifyContextStore.get(key: requestId.string)) ?? verifyClient.createVerifyContext(origin: nil, domain: domain, isScam: false) + (try? verifyContextStore.get(key: requestId.string)) ?? verifyClient.createVerifyContext(origin: nil, domain: domain, isScam: false, isVerified: nil) } diff --git a/Sources/WalletConnectSign/Auth/Services/Wallet/AuthRequestSubscriber.swift b/Sources/WalletConnectSign/Auth/Services/Wallet/AuthRequestSubscriber.swift index 53ccbe790..a3a5bbb7a 100644 --- a/Sources/WalletConnectSign/Auth/Services/Wallet/AuthRequestSubscriber.swift +++ b/Sources/WalletConnectSign/Auth/Services/Wallet/AuthRequestSubscriber.swift @@ -64,11 +64,11 @@ class AuthRequestSubscriber { let assertionId = payload.decryptedPayload.sha256().toHexString() response = try await verifyClient.verify(.v1(assertionId: assertionId)) } - let verifyContext = verifyClient.createVerifyContext(origin: response.origin, domain: payload.request.authPayload.domain, isScam: response.isScam) + let verifyContext = verifyClient.createVerifyContext(origin: response.origin, domain: payload.request.authPayload.domain, isScam: response.isScam, isVerified: response.isVerified) verifyContextStore.set(verifyContext, forKey: request.id.string) onRequest?((request, verifyContext)) } catch { - let verifyContext = verifyClient.createVerifyContext(origin: nil, domain: payload.request.authPayload.domain, isScam: nil) + let verifyContext = verifyClient.createVerifyContext(origin: nil, domain: payload.request.authPayload.domain, isScam: nil, isVerified: nil) verifyContextStore.set(verifyContext, forKey: request.id.string) onRequest?((request, verifyContext)) return diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index 79a0cd447..fbe9a1a56 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -226,7 +226,7 @@ final class ApproveEngine { expiry: Int64(expiry) ) - let verifyContext = (try? verifyContextStore.get(key: proposal.proposer.publicKey)) ?? verifyClient.createVerifyContext(origin: nil, domain: proposal.proposer.metadata.url, isScam: false) + let verifyContext = (try? verifyContextStore.get(key: proposal.proposer.publicKey)) ?? verifyClient.createVerifyContext(origin: nil, domain: proposal.proposer.metadata.url, isScam: false, isVerified: nil) let session = WCSession( @@ -411,12 +411,13 @@ private extension ApproveEngine { let verifyContext = verifyClient.createVerifyContext( origin: response.origin, domain: payload.request.proposer.metadata.url, - isScam: response.isScam + isScam: response.isScam, + isVerified: response.isVerified ) verifyContextStore.set(verifyContext, forKey: proposal.proposer.publicKey) onSessionProposal?(proposal.publicRepresentation(pairingTopic: payload.topic), verifyContext) } catch { - let verifyContext = verifyClient.createVerifyContext(origin: nil, domain: payload.request.proposer.metadata.url, isScam: nil) + let verifyContext = verifyClient.createVerifyContext(origin: nil, domain: payload.request.proposer.metadata.url, isScam: nil, isVerified: nil) onSessionProposal?(proposal.publicRepresentation(pairingTopic: payload.topic), verifyContext) return } diff --git a/Sources/WalletConnectVerify/AttestationJWTVerifier.swift b/Sources/WalletConnectVerify/AttestationJWTVerifier.swift index cea4f9a72..424cf8fc4 100644 --- a/Sources/WalletConnectVerify/AttestationJWTVerifier.swift +++ b/Sources/WalletConnectVerify/AttestationJWTVerifier.swift @@ -44,7 +44,7 @@ class AttestationJWTVerifier { throw Errors.messageIdMismatch } - return VerifyResponse(origin: claims.origin, isScam: claims.isScam) + return VerifyResponse(origin: claims.origin, isScam: claims.isScam, isVerified: claims.isVerified) } func verifyJWTAgainstPubKey(_ jwtString: String, signingPubKey: P256.Signing.PublicKey) throws { diff --git a/Sources/WalletConnectVerify/Register/VerifyResponse.swift b/Sources/WalletConnectVerify/Register/VerifyResponse.swift index 24fba83eb..5ea509b46 100644 --- a/Sources/WalletConnectVerify/Register/VerifyResponse.swift +++ b/Sources/WalletConnectVerify/Register/VerifyResponse.swift @@ -3,4 +3,5 @@ import Foundation public struct VerifyResponse: Decodable { public let origin: String? public let isScam: Bool? + public let isVerified: Bool? } diff --git a/Sources/WalletConnectVerify/VerifyClient.swift b/Sources/WalletConnectVerify/VerifyClient.swift index 0d56ad089..93fb7dbb4 100644 --- a/Sources/WalletConnectVerify/VerifyClient.swift +++ b/Sources/WalletConnectVerify/VerifyClient.swift @@ -3,7 +3,7 @@ import Foundation public protocol VerifyClientProtocol { func verify(_ verificationType: VerificationType) async throws -> VerifyResponse - func createVerifyContext(origin: String?, domain: String, isScam: Bool?) -> VerifyContext + func createVerifyContext(origin: String?, domain: String, isScam: Bool?, isVerified: Bool?) -> VerifyContext func createVerifyContextForLinkMode(redirectUniversalLink: String, domain: String) -> VerifyContext } @@ -53,8 +53,8 @@ public actor VerifyClient: VerifyClientProtocol { } } - nonisolated public func createVerifyContext(origin: String?, domain: String, isScam: Bool?) -> VerifyContext { - verifyContextFactory.createVerifyContext(origin: origin, domain: domain, isScam: isScam) + nonisolated public func createVerifyContext(origin: String?, domain: String, isScam: Bool?, isVerified: Bool?) -> VerifyContext { + verifyContextFactory.createVerifyContext(origin: origin, domain: domain, isScam: isScam, isVerified: isVerified) } nonisolated public func createVerifyContextForLinkMode(redirectUniversalLink: String, domain: String) -> VerifyContext { @@ -69,14 +69,13 @@ public actor VerifyClient: VerifyClientProtocol { #if DEBUG public struct VerifyClientMock: VerifyClientProtocol { - public init() {} public func verify(_ verificationType: VerificationType) async throws -> VerifyResponse { - return VerifyResponse(origin: "domain.com", isScam: nil) + return VerifyResponse(origin: "domain.com", isScam: nil, isVerified: nil) } - public func createVerifyContext(origin: String?, domain: String, isScam: Bool?) -> VerifyContext { + public func createVerifyContext(origin: String?, domain: String, isScam: Bool?, isVerified: Bool?) -> VerifyContext { return VerifyContext(origin: "domain.com", validation: .valid) } diff --git a/Sources/WalletConnectVerify/VerifyContextFactory.swift b/Sources/WalletConnectVerify/VerifyContextFactory.swift index f30b5ba65..e1326ca09 100644 --- a/Sources/WalletConnectVerify/VerifyContextFactory.swift +++ b/Sources/WalletConnectVerify/VerifyContextFactory.swift @@ -2,7 +2,7 @@ import Foundation class VerifyContextFactory { - public func createVerifyContext(origin: String?, domain: String, isScam: Bool?, isVerified: Bool? = nil) -> VerifyContext { + public func createVerifyContext(origin: String?, domain: String, isScam: Bool?, isVerified: Bool?) -> VerifyContext { guard isScam != true else { return VerifyContext( diff --git a/Tests/VerifyTests/VerifyContextFactoryTests.swift b/Tests/VerifyTests/VerifyContextFactoryTests.swift index f89526d91..0d43c2aed 100644 --- a/Tests/VerifyTests/VerifyContextFactoryTests.swift +++ b/Tests/VerifyTests/VerifyContextFactoryTests.swift @@ -17,22 +17,22 @@ class VerifyContextFactoryTests: XCTestCase { } func testScamValidation() { - let context = factory.createVerifyContext(origin: "http://example.com", domain: "http://example.com", isScam: true) + let context = factory.createVerifyContext(origin: "http://example.com", domain: "http://example.com", isScam: true, isVerified: nil) XCTAssertEqual(context.validation, .scam) } func testValidOriginAndDomain() { - let context = factory.createVerifyContext(origin: "http://example.com", domain: "http://example.com", isScam: false) + let context = factory.createVerifyContext(origin: "http://example.com", domain: "http://example.com", isScam: false, isVerified: nil) XCTAssertEqual(context.validation, .valid) } func testInvalidOriginAndDomain() { - let context = factory.createVerifyContext(origin: "http://example.com", domain: "http://different.com", isScam: false) + let context = factory.createVerifyContext(origin: "http://example.com", domain: "http://different.com", isScam: false, isVerified: nil) XCTAssertEqual(context.validation, .invalid) } func testUnknownValidation() { - let context = factory.createVerifyContext(origin: nil, domain: "http://example.com", isScam: false) + let context = factory.createVerifyContext(origin: nil, domain: "http://example.com", isScam: false, isVerified: nil) XCTAssertEqual(context.validation, .unknown) } From a495480c139bd5c2c5ff22465e3e251354250f5f Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 12 Aug 2024 15:39:59 +0200 Subject: [PATCH 21/26] fix redirect issue --- .../Wallet/AuthRequest/AuthRequestPresenter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift index fdd623c2f..ce57ddd3d 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift @@ -87,7 +87,7 @@ final class AuthRequestPresenter: ObservableObject { /* Redirect */ if let uri = request.requester.redirect?.native { -// WalletConnectRouter.goBack(uri: uri) + WalletConnectRouter.goBack(uri: uri) router.dismiss() } else { showSignedSheet.toggle() From 8489f46ac0a1babf165acb82a0b6abf12b9a47b4 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 13 Aug 2024 06:52:35 +0200 Subject: [PATCH 22/26] savepoint --- Sources/WalletConnectJWT/JWTValidator.swift | 2 +- .../Link/ApproveSessionAuthenticateUtil.swift | 5 +++- .../Wallet/AuthRequestSubscriber.swift | 1 - .../PublicKeyFetcher.swift | 26 ++++++++++++++++--- .../VerifyContextFactory.swift | 1 - 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/Sources/WalletConnectJWT/JWTValidator.swift b/Sources/WalletConnectJWT/JWTValidator.swift index 4b82018fa..7dc300db5 100644 --- a/Sources/WalletConnectJWT/JWTValidator.swift +++ b/Sources/WalletConnectJWT/JWTValidator.swift @@ -3,7 +3,7 @@ import CryptoKit public struct JWTValidator { - let jwtString: String + private let jwtString: String public init(jwtString: String) { self.jwtString = jwtString diff --git a/Sources/WalletConnectSign/Auth/Link/ApproveSessionAuthenticateUtil.swift b/Sources/WalletConnectSign/Auth/Link/ApproveSessionAuthenticateUtil.swift index cb439a4ff..73f14e765 100644 --- a/Sources/WalletConnectSign/Auth/Link/ApproveSessionAuthenticateUtil.swift +++ b/Sources/WalletConnectSign/Auth/Link/ApproveSessionAuthenticateUtil.swift @@ -123,7 +123,10 @@ class ApproveSessionAuthenticateUtil { func getVerifyContext(requestId: RPCID, domain: String) -> VerifyContext { - (try? verifyContextStore.get(key: requestId.string)) ?? verifyClient.createVerifyContext(origin: nil, domain: domain, isScam: false, isVerified: nil) + guard let context = try? verifyContextStore.get(key: requestId.string) else { + return verifyClient.createVerifyContext(origin: nil, domain: domain, isScam: false, isVerified: nil) + } + return context } diff --git a/Sources/WalletConnectSign/Auth/Services/Wallet/AuthRequestSubscriber.swift b/Sources/WalletConnectSign/Auth/Services/Wallet/AuthRequestSubscriber.swift index a3a5bbb7a..d8e4372b6 100644 --- a/Sources/WalletConnectSign/Auth/Services/Wallet/AuthRequestSubscriber.swift +++ b/Sources/WalletConnectSign/Auth/Services/Wallet/AuthRequestSubscriber.swift @@ -54,7 +54,6 @@ class AuthRequestSubscriber { let request = AuthenticationRequest(id: payload.id, topic: payload.topic, payload: payload.request.authPayload, requester: payload.request.requester.metadata) Task(priority: .high) { - let assertionId = payload.decryptedPayload.sha256().toHexString() do { let response: VerifyResponse if let attestation = payload.attestation, diff --git a/Sources/WalletConnectVerify/PublicKeyFetcher.swift b/Sources/WalletConnectVerify/PublicKeyFetcher.swift index bbacc7e6f..b178673b2 100644 --- a/Sources/WalletConnectVerify/PublicKeyFetcher.swift +++ b/Sources/WalletConnectVerify/PublicKeyFetcher.swift @@ -58,26 +58,44 @@ struct VerifyServerPublicKey: Codable { class PublicKeyFetcher: PublicKeyFetching { + enum Errors: Error, LocalizedError { + case invalidURL + case httpError(statusCode: Int, message: String) + case decodingError(Error) + } private let urlString = "https://verify.walletconnect.org/v2/public-key" + func fetchPublicKey() async throws -> VerifyServerPublicKey { guard let url = URL(string: urlString) else { - throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"]) + throw Errors.invalidURL } let (data, response) = try await URLSession.shared.data(from: url) if let httpResponse = response as? HTTPURLResponse, !(200...299).contains(httpResponse.statusCode) { let errorMessage = String(data: data, encoding: .utf8) ?? "Unknown error" - throw NSError(domain: "", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: errorMessage]) + throw Errors.httpError(statusCode: httpResponse.statusCode, message: errorMessage) } do { let publicKeyResponse = try JSONDecoder().decode(VerifyServerPublicKey.self, from: data) return publicKeyResponse } catch { - print("Decoding error: \(error)") - throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to decode JSON"]) + throw Errors.decodingError(error) + } + } +} + +extension PublicKeyFetcher.Errors { + var errorDescription: String? { + switch self { + case .invalidURL: + return "The URL provided is invalid." + case .httpError(let statusCode, let message): + return "HTTP Error \(statusCode): \(message)" + case .decodingError: + return "Failed to decode the JSON response." } } } diff --git a/Sources/WalletConnectVerify/VerifyContextFactory.swift b/Sources/WalletConnectVerify/VerifyContextFactory.swift index e1326ca09..629973e38 100644 --- a/Sources/WalletConnectVerify/VerifyContextFactory.swift +++ b/Sources/WalletConnectVerify/VerifyContextFactory.swift @@ -19,7 +19,6 @@ class VerifyContextFactory { ) } - // Existing behavior for origin and domain validation if let origin, let originUrl = URL(string: origin), let domainUrl = URL(string: domain) { return VerifyContext( origin: origin, From 83c7c63d5d2e92aab51df0e86213f378770b6c5b Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 13 Aug 2024 06:53:48 +0200 Subject: [PATCH 23/26] savepoint --- Sources/WalletConnectJWT/JWTValidator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/WalletConnectJWT/JWTValidator.swift b/Sources/WalletConnectJWT/JWTValidator.swift index 7dc300db5..e328c9cec 100644 --- a/Sources/WalletConnectJWT/JWTValidator.swift +++ b/Sources/WalletConnectJWT/JWTValidator.swift @@ -29,7 +29,7 @@ public struct JWTValidator { public struct P256JWTValidator { - let jwtString: String + private let jwtString: String public init(jwtString: String) { self.jwtString = jwtString From 8d228bcd3a5132fcada87298a444fb5939ea9757 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 13 Aug 2024 06:55:04 +0200 Subject: [PATCH 24/26] savepoint --- Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift index f460252ce..5002ceff4 100644 --- a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift +++ b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift @@ -45,7 +45,7 @@ class WalletRequestSubscriber { let assertionId = payload.decryptedPayload.sha256().toHexString() do { let response = try await verifyClient.verify(.v1(assertionId: assertionId)) - let verifyContext = verifyClient.createVerifyContext(origin: response.origin, domain: payload.request.payloadParams.domain, isScam: response.isScam) + let verifyContext = verifyClient.createVerifyContext(origin: response.origin, domain: payload.request.payloadParams.domain, isScam: response.isScam, isVerified: nil) verifyContextStore.set(verifyContext, forKey: request.id.string) onRequest?((request, verifyContext)) } catch { From 4d8f292a1b9ae8d979de6e7308ad05f99951d421 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 13 Aug 2024 06:59:10 +0200 Subject: [PATCH 25/26] fix unit tests --- Tests/WalletConnectSignTests/SessionEngineTests.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/WalletConnectSignTests/SessionEngineTests.swift b/Tests/WalletConnectSignTests/SessionEngineTests.swift index a27cf8807..63bff095f 100644 --- a/Tests/WalletConnectSignTests/SessionEngineTests.swift +++ b/Tests/WalletConnectSignTests/SessionEngineTests.swift @@ -30,7 +30,6 @@ final class SessionEngineTests: XCTestCase { networkingInteractor: networkingInteractor, historyService: historyService, verifyContextStore: verifyContextStore, - verifyClient: VerifyClientMock(), kms: KeyManagementServiceMock(), sessionStore: sessionStorage, logger: ConsoleLoggerMock(), @@ -75,7 +74,6 @@ final class SessionEngineTests: XCTestCase { networkingInteractor: networkingInteractor, historyService: historyService, verifyContextStore: verifyContextStore, - verifyClient: VerifyClientMock(), kms: KeyManagementServiceMock(), sessionStore: sessionStorage, logger: ConsoleLoggerMock(), From ee030b5dcc304612df6fd6082a1336e7c4178f57 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 13 Aug 2024 07:32:33 +0200 Subject: [PATCH 26/26] fix auth package issue --- Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift | 2 +- Sources/WalletConnectSign/Services/HistoryService.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift index 5002ceff4..c8697ec5d 100644 --- a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift +++ b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift @@ -49,7 +49,7 @@ class WalletRequestSubscriber { verifyContextStore.set(verifyContext, forKey: request.id.string) onRequest?((request, verifyContext)) } catch { - let verifyContext = verifyClient.createVerifyContext(origin: nil, domain: payload.request.payloadParams.domain, isScam: nil) + let verifyContext = verifyClient.createVerifyContext(origin: nil, domain: payload.request.payloadParams.domain, isScam: nil, isVerified: nil) verifyContextStore.set(verifyContext, forKey: request.id.string) onRequest?((request, verifyContext)) return diff --git a/Sources/WalletConnectSign/Services/HistoryService.swift b/Sources/WalletConnectSign/Services/HistoryService.swift index c3ac80a3a..4963b8028 100644 --- a/Sources/WalletConnectSign/Services/HistoryService.swift +++ b/Sources/WalletConnectSign/Services/HistoryService.swift @@ -110,7 +110,7 @@ final class MockHistoryService: HistoryServiceProtocol { removePendingRequestCalled(topic) } - func getSessionRequest(id: JSONRPC.RPCID) -> (request: Request, context: VerifyContext?)? { + func getSessionRequest(id: RPCID) -> (request: Request, context: VerifyContext?)? { fatalError("Unimplemented") }