Skip to content

Commit

Permalink
Sign and verify SIWE message
Browse files Browse the repository at this point in the history
  • Loading branch information
flypaper0 committed Aug 10, 2022
1 parent 565ffe5 commit 9158317
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 80 deletions.
10 changes: 10 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/WalletConnectAuth.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AuthTests"
BuildableName = "AuthTests"
BlueprintName = "AuthTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
Expand Down
35 changes: 25 additions & 10 deletions Sources/Auth/Services/App/AuthRespondSubscriber.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,48 @@ class AuthRespondSubscriber {
private let networkingInteractor: NetworkInteracting
private let logger: ConsoleLogging
private let rpcHistory: RPCHistory
private let signatureVerifier: CacaoSignatureVerifying
private let signatureVerifier: MessageSignatureVerifying
private let messageFormatter: SIWEMessageFormatting
private var publishers = [AnyCancellable]()
var onResponse: ((_ id: RPCID, _ cacao: Cacao) -> Void)?

init(networkingInteractor: NetworkInteracting,
logger: ConsoleLogging,
rpcHistory: RPCHistory,
signatureVerifier: CacaoSignatureVerifying) {
signatureVerifier: MessageSignatureVerifying,
messageFormatter: SIWEMessageFormatting) {
self.networkingInteractor = networkingInteractor
self.logger = logger
self.rpcHistory = rpcHistory
self.signatureVerifier = signatureVerifier
self.messageFormatter = messageFormatter
subscribeForResponse()
}

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

networkingInteractor.unsubscribe(topic: subscriptionPayload.topic)
guard let cacao = try? subscriptionPayload.request.result?.get(Cacao.self) else {
logger.debug("Malformed auth response params")
return
}

do {
try signatureVerifier.verifySignature(cacao)
onResponse?(subscriptionPayload.request.id!, cacao)
guard let cacao = try subscriptionPayload.request.result?.get(Cacao.self) else {
return logger.debug("Malformed auth response params")
}

let address = try DIDPKH(iss: cacao.payload.iss).account.address
let payload = AuthPayload(payload: cacao.payload)
let message = messageFormatter.formatMessage(from: payload, address: address)

try signatureVerifier.verify(
signature: cacao.signature.s,
message: message,
address: cacao.payload.iss
)
onResponse?(requestId, cacao)
} catch {
logger.debug("Received response with invalid signature")
}
Expand Down
32 changes: 0 additions & 32 deletions Sources/Auth/Services/Signer/CacaoSigner.swift

This file was deleted.

36 changes: 36 additions & 0 deletions Sources/Auth/Services/Signer/MessageSigner.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Foundation

protocol MessageSignatureVerifying {
func verify(signature: String, message: String, address: String) throws
}

protocol MessageSignatureSigning {
func sign(message: String, privateKey: Data) throws -> String
}

struct MessageSigner: MessageSignatureVerifying & MessageSignatureSigning {

enum Errors: Error {
case signatureValidationFailed
case utf8EncodingFailed
}

private let signer: Signer

init(signer: Signer) {
self.signer = signer
}

func sign(message: String, privateKey: Data) throws -> String {
guard let messageData = message.data(using: .utf8) else { throw Errors.utf8EncodingFailed }
let signature = try signer.sign(message: messageData, with: privateKey)
return signature.toHexString()
}

func verify(signature: String, message: String, address: String) throws {
guard let messageData = message.data(using: .utf8) else { throw Errors.utf8EncodingFailed }
let signatureData = Data(hex: signature)
guard try signer.isValid(signature: signatureData, message: messageData, address: address)
else { throw Errors.signatureValidationFailed }
}
}
19 changes: 12 additions & 7 deletions Sources/Auth/Services/Wallet/AuthRequestSubscriber.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,19 @@ class AuthRequestSubscriber {

private func subscribeForRequest() {
networkingInteractor.requestPublisher.sink { [unowned self] subscriptionPayload in
guard subscriptionPayload.request.method == "wc_authRequest" else { return }
guard let authRequestParams = try? subscriptionPayload.request.params?.get(AuthRequestParams.self) else {
logger.debug("Malformed auth request params")
return
}
guard
let requestId = subscriptionPayload.request.id,
subscriptionPayload.request.method == "wc_authRequest" else { return }

do {
let message = try messageFormatter.formatMessage(from: authRequestParams.payloadParams, address: address)
guard let requestId = subscriptionPayload.request.id else { return }
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)
Expand Down
15 changes: 15 additions & 0 deletions Sources/Auth/Types/AuthPayload.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,19 @@ struct AuthPayload: Codable, Equatable {
self.requestId = requestParams.requestId
self.resources = requestParams.resources
}

init(payload: CacaoPayload) {
self.type = "eip4361"
self.chainId = "1" // TODO: Check this!
self.domain = payload.domain
self.aud = payload.aud
self.version = payload.version
self.nonce = payload.nonce
self.iat = payload.iat
self.nbf = payload.nbf
self.exp = payload.exp
self.statement = payload.statement
self.requestId = payload.requestId
self.resources = payload.resources
}
}
51 changes: 20 additions & 31 deletions Tests/AuthTests/CacaoSignerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,33 @@ class CacaoSignerTest: XCTestCase {

let privateKey = Data(hex: "305c6cde3846927892cd32762f6120539f3ec74c9e3a16b9b798b1e85351ae2a")

let payload = CacaoPayload(
iss: "did:pkh:eip155:1:0x15bca56b6e2728aec2532df9d436bd1600e86688",
domain: "localhost:3000",
aud: "http://localhost:3000/login",
version: 1,
nonce: "328917",
iat: "2022-03-10T17:09:21.481+03:00",
nbf: "2022-03-10T17:09:21.481+03:00",
exp: "2022-03-10T18:09:21.481+03:00",
statement: "I accept the ServiceOrg Terms of Service: https://service.org/tos",
requestId: "request-id-random",
resources: [
"ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq",
"https://example.com/my-web2-claim.json"
]
)

var cacao: Cacao {
return Cacao(
header: .init(t: ""),
payload: payload,
signature: sig
)
}
let message: String = """
service.invalid wants you to sign in with your Ethereum account:
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
let sig = CacaoSignature(t: "eip191", s: "914b8300e471744f506407aa072cdf9a606fd3fe1a6f2a16c9f78009074c69622143c3009f4ccdedc0fdd421e5579c5e11b3a604e0a3e6ae0cb06b5e380879fb00", m: "")
I accept the ServiceOrg Terms of Service: https://service.invalid/tos
func testCacaoSign() throws {
let signer = CacaoSigner(signer: Signer())
URI: https://service.invalid/login
Version: 1
Chain ID: 1
Nonce: 32891756
Issued At: 2021-09-30T16:25:24Z
Resources:
- ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/
- https://example.com/my-web2-claim.json
"""

let signature = try signer.sign(payload: payload, privateKey: privateKey)
let signature = "df33c1bb9d0a7934e6b0861d6286d5d223eb679d059fff89ee03530f30cd8d4a767ad28abdab90268a0052277b43f83b26b45194c2eefc5a46c9de727edc098001"

func testCacaoSign() throws {
let signer = MessageSigner(signer: Signer())

XCTAssertEqual(signature, sig)
XCTAssertEqual(try signer.sign(message: message, privateKey: privateKey), signature)
}

func testCacaoVerify() throws {
let signer = CacaoSigner(signer: Signer())
let signer = MessageSigner(signer: Signer())

try signer.verifySignature(cacao)
try signer.verify(signature: signature, message: message, address: "0x15bca56b6e2728aec2532df9d436bd1600e86688")
}
}

0 comments on commit 9158317

Please sign in to comment.