Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

Commit

Permalink
Use web5-swift JWS.sign & JWS.verify instead of CryptoUtils (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
amika-sq authored Feb 9, 2024
1 parent 52b8848 commit 08346ac
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 172 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/Frizlab/swift-typeid.git", from: "0.3.0"),
.package(url: "https://github.com/flight-school/anycodable.git", from: "0.6.7"),
.package(url: "https://github.com/TBD54566975/web5-swift", exact: "0.0.2"),
.package(url: "https://github.com/TBD54566975/web5-swift", exact: "0.0.3"),
.package(url: "https://github.com/allegro/swift-junit.git", from: "2.1.0"),
.package(url: "https://github.com/pointfreeco/swift-custom-dump.git", from: "1.1.2"),
],
Expand Down
152 changes: 1 addition & 151 deletions Sources/tbDEX/Protocol/CryptoUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,8 @@ enum CryptoUtils {}
extension CryptoUtils {

static func digest<D: Codable, M: Codable>(data: D, metadata: M) throws -> Data {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
encoder.outputFormatting = .sortedKeys

let payload = DigestPayload(data: data, metadata: metadata)
let serializedPayload = try encoder.encode(payload)

let serializedPayload = try tbDEXJSONEncoder().encode(payload)
let digest = SHA256.hash(data: serializedPayload)
return Data(digest)
}
Expand All @@ -26,148 +21,3 @@ extension CryptoUtils {
}

}

// MARK: - Sign

extension CryptoUtils {

enum SigningError: Error {
case assertionMethodNotFound
case publicKeyJwkNotFound
case algorithmNotDefined
}

/// Signs the provided payload using the specified DID and key.
/// - Parameters:
/// - did: DID to use for signing
/// - payload: The data to be signed
/// - assertionMethodId: The alias of the key to be used for signing.
/// - Returns: The signed payload as a detached payload JWT (JSON Web Token).
static func sign<D>(did: BearerDID, payload: D, assertionMethodId: String? = nil) async throws -> String
where D: DataProtocol {
let assertionMethod = try await getAssertionMethod(did: did, assertionMethodId: assertionMethodId)
guard let publicKeyJwk = assertionMethod.publicKeyJwk else {
throw SigningError.publicKeyJwkNotFound
}

let keyAlias = try did.keyManager.getDeterministicAlias(key: publicKeyJwk)
let publicKey = try did.keyManager.getPublicKey(keyAlias: keyAlias)
guard let algorithm = publicKey.algorithm?.jwsAlgorithm else {
throw SigningError.algorithmNotDefined
}

let jwsHeader = JWS.Header(
algorithm: algorithm,
keyID: assertionMethod.id
)

let base64UrlEncodedHeader = try JSONEncoder().encode(jwsHeader).base64UrlEncodedString()
let base64UrlEncodedPayload = payload.base64UrlEncodedString()

let toSign = "\(base64UrlEncodedHeader).\(base64UrlEncodedPayload)"
let signatureBytes = try did.keyManager.sign(keyAlias: keyAlias, payload: Data(toSign.utf8))
let base64UrlEncodedSignature = signatureBytes.base64UrlEncodedString()

return "\(base64UrlEncodedHeader)..\(base64UrlEncodedSignature)"
}

private static func getAssertionMethod(did: BearerDID, assertionMethodId: String?) async throws
-> VerificationMethod
{
let resolutionResult = await DIDResolver.resolve(didURI: did.uri)
let assertionMethods = resolutionResult.didDocument?.assertionMethodDereferenced

guard
let assertionMethod =
if let assertionMethodId {
assertionMethods?.first(where: { $0.id == assertionMethodId })
} else {
assertionMethods?.first
}
else {
throw SigningError.assertionMethodNotFound
}

return assertionMethod
}

}

// MARK: - Verify

extension CryptoUtils {

struct VerifyError: Error {
let reason: String
}

// Verifies the integrity of a message or resource's signature.
static func verify<D: DataProtocol>(
didURI: String,
signature: String?,
detachedPayload: D? = nil
) async throws -> Bool {
guard let signature else {
throw VerifyError(reason: "Signature not present")
}

let splitJWS = signature.split(separator: ".", omittingEmptySubsequences: false)

guard splitJWS.count == 3 else {
throw VerifyError(reason: "Excpected valid JWS with 3 parts, got \(splitJWS.count)")
}

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601

let jwsHeader = String(splitJWS[0])
let jwsSignature = String(splitJWS[2])
let jwsPayload: String

if let detachedPayload {
guard splitJWS[1].count == 0 else {
throw VerifyError(reason: "Expected valid JWS with detached payload")
}
jwsPayload = String(detachedPayload.base64UrlEncodedString())
} else {
jwsPayload = String(splitJWS[1])
}

guard let jwsHeader = try? JSONDecoder().decode(JWS.Header.self, from: jwsHeader.decodeBase64Url()),
let verificationMethodID = jwsHeader.keyID
else {
throw VerifyError(reason: "")
}

let signingDID = try DID(didURI: verificationMethodID)
let signingDIDURI = signingDID.uriWithoutFragment

guard signingDIDURI == didURI else {
throw VerifyError(reason: "Was not signed by the expected DID - Expected:\(didURI) Actual:\(signingDIDURI)")
}

let resolutionResult = await DIDResolver.resolve(didURI: signingDIDURI)
if let error = resolutionResult.didResolutionMetadata.error {
throw VerifyError(reason: "Failed to resolve DID \(signingDIDURI): \(error)")
}

guard
let assertionMethod =
resolutionResult.didDocument?.assertionMethodDereferenced?.first(
where: { $0.absoluteId == verificationMethodID }
)
else {
throw VerifyError(reason: "Assertion method not found")
}

let publicKeyJwk = assertionMethod.publicKeyJwk!

return try Crypto.verify(
payload: try jwsPayload.decodeBase64Url(),
signature: try jwsSignature.decodeBase64Url(),
publicKey: publicKeyJwk,
jwsAlgorithm: jwsHeader.algorithm
)
}

}
17 changes: 13 additions & 4 deletions Sources/tbDEX/Protocol/Models/Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,21 @@ public struct Message<D: MessageData>: Codable, Equatable {
try CryptoUtils.digest(data: data, metadata: metadata)
}

mutating func sign(did: BearerDID, keyAlias: String? = nil) async throws {
signature = try await CryptoUtils.sign(did: did, payload: try digest(), assertionMethodId: keyAlias)
mutating func sign(did: BearerDID, keyAlias: String? = nil) throws {
signature = try JWS.sign(
did: did,
payload: try digest(),
detached: true,
verificationMethodID: keyAlias
)
}

func verify() async throws {
_ = try await CryptoUtils.verify(didURI: metadata.from, signature: signature, detachedPayload: try digest())
func verify() async throws -> Bool {
return try await JWS.verify(
compactJWS: signature,
detachedPayload: try digest(),
expectedSigningDIDURI: metadata.from
)
}
}

Expand Down
16 changes: 12 additions & 4 deletions Sources/tbDEX/Protocol/Models/Resource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,21 @@ public struct Resource<D: ResourceData>: Codable, Equatable {
}

mutating func sign(did: BearerDID, keyAlias: String? = nil) async throws {
self.signature = try await CryptoUtils.sign(did: did, payload: digest(), assertionMethodId: keyAlias)
self.signature = try JWS.sign(
did: did,
payload: try digest(),
detached: true,
verificationMethodID: keyAlias
)
}

func verify() async throws {
_ = try await CryptoUtils.verify(didURI: metadata.from, signature: signature, detachedPayload: digest())
func verify() async throws -> Bool {
return try await JWS.verify(
compactJWS: signature,
detachedPayload: try digest(),
expectedSigningDIDURI: metadata.from
)
}

}

/// Enum containing the different types of Resources
Expand Down
20 changes: 8 additions & 12 deletions Tests/tbDEXTests/Protocol/Models/Resources/OfferingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,14 @@ final class OfferingTests: XCTestCase {
}

func test_signAndVerifySuccess() async throws {
do {
let did = try DIDJWK.create(keyManager: InMemoryKeyManager())
var offering = createOffering(from: did.uri)

XCTAssertNil(offering.signature)
try await offering.sign(did: did)
XCTAssertNotNil(offering.signature)
try await offering.verify()
} catch {
print("Something went wrong: \(error)")
XCTFail()
}
let did = try DIDJWK.create(keyManager: InMemoryKeyManager())
var offering = createOffering(from: did.uri)

XCTAssertNil(offering.signature)
try await offering.sign(did: did)
XCTAssertNotNil(offering.signature)
let isValid = try await offering.verify()
XCTAssertTrue(isValid)
}

func test_verifyWithoutSigningFailure() async throws {
Expand Down

0 comments on commit 08346ac

Please sign in to comment.