Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:WalletConnect/WalletConnectSwift…
Browse files Browse the repository at this point in the history
…V2 into #453-invalid-signature-erroe

complete testRespondSignatureVerificationFailed
  • Loading branch information
llbartekll committed Aug 23, 2022
2 parents ffc14fb + e24ac11 commit 3f1acd1
Show file tree
Hide file tree
Showing 14 changed files with 224 additions and 86 deletions.
11 changes: 5 additions & 6 deletions Example/IntegrationTests/Auth/AuthTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ final class AuthTests: XCTestCase {
wait(for: [expectation], timeout: 5)
}


func makeClient(prefix: String, account: Account? = nil) -> AuthClient {
let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug)
let relayHost = "relay.walletconnect.com"
Expand Down Expand Up @@ -70,7 +69,7 @@ final class AuthTests: XCTestCase {
Task(priority: .high) {
let signature = try! MessageSigner().sign(message: message, privateKey: prvKey)
let cacaoSignature = CacaoSignature(t: "eip191", s: signature)
try! await wallet.respond(.success(RespondParams(id: id, signature: cacaoSignature)))
try! await wallet.respond(requestId: id, result: .success(cacaoSignature))
}
}
.store(in: &publishers)
Expand All @@ -79,24 +78,24 @@ final class AuthTests: XCTestCase {
responseExpectation.fulfill()
}
.store(in: &publishers)
wait(for: [responseExpectation], timeout: 2)
wait(for: [responseExpectation], timeout: 5)
}

func testRespondInvalidSignature() async {
func testRespondSignatureVerificationFailed() async {
let responseExpectation = expectation(description: "invalid signature response delivered")
let uri = try! await app.request(RequestParams.stub())
try! await wallet.pair(uri: uri)
wallet.authRequestPublisher.sink { [unowned self] (id, message) in
Task(priority: .high) {
let invalidSignature = "438effc459956b57fcd9f3dac6c675f9cee88abf21acab7305e8e32aa0303a883b06dcbd956279a7a2ca21ffa882ff55cc22e8ab8ec0f3fe90ab45f306938cfa1b"
let cacaoSignature = CacaoSignature(t: "eip191", s: invalidSignature)
try! await wallet.respond(.success(RespondParams(id: id, signature: cacaoSignature)))
try! await wallet.respond(requestId: id, result: .success(cacaoSignature))
}
}
.store(in: &publishers)
app.authResponsePublisher.sink { (id, result) in
guard case .failure(let error) = result else { XCTFail(); return }
// TODO - complete after reason codes are merged
XCTAssertEqual(error, .signatureVerificationFailed)
responseExpectation.fulfill()
}
.store(in: &publishers)
Expand Down
8 changes: 4 additions & 4 deletions Sources/Auth/AuthClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ public class AuthClient {
authRequestPublisherSubject.eraseToAnyPublisher()
}

private var authResponsePublisherSubject = PassthroughSubject<(id: RPCID, result: Result<Cacao, ErrorCode>), Never>()
public var authResponsePublisher: AnyPublisher<(id: RPCID, result: Result<Cacao, ErrorCode>), Never> {
private var authResponsePublisherSubject = PassthroughSubject<(id: RPCID, result: Result<Cacao, AuthError>), Never>()
public var authResponsePublisher: AnyPublisher<(id: RPCID, result: Result<Cacao, AuthError>), Never> {
authResponsePublisherSubject.eraseToAnyPublisher()
}

Expand Down Expand Up @@ -87,9 +87,9 @@ public class AuthClient {
try await appRequestService.request(params: params, topic: topic)
}

public func respond(_ result: Result<RespondParams, ErrorCode>) async throws {
public func respond(requestId: RPCID, result: Result<CacaoSignature, Never>) async throws {
guard let account = account else { throw Errors.unknownWalletAddress }
try await walletRespondService.respond(result: result, account: account)
try await walletRespondService.respond(requestId: requestId, result: result, account: account)
}

public func getPendingRequests() throws -> [AuthRequest] {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Auth/AuthClientFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public struct AuthClientFactory {
let messageSigner = MessageSigner(signer: Signer())
let appRespondSubscriber = AppRespondSubscriber(networkingInteractor: networkingInteractor, logger: logger, rpcHistory: history, signatureVerifier: messageSigner, messageFormatter: messageFormatter)
let walletPairService = WalletPairService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore)
let walletRequestSubscriber = WalletRequestSubscriber(networkingInteractor: networkingInteractor, logger: logger, messageFormatter: messageFormatter, address: account?.address)
let walletRequestSubscriber = WalletRequestSubscriber(networkingInteractor: networkingInteractor, logger: logger, kms: kms, messageFormatter: messageFormatter, address: account?.address)
let walletRespondService = WalletRespondService(networkingInteractor: networkingInteractor, logger: logger, kms: kms, rpcHistory: history)
let pendingRequestsProvider = PendingRequestsProvider(rpcHistory: history)
let cleanupService = CleanupService(pairingStore: pairingStore, kms: kms)
Expand Down
39 changes: 16 additions & 23 deletions Sources/Auth/Services/App/AppRespondSubscriber.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ class AppRespondSubscriber {
private let signatureVerifier: MessageSignatureVerifying
private let messageFormatter: SIWEMessageFormatting
private var publishers = [AnyCancellable]()
var onResponse: ((_ id: RPCID, _ result: Result<Cacao, ErrorCode>) -> Void)?

var onResponse: ((_ id: RPCID, _ result: Result<Cacao, AuthError>) -> Void)?

init(networkingInteractor: NetworkInteracting,
logger: ConsoleLogging,
Expand All @@ -26,7 +27,6 @@ class AppRespondSubscriber {
}

private func subscribeForResponse() {
// TODO - handle error response
networkingInteractor.responsePublisher.sink { [unowned self] subscriptionPayload in
guard
let requestId = subscriptionPayload.response.id,
Expand All @@ -36,30 +36,23 @@ class AppRespondSubscriber {

networkingInteractor.unsubscribe(topic: subscriptionPayload.topic)

do {
guard let cacao = try subscriptionPayload.response.result?.get(Cacao.self) else {
return logger.debug("Malformed auth response params")
}
guard
let cacao = try? subscriptionPayload.response.result?.get(Cacao.self),
let address = try? DIDPKH(iss: cacao.payload.iss).account.address,
let message = try? messageFormatter.formatMessage(from: cacao.payload)
else { self.onResponse?(requestId, .failure(.malformedResponseParams)); return }

guard let requestPayload = try? requestParams.get(AuthRequestParams.self)
else { self.onResponse?(requestId, .failure(.malformedRequestParams)); return }

guard messageFormatter.formatMessage(from: requestPayload.payloadParams, address: address) == message
else { self.onResponse?(requestId, .failure(.messageCompromised)); return }

let requestPayload = try requestParams.get(AuthRequestParams.self)
let address = try DIDPKH(iss: cacao.payload.iss).account.address
let message = try messageFormatter.formatMessage(from: cacao.payload)
let originalMessage = messageFormatter.formatMessage(from: requestPayload.payloadParams, address: address)
guard let _ = try? signatureVerifier.verify(signature: cacao.signature.s, message: message, address: address)
else { self.onResponse?(requestId, .failure(.signatureVerificationFailed)); return }

guard originalMessage == message else {
return logger.debug("Original message compromised")
}
onResponse?(requestId, .success(cacao))

try signatureVerifier.verify(
signature: cacao.signature.s,
message: message,
address: address
)
logger.debug("Received response with valid signature")
onResponse?(requestId, .success(cacao))
} catch {
logger.debug("Received response with invalid signature")
}
}.store(in: &publishers)
}
}
19 changes: 15 additions & 4 deletions Sources/Auth/Services/Common/NetworkingInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ protocol NetworkInteracting {
func request(_ request: RPCRequest, topic: String, tag: Int, envelopeType: Envelope.EnvelopeType) async throws
func requestNetworkAck(_ request: RPCRequest, topic: String, tag: Int) async throws
func respond(topic: String, response: RPCResponse, tag: Int, envelopeType: Envelope.EnvelopeType) async throws
func respondError(topic: String, requestId: RPCID, tag: Int, reason: Reason, envelopeType: Envelope.EnvelopeType) async throws
}

extension NetworkInteracting {
Expand All @@ -27,14 +28,17 @@ class NetworkingInteractor: NetworkInteracting {
private let serializer: Serializing
private let rpcHistory: RPCHistory
private let logger: ConsoleLogging

private let requestPublisherSubject = PassthroughSubject<RequestSubscriptionPayload, Never>()
var requestPublisher: AnyPublisher<RequestSubscriptionPayload, Never> {
requestPublisherSubject.eraseToAnyPublisher()
}
private let requestPublisherSubject = PassthroughSubject<RequestSubscriptionPayload, Never>()

private let responsePublisherSubject = PassthroughSubject<ResponseSubscriptionPayload, Never>()
var responsePublisher: AnyPublisher<ResponseSubscriptionPayload, Never> {
responsePublisherSubject.eraseToAnyPublisher()
}
private let responsePublisherSubject = PassthroughSubject<ResponseSubscriptionPayload, Never>()

var socketConnectionStatusPublisher: AnyPublisher<SocketConnectionStatus, Never>

init(relayClient: RelayClient,
Expand Down Expand Up @@ -99,11 +103,18 @@ class NetworkingInteractor: NetworkInteracting {
try await relayClient.publish(topic: topic, payload: message, tag: tag)
}

func respondError(topic: String, requestId: RPCID, tag: Int, reason: Reason, envelopeType: Envelope.EnvelopeType) async throws {
let error = JSONRPCError(code: reason.code, message: reason.message)
let response = RPCResponse(id: requestId, error: error)
let message = try! serializer.serialize(topic: topic, encodable: response, envelopeType: envelopeType)
try await relayClient.publish(topic: topic, payload: message, tag: tag)
}

private func manageSubscription(_ topic: String, _ encodedEnvelope: String) {
if let deserializedJsonRpcRequest: RPCRequest = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) {
handleRequest(topic: topic, request: deserializedJsonRpcRequest)
} else if let deserializedJsonRpcResponse: RPCResponse = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) {
handleResponse(response: deserializedJsonRpcResponse)
} else if let response: RPCResponse = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) {
handleResponse(response: response)
} else {
logger.debug("Networking Interactor - Received unknown object type from networking relay")
}
Expand Down
48 changes: 29 additions & 19 deletions Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,58 @@ import Combine
import Foundation
import WalletConnectUtils
import JSONRPC
import WalletConnectKMS

class WalletRequestSubscriber {
private let networkingInteractor: NetworkInteracting
private let logger: ConsoleLogging
private let kms: KeyManagementServiceProtocol
private let address: String?
private var publishers = [AnyCancellable]()
private let messageFormatter: SIWEMessageFormatting
var onRequest: ((_ id: RPCID, _ message: String) -> Void)?

init(networkingInteractor: NetworkInteracting,
logger: ConsoleLogging,
kms: KeyManagementServiceProtocol,
messageFormatter: SIWEMessageFormatting,
address: String?) {
self.networkingInteractor = networkingInteractor
self.logger = logger
self.kms = kms
self.address = address
self.messageFormatter = messageFormatter
subscribeForRequest()
}

private func subscribeForRequest() {
guard let address = address else {return}
networkingInteractor.requestPublisher.sink { [unowned self] subscriptionPayload in
guard let address = address else { return }

networkingInteractor.requestPublisher.sink { [unowned self] payload in

logger.debug("WalletRequestSubscriber: Received request")
guard
let requestId = subscriptionPayload.request.id,
subscriptionPayload.request.method == "wc_authRequest" else { return }

do {
guard let authRequestParams = try subscriptionPayload.request.params?.get(AuthRequestParams.self) else { return logger.debug("Malformed auth request params")
}

let message = messageFormatter.formatMessage(
from: authRequestParams.payloadParams,
address: address
)

onRequest?(requestId, message)
} catch {
logger.debug(error)
}

guard let requestId = payload.request.id, payload.request.method == "wc_authRequest"
else { return }

guard let authRequestParams = try? payload.request.params?.get(AuthRequestParams.self)
else { return respondError(.malformedRequestParams, topic: payload.topic, requestId: requestId) }

let message = messageFormatter.formatMessage(from: authRequestParams.payloadParams, address: address)

onRequest?(requestId, message)
}.store(in: &publishers)
}

private func respondError(_ error: AuthError, topic: String, requestId: RPCID) {
guard let pubKey = kms.getAgreementSecret(for: topic)?.publicKey
else { return logger.error("Agreement key for topic \(topic) not found") }

let tag = AuthResponseParams.tag
let envelopeType = Envelope.EnvelopeType.type1(pubKey: pubKey.rawRepresentation)

Task(priority: .high) {
try await networkingInteractor.respondError(topic: topic, requestId: requestId, tag: tag, reason: error, envelopeType: envelopeType)
}
}
}
59 changes: 42 additions & 17 deletions Sources/Auth/Services/Wallet/WalletRespondService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,55 @@ actor WalletRespondService {
self.kms = kms
self.rpcHistory = rpcHistory
}
func respond(result: Result<RespondParams, ErrorCode>, account: Account) async throws {

func respond(requestId: RPCID, result: Result<CacaoSignature, Never>, account: Account) async throws {
switch result {
case .success(let params):
try await respond(respondParams: params, account: account)
case .failure(let error):
fatalError("TODO respond with error")
case .success(let signature):
try await respond(requestId: requestId, signature: signature, account: account)
case .failure:
try await respondError(requestId: requestId)
}
}

private func respond(respondParams: RespondParams, account: Account) async throws {
guard let request = rpcHistory.get(recordId: respondParams.id)?.request else { throw Errors.recordForIdNotFound }
guard let authRequestParams = try? request.params?.get(AuthRequestParams.self) else { throw Errors.malformedAuthRequestParams }
private func respond(requestId: RPCID, signature: CacaoSignature, account: Account) async throws {
let authRequestParams = try getAuthRequestParams(requestId: requestId)
let (topic, keys) = try generateAgreementKeys(requestParams: authRequestParams)

let peerPubKey = try AgreementPublicKey(hex: authRequestParams.requester.publicKey)
let responseTopic = peerPubKey.rawRepresentation.sha256().toHexString()
let selfPubKey = try kms.createX25519KeyPair()
let agreementKeys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPubKey.hexRepresentation)
try kms.setAgreementSecret(agreementKeys, topic: responseTopic)
try kms.setAgreementSecret(keys, topic: topic)

let didpkh = DIDPKH(account: account)
let cacao = CacaoFormatter().format(authRequestParams, respondParams.signature, didpkh)
let response = RPCResponse(id: request.id!, result: cacao)
let cacao = CacaoFormatter().format(authRequestParams, signature, didpkh)
let response = RPCResponse(id: requestId, result: cacao)
try await networkingInteractor.respond(topic: topic, response: response, tag: AuthResponseParams.tag, envelopeType: .type1(pubKey: keys.publicKey.rawRepresentation))
}

private func respondError(requestId: RPCID) async throws {
let authRequestParams = try getAuthRequestParams(requestId: requestId)
let (topic, keys) = try generateAgreementKeys(requestParams: authRequestParams)

try kms.setAgreementSecret(keys, topic: topic)

let tag = AuthResponseParams.tag
let error = AuthError.userRejeted
let envelopeType = Envelope.EnvelopeType.type1(pubKey: keys.publicKey.rawRepresentation)
try await networkingInteractor.respondError(topic: topic, requestId: requestId, tag: tag, reason: error, envelopeType: envelopeType)
}

private func getAuthRequestParams(requestId: RPCID) throws -> AuthRequestParams {
guard let request = rpcHistory.get(recordId: requestId)?.request
else { throw Errors.recordForIdNotFound }

guard let authRequestParams = try request.params?.get(AuthRequestParams.self)
else { throw Errors.malformedAuthRequestParams }

try await networkingInteractor.respond(topic: responseTopic, response: response, tag: AuthResponseParams.tag, envelopeType: .type1(pubKey: selfPubKey.rawRepresentation))
return authRequestParams
}

private func generateAgreementKeys(requestParams: AuthRequestParams) throws -> (topic: String, keys: AgreementKeys) {
let peerPubKey = try AgreementPublicKey(hex: requestParams.requester.publicKey)
let topic = peerPubKey.rawRepresentation.sha256().toHexString()
let selfPubKey = try kms.createX25519KeyPair()
let keys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPubKey.hexRepresentation)
return (topic, keys)
}
}
7 changes: 0 additions & 7 deletions Sources/Auth/Types/ErrorCode.swift

This file was deleted.

42 changes: 42 additions & 0 deletions Sources/Auth/Types/Errors/AuthError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Foundation

public enum AuthError: Codable, Equatable, Error {
case userRejeted
case malformedResponseParams
case malformedRequestParams
case messageCompromised
case signatureVerificationFailed
}

extension AuthError: Reason {

public var code: Int {
switch self {
case .userRejeted:
return 14001
case .malformedResponseParams:
return 12001
case .malformedRequestParams:
return 12002
case .messageCompromised:
return 12003
case .signatureVerificationFailed:
return 12004
}
}

public var message: String {
switch self {
case .userRejeted:
return "Auth request rejected by user"
case .malformedResponseParams:
return "Response params malformed"
case .malformedRequestParams:
return "Request params malformed"
case .messageCompromised:
return "Original message compromised"
case .signatureVerificationFailed:
return "Message verification failed"
}
}
}
7 changes: 7 additions & 0 deletions Sources/Auth/Types/Errors/Reason.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

protocol Reason {
var code: Int { get }
var message: String { get }
}

Loading

0 comments on commit 3f1acd1

Please sign in to comment.