diff --git a/Package.resolved b/Package.resolved index 10dfa7a..775cedb 100644 --- a/Package.resolved +++ b/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/scinfu/SwiftSoup.git", "state" : { - "revision" : "028487d4a8a291b2fe1b4392b5425b6172056148", - "version" : "2.7.2" + "revision" : "0837db354faf9c9deb710dc597046edaadf5360f", + "version" : "2.7.6" } }, { diff --git a/Package.swift b/Package.swift index a4aaf2e..8539d58 100644 --- a/Package.swift +++ b/Package.swift @@ -46,10 +46,6 @@ let package = Package( name: "SwiftyJSON", package: "SwiftyJSON" ), - .product( - name: "JOSESwift", - package: "JOSESwift" - ), .product( name: "SwiftSoup", package: "SwiftSoup" diff --git a/Sources/DPoP/DPoPConstructor.swift b/Sources/DPoP/DPoPConstructor.swift index 3627d4a..7b55ccb 100644 --- a/Sources/DPoP/DPoPConstructor.swift +++ b/Sources/DPoP/DPoPConstructor.swift @@ -18,7 +18,7 @@ import JOSESwift import CryptoKit public protocol DPoPConstructorType { - func jwt(endpoint: URL, accessToken: String?) throws -> String + func jwt(endpoint: URL, accessToken: String?) async throws -> String } public class DPoPConstructor: DPoPConstructorType { @@ -36,15 +36,18 @@ public class DPoPConstructor: DPoPConstructorType { public let algorithm: JWSAlgorithm public let jwk: JWK - public let privateKey: SecKey + public let privateKey: SigningKeyProxy - public init(algorithm: JWSAlgorithm, jwk: JWK, privateKey: SecKey) { + public init(algorithm: JWSAlgorithm, jwk: JWK, privateKey: SigningKeyProxy) { self.algorithm = algorithm self.jwk = jwk self.privateKey = privateKey } - public func jwt(endpoint: URL, accessToken: String?) throws -> String { + public func jwt( + endpoint: URL, + accessToken: String? + ) async throws -> String { let header = try JWSHeader(parameters: [ "typ": "dpop+jwt", @@ -71,13 +74,13 @@ public class DPoPConstructor: DPoPConstructorType { throw CredentialIssuanceError.cryptographicAlgorithmNotSupported } - guard let signer = Signer( - signatureAlgorithm: signatureAlgorithm, - key: privateKey - ) else { - throw ValidationError.error(reason: "Unable to create JWS signer") - } - + let signer = try await BindingKey.createSigner( + with: header, + and: payload, + for: privateKey, + and: signatureAlgorithm + ) + let jws = try JWS( header: header, payload: payload, diff --git a/Sources/Entities/Encryption/BindingKey.swift b/Sources/Entities/Encryption/BindingKey.swift index 24eefdc..d92fecd 100644 --- a/Sources/Entities/Encryption/BindingKey.swift +++ b/Sources/Entities/Encryption/BindingKey.swift @@ -16,13 +16,18 @@ import Foundation import JOSESwift +public enum SigningKeyProxy { + case custom(any AsyncSignerProtocol) + case secKey(SecKey) +} + public enum BindingKey { // JWK Binding Key case jwk( algorithm: JWSAlgorithm, jwk: JWK, - privateKey: SecKey, + privateKey: SigningKeyProxy, issuer: String? = nil ) @@ -39,7 +44,7 @@ public extension BindingKey { issuanceRequester: IssuanceRequesterType, credentialSpec: CredentialSupported, cNonce: String? - ) throws -> Proof { + ) async throws -> Proof { switch self { case .jwk( let algorithm, @@ -84,13 +89,13 @@ public extension BindingKey { guard let signatureAlgorithm = SignatureAlgorithm(rawValue: algorithm.name) else { throw CredentialIssuanceError.cryptographicAlgorithmNotSupported } - - guard let signer = Signer( - signatureAlgorithm: signatureAlgorithm, - key: privateKey - ) else { - throw ValidationError.error(reason: "Unable to create JWS signer") - } + + let signer: Signer = try await Self.createSigner( + with: header, + and: payload, + for: privateKey, + and: signatureAlgorithm + ) let jws = try JWS( header: header, @@ -111,13 +116,6 @@ public extension BindingKey { throw CredentialIssuanceError.proofTypeNotSupported } - /* - let bindings = spec.cryptographicBindingMethodsSupported.contains { $0 == .jwk } - guard bindings else { - throw CredentialIssuanceError.cryptographicBindingMethodNotSupported - } - */ - let aud = issuanceRequester.issuerMetadata.credentialIssuerIdentifier.url.absoluteString let header = try JWSHeader(parameters: [ @@ -144,12 +142,12 @@ public extension BindingKey { throw CredentialIssuanceError.cryptographicAlgorithmNotSupported } - guard let signer = Signer( - signatureAlgorithm: signatureAlgorithm, - key: privateKey - ) else { - throw ValidationError.error(reason: "Unable to create JWS signer") - } + let signer: Signer = try await Self.createSigner( + with: header, + and: payload, + for: privateKey, + and: signatureAlgorithm + ) let jws = try JWS( header: header, @@ -170,6 +168,62 @@ public extension BindingKey { } } -private extension BindingKey { +extension BindingKey { + + static func createSigner( + with header: JWSHeader, + and payload: Payload, + for privateKey: SigningKeyProxy, + and signatureAlgorithm: SignatureAlgorithm + ) async throws -> Signer { + + if case let .secKey(secKey) = privateKey, + let secKeySigner = Signer( + signatureAlgorithm: signatureAlgorithm, + key: secKey + ) { + return secKeySigner + + } else if case let .custom(customAsyncSigner) = privateKey { + let signingInput: Data? = [ + header as DataConvertible, + payload as DataConvertible + ].map { + $0.data().base64URLEncodedString() + } + .joined(separator: ".").data(using: .ascii) + + guard let signingInput = signingInput else { + throw ValidationError.error(reason: "Invalid signing input fopr signing data") + } + + let signature = try await customAsyncSigner.signAsync(signingInput) + let customSigner = PrecomputedSigner( + signature: signature, + algorithm: signatureAlgorithm + ) + return Signer(customSigner: customSigner) + + } else { + throw ValidationError.error(reason: "Unable to create JWS signer") + } + } +} + +class PrecomputedSigner: JOSESwift.SignerProtocol { + var algorithm: JOSESwift.SignatureAlgorithm + let signature: Data + + init(signature: Data, algorithm: JOSESwift.SignatureAlgorithm) { + self.algorithm = algorithm + self.signature = signature + } + + func sign(_ signingInput: Data) throws -> Data { + return signature + } +} +public protocol AsyncSignerProtocol { + func signAsync(_ signingInput: Data) async throws -> Data } diff --git a/Sources/Entities/IssuanceAccessToken.swift b/Sources/Entities/IssuanceAccessToken.swift index a3636d7..e771a60 100644 --- a/Sources/Entities/IssuanceAccessToken.swift +++ b/Sources/Entities/IssuanceAccessToken.swift @@ -63,13 +63,14 @@ public extension IssuanceAccessToken { func dPoPOrBearerAuthorizationHeader( dpopConstructor: DPoPConstructorType?, endpoint: URL? - ) throws -> [String: String] { + ) async throws -> [String: String] { if tokenType == TokenType.bearer { return ["Authorization": "\(TokenType.bearer.rawValue) \(accessToken)"] } else if let dpopConstructor, tokenType == TokenType.dpop, let endpoint { + let jwt = try await dpopConstructor.jwt(endpoint: endpoint, accessToken: accessToken) return [ "Authorization": "\(TokenType.dpop.rawValue) \(accessToken)", - TokenType.dpop.rawValue: try dpopConstructor.jwt(endpoint: endpoint, accessToken: accessToken) + TokenType.dpop.rawValue: jwt ] } return ["Authorization": "\(TokenType.bearer.rawValue) \(accessToken)"] diff --git a/Sources/Issuers/IssuanceRequester.swift b/Sources/Issuers/IssuanceRequester.swift index 53b74bf..252d09b 100644 --- a/Sources/Issuers/IssuanceRequester.swift +++ b/Sources/Issuers/IssuanceRequester.swift @@ -69,7 +69,7 @@ public actor IssuanceRequester: IssuanceRequesterType { let endpoint = issuerMetadata.credentialEndpoint.url do { - let authorizationHeader: [String: String] = try accessToken.dPoPOrBearerAuthorizationHeader( + let authorizationHeader: [String: String] = try await accessToken.dPoPOrBearerAuthorizationHeader( dpopConstructor: dpopConstructor, endpoint: endpoint ) @@ -192,7 +192,7 @@ public actor IssuanceRequester: IssuanceRequesterType { } do { - let authorizationHeader: [String: Any] = try accessToken.dPoPOrBearerAuthorizationHeader( + let authorizationHeader: [String: Any] = try await accessToken.dPoPOrBearerAuthorizationHeader( dpopConstructor: dpopConstructor, endpoint: endpoint ) @@ -224,7 +224,7 @@ public actor IssuanceRequester: IssuanceRequesterType { throw CredentialError.issuerDoesNotSupportDeferredIssuance } - let authorizationHeader: [String: String] = try accessToken.dPoPOrBearerAuthorizationHeader( + let authorizationHeader: [String: String] = try await accessToken.dPoPOrBearerAuthorizationHeader( dpopConstructor: dpopConstructor, endpoint: deferredCredentialEndpoint.url ) @@ -298,7 +298,7 @@ public actor IssuanceRequester: IssuanceRequesterType { } let endpoint = notificationEndpoint.url - let authorizationHeader: [String: String] = try accessToken.dPoPOrBearerAuthorizationHeader( + let authorizationHeader: [String: String] = try await accessToken.dPoPOrBearerAuthorizationHeader( dpopConstructor: dpopConstructor, endpoint: endpoint ) diff --git a/Sources/Issuers/Issuer.swift b/Sources/Issuers/Issuer.swift index d36e744..3138808 100644 --- a/Sources/Issuers/Issuer.swift +++ b/Sources/Issuers/Issuer.swift @@ -520,7 +520,7 @@ public actor Issuer: IssuerType { switch proofRequest { case .proofRequired(let token, _, let cNonce, _, _): return try await requestIssuance(token: token) { - let credentialRequests: [CredentialIssuanceRequest] = try requestPayload.map { identifier in + let credentialRequests: [CredentialIssuanceRequest] = try await requestPayload.asyncMap { identifier in guard let supportedCredential = issuerMetadata .credentialsSupported[identifier.credentialConfigurationIdentifier] else { throw ValidationError.error(reason: "Invalid Supported credential for requestBatch") @@ -528,7 +528,7 @@ public actor Issuer: IssuerType { return try supportedCredential.toIssuanceRequest( requester: issuanceRequester, claimSet: identifier.claimSet, - proof: bindingKey.toSupportedProof( + proof: try await bindingKey.toSupportedProof( issuanceRequester: issuanceRequester, credentialSpec: supportedCredential, cNonce: cNonce.value @@ -559,9 +559,9 @@ private extension Issuer { private func requestIssuance( token: IssuanceAccessToken, - issuanceRequestSupplier: () throws -> CredentialIssuanceRequest + issuanceRequestSupplier: () async throws -> CredentialIssuanceRequest ) async throws -> Result { - let credentialRequest = try issuanceRequestSupplier() + let credentialRequest = try await issuanceRequestSupplier() switch credentialRequest { case .single(let single, let encryptionSpec): self.deferredResponseEncryptionSpec = encryptionSpec @@ -674,7 +674,7 @@ private extension Issuer { return try supportedCredential.toIssuanceRequest( requester: issuanceRequester, claimSet: claimSet, - proof: bindingKey?.toSupportedProof( + proof: try await bindingKey?.toSupportedProof( issuanceRequester: issuanceRequester, credentialSpec: supportedCredential, cNonce: cNonce?.value @@ -699,7 +699,7 @@ private extension Issuer { } return try await requestIssuance(token: token) { - return try supportedCredential.toIssuanceRequest( + return try await supportedCredential.toIssuanceRequest( requester: issuanceRequester, proof: bindingKey?.toSupportedProof( issuanceRequester: issuanceRequester, diff --git a/Sources/Main/Authorisers/AuthorizationServerClient.swift b/Sources/Main/Authorisers/AuthorizationServerClient.swift index 6b67796..b964c1f 100644 --- a/Sources/Main/Authorisers/AuthorizationServerClient.swift +++ b/Sources/Main/Authorisers/AuthorizationServerClient.swift @@ -351,9 +351,10 @@ public actor AuthorizationServerClient: AuthorizationServerClientType { private extension AuthorizationServerClient { - func tokenEndPointHeaders() throws -> [String: String] { + func tokenEndPointHeaders() async throws -> [String: String] { if let dpopConstructor { - return ["DPoP": try dpopConstructor.jwt(endpoint: tokenEndpoint, accessToken: nil)] + let jwt = try await dpopConstructor.jwt(endpoint: tokenEndpoint, accessToken: nil) + return ["DPoP": jwt] } else { return [:] } diff --git a/Tests/Constants/TestsConstants.swift b/Tests/Constants/TestsConstants.swift index a9d048b..2dce629 100644 --- a/Tests/Constants/TestsConstants.swift +++ b/Tests/Constants/TestsConstants.swift @@ -22,7 +22,7 @@ let PID_MsoMdoc_config_id = "eu.europa.ec.eudi.pid_mso_mdoc" let PID_SdJwtVC_config_id = "eu.europa.ec.eudi.pid_vc_sd_jwt" //let CREDENTIAL_ISSUER_PUBLIC_URL = "https://dev.issuer.eudiw.dev" -//let PID_SdJwtVC_config_id = "eu.europa.ec.eudi.mdl_jwt_vc_json" +//let PID_SdJwtVC_config_id = "eu.europa.ec.eudi.pid_jwt_vc_json" //let PID_MsoMdoc_config_id = "eu.europa.ec.eudi.pid_mdoc" //let MDL_config_id = "eu.europa.ec.eudi.mdl_mdoc" diff --git a/Tests/Issuance/IssuanceAuthorizationTest.swift b/Tests/Issuance/IssuanceAuthorizationTest.swift index 6b64752..e430c61 100644 --- a/Tests/Issuance/IssuanceAuthorizationTest.swift +++ b/Tests/Issuance/IssuanceAuthorizationTest.swift @@ -383,7 +383,7 @@ class IssuanceAuthorizationTest: XCTestCase { let bindingKey: BindingKey = .jwk( algorithm: alg, jwk: publicKeyJWK, - privateKey: privateKey, + privateKey: .secKey(privateKey), issuer: "218232426" ) @@ -452,6 +452,7 @@ class IssuanceAuthorizationTest: XCTestCase { XCTAssert(false, "Unexpected grant type") } + /// Change the transaction code with the one obtained https://dev.tester.issuer.eudiw.dev/ let result = await issuer.authorizeWithPreAuthorizationCode( credentialOffer: offer, authorizationCode: try .init( @@ -477,7 +478,7 @@ class IssuanceAuthorizationTest: XCTestCase { let bindingKey: BindingKey = .jwk( algorithm: alg, jwk: publicKeyJWK, - privateKey: privateKey, + privateKey: .secKey(privateKey), issuer: "218232426" ) @@ -528,6 +529,7 @@ class IssuanceAuthorizationTest: XCTestCase { let privateKey = try KeyController.generateECDHPrivateKey() let publicKey = try KeyController.generateECDHPublicKey(from: privateKey) + let privateKeyProxy: SigningKeyProxy = .secKey(privateKey) let alg = JWSAlgorithm(.ES256) let jwk = try ECPublicKey( publicKey: publicKey, @@ -540,7 +542,7 @@ class IssuanceAuthorizationTest: XCTestCase { let dpopConstructor: DPoPConstructor = .init( algorithm: alg, jwk: jwk, - privateKey: privateKey + privateKey: privateKeyProxy ) let offer: CredentialOffer = try resolution.get() @@ -576,7 +578,7 @@ class IssuanceAuthorizationTest: XCTestCase { let bindingKey: BindingKey = .jwk( algorithm: alg, jwk: jwk, - privateKey: privateKey, + privateKey: .secKey(privateKey), issuer: "218232426" ) diff --git a/Tests/Issuance/IssuanceBatchRequestTest.swift b/Tests/Issuance/IssuanceBatchRequestTest.swift index 18e5656..61a59de 100644 --- a/Tests/Issuance/IssuanceBatchRequestTest.swift +++ b/Tests/Issuance/IssuanceBatchRequestTest.swift @@ -201,7 +201,7 @@ class IssuanceBatchRequestTest: XCTestCase { let bindingKey: BindingKey = .jwk( algorithm: alg, jwk: publicKeyJWK, - privateKey: privateKey + privateKey: .secKey(privateKey) ) let wallet = Wallet( diff --git a/Tests/Wallet/VCIFlowNoOffer.swift b/Tests/Wallet/VCIFlowNoOffer.swift index f8bc9d7..7eb5c0a 100644 --- a/Tests/Wallet/VCIFlowNoOffer.swift +++ b/Tests/Wallet/VCIFlowNoOffer.swift @@ -55,7 +55,7 @@ class VCIFlowNoOffer: XCTestCase { let bindingKey: BindingKey = .jwk( algorithm: alg, jwk: publicKeyJWK, - privateKey: privateKey + privateKey: .secKey(privateKey) ) let user = ActingUser( @@ -100,7 +100,7 @@ class VCIFlowNoOffer: XCTestCase { let bindingKey: BindingKey = .jwk( algorithm: alg, jwk: publicKeyJWK, - privateKey: privateKey + privateKey: .secKey(privateKey) ) let user = ActingUser( @@ -144,7 +144,7 @@ class VCIFlowNoOffer: XCTestCase { let bindingKey: BindingKey = .jwk( algorithm: alg, jwk: publicKeyJWK, - privateKey: privateKey + privateKey: .secKey(privateKey) ) let user = ActingUser( diff --git a/Tests/Wallet/VCIFlowWithOffer.swift b/Tests/Wallet/VCIFlowWithOffer.swift index 9e0db2f..ffc0a61 100644 --- a/Tests/Wallet/VCIFlowWithOffer.swift +++ b/Tests/Wallet/VCIFlowWithOffer.swift @@ -46,7 +46,7 @@ class VCIFlowWithOffer: XCTestCase { let bindingKey: BindingKey = .jwk( algorithm: alg, jwk: publicKeyJWK, - privateKey: privateKey + privateKey: .secKey(privateKey) ) let user = ActingUser( @@ -91,7 +91,7 @@ class VCIFlowWithOffer: XCTestCase { let bindingKey: BindingKey = .jwk( algorithm: alg, jwk: publicKeyJWK, - privateKey: privateKey + privateKey: .secKey(privateKey) ) let user = ActingUser( @@ -135,7 +135,7 @@ class VCIFlowWithOffer: XCTestCase { let bindingKey: BindingKey = .jwk( algorithm: alg, jwk: publicKeyJWK, - privateKey: privateKey + privateKey: .secKey(privateKey) ) let user = ActingUser( @@ -179,7 +179,7 @@ class VCIFlowWithOffer: XCTestCase { let bindingKey: BindingKey = .jwk( algorithm: alg, jwk: publicKeyJWK, - privateKey: privateKey + privateKey: .secKey(privateKey) ) let user = ActingUser( @@ -224,7 +224,7 @@ class VCIFlowWithOffer: XCTestCase { let bindingKey: BindingKey = .jwk( algorithm: alg, jwk: publicKeyJWK, - privateKey: privateKey + privateKey: .secKey(privateKey) ) let user = ActingUser( @@ -266,10 +266,11 @@ class VCIFlowWithOffer: XCTestCase { "kid": UUID().uuidString ]) + let privateKeyProxy: SigningKeyProxy = .secKey(privateKey) let bindingKey: BindingKey = .jwk( algorithm: alg, jwk: publicKeyJWK, - privateKey: privateKey + privateKey: privateKeyProxy ) let user = ActingUser( @@ -283,7 +284,7 @@ class VCIFlowWithOffer: XCTestCase { dPoPConstructor: DPoPConstructor( algorithm: alg, jwk: publicKeyJWK, - privateKey: privateKey + privateKey: privateKeyProxy ) )