Skip to content

Commit

Permalink
Merge pull request #99 from niscy-eudiw/SecureArea
Browse files Browse the repository at this point in the history
Updated signing logic
  • Loading branch information
dtsiflit authored Nov 26, 2024
2 parents 1ab344b + d5d9a0e commit 18f4906
Show file tree
Hide file tree
Showing 13 changed files with 128 additions and 70 deletions.
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
},
{
Expand Down
4 changes: 0 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,6 @@ let package = Package(
name: "SwiftyJSON",
package: "SwiftyJSON"
),
.product(
name: "JOSESwift",
package: "JOSESwift"
),
.product(
name: "SwiftSoup",
package: "SwiftSoup"
Expand Down
25 changes: 14 additions & 11 deletions Sources/DPoP/DPoPConstructor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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",
Expand All @@ -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,
Expand Down
100 changes: 77 additions & 23 deletions Sources/Entities/Encryption/BindingKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand All @@ -39,7 +44,7 @@ public extension BindingKey {
issuanceRequester: IssuanceRequesterType,
credentialSpec: CredentialSupported,
cNonce: String?
) throws -> Proof {
) async throws -> Proof {
switch self {
case .jwk(
let algorithm,
Expand Down Expand Up @@ -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,
Expand All @@ -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: [
Expand All @@ -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,
Expand All @@ -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
}
5 changes: 3 additions & 2 deletions Sources/Entities/IssuanceAccessToken.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)"]
Expand Down
8 changes: 4 additions & 4 deletions Sources/Issuers/IssuanceRequester.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down Expand Up @@ -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
)
Expand Down Expand Up @@ -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
)
Expand Down Expand Up @@ -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
)
Expand Down
12 changes: 6 additions & 6 deletions Sources/Issuers/Issuer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -520,15 +520,15 @@ 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")
}
return try supportedCredential.toIssuanceRequest(
requester: issuanceRequester,
claimSet: identifier.claimSet,
proof: bindingKey.toSupportedProof(
proof: try await bindingKey.toSupportedProof(
issuanceRequester: issuanceRequester,
credentialSpec: supportedCredential,
cNonce: cNonce.value
Expand Down Expand Up @@ -559,9 +559,9 @@ private extension Issuer {

private func requestIssuance(
token: IssuanceAccessToken,
issuanceRequestSupplier: () throws -> CredentialIssuanceRequest
issuanceRequestSupplier: () async throws -> CredentialIssuanceRequest
) async throws -> Result<SubmittedRequest, Error> {
let credentialRequest = try issuanceRequestSupplier()
let credentialRequest = try await issuanceRequestSupplier()
switch credentialRequest {
case .single(let single, let encryptionSpec):
self.deferredResponseEncryptionSpec = encryptionSpec
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down
5 changes: 3 additions & 2 deletions Sources/Main/Authorisers/AuthorizationServerClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 [:]
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/Constants/TestsConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
10 changes: 6 additions & 4 deletions Tests/Issuance/IssuanceAuthorizationTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ class IssuanceAuthorizationTest: XCTestCase {
let bindingKey: BindingKey = .jwk(
algorithm: alg,
jwk: publicKeyJWK,
privateKey: privateKey,
privateKey: .secKey(privateKey),
issuer: "218232426"
)

Expand Down Expand Up @@ -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(
Expand All @@ -477,7 +478,7 @@ class IssuanceAuthorizationTest: XCTestCase {
let bindingKey: BindingKey = .jwk(
algorithm: alg,
jwk: publicKeyJWK,
privateKey: privateKey,
privateKey: .secKey(privateKey),
issuer: "218232426"
)

Expand Down Expand Up @@ -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,
Expand All @@ -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()
Expand Down Expand Up @@ -576,7 +578,7 @@ class IssuanceAuthorizationTest: XCTestCase {
let bindingKey: BindingKey = .jwk(
algorithm: alg,
jwk: jwk,
privateKey: privateKey,
privateKey: .secKey(privateKey),
issuer: "218232426"
)

Expand Down
Loading

0 comments on commit 18f4906

Please sign in to comment.