Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Auth] #462 test message compromised #463

Merged
merged 4 commits into from
Aug 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions Example/IntegrationTests/Auth/AuthTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,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(requestId: id, result: .success(cacaoSignature))
try! await wallet.respond(requestId: id, signature: cacaoSignature)
}
}
.store(in: &publishers)
Expand All @@ -81,6 +81,25 @@ final class AuthTests: XCTestCase {
wait(for: [responseExpectation], timeout: 5)
}

func testUserRespondError() async {
let responseExpectation = expectation(description: "error 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) {
try! await wallet.reject(requestId: id)
}
}
.store(in: &publishers)
app.authResponsePublisher.sink { (id, result) in
guard case .failure(let error) = result else { XCTFail(); return }
XCTAssertEqual(error, .userRejeted)
responseExpectation.fulfill()
}
.store(in: &publishers)
wait(for: [responseExpectation], timeout: 5)
}

func testRespondSignatureVerificationFailed() async {
let responseExpectation = expectation(description: "invalid signature response delivered")
let uri = try! await app.request(RequestParams.stub())
Expand All @@ -89,7 +108,7 @@ final class AuthTests: XCTestCase {
Task(priority: .high) {
let invalidSignature = "438effc459956b57fcd9f3dac6c675f9cee88abf21acab7305e8e32aa0303a883b06dcbd956279a7a2ca21ffa882ff55cc22e8ab8ec0f3fe90ab45f306938cfa1b"
let cacaoSignature = CacaoSignature(t: "eip191", s: invalidSignature)
try! await wallet.respond(requestId: id, result: .success(cacaoSignature))
try! await wallet.respond(requestId: id, signature: cacaoSignature)
}
}
.store(in: &publishers)
Expand Down
9 changes: 7 additions & 2 deletions Sources/Auth/AuthClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import WalletConnectUtils
import WalletConnectPairing
import WalletConnectRelay


public class AuthClient {
enum Errors: Error {
case malformedPairingURI
Expand Down Expand Up @@ -87,9 +88,13 @@ public class AuthClient {
try await appRequestService.request(params: params, topic: topic)
}

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

public func reject(requestId: RPCID) async throws {
try await walletRespondService.respondError(requestId: requestId)
}

public func getPendingRequests() throws -> [AuthRequest] {
Expand Down
11 changes: 9 additions & 2 deletions Sources/Auth/Services/App/AppRespondSubscriber.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,23 @@ class AppRespondSubscriber {

private func subscribeForResponse() {
networkingInteractor.responsePublisher.sink { [unowned self] subscriptionPayload in
let response = subscriptionPayload.response
guard
let requestId = subscriptionPayload.response.id,
let requestId = response.id,
let request = rpcHistory.get(recordId: requestId)?.request,
let requestParams = request.params, request.method == "wc_authRequest"
else { return }

networkingInteractor.unsubscribe(topic: subscriptionPayload.topic)

if let errorResponse = response.error,
let error = AuthError(code: errorResponse.code) {
onResponse?(requestId, .failure(error))
return
}

guard
let cacao = try? subscriptionPayload.response.result?.get(Cacao.self),
let cacao = try? 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 }
Expand Down
13 changes: 2 additions & 11 deletions Sources/Auth/Services/Wallet/WalletRespondService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,7 @@ actor WalletRespondService {
self.rpcHistory = rpcHistory
}

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

private func respond(requestId: RPCID, signature: CacaoSignature, account: Account) async throws {
func respond(requestId: RPCID, signature: CacaoSignature, account: Account) async throws {
let authRequestParams = try getAuthRequestParams(requestId: requestId)
let (topic, keys) = try generateAgreementKeys(requestParams: authRequestParams)

Expand All @@ -44,7 +35,7 @@ actor WalletRespondService {
try await networkingInteractor.respond(topic: topic, response: response, tag: AuthResponseParams.tag, envelopeType: .type1(pubKey: keys.publicKey.rawRepresentation))
}

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

Expand Down
17 changes: 17 additions & 0 deletions Sources/Auth/Types/Errors/AuthError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,23 @@ public enum AuthError: Codable, Equatable, Error {

extension AuthError: Reason {

init?(code: Int) {
switch code {
case Self.userRejeted.code:
self = .userRejeted
case Self.malformedResponseParams.code:
self = .malformedResponseParams
case Self.malformedRequestParams.code:
self = .malformedRequestParams
case Self.messageCompromised.code:
self = .messageCompromised
case Self.signatureVerificationFailed.code:
self = .signatureVerificationFailed
default:
return nil
}
}

public var code: Int {
switch self {
case .userRejeted:
Expand Down
67 changes: 67 additions & 0 deletions Tests/AuthTests/AppRespondSubscriberTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import Foundation
import XCTest
@testable import Auth
import WalletConnectUtils
@testable import WalletConnectKMS
@testable import TestingUtils
import JSONRPC

class AppRespondSubscriberTests: XCTestCase {
var networkingInteractor: NetworkingInteractorMock!
var sut: AppRespondSubscriber!
var messageFormatter: SIWEMessageFormatter!
var rpcHistory: RPCHistory!
let defaultTimeout: TimeInterval = 0.01
let walletAccount = Account(chainIdentifier: "eip155:1", address: "0x724d0D2DaD3fbB0C168f947B87Fa5DBe36F1A8bf")!
let prvKey = Data(hex: "462c1dad6832d7d96ccf87bd6a686a4110e114aaaebd5512e552c0e3a87b480f")
var messageSigner: MessageSigner!

override func setUp() {
networkingInteractor = NetworkingInteractorMock()
messageFormatter = SIWEMessageFormatter()
messageSigner = MessageSigner()
let historyStorage = CodableStore<RPCHistory.Record>(defaults: RuntimeKeyValueStorage(), identifier: StorageDomainIdentifiers.jsonRpcHistory.rawValue)
rpcHistory = RPCHistory(keyValueStore: historyStorage)
sut = AppRespondSubscriber(
networkingInteractor: networkingInteractor,
logger: ConsoleLoggerMock(),
rpcHistory: rpcHistory,
signatureVerifier: messageSigner,
messageFormatter: messageFormatter)
}

func testMessageCompromisedFailure() {
let messageExpectation = expectation(description: "receives response")

// set history record for a request
let topic = "topic"
let requestId: RPCID = RPCID(1234)
let request = RPCRequest(method: "wc_authRequest", params: AuthRequestParams.stub(), id: requestId.right!)
try! rpcHistory.set(request, forTopic: topic, emmitedBy: .local)

var messageId: RPCID!
var result: Result<Cacao, AuthError>!
sut.onResponse = { id, r in
messageId = id
result = r
messageExpectation.fulfill()
}

// subscribe on compromised cacao
let header = CacaoHeader(t: "eip4361")
let payload = CacaoPayload(params: AuthPayload.stub(nonce: "compromised nonce"), didpkh: DIDPKH(account: walletAccount))

let message = try! messageFormatter.formatMessage(from: payload)
let signature = try! messageSigner.sign(message: message, privateKey: prvKey)
let cacaoSignature = CacaoSignature(t: "eip191", s: signature)

let cacao = Cacao(header: header, payload: payload, signature: cacaoSignature)

let response = RPCResponse(id: requestId, result: cacao)
networkingInteractor.responsePublisherSubject.send(ResponseSubscriptionPayload(topic: topic, response: response))

wait(for: [messageExpectation], timeout: defaultTimeout)
XCTAssertEqual(result, .failure(AuthError.messageCompromised))
XCTAssertEqual(messageId, requestId)
}
}
14 changes: 14 additions & 0 deletions Tests/AuthTests/Mocks/AppMetadata.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

import Foundation
import WalletConnectPairing

public extension AppMetadata {
static func stub() -> AppMetadata {
AppMetadata(
name: "Wallet Connect",
description: "A protocol to connect blockchain wallets to dapps.",
url: "https://walletconnect.com/",
icons: []
)
}
}
2 changes: 1 addition & 1 deletion Tests/AuthTests/Mocks/NetworkingInteractorMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ struct NetworkingInteractorMock: NetworkInteracting {
var responsePublisher: AnyPublisher<ResponseSubscriptionPayload, Never> {
responsePublisherSubject.eraseToAnyPublisher()
}
private let responsePublisherSubject = PassthroughSubject<ResponseSubscriptionPayload, Never>()
let responsePublisherSubject = PassthroughSubject<ResponseSubscriptionPayload, Never>()

let requestPublisherSubject = PassthroughSubject<RequestSubscriptionPayload, Never>()
var requestPublisher: AnyPublisher<RequestSubscriptionPayload, Never> {
Expand Down
7 changes: 7 additions & 0 deletions Tests/AuthTests/Stubs/AuthPayload.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,10 @@ extension AuthPayload {
AuthPayload(requestParams: requestParams, iat: "2021-09-30T16:25:24Z")
}
}

extension AuthPayload {
static func stub(nonce: String) -> AuthPayload {
let issueAt = ISO8601DateFormatter().string(from: Date())
return AuthPayload(requestParams: RequestParams.stub(nonce: nonce), iat: issueAt)
}
}
16 changes: 16 additions & 0 deletions Tests/AuthTests/Stubs/AuthRequestParams.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@testable import Auth
import Foundation
import WalletConnectPairing

extension AuthRequestParams {
static func stub(nonce: String = "32891756") -> AuthRequestParams {
let payload = AuthPayload.stub(nonce: nonce)
return AuthRequestParams(requester: Requester.stub(), payloadParams: payload)
}
}

extension AuthRequestParams.Requester {
static func stub() -> AuthRequestParams.Requester {
AuthRequestParams.Requester(publicKey: "", metadata: AppMetadata.stub())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import WalletConnectUtils
@testable import TestingUtils
import JSONRPC

class AuthRequstSubscriberTests: XCTestCase {
class WalletRequestSubscriberTests: XCTestCase {
var networkingInteractor: NetworkingInteractorMock!
var sut: WalletRequestSubscriber!
var messageFormatter: SIWEMessageFormatterMock!
Expand Down
14 changes: 14 additions & 0 deletions Tests/WalletConnectSignTests/Mocks/AppMetadata.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

import Foundation
import WalletConnectPairing

public extension AppMetadata {
static func stub() -> AppMetadata {
AppMetadata(
name: "Wallet Connect",
description: "A protocol to connect blockchain wallets to dapps.",
url: "https://walletconnect.com/",
icons: []
)
}
}
10 changes: 0 additions & 10 deletions Tests/WalletConnectSignTests/Stub/Stubs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,6 @@ import WalletConnectUtils
import TestingUtils
import WalletConnectPairing

extension AppMetadata {
static func stub() -> AppMetadata {
AppMetadata(
name: "Wallet Connect",
description: "A protocol to connect blockchain wallets to dapps.",
url: "https://walletconnect.com/",
icons: []
)
}
}

extension Pairing {
static func stub(expiryDate: Date = Date(timeIntervalSinceNow: 10000), topic: String = String.generateTopic()) -> Pairing {
Expand Down