Skip to content

Commit

Permalink
[Core] Feature/#263 add SocketAuthenicator and JWT (#283)
Browse files Browse the repository at this point in the history
* Add SerializationPolicy type

* savepoint

* savepoint

* Add envelope

* update serialize method

* update envelope

* handle envelope in Sign sdk

* extract envelope to a new file

* add getPublicKey method to kms

* fix kms errors

* fix kms tests

* add kms tests scheme
fix codec tests

* fix serializer tests

* update kms test, fix type 0 envelope key size issue

* simplify serialiser, add envelope init

* Fix envelope type, fix serialiser tests

* remove debugging prints

* update codec docs

* remove unused error

* remove unused error

* move serializing protocol to serializer

* prevent potential crash on envelope init

* add serializing file

* run lint

* Add client auth core components

* Add client auth mocks

* Add signing private key

* Add signing crypto kit wrapper

* Add Socket Authenticator Test

* update SocketAuthenticator

* savepoint

* savepoint

* pass jwt test

* extract jwt encoder

* savepoint

* update signer

* fix tests

* extract ED25519DIDKeyFactoryMock

* clean up

* remove commented code

* run lint fix

* change relay url

* test

* update relay url

* test

* test

* revert

* test

* fix typo

* savepoint

* Update ED25519DIDKeyFactoryTests

* update test
  • Loading branch information
llbartekll committed Jul 5, 2022
1 parent ec9f3a3 commit 8e2fc42
Show file tree
Hide file tree
Showing 24 changed files with 421 additions and 6 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ let package = Package(
path: "Sources/Chat"),
.target(
name: "WalletConnectRelay",
dependencies: ["WalletConnectUtils", "Starscream"],
dependencies: ["WalletConnectUtils", "Starscream", "WalletConnectKMS"],
path: "Sources/WalletConnectRelay"),
.target(
name: "WalletConnectKMS",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public struct AgreementPrivateKey: GenericPasswordConvertible, Equatable {
}

public init<D>(rawRepresentation: D) throws where D: ContiguousBytes {

self.key = try Curve25519.KeyAgreement.PrivateKey(rawRepresentation: rawRepresentation)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import Foundation
import CryptoKit

// MARK: - CryptoKit extensions

extension Curve25519.Signing.PublicKey: Equatable {
public static func == (lhs: Curve25519.Signing.PublicKey, rhs: Curve25519.Signing.PublicKey) -> Bool {
lhs.rawRepresentation == rhs.rawRepresentation
}
}

extension Curve25519.Signing.PrivateKey: Equatable {
public static func == (lhs: Curve25519.Signing.PrivateKey, rhs: Curve25519.Signing.PrivateKey) -> Bool {
lhs.rawRepresentation == rhs.rawRepresentation
}
}

// MARK: - Public Key

public struct SigningPublicKey: GenericPasswordConvertible, Equatable {
public init<D>(rawRepresentation data: D) throws where D: ContiguousBytes {
self.key = try Curve25519.Signing.PublicKey(rawRepresentation: data)
}

fileprivate let key: Curve25519.Signing.PublicKey

fileprivate init(publicKey: Curve25519.Signing.PublicKey) {
self.key = publicKey
}

public init(hex: String) throws {
let data = Data(hex: hex)
try self.init(rawRepresentation: data)
}

public var rawRepresentation: Data {
key.rawRepresentation
}

public var hexRepresentation: String {
key.rawRepresentation.toHexString()
}
}

extension SigningPublicKey: Codable {

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(key.rawRepresentation)
}

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let buffer = try container.decode(Data.self)
try self.init(rawRepresentation: buffer)
}
}

public struct SigningPrivateKey: GenericPasswordConvertible, Equatable {

private let key: Curve25519.Signing.PrivateKey

public init() {
self.key = Curve25519.Signing.PrivateKey()
}

public init<D>(rawRepresentation: D) throws where D: ContiguousBytes {

self.key = try Curve25519.Signing.PrivateKey(rawRepresentation: rawRepresentation)
}

public var rawRepresentation: Data {
key.rawRepresentation
}

public var publicKey: SigningPublicKey {
SigningPublicKey(publicKey: key.publicKey)
}

public func signature(_ data: Data) throws -> Data {
return try key.signature(for: data)
}
}
11 changes: 11 additions & 0 deletions Sources/WalletConnectRelay/ClientAuth/AuthChallengeProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

protocol AuthChallengeProviding {
func getChallenge(for clientId: String) async throws -> String
}

actor AuthChallengeProvider: AuthChallengeProviding {
func getChallenge(for clientId: String) async throws -> String {
fatalError("not implemented")
}
}
13 changes: 13 additions & 0 deletions Sources/WalletConnectRelay/ClientAuth/ClientIdStorage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Foundation
import WalletConnectKMS

protocol ClientIdStoring {
func getOrCreateKeyPair() async throws -> SigningPrivateKey
}

actor ClientIdStorage: ClientIdStoring {
func getOrCreateKeyPair() async throws -> SigningPrivateKey {
fatalError("not implemented")
}

}
33 changes: 33 additions & 0 deletions Sources/WalletConnectRelay/ClientAuth/ED25519DIDKeyFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Foundation

protocol ED25519DIDKeyFactory {
func make(pubKey: Data) -> String
}

/// did-key-format := did:key:MULTIBASE(base58-btc, MULTICODEC(public-key-type, raw-public-key-bytes))
struct ED25519DIDKeyFactoryImpl: ED25519DIDKeyFactory {
private static let DID_DELIMITER = ":"
private static let DID_PREFIX = "did"
private static let DID_METHOD = "key"
private static let MULTICODEC_ED25519_HEADER = "K36"
private static let MULTICODEC_ED25519_ENCODING = "base58btc"
private static let MULTICODEC_ED25519_BASE = "z"

func make(pubKey: Data) -> String {
fatalError("not implemented")

let multicodec = multicodec()

let multibase = multibase(multicodec: multicodec)

return [Self.DID_PREFIX, Self.DID_METHOD, multibase].joined(separator: Self.DID_DELIMITER)
}

private func multibase(multicodec: String) -> String {
fatalError("not implemented")
}

private func multicodec() -> String {
fatalError("not implemented")
}
}
14 changes: 14 additions & 0 deletions Sources/WalletConnectRelay/ClientAuth/JWT/JWT+Claims.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Foundation

extension JWT {
struct Claims: Codable, Equatable {
let iss: String
let sub: String
func encode() throws -> String {
let jsonEncoder = JSONEncoder()
jsonEncoder.dateEncodingStrategy = .secondsSince1970
let data = try jsonEncoder.encode(self)
return JWTEncoder.base64urlEncodedString(data: data)
}
}
}
20 changes: 20 additions & 0 deletions Sources/WalletConnectRelay/ClientAuth/JWT/JWT+Header.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Foundation

extension JWT {
struct Header: Codable, Equatable {
var alg: String!
let typ: String

init(alg: String? = nil) {
self.alg = alg
typ = "JWT"
}

func encode() throws -> String {
let jsonEncoder = JSONEncoder()
jsonEncoder.dateEncodingStrategy = .secondsSince1970
let data = try jsonEncoder.encode(self)
return JWTEncoder.base64urlEncodedString(data: data)
}
}
}
30 changes: 30 additions & 0 deletions Sources/WalletConnectRelay/ClientAuth/JWT/JWT.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Foundation

struct JWT: Codable, Equatable {
enum Errors: Error {
case jwtNotSigned
}

var header: Header
var claims: Claims
var signature: String?

public init(header: Header = Header(), claims: Claims) {
self.header = header
self.claims = claims
}

public mutating func sign(using jwtSigner: JWTSigning) throws {
header.alg = jwtSigner.alg
let headerString = try header.encode()
let claimsString = try claims.encode()
self.signature = try jwtSigner.sign(header: headerString, claims: claimsString)
}

func encoded() throws -> String {
guard let signature = signature else { throw Errors.jwtNotSigned }
let headerString = try header.encode()
let claimsString = try claims.encode()
return [headerString, claimsString, signature].joined(separator: ".")
}
}
14 changes: 14 additions & 0 deletions Sources/WalletConnectRelay/ClientAuth/JWT/JWTEncoder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Foundation

struct JWTEncoder {
/// Returns a `String` representation of this data, encoded in base64url format
/// as defined in RFC4648 (https://tools.ietf.org/html/rfc4648).
///
/// This is the appropriate format for encoding the header and claims of a JWT.
public static func base64urlEncodedString(data: Data) -> String {
let result = data.base64EncodedString()
return result.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
}
}
29 changes: 29 additions & 0 deletions Sources/WalletConnectRelay/ClientAuth/JWT/JWTSigning.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Foundation
import WalletConnectKMS

protocol JWTSigning {
var alg: String {get}
func sign(header: String, claims: String) throws -> String
}

struct EdDSASigner: JWTSigning {
enum Errors: Error {
case invalidJWTString
}
var alg = "EdDSA"
let privateKey: SigningPrivateKey

init(_ keys: SigningPrivateKey) {
self.privateKey = keys
}

func sign(header: String, claims: String) throws -> String {
let unsignedJWT = header + "." + claims
guard let unsignedData = unsignedJWT.data(using: .utf8) else {
throw Errors.invalidJWTString
}
let signature = try privateKey.signature(unsignedData)
let signatureString = JWTEncoder.base64urlEncodedString(data: signature)
return signatureString
}
}
34 changes: 34 additions & 0 deletions Sources/WalletConnectRelay/ClientAuth/SocketAuthenticator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Foundation
import WalletConnectKMS

protocol SocketAuthenticating {
func createAuthToken() async throws -> String
}

actor SocketAuthenticator: SocketAuthenticating {
private let authChallengeProvider: AuthChallengeProviding
private let clientIdStorage: ClientIdStoring
private let didKeyFactory: ED25519DIDKeyFactory

init(authChallengeProvider: AuthChallengeProviding = AuthChallengeProvider(),
clientIdStorage: ClientIdStoring = ClientIdStorage(),
didKeyFactory: ED25519DIDKeyFactory = ED25519DIDKeyFactoryImpl()) {
self.authChallengeProvider = authChallengeProvider
self.clientIdStorage = clientIdStorage
self.didKeyFactory = didKeyFactory
}

func createAuthToken() async throws -> String {
let clientIdKeyPair = try await clientIdStorage.getOrCreateKeyPair()
let challenge = try await authChallengeProvider.getChallenge(for: clientIdKeyPair.publicKey.hexRepresentation)
return try signJWT(subject: challenge, keyPair: clientIdKeyPair)
}

private func signJWT(subject: String, keyPair: SigningPrivateKey) throws -> String {
let issuer = didKeyFactory.make(pubKey: keyPair.publicKey.rawRepresentation)
let claims = JWT.Claims(iss: issuer, sub: subject)
var jwt = JWT(claims: claims)
try jwt.sign(using: EdDSASigner(keyPair))
return try jwt.encoded()
}
}
2 changes: 1 addition & 1 deletion Tests/ChatTests/EndToEndTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ final class ChatTests: XCTestCase {

func makeClient(prefix: String) -> Chat {
let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug)
let relayHost = "beta.relay.walletconnect.com"
let relayHost = "dev.relay.walletconnect.com"
let projectId = "8ba9ee138960775e5231b70cc5ef1c3a"
let relayClient = RelayClient(relayHost: relayHost, projectId: projectId, logger: logger)
let keychain = KeychainStorage(keychainService: KeychainServiceFake(), serviceIdentifier: "")
Expand Down
2 changes: 1 addition & 1 deletion Tests/IntegrationTests/SignClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ final class SignClientTests: XCTestCase {

static private func makeClientDelegate(
name: String,
relayHost: String = "beta.relay.walletconnect.com",
relayHost: String = "dev.relay.walletconnect.com",
projectId: String = "8ba9ee138960775e5231b70cc5ef1c3a"
) -> ClientDelegate {
let logger = ConsoleLogger(suffix: name, loggingLevel: .debug)
Expand Down
19 changes: 19 additions & 0 deletions Tests/RelayerTests/AuthTests/ED25519DIDKeyTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Foundation
import XCTest
@testable import WalletConnectRelay

final class ED25519DIDKeyFactoryTests: XCTestCase {
let expectedDid = "did:key:z6MkodHZwneVRShtaLf8JKYkxpDGp1vGZnpGmdBpX8M2exxH"
let pubKey = Data(hex: "884ab67f787b69e534bfdba8d5beb4e719700e90ac06317ed177d49e5a33be5a")
var sut: ED25519DIDKeyFactoryImpl!

override func setUp() {
sut = ED25519DIDKeyFactoryImpl()
}

func test() {
// let did = sut.make(pubKey: pubKey)
// XCTAssertEqual(expectedDid, did)
}

}
21 changes: 21 additions & 0 deletions Tests/RelayerTests/AuthTests/EdDSASignerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Foundation
import XCTest
import WalletConnectKMS
@testable import WalletConnectRelay

final class EdDSASignerTests: XCTestCase {
var sut: EdDSASigner!

func testSign() {
let keyRaw = Data(hex: "58e0254c211b858ef7896b00e3f36beeb13d568d47c6031c4218b87718061295")
let signingKey = try! SigningPrivateKey(rawRepresentation: keyRaw)
sut = EdDSASigner(signingKey)
let header = try! JWT.Header(alg: "EdDSA").encode()
let claims = try! JWT.Claims(
iss: "did:key:z6MkodHZwneVRShtaLf8JKYkxpDGp1vGZnpGmdBpX8M2exxH",
sub: "c479fe5dc464e771e78b193d239a65b58d278cad1c34bfb0b5716e5bb514928e")
.encode()
let signature = try! sut.sign(header: header, claims: claims)
XCTAssertNotNil(signature)
}
}
20 changes: 20 additions & 0 deletions Tests/RelayerTests/AuthTests/JWTTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Foundation
import XCTest
@testable import WalletConnectRelay

final class JWTTests: XCTestCase {
let expectedJWT = "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtvZEhad25lVlJTaHRhTGY4SktZa3hwREdwMXZHWm5wR21kQnBYOE0yZXh4SCIsInN1YiI6ImM0NzlmZTVkYzQ2NGU3NzFlNzhiMTkzZDIzOWE2NWI1OGQyNzhjYWQxYzM0YmZiMGI1NzE2ZTViYjUxNDkyOGUifQ.0JkxOM-FV21U7Hk-xycargj_qNRaYV2H5HYtE4GzAeVQYiKWj7YySY5AdSqtCgGzX4Gt98XWXn2kSr9rE1qvCA"

func testJWTEncoding() {
let iss = "did:key:z6MkodHZwneVRShtaLf8JKYkxpDGp1vGZnpGmdBpX8M2exxH"
let sub = "c479fe5dc464e771e78b193d239a65b58d278cad1c34bfb0b5716e5bb514928e"
let claims = JWT.Claims(iss: iss, sub: sub)
var jwt = JWT(claims: claims)
let signer = EdDSASignerMock()
signer.signature = "0JkxOM-FV21U7Hk-xycargj_qNRaYV2H5HYtE4GzAeVQYiKWj7YySY5AdSqtCgGzX4Gt98XWXn2kSr9rE1qvCA"
try! jwt.sign(using: signer)
let encoded = try! jwt.encoded()
XCTAssertEqual(expectedJWT, encoded)
}

}
Loading

0 comments on commit 8e2fc42

Please sign in to comment.