From 447672cd14725e93ad4f6d1d9209a4a80502d34e Mon Sep 17 00:00:00 2001 From: Mumthasir-vp Date: Sat, 11 May 2024 10:25:25 +0530 Subject: [PATCH 01/33] Fix #12: Test case for resolving credential and discovery phase --- .../eudiWalletOidcIosTests.swift | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Tests/eudiWalletOidcIosTests/eudiWalletOidcIosTests.swift diff --git a/Tests/eudiWalletOidcIosTests/eudiWalletOidcIosTests.swift b/Tests/eudiWalletOidcIosTests/eudiWalletOidcIosTests.swift new file mode 100644 index 0000000..8aa7934 --- /dev/null +++ b/Tests/eudiWalletOidcIosTests/eudiWalletOidcIosTests.swift @@ -0,0 +1,30 @@ +import XCTest +@testable import eudiWalletOidcIos + +final class eudi_wallet_oidc_iosTests: XCTestCase { + func testRecieveAndStoreCredential() async throws { + // XCTest Documentation + // https://developer.apple.com/documentation/xctest + + // Defining Test Cases and Test Methods + // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods + + // MARK: Resolve credential offer (EWC RFC 001 - 3.1, 3.2) + // Credential offer is one time usable, please ensure it is replaced before running this test + // FIXME: Throw error if credential offer is not resolved + var inTimeCredentialOffer = "openid-credential-offer://?credential_offer_uri=https://oid4vc.igrant.io/organisation/a6b946b8-06a3-445f-8b75-ec0e7b17a040/service/credential-offer/eb0aa2d6-e9f0-4af9-840b-95b468cd7870" + var resolvedCredentialOffer = try await IssueService.shared.resolveCredentialOffer(credentialOfferString: inTimeCredentialOffer) + XCTAssertEqual(resolvedCredentialOffer?.grants?.authCode?.preAuthorizedCode, nil) + XCTAssertNotEqual(resolvedCredentialOffer?.grants?.authorizationCode?.issuerState, nil) + + // MARK: Discovery (EWC RFC 001 - 3.3, 3.4) + var issuerConfig = try? await DiscoveryService.shared.getIssuerConfig(credentialIssuerWellKnownURI: resolvedCredentialOffer?.credentialIssuer) + var authConfig = try? await DiscoveryService.shared.getAuthConfig(authorisationServerWellKnownURI: issuerConfig?.authorizationServer ?? issuerConfig?.credentialIssuer) +// debugPrint(issuerConfig) +// debugPrint(authConfig) + + XCTAssertNotNil(authConfig?.authorizationEndpoint) + } + + +} From 9f5530e678e58b3f3d6bd95815bf5bc7b5ef288c Mon Sep 17 00:00:00 2001 From: Mumthasir-vp Date: Sat, 11 May 2024 10:22:23 +0530 Subject: [PATCH 02/33] Fix #7: Change swift-tool-version support to 5.7 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 874ae67..85b6a7b 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 5.7 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription From d5a1c09a6d8aa35272d0b3e3b1db063670fdb34d Mon Sep 17 00:00:00 2001 From: Mumthasir-vp Date: Sat, 11 May 2024 10:20:33 +0530 Subject: [PATCH 03/33] Fix #10: Make shared instances public accessible --- Sources/eudiWalletOidcIos/Service/CodeVerifierService.swift | 2 +- Sources/eudiWalletOidcIos/Service/DidService.swift | 2 +- Sources/eudiWalletOidcIos/Service/DiscoveryService.swift | 2 +- Sources/eudiWalletOidcIos/Service/SDJWTService.swift | 2 +- Sources/eudiWalletOidcIos/Service/VerificationService.swift | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/eudiWalletOidcIos/Service/CodeVerifierService.swift b/Sources/eudiWalletOidcIos/Service/CodeVerifierService.swift index 6ee9214..d4a15e8 100644 --- a/Sources/eudiWalletOidcIos/Service/CodeVerifierService.swift +++ b/Sources/eudiWalletOidcIos/Service/CodeVerifierService.swift @@ -10,7 +10,7 @@ import CryptoKit public class CodeVerifierService: CodeVerifierProtocol { - static var shared = CodeVerifierService() + public static var shared = CodeVerifierService() private init(){} // Generates a code challenge string for PKCE (Proof Key for Code Exchange) based on the provided code verifier. diff --git a/Sources/eudiWalletOidcIos/Service/DidService.swift b/Sources/eudiWalletOidcIos/Service/DidService.swift index 1710c64..2f53bb4 100644 --- a/Sources/eudiWalletOidcIos/Service/DidService.swift +++ b/Sources/eudiWalletOidcIos/Service/DidService.swift @@ -10,7 +10,7 @@ import CryptoKit import Base58Swift public class DidService { - static var shared = DidService() + public static var shared = DidService() private init(){} // MARK: - Creates a Decentralized Identifier (DID) asynchronously based on the provided JWK (JSON Web Key). diff --git a/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift b/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift index 3d4f292..1d73d43 100644 --- a/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift +++ b/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift @@ -10,7 +10,7 @@ import CryptoKit public class DiscoveryService: DiscoveryServiceProtocol { - static var shared = DiscoveryService() + public static var shared = DiscoveryService() private init(){} // MARK: - Retrieves the issuer configuration asynchronously based on the provided credential issuer well-known URI. diff --git a/Sources/eudiWalletOidcIos/Service/SDJWTService.swift b/Sources/eudiWalletOidcIos/Service/SDJWTService.swift index dc99571..e0dcba4 100644 --- a/Sources/eudiWalletOidcIos/Service/SDJWTService.swift +++ b/Sources/eudiWalletOidcIos/Service/SDJWTService.swift @@ -10,7 +10,7 @@ import CryptoKit public class SDJWTService { - static var shared = SDJWTService() + public static var shared = SDJWTService() private init() {} /** diff --git a/Sources/eudiWalletOidcIos/Service/VerificationService.swift b/Sources/eudiWalletOidcIos/Service/VerificationService.swift index 49aed09..0f72092 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationService.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationService.swift @@ -11,7 +11,7 @@ import PresentationExchangeSdkiOS public class VerificationService: VerificationServiceProtocol { - static var shared = VerificationService() + public static var shared = VerificationService() private init() {} // MARK: - Sends a Verifiable Presentation (VP) token asynchronously. From 31fc508db0e33cc20219fb950c42b33a2bd86e1a Mon Sep 17 00:00:00 2001 From: Mumthasir-vp Date: Wed, 15 May 2024 17:33:26 +0530 Subject: [PATCH 04/33] Fix #17: Update in SDK models and VerificationService class --- .../Model/PresentationDefinitionModel.swift | 5 ++++ .../Model/PresentationRequest.swift | 12 ++++++++ .../Service/VerificationService.swift | 29 ++++++++++++++----- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/Sources/eudiWalletOidcIos/Model/PresentationDefinitionModel.swift b/Sources/eudiWalletOidcIos/Model/PresentationDefinitionModel.swift index 2801612..62c4f4a 100644 --- a/Sources/eudiWalletOidcIos/Model/PresentationDefinitionModel.swift +++ b/Sources/eudiWalletOidcIos/Model/PresentationDefinitionModel.swift @@ -45,7 +45,12 @@ struct InputDescriptor: Codable { // MARK: - Constraints struct Constraints: Codable { + let limitDisclosure: String? let fields: [Field]? + enum CodingKeys: String, CodingKey { + case fields + case limitDisclosure = "limit_disclosure" + } } // MARK: - Field diff --git a/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift b/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift index 64b9632..be4c1da 100644 --- a/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift +++ b/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift @@ -21,4 +21,16 @@ public struct PresentationRequest: Codable { case requestUri = "request_uri" case presentationDefinition = "presentation_definition" } + + public init(state: String?, clientId: String?, redirectUri: String?, responseType: String?, responseMode: String?, scope: String?, nonce: String?, requestUri: String?, presentationDefinition: String?) { + self.state = state + self.clientId = clientId + self.redirectUri = redirectUri + self.responseType = responseType + self.responseMode = responseMode + self.scope = scope + self.nonce = nonce + self.requestUri = requestUri + self.presentationDefinition = presentationDefinition + } } diff --git a/Sources/eudiWalletOidcIos/Service/VerificationService.swift b/Sources/eudiWalletOidcIos/Service/VerificationService.swift index 0f72092..6b511c2 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationService.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationService.swift @@ -235,8 +235,18 @@ public class VerificationService: VerificationServiceProtocol { public func filterCredentials(credentialList: [String?], presentationDefinition: PresentationDefinitionModel) -> [[String]] { var response: [[String]] = [] + var tempCredentialList: [String?] = [] + for item in credentialList { + if let limitDisclosure = presentationDefinition.inputDescriptors?.first?.constraints?.limitDisclosure, + item?.contains("~") == true { + tempCredentialList.append(item) + } else if presentationDefinition.inputDescriptors?.first?.constraints?.limitDisclosure == nil, + item?.contains("~") == false { + tempCredentialList.append(item) + } + } var processedCredentials = [String]() - for cred in credentialList { + for cred in tempCredentialList { guard let cred = cred else { continue } let split = cred.split(separator: ".") @@ -253,10 +263,11 @@ public class VerificationService: VerificationServiceProtocol { let json = try? JSONSerialization.jsonObject(with: Data(jsonString.utf8), options: []) as? [String: Any] ?? [:] - let vcString = if let vc = json?["vc"] as? [String: Any] { - vc.toString() ?? "" + var vcString = "" + if let vc = json?["vc"] as? [String: Any] { + vcString = vc.toString() ?? "" } else { - jsonString + vcString = jsonString } processedCredentials.append(vcString) @@ -282,10 +293,12 @@ public class VerificationService: VerificationServiceProtocol { // Assuming `matchesString` contains a JSON array of matches if let matchesData = matchesString.data(using: .utf8), - let matchesArray = try? JSONSerialization.jsonObject(with: matchesData) as? [String: Any] { - for index in 0.. Date: Wed, 29 May 2024 09:55:17 +0530 Subject: [PATCH 05/33] Fix #19: Support verification QR code by reference --- .../Helper/UIApplicationUtils.swift | 29 ++++++++++ .../Model/PresentationRequest.swift | 5 +- .../Service/VerificationService.swift | 53 +++++++++++++------ 3 files changed, 70 insertions(+), 17 deletions(-) diff --git a/Sources/eudiWalletOidcIos/Helper/UIApplicationUtils.swift b/Sources/eudiWalletOidcIos/Helper/UIApplicationUtils.swift index 1991052..b4105f7 100644 --- a/Sources/eudiWalletOidcIos/Helper/UIApplicationUtils.swift +++ b/Sources/eudiWalletOidcIos/Helper/UIApplicationUtils.swift @@ -36,3 +36,32 @@ class UIApplicationUtils { return data.map { String($0) }.joined(separator: "&") } } + +extension String { + func decodeJWT(jwtToken jwt: String) throws -> [String: Any] { + func base64Decode(_ base64: String) throws -> Data? { + let base64 = base64 + .replacingOccurrences(of: "-", with: "+") + .replacingOccurrences(of: "_", with: "/") + let padded = base64.padding(toLength: ((base64.count + 3) / 4) * 4, withPad: "=", startingAt: 0) + guard let decoded = Data(base64Encoded: padded) else { + debugPrint("DecodeErrors.badToken") + return nil + } + return decoded + } + + func decodeJWTPart(_ value: String) throws -> [String: Any] { + guard let bodyData = try base64Decode(value) else { return [:]} + let json = try JSONSerialization.jsonObject(with: bodyData, options: []) + guard let payload = json as? [String: Any] else { + debugPrint("DecodeErrors.other") + return [:] + } + return payload + } + + let segments = jwt.components(separatedBy: ".") + return try decodeJWTPart(segments[1]) + } +} diff --git a/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift b/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift index be4c1da..58fc1b5 100644 --- a/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift +++ b/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift @@ -8,7 +8,8 @@ import Foundation public struct PresentationRequest: Codable { - var state, clientId, redirectUri, responseType, responseMode, scope, nonce, requestUri, presentationDefinition: String? + var state, clientId, redirectUri, responseType, responseMode, scope, nonce, requestUri: String? + var presentationDefinition: PresentationDefinitionModel? enum CodingKeys: String, CodingKey { case state = "state" @@ -22,7 +23,7 @@ public struct PresentationRequest: Codable { case presentationDefinition = "presentation_definition" } - public init(state: String?, clientId: String?, redirectUri: String?, responseType: String?, responseMode: String?, scope: String?, nonce: String?, requestUri: String?, presentationDefinition: String?) { + public init(state: String?, clientId: String?, redirectUri: String?, responseType: String?, responseMode: String?, scope: String?, nonce: String?, requestUri: String?, presentationDefinition: PresentationDefinitionModel?) { self.state = state self.clientId = clientId self.redirectUri = redirectUri diff --git a/Sources/eudiWalletOidcIos/Service/VerificationService.swift b/Sources/eudiWalletOidcIos/Service/VerificationService.swift index 6b511c2..51d5978 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationService.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationService.swift @@ -64,20 +64,27 @@ public class VerificationService: VerificationServiceProtocol { let requestUri = URL(string: code)?.queryParameters?["request_uri"] ?? "" let responseUri = URL(string: code)?.queryParameters?["response_uri"] ?? "" let responseMode = URL(string: code)?.queryParameters?["response_mode"] ?? "" - let presentationDefinition = URL(string: code)?.queryParameters?["presentation_definition"] ?? "" + var presentationDefinition = URL(string: code)?.queryParameters?["presentation_definition"] ?? "" if presentationDefinition != "" { - let presentationRequest = PresentationRequest(state: state, - clientId: clientID, - redirectUri: redirectUri, - responseType: responseType, - responseMode: responseMode, - scope: scope, - nonce: nonce, - requestUri: requestUri, - presentationDefinition: presentationDefinition) - - return presentationRequest + let presentationDefinitionModel: PresentationDefinitionModel? + presentationDefinition = presentationDefinition.replacingOccurrences(of: "+", with: "") + let jsonData = presentationDefinition.data(using: .utf8)! + do { + presentationDefinitionModel = try JSONDecoder().decode(PresentationDefinitionModel.self, from: jsonData) + let presentationRequest = PresentationRequest(state: state, + clientId: clientID, + redirectUri: redirectUri, + responseType: responseType, + responseMode: responseMode, + scope: scope, + nonce: nonce, + requestUri: requestUri, + presentationDefinition: presentationDefinitionModel) + return presentationRequest + } catch { + debugPrint("Failed to decode JSON: \(error.localizedDescription)") + } } else if requestUri != "" { var request = URLRequest(url: URL(string: requestUri)!) request.httpMethod = "GET" @@ -87,12 +94,28 @@ public class VerificationService: VerificationServiceProtocol { let jsonDecoder = JSONDecoder() let model = try? jsonDecoder.decode(PresentationRequest.self, from: data) if model == nil { - _ = Error(message:"Invalid DID", code: nil) - return nil + if let jwtString = String(data: data, encoding: .utf8) { + do { + let segments = jwtString.split(separator: ".") + if segments.count == 3 { + // Decoding received JWT here + guard let jsonPayload = try? jwtString.decodeJWT(jwtToken: jwtString) else { return nil } + guard let data = try? JSONSerialization.data(withJSONObject: jsonPayload, options: []) else { return nil } + let model = try jsonDecoder.decode(PresentationRequest.self, from: data) + return model + } + } catch { + debugPrint("Error:\(error)") + } + } else { + let error = Error(message:"Invalid DID", code: nil) + debugPrint(error) + return nil + } } return model } catch { - + debugPrint("Error:\(error)") } } } From 5a55f612e4e2f5dc9710dfb76db002ffb157127d Mon Sep 17 00:00:00 2001 From: Mumthasir-vp Date: Fri, 31 May 2024 06:31:47 +0530 Subject: [PATCH 06/33] Fix #19: Make PresentationDefinition model to be compatible with String as well. --- .../Helper/UIApplicationUtils.swift | 13 +++++++++ .../Model/PresentationRequest.swift | 28 ++++++++++++++++--- .../Service/VerificationService.swift | 12 ++------ 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/Sources/eudiWalletOidcIos/Helper/UIApplicationUtils.swift b/Sources/eudiWalletOidcIos/Helper/UIApplicationUtils.swift index b4105f7..b27edb0 100644 --- a/Sources/eudiWalletOidcIos/Helper/UIApplicationUtils.swift +++ b/Sources/eudiWalletOidcIos/Helper/UIApplicationUtils.swift @@ -65,3 +65,16 @@ extension String { return try decodeJWTPart(segments[1]) } } + +extension Encodable { + func toJSONString() -> String? { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + + if let jsonData = try? encoder.encode(self) { + return String(data: jsonData, encoding: .utf8) + } + + return nil + } +} diff --git a/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift b/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift index 58fc1b5..14617a5 100644 --- a/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift +++ b/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift @@ -9,8 +9,8 @@ import Foundation public struct PresentationRequest: Codable { var state, clientId, redirectUri, responseType, responseMode, scope, nonce, requestUri: String? - var presentationDefinition: PresentationDefinitionModel? - + var presentationDefinition: String? + enum CodingKeys: String, CodingKey { case state = "state" case clientId = "client_id" @@ -22,8 +22,8 @@ public struct PresentationRequest: Codable { case requestUri = "request_uri" case presentationDefinition = "presentation_definition" } - - public init(state: String?, clientId: String?, redirectUri: String?, responseType: String?, responseMode: String?, scope: String?, nonce: String?, requestUri: String?, presentationDefinition: PresentationDefinitionModel?) { + + public init(state: String?, clientId: String?, redirectUri: String?, responseType: String?, responseMode: String?, scope: String?, nonce: String?, requestUri: String?, presentationDefinition: String?) { self.state = state self.clientId = clientId self.redirectUri = redirectUri @@ -34,4 +34,24 @@ public struct PresentationRequest: Codable { self.requestUri = requestUri self.presentationDefinition = presentationDefinition } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + state = try container.decodeIfPresent(String.self, forKey: .state) + clientId = try container.decodeIfPresent(String.self, forKey: .clientId) + redirectUri = try container.decodeIfPresent(String.self, forKey: .redirectUri) + responseType = try container.decodeIfPresent(String.self, forKey: .responseType) + responseMode = try container.decodeIfPresent(String.self, forKey: .responseMode) + scope = try container.decodeIfPresent(String.self, forKey: .scope) + nonce = try container.decodeIfPresent(String.self, forKey: .nonce) + requestUri = try container.decodeIfPresent(String.self, forKey: .requestUri) + + if let presentationDefinitionString = try? container.decode(String.self, forKey: .presentationDefinition) { + presentationDefinition = presentationDefinitionString + } else if let presentationDefinitionModel = try? container.decode(PresentationDefinitionModel.self, forKey: .presentationDefinition) { + presentationDefinition = presentationDefinitionModel.toJSONString() + } else { + presentationDefinition = nil + } + } } diff --git a/Sources/eudiWalletOidcIos/Service/VerificationService.swift b/Sources/eudiWalletOidcIos/Service/VerificationService.swift index 51d5978..d26262b 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationService.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationService.swift @@ -67,11 +67,7 @@ public class VerificationService: VerificationServiceProtocol { var presentationDefinition = URL(string: code)?.queryParameters?["presentation_definition"] ?? "" if presentationDefinition != "" { - let presentationDefinitionModel: PresentationDefinitionModel? - presentationDefinition = presentationDefinition.replacingOccurrences(of: "+", with: "") - let jsonData = presentationDefinition.data(using: .utf8)! - do { - presentationDefinitionModel = try JSONDecoder().decode(PresentationDefinitionModel.self, from: jsonData) + let presentationRequest = PresentationRequest(state: state, clientId: clientID, redirectUri: redirectUri, @@ -80,11 +76,9 @@ public class VerificationService: VerificationServiceProtocol { scope: scope, nonce: nonce, requestUri: requestUri, - presentationDefinition: presentationDefinitionModel) + presentationDefinition: presentationDefinition) return presentationRequest - } catch { - debugPrint("Failed to decode JSON: \(error.localizedDescription)") - } + } else if requestUri != "" { var request = URLRequest(url: URL(string: requestUri)!) request.httpMethod = "GET" From e29f0d8a9b9a1b60d3610f51f3c89eaeb490da15 Mon Sep 17 00:00:00 2001 From: Mumthasir-vp Date: Sat, 1 Jun 2024 09:45:16 +0530 Subject: [PATCH 07/33] #Fix: Make PresentationRequest model variables public --- Sources/eudiWalletOidcIos/Model/PresentationRequest.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift b/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift index 14617a5..b8a5c68 100644 --- a/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift +++ b/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift @@ -8,8 +8,8 @@ import Foundation public struct PresentationRequest: Codable { - var state, clientId, redirectUri, responseType, responseMode, scope, nonce, requestUri: String? - var presentationDefinition: String? + public var state, clientId, redirectUri, responseType, responseMode, scope, nonce, requestUri: String? + public var presentationDefinition: String? enum CodingKeys: String, CodingKey { case state = "state" From 922a8186e56e397a0e5a200e339ed586b1fbe1fa Mon Sep 17 00:00:00 2001 From: ArunRajasekhar84 Date: Wed, 5 Jun 2024 18:46:54 +0530 Subject: [PATCH 08/33] Added an extra layer of custom data model on top of api response (#21) Added custom models for capturing data received from api json so that any changes to api data has least impact at consuming end. Also this makes it easier to format or introduce validation to received data. Co-authored-by: Josep Milan K.A --- .../Model/CredentialOffer.swift | 107 ++++++++++-------- .../Model/CredentialOfferResponse.swift | 84 ++++++++++++++ Sources/eudiWalletOidcIos/Model/Error.swift | 12 +- .../Service/DiscoveryService.swift | 4 +- .../Service/IssueService.swift | 24 ++-- .../Service/VerificationService.swift | 2 +- 6 files changed, 172 insertions(+), 61 deletions(-) create mode 100644 Sources/eudiWalletOidcIos/Model/CredentialOfferResponse.swift diff --git a/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift b/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift index b8a0fa8..16c77b0 100644 --- a/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift +++ b/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift @@ -8,77 +8,94 @@ import Foundation // MARK: - CredentialOffer model -public struct CredentialOffer: Codable { - var credentialIssuer: String? - var credentials: [Credential]? - var grants: Grants? - var error: Error? +public struct CredentialOffer { + public var credentialIssuer: String? + public var credentials: [Credential]? + public var grants: Grants? + public var error: Error? - enum CodingKeys: String, CodingKey { - case credentialIssuer = "credential_issuer" - case credentials, grants, error + public init(from: CredentialOfferResponse) { + credentialIssuer = from.credentialIssuer + + if let credentialList = from.credentials, credentialList.count > 0{ + credentials = credentialList.map({ Credential(from: $0) }) + } + grants = from.grants == nil ? nil : Grants(from: from.grants!) + error = from.error == nil ? nil : Error(from: from.error!) } + + public init(fromError: Error) { + error = fromError + } + } // MARK: - Credential -struct Credential: Codable { - let format: String? - let types: [String]? - let trustFramework: TrustFramework? - var credentialDefinition: CredentialDefinition? +public struct Credential { + public let format: String? + public let types: [String]? + public let trustFramework: TrustFramework? + public var credentialDefinition: CredentialDefinition? - enum CodingKeys: String, CodingKey { - case format, types - case trustFramework = "trust_framework" - case credentialDefinition + init(from: CredentialDataResponse) { + format = from.format + types = from.types + trustFramework = from.trustFramework == nil ? nil : TrustFramework(from: from.trustFramework!) + credentialDefinition = from.credentialDefinition == nil ? nil : CredentialDefinition(from: from.credentialDefinition!) } } // MARK: - CredentialDefinition -struct CredentialDefinition: Codable { - var context: [String]? - var types: [String]? - - enum CodingKeys: String, CodingKey { - case context - case types +public struct CredentialDefinition { + public var context: [String]? + public var types: [String]? + + init(from: CredentialDefinitionResponse) { + context = from.context + types = from.types } } // MARK: - TrustFramework -struct TrustFramework: Codable { - let name, type, uri: String? +public struct TrustFramework { + public let name, type, uri: String? + + init(from: TrustFrameworkResponse) { + name = from.name + type = from.type + uri = from.uri + } } // MARK: - Grants -struct Grants: Codable { - let authorizationCode: AuthorizationCode? - let urnIETFParamsOauthGrantTypePreAuthorizedCode: UrnIETFParamsOauthGrantTypePreAuthorizedCode? - let authCode: UrnIETFParamsOauthGrantTypePreAuthorizedCode? - - enum CodingKeys: String, CodingKey { - case authorizationCode = "authorization_code" - case urnIETFParamsOauthGrantTypePreAuthorizedCode - case authCode = "urn:ietf:params:oauth:grant-type:pre-authorized_code" +public struct Grants { + public let authorizationCode: AuthorizationCode? + public let urnIETFParamsOauthGrantTypePreAuthorizedCode: UrnIETFParamsOauthGrantTypePreAuthorizedCode? + public let authCode: UrnIETFParamsOauthGrantTypePreAuthorizedCode? + + init(from: GrantsResponse) { + authorizationCode = from.authorizationCode == nil ? nil : AuthorizationCode(from: from.authorizationCode!) + urnIETFParamsOauthGrantTypePreAuthorizedCode = from.urnIETFParamsOauthGrantTypePreAuthorizedCode == nil ? nil : UrnIETFParamsOauthGrantTypePreAuthorizedCode(from: from.urnIETFParamsOauthGrantTypePreAuthorizedCode!) + authCode = from.authCode == nil ? nil : UrnIETFParamsOauthGrantTypePreAuthorizedCode(from: from.authCode!) } } // MARK: - AuthorizationCode -struct AuthorizationCode: Codable { - let issuerState: String? +public struct AuthorizationCode { + public let issuerState: String? - enum CodingKeys: String, CodingKey { - case issuerState = "issuer_state" + init(from: AuthorizationCodeResponse) { + issuerState = from.issuerState } } // MARK: - UrnIETFParamsOauthGrantTypePreAuthorizedCode -struct UrnIETFParamsOauthGrantTypePreAuthorizedCode: Codable { - let preAuthorizedCode: String? - let userPinRequired: Bool? +public struct UrnIETFParamsOauthGrantTypePreAuthorizedCode { + public let preAuthorizedCode: String? + public let userPinRequired: Bool? - enum CodingKeys: String, CodingKey { - case preAuthorizedCode = "pre-authorized_code" - case userPinRequired = "user_pin_required" + init(from: UrnIETFParamsOauthGrantTypePreAuthorizedCodeResponse) { + preAuthorizedCode = from.preAuthorizedCode + userPinRequired = from.userPinRequired } } diff --git a/Sources/eudiWalletOidcIos/Model/CredentialOfferResponse.swift b/Sources/eudiWalletOidcIos/Model/CredentialOfferResponse.swift new file mode 100644 index 0000000..fea103c --- /dev/null +++ b/Sources/eudiWalletOidcIos/Model/CredentialOfferResponse.swift @@ -0,0 +1,84 @@ +// +// File.swift +// +// +// Created by Arun Raj on 05/06/24. +// + +import Foundation + +// MARK: - CredentialOffer model +public struct CredentialOfferResponse: Codable { + var credentialIssuer: String? + var credentials: [CredentialDataResponse]? + var grants: GrantsResponse? + var error: ErrorResponse? + + enum CodingKeys: String, CodingKey { + case credentialIssuer = "credential_issuer" + case credentials, grants, error + } +} + +// MARK: - Credential +struct CredentialDataResponse: Codable { + let format: String? + let types: [String]? + let trustFramework: TrustFrameworkResponse? + var credentialDefinition: CredentialDefinitionResponse? + + enum CodingKeys: String, CodingKey { + case format, types + case trustFramework = "trust_framework" + case credentialDefinition + } +} + +// MARK: - CredentialDefinition +struct CredentialDefinitionResponse: Codable { + var context: [String]? + var types: [String]? + + enum CodingKeys: String, CodingKey { + case context + case types + } +} + +// MARK: - TrustFramework +struct TrustFrameworkResponse: Codable { + let name, type, uri: String? +} + +// MARK: - Grants +struct GrantsResponse: Codable { + let authorizationCode: AuthorizationCodeResponse? + let urnIETFParamsOauthGrantTypePreAuthorizedCode: UrnIETFParamsOauthGrantTypePreAuthorizedCodeResponse? + let authCode: UrnIETFParamsOauthGrantTypePreAuthorizedCodeResponse? + + enum CodingKeys: String, CodingKey { + case authorizationCode = "authorization_code" + case urnIETFParamsOauthGrantTypePreAuthorizedCode + case authCode = "urn:ietf:params:oauth:grant-type:pre-authorized_code" + } +} + +// MARK: - AuthorizationCode +struct AuthorizationCodeResponse: Codable { + let issuerState: String? + + enum CodingKeys: String, CodingKey { + case issuerState = "issuer_state" + } +} + +// MARK: - UrnIETFParamsOauthGrantTypePreAuthorizedCode +struct UrnIETFParamsOauthGrantTypePreAuthorizedCodeResponse: Codable { + let preAuthorizedCode: String? + let userPinRequired: Bool? + + enum CodingKeys: String, CodingKey { + case preAuthorizedCode = "pre-authorized_code" + case userPinRequired = "user_pin_required" + } +} diff --git a/Sources/eudiWalletOidcIos/Model/Error.swift b/Sources/eudiWalletOidcIos/Model/Error.swift index 23e4e7c..f826fe6 100644 --- a/Sources/eudiWalletOidcIos/Model/Error.swift +++ b/Sources/eudiWalletOidcIos/Model/Error.swift @@ -7,7 +7,7 @@ import Foundation -struct Error: Codable { +struct ErrorResponse: Codable { var message: String? var code: Int? @@ -16,3 +16,13 @@ struct Error: Codable { case code = "code" } } + +public struct Error { + var message: String? + var code: Int? + + init(from: ErrorResponse) { + message = from.message + code = from.code + } +} diff --git a/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift b/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift index 1d73d43..2cbde19 100644 --- a/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift +++ b/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift @@ -38,7 +38,7 @@ public class DiscoveryService: DiscoveryServiceProtocol { debugPrint("Get Issuer config failed: \(error)") let nsError = error as NSError let errorCode = nsError.code - let error = Error(message:error.localizedDescription, code: errorCode) + let error = Error(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) return try IssuerWellKnownConfiguration(from: error as! Decoder) } } @@ -67,7 +67,7 @@ public class DiscoveryService: DiscoveryServiceProtocol { debugPrint("Get Auth config failed: \(error)") let nsError = error as NSError let errorCode = nsError.code - let error = Error(message:error.localizedDescription, code: errorCode) + let error = Error(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) return AuthorisationServerWellKnownConfiguration(error: error) } } diff --git a/Sources/eudiWalletOidcIos/Service/IssueService.swift b/Sources/eudiWalletOidcIos/Service/IssueService.swift index 23792a9..279d1d7 100644 --- a/Sources/eudiWalletOidcIos/Service/IssueService.swift +++ b/Sources/eudiWalletOidcIos/Service/IssueService.swift @@ -32,12 +32,12 @@ public class IssueService { let (data, _) = try await URLSession.shared.data(for: request) do { - let model = try? jsonDecoder.decode(CredentialOffer.self, from: data) + let model = try? jsonDecoder.decode(CredentialOfferResponse.self, from: data) if model?.credentialIssuer == nil { - let error = Error(message:"Invalid DID", code: nil) - return CredentialOffer(error: error) + let error = Error(from: ErrorResponse(message:"Invalid DID", code: nil)) + return CredentialOffer(fromError: error) } - return model + return (model == nil ? nil : CredentialOffer(from: model!)) } } else { guard let credentialOffer = credentialOfferUrl?.queryParameters?["credential_offer"] else { return nil } @@ -45,12 +45,12 @@ public class IssueService { if credentialOffer != "" { do { - let model = try? jsonDecoder.decode(CredentialOffer.self, from: jsonData) + let model = try? jsonDecoder.decode(CredentialOfferResponse.self, from: jsonData) if model?.credentialIssuer == nil { - let error = Error(message:"Invalid DID", code: nil) - return CredentialOffer(error: error) + let error = Error(from: ErrorResponse(message:"Invalid DID", code: nil)) + return CredentialOffer(fromError: error) } - return model + return (model == nil ? nil : CredentialOffer(from: model!)) } } else { return nil @@ -365,7 +365,7 @@ public class IssueService { debugPrint("Process credential request failed: \(error)") let nsError = error as NSError let errorCode = nsError.code - let error = Error(message:error.localizedDescription, code: errorCode) + let error = Error(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) return CredentialResponse(error: error) } } @@ -402,7 +402,7 @@ public class IssueService { debugPrint("Process deferred credential request failed: \(error)") let nsError = error as NSError let errorCode = nsError.code - let error = Error(message:error.localizedDescription, code: errorCode) + let error = Error(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) return CredentialResponse(error: error) } } @@ -437,7 +437,7 @@ public class IssueService { debugPrint("Get access token for preauth credential failed: \(error)") let nsError = error as NSError let errorCode = nsError.code - let error = Error(message:error.localizedDescription, code: errorCode) + let error = Error(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) return TokenResponse(error: error) } } @@ -473,7 +473,7 @@ public class IssueService { debugPrint("Get access token for preauth credential failed: \(error)") let nsError = error as NSError let errorCode = nsError.code - let error = Error(message:error.localizedDescription, code: errorCode) + let error = Error(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) return TokenResponse(error: error) } } diff --git a/Sources/eudiWalletOidcIos/Service/VerificationService.swift b/Sources/eudiWalletOidcIos/Service/VerificationService.swift index d26262b..d8c29b2 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationService.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationService.swift @@ -102,7 +102,7 @@ public class VerificationService: VerificationServiceProtocol { debugPrint("Error:\(error)") } } else { - let error = Error(message:"Invalid DID", code: nil) + let error = Error(from: ErrorResponse(message:"Invalid DID", code: nil)) debugPrint(error) return nil } From 3aba818f0b8e9c3caf08ad04d5e9859c158a7758 Mon Sep 17 00:00:00 2001 From: ArunRajasekhar84 Date: Wed, 5 Jun 2024 23:02:58 +0530 Subject: [PATCH 09/33] Fix : Modified credential offer data models * Added an extra layer of custom data model on top of api response Added custom models for capturing data received from api json so that any changes to api data has least impact at consuming end. Also this makes it easier to format or introduce validation to received data. * Modified the credential offer models Modified credential offer json response models and data models to dynamically respond to credentials received as string arrays and object arrays. --------- Co-authored-by: Josep Milan K.A --- .../Model/CredentialOffer.swift | 17 ++++++++++- .../Model/CredentialOfferResponse.swift | 28 ++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift b/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift index 16c77b0..1ba706b 100644 --- a/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift +++ b/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift @@ -18,7 +18,15 @@ public struct CredentialOffer { credentialIssuer = from.credentialIssuer if let credentialList = from.credentials, credentialList.count > 0{ - credentials = credentialList.map({ Credential(from: $0) }) + + if let strCredentialList = credentialList as? [String]{ + credentials = [Credential(fromTypes: strCredentialList)] + } else if let objCredentialList = credentialList as? [CredentialDataResponse]{ + credentials = credentialList.map({ + let obj = $0 as! CredentialDataResponse + return Credential(from: obj) + }) + } } grants = from.grants == nil ? nil : Grants(from: from.grants!) error = from.error == nil ? nil : Error(from: from.error!) @@ -43,6 +51,13 @@ public struct Credential { trustFramework = from.trustFramework == nil ? nil : TrustFramework(from: from.trustFramework!) credentialDefinition = from.credentialDefinition == nil ? nil : CredentialDefinition(from: from.credentialDefinition!) } + + init(fromTypes: [String]) { + types = fromTypes + format = nil + trustFramework = nil + credentialDefinition = nil + } } // MARK: - CredentialDefinition diff --git a/Sources/eudiWalletOidcIos/Model/CredentialOfferResponse.swift b/Sources/eudiWalletOidcIos/Model/CredentialOfferResponse.swift index fea103c..67f81c2 100644 --- a/Sources/eudiWalletOidcIos/Model/CredentialOfferResponse.swift +++ b/Sources/eudiWalletOidcIos/Model/CredentialOfferResponse.swift @@ -10,7 +10,7 @@ import Foundation // MARK: - CredentialOffer model public struct CredentialOfferResponse: Codable { var credentialIssuer: String? - var credentials: [CredentialDataResponse]? + var credentials: [AnyObject]? var grants: GrantsResponse? var error: ErrorResponse? @@ -18,6 +18,26 @@ public struct CredentialOfferResponse: Codable { case credentialIssuer = "credential_issuer" case credentials, grants, error } + + + public func encode(to encoder: Encoder) throws { + + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.credentialIssuer = try container.decodeIfPresent(String.self, forKey: .credentialIssuer) + if let stringArray = try? container.decode([String].self, forKey: .credentials) { + credentials = stringArray as? [AnyObject] + } else if let credentialArray = try? container.decode([CredentialDataResponse].self, forKey: .credentials) { + credentials = credentialArray as? [AnyObject] + } else { + credentials = nil + } + self.grants = try container.decodeIfPresent(GrantsResponse.self, forKey: .grants) + self.error = try container.decodeIfPresent(ErrorResponse.self, forKey: .error) + } + } // MARK: - Credential @@ -34,6 +54,12 @@ struct CredentialDataResponse: Codable { } } + +struct CredentialStringResponse{ + +} + + // MARK: - CredentialDefinition struct CredentialDefinitionResponse: Codable { var context: [String]? From 6275616006985aafc8a0ef867b6be4ce531d7304 Mon Sep 17 00:00:00 2001 From: ArunRajasekhar84 Date: Mon, 10 Jun 2024 12:35:34 +0530 Subject: [PATCH 10/33] restructuring interfaces * Added an extra layer of custom data model on top of api response Added custom models for capturing data received from api json so that any changes to api data has least impact at consuming end. Also this makes it easier to format or introduce validation to received data. * Modified the credential offer models Modified credential offer json response models and data models to dynamically respond to credentials received as string arrays and object arrays. --------- Co-authored-by: Josep Milan K.A From 178c8cf8d1e024d8f64e661166dffdfa25dfc423 Mon Sep 17 00:00:00 2001 From: ArunRajasekhar84 Date: Mon, 10 Jun 2024 13:54:44 +0530 Subject: [PATCH 11/33] Update to IssuerWellKnownConfiguration Model * Added an extra layer of custom data model on top of api response Added custom models for capturing data received from api json so that any changes to api data has least impact at consuming end. Also this makes it easier to format or introduce validation to received data. * Modified the credential offer models Modified credential offer json response models and data models to dynamically respond to credentials received as string arrays and object arrays. * Changes to IssuerWellKnownConfiguration The data models changed to support new and old formats for issuer config in Issuance flow --------- Co-authored-by: Josep Milan K.A --- .../Model/IssuerWellKnownConfiguration.swift | 319 ++++++++++-------- ...IssuerWellKnownConfigurationResponse.swift | 200 +++++++++++ .../Service/DiscoveryService.swift | 6 +- 3 files changed, 373 insertions(+), 152 deletions(-) create mode 100644 Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift diff --git a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift index b36d6eb..a5fb7a3 100644 --- a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift +++ b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift @@ -8,191 +8,212 @@ import Foundation // MARK: - IssuerWellKnownConfiguration -struct Display: Codable { - let name: String? - let location: String? - let locale: String? - let description: String? -} -struct TrustFrameworkInIssuer: Codable { - let name: String? - let type: String? - let uri: String? - let display: Display? -} -struct CredentialsSupported: Codable { - let format: String? - let types: [String]? - let trustFramework: TrustFrameworkInIssuer? - let display: DisplayOrArray? +public struct Display{ + public let name: String? + public let location: String? + public let locale: String? + public let description: String? + public var cover, logo: DisplayCover? + + init(from: DisplayResponse) { + name = from.name + location = from.location + locale = from.locale + description = from.description + cover = from.cover == nil ? nil : DisplayCover(from: from.cover!) + logo = from.logo == nil ? nil : DisplayCover(from: from.logo!) + } } - -// MARK: - CredentialsSupportedObject -struct CredentialsSupportedObjectType: Codable { - var credentialsSupported: CredentialSupportedObject? - - enum CodingKeys: String, CodingKey { - case credentialsSupported = "credentials_supported" +public struct TrustFrameworkInIssuer { + public let name: String? + public let type: String? + public let uri: String? + public let display: Display? + + init(from: TrustFrameworkInIssuerResponse) { + name = from.name + type = from.type + uri = from.uri + display = from.display == nil ? nil : Display(from: from.display!) } } +//struct CredentialsSupported: Codable { +// let format: String? +// let types: [String]? +// let trustFramework: TrustFrameworkInIssuer? +// let display: DisplayOrArray? +//} +// +//// MARK: - CredentialsSupportedObject +//struct CredentialsSupportedObjectType: Codable { +// var credentialsSupported: CredentialSupportedObject? +// +// enum CodingKeys: String, CodingKey { +// case credentialsSupported = "credentials_supported" +// } +//} // MARK: - CredentialObj -struct CredentialSupportedObject : Codable { - var portableDocumentA1, parkingTicket, testDraft2, dataSharing: DataSharing? - var drivingLicense: DataSharing? - var format: Format? - var types: [String]? - var trustFramework: TrustFramework? - var display: [DisplayElement]? +public struct CredentialSupportedObject { - enum CodingKeys: String, CodingKey { - case portableDocumentA1 = "PortableDocumentA1" - case parkingTicket = "Parking ticket" - case testDraft2 = "Test draft 2" - case dataSharing = "Data Sharing" - case drivingLicense = "DrivingLicense" + public var dataSharing: [String : DataSharing]? + + init(from: [String:DataSharingResponse]) { + + dataSharing = from.mapValues({ + DataSharing(from: $0) + }) + } + + init(from: [DataSharingOldFormatResponse]) { + var ldataSharing = [String:DataSharing]() + if from.count > 0{ + for item in from{ + let dataSharingVal = DataSharing(from: item) + if let key = dataSharingVal.types?.last{ + ldataSharing[key] = dataSharingVal + } + } + + if ldataSharing.count > 0{ + dataSharing = ldataSharing + } + } + } + } -struct DisplayElement: Codable { - var name: String? - var locale: Locale? +public struct DisplayElement { + public var name: String? + public var locale: Locale? + + init(from: DisplayElementResponse) { + name = from.name + locale = from.locale + } } -enum Format: String, Codable { - case jwtVc = "jwt_vc" +public struct IssuerCredentialDefinition { + public var type: [String]? + public var vct: String? + + init(from: IssuerCredentialDefinitionResponse) { + type = from.type + vct = from.vct + } } +//enum Format: String, Codable { +// case jwtVc = "jwt_vc" +//} + // MARK: - DataSharing -struct DataSharing: Codable { - var format, scope: String? - var cryptographicBindingMethodsSupported, cryptographicSuitesSupported: [String]? - var display: [DataSharingDisplay]? +public struct DataSharing { + public var format, scope: String? + public var cryptographicBindingMethodsSupported, cryptographicSuitesSupported: [String]? + public var display: [DataSharingDisplay]? + public var types: [String]? + public var trustFramework: TrustFramework? + public var credentialDefinition: IssuerCredentialDefinition? + + init(from: DataSharingResponse) { + format = from.format + scope = from.scope + cryptographicBindingMethodsSupported = from.cryptographicBindingMethodsSupported + cryptographicSuitesSupported = from.cryptographicSuitesSupported + if let dataSharingDisplayList = from.display, dataSharingDisplayList.count > 0{ + display = dataSharingDisplayList.map({ DataSharingDisplay(from: $0) }) + } + credentialDefinition = from.credentialDefinition == nil ? nil : IssuerCredentialDefinition(from: from.credentialDefinition!) + } - enum CodingKeys: String, CodingKey { - case format, scope - case cryptographicBindingMethodsSupported = "cryptographic_binding_methods_supported" - case cryptographicSuitesSupported = "cryptographic_suites_supported" - case display + init(from: DataSharingOldFormatResponse) { + format = from.format + types = from.types + trustFramework = from.trustFramework == nil ? nil : TrustFramework(from: from.trustFramework!) + if let dataSharingDisplayList = from.display, dataSharingDisplayList.count > 0{ + display = dataSharingDisplayList.map({ DataSharingDisplay(from: $0)}) + } } } // MARK: - DataSharingDisplay -struct DataSharingDisplay: Codable { - var name, locale, backgroundColor, textColor: String? - - enum CodingKeys: String, CodingKey { - case name, locale - case backgroundColor = "background_color" - case textColor = "text_color" +public struct DataSharingDisplay { + public var name, locale, backgroundColor, textColor: String? + + init(from: DataSharingDisplayResponse) { + name = from.name + locale = from.locale + backgroundColor = from.backgroundColor + textColor = from.textColor } + } // MARK: - CredentialsSupportedObjectDisplay -struct CredentialsSupportedObjectDisplay: Codable { - var name, location, locale: String? - var cover, logo: Cover? - var description: String? +public struct CredentialsSupportedObjectDisplay { + public var name, location, locale: String? + public var cover, logo: DisplayCover? + public var description: String? } // MARK: - Cover -struct Cover: Codable { - var url: String? - var altText: String? - - enum CodingKeys: String, CodingKey { - case url - case altText = "alt_text" +public struct DisplayCover{ + public var url: String? + public var altText: String? + + init(from: DisplayCoverResponse) { + url = from.url + altText = from.altText } } -public struct IssuerWellKnownConfiguration: Codable { - let credentialIssuer: String? - let authorizationServer: String? - let credentialEndpoint: String? - let deferredCredentialEndpoint: String? - let display: DisplayOrArray? - let credentialsSupported: SingleCredentialsSupportedOrArray? - - enum CodingKeys: String, CodingKey { - case credentialIssuer = "credential_issuer" - case authorizationServer = "authorization_server" - case credentialEndpoint = "credential_endpoint" - case deferredCredentialEndpoint = "deferred_credential_endpoint" - case display = "display" - case credentialsSupported = "credentials_supported" - } +public struct IssuerWellKnownConfiguration { + public let credentialIssuer: String? + public let authorizationServer: String? + public let credentialEndpoint: String? + public let deferredCredentialEndpoint: String? + public let display: [Display]? + public let credentialsSupported: CredentialSupportedObject? + public let error: Error? - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - credentialIssuer = try container.decode(String.self, forKey: .credentialIssuer) - authorizationServer = try container.decode(String.self, forKey: .authorizationServer) - credentialEndpoint = try container.decode(String.self, forKey: .credentialEndpoint) - deferredCredentialEndpoint = try container.decode(String.self, forKey: .deferredCredentialEndpoint) + public init(from: IssuerWellKnownConfigurationResponse) { + credentialIssuer = from.credentialIssuer + authorizationServer = from.authorizationServer + credentialEndpoint = from.credentialEndpoint + deferredCredentialEndpoint = from.deferredCredentialEndpoint - if let singleCredentialSupported = try? container.decode(CredentialSupportedObject.self, forKey: .credentialsSupported) { - credentialsSupported = .single(singleCredentialSupported) - } else if let credentialSupportedArray = try? container.decode([CredentialsSupported].self, forKey: .credentialsSupported) { - credentialsSupported = .array(credentialSupportedArray) - } else { - throw DecodingError.dataCorruptedError(forKey: .credentialsSupported, in: container, debugDescription: "Display value is missing or invalid.") + if let displayList = from.display as? [DisplayResponse] + { + display = displayList.map({ Display(from: $0) }) + } else{ + display = nil } - if let singleDisplay = try? container.decode(Display.self, forKey: .display) { - display = .single(singleDisplay) - } else if let displayArray = try? container.decode([Display].self, forKey: .display) { - display = .array(displayArray) - } else { - throw DecodingError.dataCorruptedError(forKey: .display, in: container, debugDescription: "Display value is missing or invalid.") - } - } -} -enum DisplayOrArray: Codable { - case single(Display) - case array([Display]) - func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - switch self { - case .single(let display): - try container.encode(display) - case .array(let displayArray): - try container.encode(displayArray) + if let credentialsSupportList = from.credentialsSupported as? [[String:DataSharingResponse]], let firstObj = credentialsSupportList.first{ + credentialsSupported = CredentialSupportedObject(from: firstObj) + } else if let credentialsSupportListOldFormat = from.credentialsSupported as? [DataSharingOldFormatResponse]{ + credentialsSupported = CredentialSupportedObject(from: credentialsSupportListOldFormat) + } else{ + credentialsSupported = nil } + + error = nil + } - init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - if let display = try? container.decode(Display.self) { - self = .single(display) - } else if let displayArray = try? container.decode([Display].self) { - self = .array(displayArray) - } else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Display value is missing or invalid.") - } + + init(from: Error) { + error = from + credentialIssuer = nil + authorizationServer = nil + credentialEndpoint = nil + deferredCredentialEndpoint = nil + display = nil + credentialsSupported = nil } + + } - -enum SingleCredentialsSupportedOrArray: Codable { - case single(CredentialSupportedObject) - case array([CredentialsSupported]) - func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - switch self { - case .single(let credentialSupported): - try container.encode(credentialSupported) - case .array(let credentialSupportedArray): - try container.encode(credentialSupportedArray) - } - } - init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - if let credentialSupported = try? container.decode(CredentialSupportedObject.self) { - self = .single(credentialSupported) - } else if let credentialSupportedArray = try? container.decode([CredentialsSupported].self) { - self = .array(credentialSupportedArray) - } else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Credential supported value is missing or invalid.") - } - } -} diff --git a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift new file mode 100644 index 0000000..4519c59 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift @@ -0,0 +1,200 @@ +// +// IssuerWellKnownConfigurationResponse.swift +// +// +// Created by Arun Raj on 07/06/24. +// + +import Foundation + +// MARK: - IssuerWellKnownConfiguration +struct DisplayResponse: Codable { + let name: String? + let location: String? + let locale: String? + let description: String? + var cover, logo: DisplayCoverResponse? +} +struct TrustFrameworkInIssuerResponse: Codable { + let name: String? + let type: String? + let uri: String? + let display: DisplayResponse? +} +struct CredentialsSupportedResponse: Codable { + let format: String? + let types: [String]? + let trustFramework: TrustFrameworkInIssuerResponse? + let display: [AnyObject]? + + enum CodingKeys: String, CodingKey { + case format = "format" + case types = "types" + case trustFramework = "trustFramework" + case display = "display" + } + + public func encode(to encoder: Encoder) throws { + + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + format = try container.decode(String.self, forKey: .format) + types = try container.decode([String].self, forKey: .types) + trustFramework = try container.decode(TrustFrameworkInIssuerResponse.self, forKey: .trustFramework) + if let singleDisplay = try? container.decode(DisplayResponse.self, forKey: .display) { + display = [singleDisplay] as? [AnyObject] + } else if let displayArray = try? container.decode([DisplayResponse].self, forKey: .display) { + display = displayArray as? [AnyObject] + } else { + throw DecodingError.dataCorruptedError(forKey: .display, in: container, debugDescription: "Display value is missing or invalid.") + } + } +} + +// MARK: - CredentialsSupportedObject +struct CredentialsSupportedObjectTypeResponse: Codable { + var credentialsSupported: CredentialSupportedObjectResponse? + + enum CodingKeys: String, CodingKey { + case credentialsSupported = "credentials_supported" + } +} + +// MARK: - CredentialObj +struct CredentialSupportedObjectResponse : Codable { + //var portableDocumentA1, parkingTicket, testDraft2, dataSharing: DataSharingResponse? + var credentialsSupported: [String: DataSharingResponse]? + var format: String? + var types: [String]? + var trustFramework: TrustFrameworkResponse? + var display: [DisplayElementResponse]? + + enum CodingKeys: String, CodingKey { + case credentialsSupported = "credentials_supported" + } + +} + +struct DisplayElementResponse: Codable { + var name: String? + var locale: Locale? +} + +enum FormatResponse: String, Codable { + case jwtVc = "jwt_vc" +} + +// MARK: - DataSharing +struct DataSharingResponse: Codable { + var format, scope: String? + var cryptographicBindingMethodsSupported, cryptographicSuitesSupported: [String]? + var display: [DataSharingDisplayResponse]? + var credentialDefinition: IssuerCredentialDefinitionResponse? + + enum CodingKeys: String, CodingKey { + case format, scope + case cryptographicBindingMethodsSupported = "cryptographic_binding_methods_supported" + case cryptographicSuitesSupported = "cryptographic_suites_supported" + case display + case credentialDefinition = "credential_definition" + } +} + +struct DataSharingOldFormatResponse: Codable { + var format: String? + var types: [String]? + var trustFramework: TrustFrameworkResponse? + var display: [DataSharingDisplayResponse]? + + enum CodingKeys: String, CodingKey { + case format, types + case trustFramework = "trust_framework" + case display + } +} + +// MARK: - DataSharingDisplay +struct DataSharingDisplayResponse: Codable { + var name, locale, backgroundColor, textColor: String? + + enum CodingKeys: String, CodingKey { + case name, locale + case backgroundColor = "background_color" + case textColor = "text_color" + } +} + +// MARK: - CredentialsSupportedObjectDisplay +struct CredentialsSupportedObjectDisplayResponse: Codable { + var name, location, locale: String? + var cover, logo: DisplayCoverResponse? + var description: String? +} + +//MARK: Credential Definition +struct IssuerCredentialDefinitionResponse: Codable { + var type: [String]? + var vct: String? +} + +// MARK: - Cover +struct DisplayCoverResponse: Codable { + var url: String? + var altText: String? + + enum CodingKeys: String, CodingKey { + case url + case altText = "alt_text" + } +} + + +public struct IssuerWellKnownConfigurationResponse: Codable { + let credentialIssuer: String? + let authorizationServer: String? + let credentialEndpoint: String? + let deferredCredentialEndpoint: String? + let display: [AnyObject]? + let credentialsSupported: [AnyObject]? + + enum CodingKeys: String, CodingKey { + case credentialIssuer = "credential_issuer" + case authorizationServer = "authorization_server" + case credentialEndpoint = "credential_endpoint" + case deferredCredentialEndpoint = "deferred_credential_endpoint" + case display = "display" + case credentialsSupported = "credentials_supported" + } + + public func encode(to encoder: Encoder) throws { + + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + credentialIssuer = try container.decode(String.self, forKey: .credentialIssuer) + authorizationServer = try container.decode(String.self, forKey: .authorizationServer) + credentialEndpoint = try container.decode(String.self, forKey: .credentialEndpoint) + deferredCredentialEndpoint = try container.decode(String.self, forKey: .deferredCredentialEndpoint) + + if let singleCredentialSupported = try? container.decode([String:DataSharingResponse].self, forKey: .credentialsSupported) { + credentialsSupported = [singleCredentialSupported] as? [AnyObject] + } else if let credentialSupportedArray = try? container.decode([DataSharingOldFormatResponse].self, forKey: .credentialsSupported) { + credentialsSupported = credentialSupportedArray as? [AnyObject] + } else { + throw DecodingError.dataCorruptedError(forKey: .credentialsSupported, in: container, debugDescription: "Display value is missing or invalid.") + } + + if let singleDisplay = try? container.decode(DisplayResponse.self, forKey: .display) { + display = [singleDisplay] as? [AnyObject] + } else if let displayArray = try? container.decode([DisplayResponse].self, forKey: .display) { + display = displayArray as? [AnyObject] + } else { + throw DecodingError.dataCorruptedError(forKey: .display, in: container, debugDescription: "Display value is missing or invalid.") + } + } +} + + diff --git a/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift b/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift index 2cbde19..580cc32 100644 --- a/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift +++ b/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift @@ -32,14 +32,14 @@ public class DiscoveryService: DiscoveryServiceProtocol { let (data, _) = try await URLSession.shared.data(for: request) do { - let model = try jsonDecoder.decode(IssuerWellKnownConfiguration.self, from: data) - return model + let model = try jsonDecoder.decode(IssuerWellKnownConfigurationResponse.self, from: data) + return IssuerWellKnownConfiguration(from: model) } catch { debugPrint("Get Issuer config failed: \(error)") let nsError = error as NSError let errorCode = nsError.code let error = Error(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) - return try IssuerWellKnownConfiguration(from: error as! Decoder) + return try IssuerWellKnownConfiguration(from: error) } } From daa55f1383455a6ea57e2897cd9a532f30adc33a Mon Sep 17 00:00:00 2001 From: Joseph Milan Date: Mon, 10 Jun 2024 17:40:30 +0530 Subject: [PATCH 12/33] Fix: Updated Error model name to remove conflict --- Package.resolved | 41 +++++++++++++++++++ ...risationServerWellKnownConfiguration.swift | 2 +- .../Model/CredentialOffer.swift | 6 +-- .../Model/CredentialResponse.swift | 2 +- Sources/eudiWalletOidcIos/Model/Error.swift | 4 +- .../Model/IssuerWellKnownConfiguration.swift | 28 ++++--------- ...IssuerWellKnownConfigurationResponse.swift | 8 +++- .../Model/TokenResponse.swift | 2 +- .../Service/DiscoveryService.swift | 4 +- .../Service/IssueService.swift | 12 +++--- .../Service/VerificationService.swift | 2 +- 11 files changed, 73 insertions(+), 38 deletions(-) create mode 100644 Package.resolved diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..4bdcb48 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,41 @@ +{ + "pins" : [ + { + "identity" : "base58swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/keefertaylor/Base58Swift.git", + "state" : { + "branch" : "master", + "revision" : "1a7fe530e5198b81a1b9ddb5629b341116aa8c49" + } + }, + { + "identity" : "bigint", + "kind" : "remoteSourceControl", + "location" : "https://github.com/attaswift/BigInt.git", + "state" : { + "revision" : "0ed110f7555c34ff468e72e1686e59721f2b0da6", + "version" : "5.3.0" + } + }, + { + "identity" : "cryptoswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", + "state" : { + "revision" : "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0", + "version" : "1.8.2" + } + }, + { + "identity" : "presentationexchangesdkios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/decentralised-dataexchange/PresentationExchangeSdkiOS.git", + "state" : { + "revision" : "7038d35f98505f364d4cc50b65ab81de6a3d336c", + "version" : "2024.5.1" + } + } + ], + "version" : 2 +} diff --git a/Sources/eudiWalletOidcIos/Model/AuthorisationServerWellKnownConfiguration.swift b/Sources/eudiWalletOidcIos/Model/AuthorisationServerWellKnownConfiguration.swift index dc68ec4..3c20b81 100644 --- a/Sources/eudiWalletOidcIos/Model/AuthorisationServerWellKnownConfiguration.swift +++ b/Sources/eudiWalletOidcIos/Model/AuthorisationServerWellKnownConfiguration.swift @@ -18,7 +18,7 @@ public struct AuthorisationServerWellKnownConfiguration: Codable { var requestAuthenticationMethodsSupported: RequestAuthenticationMethodsSupported? var vpFormatsSupported: VpFormatsSupported? var subjectSyntaxTypesSupported, subjectSyntaxTypesDiscriminations, subjectTrustFrameworksSupported, idTokenTypesSupported: [String]? - var error: Error? + var error: EUDIError? enum CodingKeys: String, CodingKey { case redirectUris = "redirect_uris" diff --git a/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift b/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift index 1ba706b..deaf9e1 100644 --- a/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift +++ b/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift @@ -12,7 +12,7 @@ public struct CredentialOffer { public var credentialIssuer: String? public var credentials: [Credential]? public var grants: Grants? - public var error: Error? + public var error: EUDIError? public init(from: CredentialOfferResponse) { credentialIssuer = from.credentialIssuer @@ -29,10 +29,10 @@ public struct CredentialOffer { } } grants = from.grants == nil ? nil : Grants(from: from.grants!) - error = from.error == nil ? nil : Error(from: from.error!) + error = from.error == nil ? nil : EUDIError(from: from.error!) } - public init(fromError: Error) { + public init(fromError: EUDIError) { error = fromError } diff --git a/Sources/eudiWalletOidcIos/Model/CredentialResponse.swift b/Sources/eudiWalletOidcIos/Model/CredentialResponse.swift index b914cfd..f0b7d37 100644 --- a/Sources/eudiWalletOidcIos/Model/CredentialResponse.swift +++ b/Sources/eudiWalletOidcIos/Model/CredentialResponse.swift @@ -14,7 +14,7 @@ public struct CredentialResponse: Codable { var issuerConfig: IssuerWellKnownConfiguration? var authorizationConfig: AuthorisationServerWellKnownConfiguration? var credentialOffer: CredentialOffer? - var error: Error? + var error: EUDIError? enum CodingKeys: String, CodingKey { case acceptanceToken = "acceptance_token" diff --git a/Sources/eudiWalletOidcIos/Model/Error.swift b/Sources/eudiWalletOidcIos/Model/Error.swift index f826fe6..a32ad6f 100644 --- a/Sources/eudiWalletOidcIos/Model/Error.swift +++ b/Sources/eudiWalletOidcIos/Model/Error.swift @@ -1,6 +1,6 @@ // // Error.swift -// +// // // Created by Mumthasir mohammed on 19/03/24. // @@ -17,7 +17,7 @@ struct ErrorResponse: Codable { } } -public struct Error { +public struct EUDIError { var message: String? var code: Int? diff --git a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift index a5fb7a3..040a185 100644 --- a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift +++ b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift @@ -14,6 +14,7 @@ public struct Display{ public let locale: String? public let description: String? public var cover, logo: DisplayCover? + public var backgroundColor, textColor: String? init(from: DisplayResponse) { name = from.name @@ -22,6 +23,8 @@ public struct Display{ description = from.description cover = from.cover == nil ? nil : DisplayCover(from: from.cover!) logo = from.logo == nil ? nil : DisplayCover(from: from.logo!) + backgroundColor = from.backgroundColor + textColor = from.textColor } } public struct TrustFrameworkInIssuer { @@ -112,7 +115,7 @@ public struct IssuerCredentialDefinition { public struct DataSharing { public var format, scope: String? public var cryptographicBindingMethodsSupported, cryptographicSuitesSupported: [String]? - public var display: [DataSharingDisplay]? + public var display: [Display]? public var types: [String]? public var trustFramework: TrustFramework? public var credentialDefinition: IssuerCredentialDefinition? @@ -123,7 +126,7 @@ public struct DataSharing { cryptographicBindingMethodsSupported = from.cryptographicBindingMethodsSupported cryptographicSuitesSupported = from.cryptographicSuitesSupported if let dataSharingDisplayList = from.display, dataSharingDisplayList.count > 0{ - display = dataSharingDisplayList.map({ DataSharingDisplay(from: $0) }) + display = dataSharingDisplayList.map({ Display(from: $0) }) } credentialDefinition = from.credentialDefinition == nil ? nil : IssuerCredentialDefinition(from: from.credentialDefinition!) } @@ -133,24 +136,11 @@ public struct DataSharing { types = from.types trustFramework = from.trustFramework == nil ? nil : TrustFramework(from: from.trustFramework!) if let dataSharingDisplayList = from.display, dataSharingDisplayList.count > 0{ - display = dataSharingDisplayList.map({ DataSharingDisplay(from: $0)}) + display = dataSharingDisplayList.map({ Display(from: $0)}) } } } -// MARK: - DataSharingDisplay -public struct DataSharingDisplay { - public var name, locale, backgroundColor, textColor: String? - - init(from: DataSharingDisplayResponse) { - name = from.name - locale = from.locale - backgroundColor = from.backgroundColor - textColor = from.textColor - } - -} - // MARK: - CredentialsSupportedObjectDisplay public struct CredentialsSupportedObjectDisplay { public var name, location, locale: String? @@ -177,11 +167,11 @@ public struct IssuerWellKnownConfiguration { public let deferredCredentialEndpoint: String? public let display: [Display]? public let credentialsSupported: CredentialSupportedObject? - public let error: Error? + public let error: EUDIError? public init(from: IssuerWellKnownConfigurationResponse) { credentialIssuer = from.credentialIssuer - authorizationServer = from.authorizationServer + authorizationServer = from.authorizationServer ?? "" credentialEndpoint = from.credentialEndpoint deferredCredentialEndpoint = from.deferredCredentialEndpoint @@ -204,7 +194,7 @@ public struct IssuerWellKnownConfiguration { } - init(from: Error) { + init(from: EUDIError) { error = from credentialIssuer = nil authorizationServer = nil diff --git a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift index 4519c59..99189b6 100644 --- a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift +++ b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift @@ -14,6 +14,7 @@ struct DisplayResponse: Codable { let locale: String? let description: String? var cover, logo: DisplayCoverResponse? + var backgroundColor, textColor: String? } struct TrustFrameworkInIssuerResponse: Codable { let name: String? @@ -90,7 +91,7 @@ enum FormatResponse: String, Codable { struct DataSharingResponse: Codable { var format, scope: String? var cryptographicBindingMethodsSupported, cryptographicSuitesSupported: [String]? - var display: [DataSharingDisplayResponse]? + var display: [DisplayResponse]? var credentialDefinition: IssuerCredentialDefinitionResponse? enum CodingKeys: String, CodingKey { @@ -106,7 +107,7 @@ struct DataSharingOldFormatResponse: Codable { var format: String? var types: [String]? var trustFramework: TrustFrameworkResponse? - var display: [DataSharingDisplayResponse]? + var display: [DisplayResponse]? enum CodingKeys: String, CodingKey { case format, types @@ -154,6 +155,7 @@ struct DisplayCoverResponse: Codable { public struct IssuerWellKnownConfigurationResponse: Codable { let credentialIssuer: String? let authorizationServer: String? + // let authorizationServers: [String]? let credentialEndpoint: String? let deferredCredentialEndpoint: String? let display: [AnyObject]? @@ -162,6 +164,7 @@ public struct IssuerWellKnownConfigurationResponse: Codable { enum CodingKeys: String, CodingKey { case credentialIssuer = "credential_issuer" case authorizationServer = "authorization_server" + // case authorizationServers = "authorization_servers" case credentialEndpoint = "credential_endpoint" case deferredCredentialEndpoint = "deferred_credential_endpoint" case display = "display" @@ -176,6 +179,7 @@ public struct IssuerWellKnownConfigurationResponse: Codable { let container = try decoder.container(keyedBy: CodingKeys.self) credentialIssuer = try container.decode(String.self, forKey: .credentialIssuer) authorizationServer = try container.decode(String.self, forKey: .authorizationServer) + // authorizationServers = try container.decode([String].self, forKey: .authorizationServers) credentialEndpoint = try container.decode(String.self, forKey: .credentialEndpoint) deferredCredentialEndpoint = try container.decode(String.self, forKey: .deferredCredentialEndpoint) diff --git a/Sources/eudiWalletOidcIos/Model/TokenResponse.swift b/Sources/eudiWalletOidcIos/Model/TokenResponse.swift index f2ca739..07ffef4 100644 --- a/Sources/eudiWalletOidcIos/Model/TokenResponse.swift +++ b/Sources/eudiWalletOidcIos/Model/TokenResponse.swift @@ -16,7 +16,7 @@ public struct TokenResponse: Codable { var cNonce: String? var cNonceExpiresIn: Int? var scope: String? - var error: Error? // Optional error property + var error: EUDIError? // Optional error property enum CodingKeys: String, CodingKey { case accessToken = "access_token" diff --git a/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift b/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift index 580cc32..993af3c 100644 --- a/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift +++ b/Sources/eudiWalletOidcIos/Service/DiscoveryService.swift @@ -38,7 +38,7 @@ public class DiscoveryService: DiscoveryServiceProtocol { debugPrint("Get Issuer config failed: \(error)") let nsError = error as NSError let errorCode = nsError.code - let error = Error(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) + let error = EUDIError(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) return try IssuerWellKnownConfiguration(from: error) } } @@ -67,7 +67,7 @@ public class DiscoveryService: DiscoveryServiceProtocol { debugPrint("Get Auth config failed: \(error)") let nsError = error as NSError let errorCode = nsError.code - let error = Error(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) + let error = EUDIError(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) return AuthorisationServerWellKnownConfiguration(error: error) } } diff --git a/Sources/eudiWalletOidcIos/Service/IssueService.swift b/Sources/eudiWalletOidcIos/Service/IssueService.swift index 279d1d7..79863bb 100644 --- a/Sources/eudiWalletOidcIos/Service/IssueService.swift +++ b/Sources/eudiWalletOidcIos/Service/IssueService.swift @@ -34,7 +34,7 @@ public class IssueService { do { let model = try? jsonDecoder.decode(CredentialOfferResponse.self, from: data) if model?.credentialIssuer == nil { - let error = Error(from: ErrorResponse(message:"Invalid DID", code: nil)) + let error = EUDIError(from: ErrorResponse(message:"Invalid DID", code: nil)) return CredentialOffer(fromError: error) } return (model == nil ? nil : CredentialOffer(from: model!)) @@ -47,7 +47,7 @@ public class IssueService { do { let model = try? jsonDecoder.decode(CredentialOfferResponse.self, from: jsonData) if model?.credentialIssuer == nil { - let error = Error(from: ErrorResponse(message:"Invalid DID", code: nil)) + let error = EUDIError(from: ErrorResponse(message:"Invalid DID", code: nil)) return CredentialOffer(fromError: error) } return (model == nil ? nil : CredentialOffer(from: model!)) @@ -365,7 +365,7 @@ public class IssueService { debugPrint("Process credential request failed: \(error)") let nsError = error as NSError let errorCode = nsError.code - let error = Error(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) + let error = EUDIError(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) return CredentialResponse(error: error) } } @@ -402,7 +402,7 @@ public class IssueService { debugPrint("Process deferred credential request failed: \(error)") let nsError = error as NSError let errorCode = nsError.code - let error = Error(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) + let error = EUDIError(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) return CredentialResponse(error: error) } } @@ -437,7 +437,7 @@ public class IssueService { debugPrint("Get access token for preauth credential failed: \(error)") let nsError = error as NSError let errorCode = nsError.code - let error = Error(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) + let error = EUDIError(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) return TokenResponse(error: error) } } @@ -473,7 +473,7 @@ public class IssueService { debugPrint("Get access token for preauth credential failed: \(error)") let nsError = error as NSError let errorCode = nsError.code - let error = Error(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) + let error = EUDIError(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) return TokenResponse(error: error) } } diff --git a/Sources/eudiWalletOidcIos/Service/VerificationService.swift b/Sources/eudiWalletOidcIos/Service/VerificationService.swift index d8c29b2..3d24f29 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationService.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationService.swift @@ -102,7 +102,7 @@ public class VerificationService: VerificationServiceProtocol { debugPrint("Error:\(error)") } } else { - let error = Error(from: ErrorResponse(message:"Invalid DID", code: nil)) + let error = EUDIError(from: ErrorResponse(message:"Invalid DID", code: nil)) debugPrint(error) return nil } From e995c4931c37a0a724714d9a9149731f6eef5a97 Mon Sep 17 00:00:00 2001 From: Joseph Milan Date: Tue, 11 Jun 2024 09:15:43 +0530 Subject: [PATCH 13/33] Fix: Data model updated for authorisation config --- ...risationServerWellKnownConfiguration.swift | 30 +++++++++---------- .../Model/CredentialOffer.swift | 3 -- .../Model/CredentialOfferResponse.swift | 6 ++-- Sources/eudiWalletOidcIos/Model/Error.swift | 4 +-- .../Model/IssuerWellKnownConfiguration.swift | 2 +- ...IssuerWellKnownConfigurationResponse.swift | 10 +++---- 6 files changed, 25 insertions(+), 30 deletions(-) diff --git a/Sources/eudiWalletOidcIos/Model/AuthorisationServerWellKnownConfiguration.swift b/Sources/eudiWalletOidcIos/Model/AuthorisationServerWellKnownConfiguration.swift index 3c20b81..0b848cb 100644 --- a/Sources/eudiWalletOidcIos/Model/AuthorisationServerWellKnownConfiguration.swift +++ b/Sources/eudiWalletOidcIos/Model/AuthorisationServerWellKnownConfiguration.swift @@ -9,16 +9,16 @@ import Foundation // MARK: - AuthorisationServerWellKnownConfiguration public struct AuthorisationServerWellKnownConfiguration: Codable { - var redirectUris: [String]? - var issuer, authorizationEndpoint, tokenEndpoint, jwksURI: String? - var scopesSupported, responseTypesSupported, responseModesSupported, grantTypesSupported: [String]? - var subjectTypesSupported, idTokenSigningAlgValuesSupported, requestObjectSigningAlgValuesSupported: [String]? - var requestParameterSupported, requestURIParameterSupported: Bool? - var tokenEndpointAuthMethodsSupported: [String]? - var requestAuthenticationMethodsSupported: RequestAuthenticationMethodsSupported? - var vpFormatsSupported: VpFormatsSupported? - var subjectSyntaxTypesSupported, subjectSyntaxTypesDiscriminations, subjectTrustFrameworksSupported, idTokenTypesSupported: [String]? - var error: EUDIError? + public var redirectUris: [String]? + public var issuer, authorizationEndpoint, tokenEndpoint, jwksURI: String? + public var scopesSupported, responseTypesSupported, responseModesSupported, grantTypesSupported: [String]? + public var subjectTypesSupported, idTokenSigningAlgValuesSupported, requestObjectSigningAlgValuesSupported: [String]? + public var requestParameterSupported, requestURIParameterSupported: Bool? + public var tokenEndpointAuthMethodsSupported: [String]? + public var requestAuthenticationMethodsSupported: RequestAuthenticationMethodsSupported? + public var vpFormatsSupported: VpFormatsSupported? + public var subjectSyntaxTypesSupported, subjectSyntaxTypesDiscriminations, subjectTrustFrameworksSupported, idTokenTypesSupported: [String]? + public var error: EUDIError? enum CodingKeys: String, CodingKey { case redirectUris = "redirect_uris" @@ -46,8 +46,8 @@ public struct AuthorisationServerWellKnownConfiguration: Codable { } // MARK: - RequestAuthenticationMethodsSupported -struct RequestAuthenticationMethodsSupported: Codable { - var authorizationEndpoint: [String]? +public struct RequestAuthenticationMethodsSupported: Codable { + public var authorizationEndpoint: [String]? enum CodingKeys: String, CodingKey { case authorizationEndpoint = "authorization_endpoint" @@ -55,8 +55,8 @@ struct RequestAuthenticationMethodsSupported: Codable { } // MARK: - VpFormatsSupported -struct VpFormatsSupported: Codable { - var jwtVp, jwtVc: JwtV? +public struct VpFormatsSupported: Codable { + public var jwtVp, jwtVc: JwtV? enum CodingKeys: String, CodingKey { case jwtVp = "jwt_vp" @@ -65,7 +65,7 @@ struct VpFormatsSupported: Codable { } // MARK: - JwtV -struct JwtV: Codable { +public struct JwtV: Codable { var algValuesSupported: [String]? enum CodingKeys: String, CodingKey { diff --git a/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift b/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift index deaf9e1..ea077c7 100644 --- a/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift +++ b/Sources/eudiWalletOidcIos/Model/CredentialOffer.swift @@ -82,16 +82,13 @@ public struct TrustFramework { } } -// MARK: - Grants public struct Grants { public let authorizationCode: AuthorizationCode? public let urnIETFParamsOauthGrantTypePreAuthorizedCode: UrnIETFParamsOauthGrantTypePreAuthorizedCode? - public let authCode: UrnIETFParamsOauthGrantTypePreAuthorizedCode? init(from: GrantsResponse) { authorizationCode = from.authorizationCode == nil ? nil : AuthorizationCode(from: from.authorizationCode!) urnIETFParamsOauthGrantTypePreAuthorizedCode = from.urnIETFParamsOauthGrantTypePreAuthorizedCode == nil ? nil : UrnIETFParamsOauthGrantTypePreAuthorizedCode(from: from.urnIETFParamsOauthGrantTypePreAuthorizedCode!) - authCode = from.authCode == nil ? nil : UrnIETFParamsOauthGrantTypePreAuthorizedCode(from: from.authCode!) } } diff --git a/Sources/eudiWalletOidcIos/Model/CredentialOfferResponse.swift b/Sources/eudiWalletOidcIos/Model/CredentialOfferResponse.swift index 67f81c2..cb517e0 100644 --- a/Sources/eudiWalletOidcIos/Model/CredentialOfferResponse.swift +++ b/Sources/eudiWalletOidcIos/Model/CredentialOfferResponse.swift @@ -1,6 +1,6 @@ // // File.swift -// +// // // Created by Arun Raj on 05/06/24. // @@ -80,12 +80,10 @@ struct TrustFrameworkResponse: Codable { struct GrantsResponse: Codable { let authorizationCode: AuthorizationCodeResponse? let urnIETFParamsOauthGrantTypePreAuthorizedCode: UrnIETFParamsOauthGrantTypePreAuthorizedCodeResponse? - let authCode: UrnIETFParamsOauthGrantTypePreAuthorizedCodeResponse? enum CodingKeys: String, CodingKey { case authorizationCode = "authorization_code" - case urnIETFParamsOauthGrantTypePreAuthorizedCode - case authCode = "urn:ietf:params:oauth:grant-type:pre-authorized_code" + case urnIETFParamsOauthGrantTypePreAuthorizedCode = "urn:ietf:params:oauth:grant-type:pre-authorized_code" } } diff --git a/Sources/eudiWalletOidcIos/Model/Error.swift b/Sources/eudiWalletOidcIos/Model/Error.swift index a32ad6f..138b8c5 100644 --- a/Sources/eudiWalletOidcIos/Model/Error.swift +++ b/Sources/eudiWalletOidcIos/Model/Error.swift @@ -18,8 +18,8 @@ struct ErrorResponse: Codable { } public struct EUDIError { - var message: String? - var code: Int? + public var message: String? + public var code: Int? init(from: ErrorResponse) { message = from.message diff --git a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift index 040a185..e02825f 100644 --- a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift +++ b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift @@ -171,7 +171,7 @@ public struct IssuerWellKnownConfiguration { public init(from: IssuerWellKnownConfigurationResponse) { credentialIssuer = from.credentialIssuer - authorizationServer = from.authorizationServer ?? "" + authorizationServer = from.authorizationServer ?? from.authorizationServers?[0] ?? "" credentialEndpoint = from.credentialEndpoint deferredCredentialEndpoint = from.deferredCredentialEndpoint diff --git a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift index 99189b6..ad71470 100644 --- a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift +++ b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift @@ -155,7 +155,7 @@ struct DisplayCoverResponse: Codable { public struct IssuerWellKnownConfigurationResponse: Codable { let credentialIssuer: String? let authorizationServer: String? - // let authorizationServers: [String]? + let authorizationServers: [String]? let credentialEndpoint: String? let deferredCredentialEndpoint: String? let display: [AnyObject]? @@ -164,7 +164,7 @@ public struct IssuerWellKnownConfigurationResponse: Codable { enum CodingKeys: String, CodingKey { case credentialIssuer = "credential_issuer" case authorizationServer = "authorization_server" - // case authorizationServers = "authorization_servers" + case authorizationServers = "authorization_servers" case credentialEndpoint = "credential_endpoint" case deferredCredentialEndpoint = "deferred_credential_endpoint" case display = "display" @@ -178,8 +178,8 @@ public struct IssuerWellKnownConfigurationResponse: Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) credentialIssuer = try container.decode(String.self, forKey: .credentialIssuer) - authorizationServer = try container.decode(String.self, forKey: .authorizationServer) - // authorizationServers = try container.decode([String].self, forKey: .authorizationServers) + authorizationServer = try? container.decode(String.self, forKey: .authorizationServer) + authorizationServers = try? container.decode([String].self, forKey: .authorizationServers) credentialEndpoint = try container.decode(String.self, forKey: .credentialEndpoint) deferredCredentialEndpoint = try container.decode(String.self, forKey: .deferredCredentialEndpoint) @@ -196,7 +196,7 @@ public struct IssuerWellKnownConfigurationResponse: Codable { } else if let displayArray = try? container.decode([DisplayResponse].self, forKey: .display) { display = displayArray as? [AnyObject] } else { - throw DecodingError.dataCorruptedError(forKey: .display, in: container, debugDescription: "Display value is missing or invalid.") + display = [] } } } From e2379e80c6a52afce36ac3a213309e23f7fd33a5 Mon Sep 17 00:00:00 2001 From: Joseph Milan Date: Tue, 11 Jun 2024 11:52:30 +0530 Subject: [PATCH 14/33] Fix: Updated functions to fetch types, format and cryptographic suits from issuer config --- .../Service/IssueService.swift | 220 ++++++++++++------ .../Service/IssueServiceProtocol.swift | 12 +- 2 files changed, 162 insertions(+), 70 deletions(-) diff --git a/Sources/eudiWalletOidcIos/Service/IssueService.swift b/Sources/eudiWalletOidcIos/Service/IssueService.swift index 79863bb..5678b8b 100644 --- a/Sources/eudiWalletOidcIos/Service/IssueService.swift +++ b/Sources/eudiWalletOidcIos/Service/IssueService.swift @@ -292,82 +292,120 @@ public class IssueService { public func processCredentialRequest( did: String, privateKey: P256.Signing.PrivateKey, + nonce: String, credentialOffer: CredentialOffer, - credentialEndpointUrlString: String, - c_nonce: String, - accessToken: String) async -> CredentialResponse? { + issuerConfig: IssuerWellKnownConfiguration, + accessToken: String, + format: String) async -> CredentialResponse? { - let jsonDecoder = JSONDecoder() - let methodSpecificId = did.replacingOccurrences(of: "did:key:", with: "") + let jsonDecoder = JSONDecoder() + let methodSpecificId = did.replacingOccurrences(of: "did:key:", with: "") + + + // Generate JWT header + let header = ([ + "typ": "openid4vci-proof+jwt", + "alg": "ES256", + "kid": "\(did)#\(methodSpecificId)" + ]).toString() ?? "" + // Generate JWT payload + let currentTime = Int(Date().epochTime) ?? 0 + let payload = ([ + "iss": "\(did)", + "iat": currentTime, + "aud": "\(credentialOffer.credentialIssuer ?? "")", + "exp": currentTime + 86400, + "nonce": "\(nonce)" + ] as [String : Any]).toString() ?? "" - // Generate JWT header - let header = ([ - "typ": "openid4vci-proof+jwt", - "alg": "ES256", - "kid": "\(did)#\(methodSpecificId)" - ]).toString() ?? "" - - // Generate JWT payload - let currentTime = Int(Date().epochTime) ?? 0 - let payload = ([ - "iss": "\(did)", - "iat": currentTime, - "aud": "\(credentialOffer.credentialIssuer ?? "")", - "exp": currentTime + 86400, - "nonce": "\(c_nonce)" - ] as [String : Any]).toString() ?? "" - - guard let url = URL(string: credentialOffer.credentialIssuer ?? "") else { return nil } - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.setValue( "Bearer \(accessToken)", forHTTPHeaderField: "Authorization") - - // Create JWT token - let headerData = Data(header.utf8) - let payloadData = Data(payload.utf8) - let unsignedToken = "\(headerData.base64URLEncodedString()).\(payloadData.base64URLEncodedString())" - let signatureData = try! privateKey.signature(for: unsignedToken.data(using: .utf8)!) - let signature = signatureData.rawRepresentation - let idToken = "\(unsignedToken).\(signature.base64URLEncodedString())" - let format = credentialOffer.credentials?[0].format ?? "" - - // Set up parameters for the request - let params = [ - "types": credentialOffer.credentials?[0].types ?? [], - "format": format, - "proof": [ - "proof_type": "jwt", - "jwt": idToken + guard let url = URL(string: credentialOffer.credentialIssuer ?? "") else { return nil } + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue( "Bearer \(accessToken)", forHTTPHeaderField: "Authorization") + + // Create JWT token + let headerData = Data(header.utf8) + let payloadData = Data(payload.utf8) + let unsignedToken = "\(headerData.base64URLEncodedString()).\(payloadData.base64URLEncodedString())" + let signatureData = try! privateKey.signature(for: unsignedToken.data(using: .utf8)!) + let signature = signatureData.rawRepresentation + let idToken = "\(unsignedToken).\(signature.base64URLEncodedString())" + let format = credentialOffer.credentials?[0].format ?? "" + + let credentialTypes = credentialOffer.credentials?[0].types ?? [] + var params: [String: Any] = [ + "credential_definition": [ + "type": credentialTypes + ], + "format": format, + "proof": [ + "proof_type": "jwt", + "jwt": idToken + ] ] - ] as [String: Any] - - // Create URL for the credential endpoint - guard let url = URL(string: credentialEndpointUrlString) else { return nil } + if credentialOffer.credentials?[0].trustFramework != nil { + params = [ + "types": credentialTypes, + "format": format, + "proof": [ + "proof_type": "jwt", + "jwt": idToken + ] + ] + } else { + + if let data = getTypesFromIssuerConfig(issuerConfig: issuerConfig, type: credentialTypes.last ?? "") { + if let dataArray = data as? [String] { + params = [ + "credential_definition": [ + "type": dataArray + ], + "format": format, + "proof": [ + "proof_type": "jwt", + "jwt": idToken + ] + ] + } else if let dataString = data as? String { + params = [ + "vct": dataString, + "format": format, + "proof": [ + "proof_type": "jwt", + "jwt": idToken + ] + ] + } + } + } - // Set up the request for the credential endpoint - request = URLRequest(url: url) - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.setValue( "Bearer \(accessToken)", forHTTPHeaderField: "Authorization") - request.httpMethod = "POST" + // Create URL for the credential endpoint + guard let url = URL(string: issuerConfig.credentialEndpoint ?? "") else { return nil } + + // Set up the request for the credential endpoint + request = URLRequest(url: url) + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue( "Bearer \(accessToken)", forHTTPHeaderField: "Authorization") + request.httpMethod = "POST" - // Convert the parameters to JSON data and set it as the request body - let requestBodyData = try? JSONSerialization.data(withJSONObject: params) - request.httpBody = requestBodyData - - // Perform the request and handle the response - do { - let (data, _) = try await URLSession.shared.data(for: request) - let model = try jsonDecoder.decode(CredentialResponse.self, from: data) - return model - } catch { - debugPrint("Process credential request failed: \(error)") - let nsError = error as NSError - let errorCode = nsError.code - let error = EUDIError(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) - return CredentialResponse(error: error) - } + // Convert the parameters to JSON data and set it as the request body + let requestBodyData = try? JSONSerialization.data(withJSONObject: params) + request.httpBody = requestBodyData + + // Perform the request and handle the response + do { + let (data, _) = try await URLSession.shared.data(for: request) + let model = try jsonDecoder.decode(CredentialResponse.self, from: data) + return model + } catch { + debugPrint("Process credential request failed: \(error)") + let nsError = error as NSError + let errorCode = nsError.code + let error = EUDIError(from: ErrorResponse(message:error.localizedDescription, code: errorCode)) + return CredentialResponse(error: error) + } } @@ -477,4 +515,48 @@ public class IssueService { return TokenResponse(error: error) } } + + public func getFormatFromIssuerConfig(issuerConfig: IssuerWellKnownConfiguration?, type: String?) -> String? { + guard let issuerConfig = issuerConfig else { return nil } + + if let credentialSupported = issuerConfig.credentialsSupported?.dataSharing?[type ?? ""] { + return credentialSupported.format + } else { + return nil + } + } + + public func getTypesFromCredentialOffer(credentialOffer: CredentialOffer?) -> [String]? { + guard let credentialOffer = credentialOffer else { return nil } + + if let types = credentialOffer.credentials?[0].types { + return types + } else { + return nil + } + } + + public func getTypesFromIssuerConfig(issuerConfig: IssuerWellKnownConfiguration?, type: String?) -> Any? { + guard let issuerConfig = issuerConfig else { return nil } + + if let credentialSupported = issuerConfig.credentialsSupported?.dataSharing?[type ?? ""] { + if credentialSupported.format == "vc+sd-jwt" { + return credentialSupported.credentialDefinition?.vct + } else { + return credentialSupported.credentialDefinition?.type + } + } else { + return nil + } + } + + public func getCryptoFromIssuerConfig(issuerConfig: IssuerWellKnownConfiguration?, type: String?) -> [String]? { + guard let issuerConfig = issuerConfig else { return nil } + + if let credentialSupported = issuerConfig.credentialsSupported?.dataSharing?[type ?? ""] { + return credentialSupported.cryptographicSuitesSupported + } else { + return nil + } + } } diff --git a/Sources/eudiWalletOidcIos/Service/IssueServiceProtocol.swift b/Sources/eudiWalletOidcIos/Service/IssueServiceProtocol.swift index bb413ab..1899283 100644 --- a/Sources/eudiWalletOidcIos/Service/IssueServiceProtocol.swift +++ b/Sources/eudiWalletOidcIos/Service/IssueServiceProtocol.swift @@ -54,7 +54,7 @@ protocol IssueServiceProtocol { - Returns: A `CredentialResponse` object if the request is successful, otherwise `nil`. */ - func processCredentialRequest(did: String, privateKey: P256.Signing.PrivateKey, credentialOffer: CredentialOffer, credentialEndpointUrlString: String, c_nonce: String, accessToken: String) async -> CredentialResponse? + func processCredentialRequest(did: String, privateKey: P256.Signing.PrivateKey, nonce: String, credentialOffer: CredentialOffer, issuerConfig: IssuerWellKnownConfiguration, accessToken: String, format: String) async -> CredentialResponse? // Processes a deferred credential request to obtain the credential response in deffered manner. @@ -64,4 +64,14 @@ protocol IssueServiceProtocol { **/ // - Returns: A `CredentialResponse` object if the request is successful, otherwise `nil`. func processDeferredCredentialRequest(acceptanceToken: String, deferredCredentialEndPoint: String) async -> CredentialResponse? + + func getFormatFromIssuerConfig( + issuerConfig: IssuerWellKnownConfiguration?, + type: String?) -> String? + + func getTypesFromCredentialOffer(credentialOffer: CredentialOffer?) -> [String]? + + func getTypesFromIssuerConfig(issuerConfig: IssuerWellKnownConfiguration?, type: String?) -> Any? + + func getCryptoFromIssuerConfig(issuerConfig: IssuerWellKnownConfiguration?, type: String?) -> [String]? } From 3ea478b1fce7b2d3d90fb1f00138ba96f2485587 Mon Sep 17 00:00:00 2001 From: Joseph Milan Date: Thu, 13 Jun 2024 16:11:28 +0530 Subject: [PATCH 15/33] Fix: made some models public and added response_uri in presentation definition --- .../Model/CredentialResponse.swift | 12 ++-- ...IssuerWellKnownConfigurationResponse.swift | 14 ++--- .../Model/PresentationDefinitionModel.swift | 57 ++++++++----------- .../Model/PresentationRequest.swift | 6 +- .../Service/VerificationService.swift | 1 + 5 files changed, 43 insertions(+), 47 deletions(-) diff --git a/Sources/eudiWalletOidcIos/Model/CredentialResponse.swift b/Sources/eudiWalletOidcIos/Model/CredentialResponse.swift index f0b7d37..ec5bdec 100644 --- a/Sources/eudiWalletOidcIos/Model/CredentialResponse.swift +++ b/Sources/eudiWalletOidcIos/Model/CredentialResponse.swift @@ -9,12 +9,12 @@ import Foundation // MARK: - CredentialResponse public struct CredentialResponse: Codable { - var format, credential, acceptanceToken: String? - var isDeferred, isPinRequired: Bool? - var issuerConfig: IssuerWellKnownConfiguration? - var authorizationConfig: AuthorisationServerWellKnownConfiguration? - var credentialOffer: CredentialOffer? - var error: EUDIError? + public var format, credential, acceptanceToken: String? + public var isDeferred, isPinRequired: Bool? + public var issuerConfig: IssuerWellKnownConfiguration? + public var authorizationConfig: AuthorisationServerWellKnownConfiguration? + public var credentialOffer: CredentialOffer? + public var error: EUDIError? enum CodingKeys: String, CodingKey { case acceptanceToken = "acceptance_token" diff --git a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift index ad71470..b8f7201 100644 --- a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift +++ b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfigurationResponse.swift @@ -41,9 +41,9 @@ struct CredentialsSupportedResponse: Codable { init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - format = try container.decode(String.self, forKey: .format) - types = try container.decode([String].self, forKey: .types) - trustFramework = try container.decode(TrustFrameworkInIssuerResponse.self, forKey: .trustFramework) + format = try? container.decode(String.self, forKey: .format) + types = try? container.decode([String].self, forKey: .types) + trustFramework = try? container.decode(TrustFrameworkInIssuerResponse.self, forKey: .trustFramework) if let singleDisplay = try? container.decode(DisplayResponse.self, forKey: .display) { display = [singleDisplay] as? [AnyObject] } else if let displayArray = try? container.decode([DisplayResponse].self, forKey: .display) { @@ -177,18 +177,18 @@ public struct IssuerWellKnownConfigurationResponse: Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - credentialIssuer = try container.decode(String.self, forKey: .credentialIssuer) + credentialIssuer = try? container.decode(String.self, forKey: .credentialIssuer) authorizationServer = try? container.decode(String.self, forKey: .authorizationServer) authorizationServers = try? container.decode([String].self, forKey: .authorizationServers) - credentialEndpoint = try container.decode(String.self, forKey: .credentialEndpoint) - deferredCredentialEndpoint = try container.decode(String.self, forKey: .deferredCredentialEndpoint) + credentialEndpoint = try? container.decode(String.self, forKey: .credentialEndpoint) + deferredCredentialEndpoint = try? container.decode(String.self, forKey: .deferredCredentialEndpoint) if let singleCredentialSupported = try? container.decode([String:DataSharingResponse].self, forKey: .credentialsSupported) { credentialsSupported = [singleCredentialSupported] as? [AnyObject] } else if let credentialSupportedArray = try? container.decode([DataSharingOldFormatResponse].self, forKey: .credentialsSupported) { credentialsSupported = credentialSupportedArray as? [AnyObject] } else { - throw DecodingError.dataCorruptedError(forKey: .credentialsSupported, in: container, debugDescription: "Display value is missing or invalid.") + credentialsSupported = [] } if let singleDisplay = try? container.decode(DisplayResponse.self, forKey: .display) { diff --git a/Sources/eudiWalletOidcIos/Model/PresentationDefinitionModel.swift b/Sources/eudiWalletOidcIos/Model/PresentationDefinitionModel.swift index 62c4f4a..f27c659 100644 --- a/Sources/eudiWalletOidcIos/Model/PresentationDefinitionModel.swift +++ b/Sources/eudiWalletOidcIos/Model/PresentationDefinitionModel.swift @@ -9,9 +9,9 @@ import Foundation // MARK: - PresentationDefinitionModel public struct PresentationDefinitionModel: Codable { - let id: String? - let format: [String: JwtVp]? - let inputDescriptors: [InputDescriptor]? + public let id: String? + public let format: [String: JwtVp]? + public let inputDescriptors: [InputDescriptor]? enum CodingKeys: String, CodingKey { case id, format @@ -19,34 +19,24 @@ public struct PresentationDefinitionModel: Codable { } } -// MARK: - PresentationDefinitionModelFormat -struct PresentationDefinitionModelFormat: Codable { - let jwtVc, jwtVp: JwtVp? - - enum CodingKeys: String, CodingKey { - case jwtVc = "jwt_vc" - case jwtVp = "jwt_vp" - } -} - // MARK: - JwtVp -struct JwtVp: Codable { - let alg: [String]? +public struct JwtVp: Codable { + public let alg: [String]? } // MARK: - InputDescriptor -struct InputDescriptor: Codable { - var id: String? - let name: String? - let purpose: String? - let constraints: Constraints? - let format: InputDescriptorFormat? +public struct InputDescriptor: Codable { + public var id: String? + public let name: String? + public let purpose: String? + public let constraints: Constraints? + public let format: InputDescriptorFormat? } // MARK: - Constraints -struct Constraints: Codable { - let limitDisclosure: String? - let fields: [Field]? +public struct Constraints: Codable { + public let limitDisclosure: String? + public let fields: [Field]? enum CodingKeys: String, CodingKey { case fields case limitDisclosure = "limit_disclosure" @@ -54,24 +44,25 @@ struct Constraints: Codable { } // MARK: - Field -struct Field: Codable { - let path: [String]? - let filter: Filter? +public struct Field: Codable { + public let path: [String]? + public let filter: Filter? } // MARK: - Filter -struct Filter: Codable { - let type: String? - let contains: Contains? +public struct Filter: Codable { + public let type: String? + public let contains: Contains? + public let pattern: String? } // MARK: - Contains -struct Contains: Codable { - let const: String? +public struct Contains: Codable { + public let const: String? } // MARK: - InputDescriptorFormat -struct InputDescriptorFormat: Codable { +public struct InputDescriptorFormat: Codable { let jwtVc: JwtVp? enum CodingKeys: String, CodingKey { diff --git a/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift b/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift index b8a5c68..a03e7e5 100644 --- a/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift +++ b/Sources/eudiWalletOidcIos/Model/PresentationRequest.swift @@ -9,12 +9,14 @@ import Foundation public struct PresentationRequest: Codable { public var state, clientId, redirectUri, responseType, responseMode, scope, nonce, requestUri: String? + public var responseUri: String? public var presentationDefinition: String? enum CodingKeys: String, CodingKey { case state = "state" case clientId = "client_id" case redirectUri = "redirect_uri" + case responseUri = "response_uri" case responseType = "response_type" case responseMode = "response_mode" case scope = "scope" @@ -23,10 +25,11 @@ public struct PresentationRequest: Codable { case presentationDefinition = "presentation_definition" } - public init(state: String?, clientId: String?, redirectUri: String?, responseType: String?, responseMode: String?, scope: String?, nonce: String?, requestUri: String?, presentationDefinition: String?) { + public init(state: String?, clientId: String?, redirectUri: String?, responseUri: String?, responseType: String?, responseMode: String?, scope: String?, nonce: String?, requestUri: String?, presentationDefinition: String?) { self.state = state self.clientId = clientId self.redirectUri = redirectUri + self.responseUri = responseUri self.responseType = responseType self.responseMode = responseMode self.scope = scope @@ -40,6 +43,7 @@ public struct PresentationRequest: Codable { state = try container.decodeIfPresent(String.self, forKey: .state) clientId = try container.decodeIfPresent(String.self, forKey: .clientId) redirectUri = try container.decodeIfPresent(String.self, forKey: .redirectUri) + responseUri = try container.decodeIfPresent(String.self, forKey: .responseUri) responseType = try container.decodeIfPresent(String.self, forKey: .responseType) responseMode = try container.decodeIfPresent(String.self, forKey: .responseMode) scope = try container.decodeIfPresent(String.self, forKey: .scope) diff --git a/Sources/eudiWalletOidcIos/Service/VerificationService.swift b/Sources/eudiWalletOidcIos/Service/VerificationService.swift index 3d24f29..a2c9b38 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationService.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationService.swift @@ -71,6 +71,7 @@ public class VerificationService: VerificationServiceProtocol { let presentationRequest = PresentationRequest(state: state, clientId: clientID, redirectUri: redirectUri, + responseUri: responseUri, responseType: responseType, responseMode: responseMode, scope: scope, From 5c7750b511cfebbd3b855eb981e8446936390d3c Mon Sep 17 00:00:00 2001 From: Arun Raj Date: Fri, 21 Jun 2024 12:24:25 +0530 Subject: [PATCH 16/33] Bug fixes for Issuance and Verification flow The bug related to issuance auth flow has been fixed --- .../Model/PresentationDefinitionModel.swift | 19 ++--- .../Model/TokenResponse.swift | 16 ++--- .../Service/IssueService.swift | 72 ++++++++++++++----- .../Service/VerificationService.swift | 32 +++++++-- 4 files changed, 92 insertions(+), 47 deletions(-) diff --git a/Sources/eudiWalletOidcIos/Model/PresentationDefinitionModel.swift b/Sources/eudiWalletOidcIos/Model/PresentationDefinitionModel.swift index f27c659..cfe9f14 100644 --- a/Sources/eudiWalletOidcIos/Model/PresentationDefinitionModel.swift +++ b/Sources/eudiWalletOidcIos/Model/PresentationDefinitionModel.swift @@ -4,69 +4,58 @@ // // Created by Mumthasir mohammed on 13/03/24. // - import Foundation - // MARK: - PresentationDefinitionModel public struct PresentationDefinitionModel: Codable { public let id: String? public let format: [String: JwtVp]? public let inputDescriptors: [InputDescriptor]? - enum CodingKeys: String, CodingKey { case id, format case inputDescriptors = "input_descriptors" } } - // MARK: - JwtVp public struct JwtVp: Codable { public let alg: [String]? } - // MARK: - InputDescriptor public struct InputDescriptor: Codable { public var id: String? public let name: String? public let purpose: String? - public let constraints: Constraints? + public var constraints: Constraints? public let format: InputDescriptorFormat? } - // MARK: - Constraints public struct Constraints: Codable { public let limitDisclosure: String? - public let fields: [Field]? + public var fields: [Field]? enum CodingKeys: String, CodingKey { case fields case limitDisclosure = "limit_disclosure" } } - // MARK: - Field public struct Field: Codable { - public let path: [String]? + public var path: [String]? public let filter: Filter? } - // MARK: - Filter public struct Filter: Codable { public let type: String? public let contains: Contains? public let pattern: String? } - // MARK: - Contains public struct Contains: Codable { public let const: String? + public let pattern: String? } - // MARK: - InputDescriptorFormat public struct InputDescriptorFormat: Codable { let jwtVc: JwtVp? - enum CodingKeys: String, CodingKey { case jwtVc = "jwt_vc" } } - diff --git a/Sources/eudiWalletOidcIos/Model/TokenResponse.swift b/Sources/eudiWalletOidcIos/Model/TokenResponse.swift index 07ffef4..69b501b 100644 --- a/Sources/eudiWalletOidcIos/Model/TokenResponse.swift +++ b/Sources/eudiWalletOidcIos/Model/TokenResponse.swift @@ -9,14 +9,14 @@ import Foundation // MARK: - TokenResponse (getAccessToken() Api call response model)) public struct TokenResponse: Codable { - var accessToken: String? - var tokenType: String? - var expiresIn: Int? - var idToken: String? - var cNonce: String? - var cNonceExpiresIn: Int? - var scope: String? - var error: EUDIError? // Optional error property + public var accessToken: String? + public var tokenType: String? + public var expiresIn: Int? + public var idToken: String? + public var cNonce: String? + public var cNonceExpiresIn: Int? + public var scope: String? + public var error: EUDIError? // Optional error property enum CodingKeys: String, CodingKey { case accessToken = "access_token" diff --git a/Sources/eudiWalletOidcIos/Service/IssueService.swift b/Sources/eudiWalletOidcIos/Service/IssueService.swift index 5678b8b..142ac8a 100644 --- a/Sources/eudiWalletOidcIos/Service/IssueService.swift +++ b/Sources/eudiWalletOidcIos/Service/IssueService.swift @@ -10,10 +10,14 @@ import CryptoKit //import KeychainSwift import CryptoSwift -public class IssueService { +public class IssueService: NSObject { public static var shared = IssueService() - private init() {} + var session: URLSession? + private override init() { + super.init() + session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) + } // MARK: - Retrieves credential issuer asynchronously based on the provided credential_offer / credential_offer_uri. /// @@ -130,13 +134,24 @@ public class IssueService { // Service call to get authorisation response var request = URLRequest(url: authorizationURL) request.httpMethod = "GET" - + request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + var responseUrl = "" do { // Try to fetch data from the URL session - let (data, _) = try await URLSession.shared.data(for: request) - guard let authorization_response = String.init(data: data, encoding: .utf8) else { return nil } - responseUrl = authorization_response + if session == nil{ + session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) + } + let (data, response) = try await session!.data(for: request) + + let httpres = response as? HTTPURLResponse + if httpres?.statusCode == 302, let location = httpres?.value(forHTTPHeaderField: "Location"){ + responseUrl = location + } else{ + guard let authorization_response = String.init(data: data, encoding: .utf8) else { return nil } + responseUrl = authorization_response + } + } catch { // If an error occurs, attempt to extract the failing URL from the error let nsError = error as NSError @@ -144,6 +159,7 @@ public class IssueService { responseUrl = String(describing: response ?? "") } + if responseUrl.contains("code=") { let url = URL(string: responseUrl) let code = url?.queryParameters?["code"] @@ -227,10 +243,22 @@ public class IssueService { do { // Perform the request to the redirect URI - let (data, _) = try await URLSession.shared.data(for: request) - let authorization_response = String.init(data: data, encoding: .utf8) ?? "" - guard let authorisation_url = URL(string: authorization_response) else { return nil } - + //let (data, _) = try await URLSession.shared.data(for: request) + if session == nil{ + session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) + } + let (data, response) = try await session!.data(for: request) + let httpres = response as? HTTPURLResponse + if httpres?.statusCode == 302, let location = httpres?.value(forHTTPHeaderField: "Location"){ + responseUrl = location + } else{ + let authorization_response = String.init(data: data, encoding: .utf8) ?? "" + + responseUrl = authorization_response + } +// let authorization_response = String.init(data: data, encoding: .utf8) ?? "" +// guard let authorisation_url = URL(string: authorization_response) else { return nil } + guard let authorisation_url = URL(string: responseUrl) else { return nil } if let components = URLComponents(url: authorisation_url, resolvingAgainstBaseURL: false), let auth_code = components.queryItems?.first(where: { $0.name == "code" })?.value { return auth_code @@ -319,7 +347,7 @@ public class IssueService { "nonce": "\(nonce)" ] as [String : Any]).toString() ?? "" - guard let url = URL(string: credentialOffer.credentialIssuer ?? "") else { return nil } + guard let url = URL(string: issuerConfig.credentialEndpoint ?? "") else { return nil } var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -332,14 +360,14 @@ public class IssueService { let signatureData = try! privateKey.signature(for: unsignedToken.data(using: .utf8)!) let signature = signatureData.rawRepresentation let idToken = "\(unsignedToken).\(signature.base64URLEncodedString())" - let format = credentialOffer.credentials?[0].format ?? "" let credentialTypes = credentialOffer.credentials?[0].types ?? [] + let formatT = getFormatFromIssuerConfig(issuerConfig: issuerConfig, type: credentialTypes.last) var params: [String: Any] = [ "credential_definition": [ "type": credentialTypes ], - "format": format, + "format": formatT, "proof": [ "proof_type": "jwt", "jwt": idToken @@ -348,7 +376,7 @@ public class IssueService { if credentialOffer.credentials?[0].trustFramework != nil { params = [ "types": credentialTypes, - "format": format, + "format": formatT, "proof": [ "proof_type": "jwt", "jwt": idToken @@ -362,7 +390,7 @@ public class IssueService { "credential_definition": [ "type": dataArray ], - "format": format, + "format": formatT, "proof": [ "proof_type": "jwt", "jwt": idToken @@ -371,7 +399,7 @@ public class IssueService { } else if let dataString = data as? String { params = [ "vct": dataString, - "format": format, + "format": formatT, "proof": [ "proof_type": "jwt", "jwt": idToken @@ -396,7 +424,7 @@ public class IssueService { // Perform the request and handle the response do { - let (data, _) = try await URLSession.shared.data(for: request) + let (data, response) = try await URLSession.shared.data(for: request) let model = try jsonDecoder.decode(CredentialResponse.self, from: data) return model } catch { @@ -522,7 +550,7 @@ public class IssueService { if let credentialSupported = issuerConfig.credentialsSupported?.dataSharing?[type ?? ""] { return credentialSupported.format } else { - return nil + return "jwt_vc" } } @@ -560,3 +588,11 @@ public class IssueService { } } } + + +extension IssueService: URLSessionDelegate, URLSessionTaskDelegate { + public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { + // Stops the redirection, and returns (internally) the response body. + completionHandler(nil) + } + } diff --git a/Sources/eudiWalletOidcIos/Service/VerificationService.swift b/Sources/eudiWalletOidcIos/Service/VerificationService.swift index a2c9b38..baf43f6 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationService.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationService.swift @@ -4,11 +4,9 @@ // // Created by Mumthasir mohammed on 11/03/24. // - import Foundation import CryptoKit import PresentationExchangeSdkiOS - public class VerificationService: VerificationServiceProtocol { public static var shared = VerificationService() @@ -249,7 +247,6 @@ public class VerificationService: VerificationServiceProtocol { throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Error processing presentation definition"]) } } - public func filterCredentials(credentialList: [String?], presentationDefinition: PresentationDefinitionModel) -> [[String]] { var response: [[String]] = [] @@ -293,22 +290,21 @@ public class VerificationService: VerificationServiceProtocol { if let inputDescriptors = presentationDefinition.inputDescriptors { for inputDescriptor in inputDescriptors { + let updatedDescriptor = updatePath(in: inputDescriptor) var filteredCredentialList: [String] = [] let jsonEncoder = JSONEncoder() jsonEncoder.keyEncodingStrategy = .convertToSnakeCase - guard let jsonData = try? jsonEncoder.encode(inputDescriptor), + guard let jsonData = try? jsonEncoder.encode(updatedDescriptor), let dictionary = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] else { fatalError("Failed to convert Person to dictionary") } - // Convert the dictionary to a string guard let inputDescriptorString = String(data: try! JSONSerialization.data(withJSONObject: dictionary, options: .withoutEscapingSlashes), encoding: .utf8) else { fatalError("Failed to convert dictionary to string") } let matchesString = matchCredentials(inputDescriptorJson: inputDescriptorString, credentials: processedCredentials) - // Assuming `matchesString` contains a JSON array of matches if let matchesData = matchesString.data(using: .utf8), let matchesArray = try? JSONSerialization.jsonObject(with: matchesData) as? [String: Any], @@ -327,4 +323,28 @@ public class VerificationService: VerificationServiceProtocol { return response } +func updatePath(in descriptor: InputDescriptor) -> InputDescriptor { + var updatedDescriptor = descriptor + guard var constraints = updatedDescriptor.constraints else { return updatedDescriptor } + guard var fields = constraints.fields else { return updatedDescriptor } + + for j in 0.. Date: Thu, 4 Jul 2024 11:57:02 +0530 Subject: [PATCH 17/33] Encryption key Management * Changes for encryption key management Segregated the code for encryption key generation and data signing to separate class to enable scalability to other encryption key generation and storage techniques. * Changing the interface descriptions Changed the interface descriptions to match the new changes related to key generation handler * Cleaning up spare code Removing test code for secure enclave --- .../Crypto Kit/CryptoKitHandler.swift | 32 ++++++++++++++ .../SecureKeyProtocol.swift | 24 +++++++++++ .../Service/DidService.swift | 31 +++++++------- .../Service/DidServiceProtocol.swift | 2 +- .../Service/IssueService.swift | 42 +++++++++++-------- .../Service/IssueServiceProtocol.swift | 10 +++-- .../Service/SDJWTService.swift | 2 +- .../Service/VerificationService.swift | 33 +++++++++------ .../Service/VerificationServiceProtocol.swift | 5 ++- 9 files changed, 128 insertions(+), 53 deletions(-) create mode 100644 Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/Crypto Kit/CryptoKitHandler.swift create mode 100644 Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/SecureKeyProtocol.swift diff --git a/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/Crypto Kit/CryptoKitHandler.swift b/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/Crypto Kit/CryptoKitHandler.swift new file mode 100644 index 0000000..3b1de95 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/Crypto Kit/CryptoKitHandler.swift @@ -0,0 +1,32 @@ +// +// File.swift +// +// +// Created by Arun Raj on 27/06/24. +// + +import Foundation +import CryptoKit + +public class CryptoKitHandler:NSObject, SecureKeyProtocol{ + + public func generateSecureKey() -> SecureKeyData?{ + let privateKey = P256.Signing.PrivateKey() + return SecureKeyData(publicKey: privateKey.publicKey.rawRepresentation, privateKey: privateKey.rawRepresentation) + } + + public func sign(data: Data, withKey privateKey: Data?) -> Data?{ + if let privateKeyData = privateKey{ + do{ + let privateKey = try P256.Signing.PrivateKey(rawRepresentation: privateKeyData) + let signedData = try privateKey.signature(for: data) + return signedData.rawRepresentation + } + catch{ + return nil + } + } + return nil + } + +} diff --git a/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/SecureKeyProtocol.swift b/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/SecureKeyProtocol.swift new file mode 100644 index 0000000..a49ff86 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/SecureKeyProtocol.swift @@ -0,0 +1,24 @@ +// +// File.swift +// +// +// Created by Arun Raj on 27/06/24. +// + +import Foundation + +public struct SecureKeyData{ + public var publicKey: Data + public var privateKey: Data? + + public init(publicKey: Data, privateKey: Data? = nil) { + self.publicKey = publicKey + self.privateKey = privateKey + } +} + +public protocol SecureKeyProtocol: NSObjectProtocol{ + func generateSecureKey() -> SecureKeyData? + func sign(data: Data, withKey privateKey: Data?) -> Data? + +} diff --git a/Sources/eudiWalletOidcIos/Service/DidService.swift b/Sources/eudiWalletOidcIos/Service/DidService.swift index 2f53bb4..8e2ea60 100644 --- a/Sources/eudiWalletOidcIos/Service/DidService.swift +++ b/Sources/eudiWalletOidcIos/Service/DidService.swift @@ -52,23 +52,22 @@ public class DidService { } // MARK: - Exposed method to create a JSON Web Key (JWK) asynchronously. - /// + /// - Parameter keyHandler: A handler to encryption key generation class /// - Returns: A dictionary representing the JWK, or nil if an error occurs. - public func createJWK() async -> ([String: Any], P256.Signing.PrivateKey)?{ - let privateKey = P256.Signing.PrivateKey() - // Step 1: Create P-256 public and private key pair - let publicKey = privateKey.publicKey + public func createJWK(keyHandler: SecureKeyProtocol) async -> ([String: Any], SecureKeyData)?{ - // Step 2: Export public key JWK - let rawRepresentation = publicKey.rawRepresentation - let x = rawRepresentation[rawRepresentation.startIndex.. [String: Any]? + func createJWK(keyHandler: SecureKeyProtocol) async -> ([String: Any], SecureKeyData)? } diff --git a/Sources/eudiWalletOidcIos/Service/IssueService.swift b/Sources/eudiWalletOidcIos/Service/IssueService.swift index 142ac8a..e8a9354 100644 --- a/Sources/eudiWalletOidcIos/Service/IssueService.swift +++ b/Sources/eudiWalletOidcIos/Service/IssueService.swift @@ -10,13 +10,21 @@ import CryptoKit //import KeychainSwift import CryptoSwift -public class IssueService: NSObject { - - public static var shared = IssueService() +public class IssueService: NSObject, IssueServiceProtocol { + var session: URLSession? - private override init() { + var keyHandler: SecureKeyProtocol! + + // MARK: - A custom initialiser with dependency injection for encryption key generation handler + /// + /// - Parameters: + /// - keyhandler: A handler to encryption key generation class + /// - Returns: An `IssueService` object + + public init(keyHandler: SecureKeyProtocol) { super.init() session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) + self.keyHandler = keyHandler } // MARK: - Retrieves credential issuer asynchronously based on the provided credential_offer / credential_offer_uri. @@ -24,7 +32,7 @@ public class IssueService: NSObject { /// - Parameters: /// - credentialOffer: The string representation of the credential offer. /// - Returns: A `CredentialOffer` object if the resolution is successful; otherwise, `nil`. - public func resolveCredentialOffer(credentialOfferString: String) async throws -> CredentialOffer? { + public func resolveCredentialOffer(credentialOffer credentialOfferString: String) async throws -> CredentialOffer? { let credentialOfferUrl = URL(string: credentialOfferString) guard let credentialOfferUri = credentialOfferUrl?.queryParameters?["credential_offer_uri"] else { return nil } let jsonDecoder = JSONDecoder() @@ -65,12 +73,13 @@ public class IssueService: NSObject { // MARK: - To process the authorisation request, The authorisation request is to grant access to the credential endpoint. /// - Parameters: /// - did - DID created for the issuance + /// - secureKey: A wrapper object containing the public and private encryption keys /// - credentialOffer: The credential offer containing the necessary details for authorization. - /// - authServer: The authorization server configuration. /// - codeVerifier - to build the authorisation request + /// - authServer: The authorization server configuration. /// - Returns: code if successful; otherwise, nil. public func processAuthorisationRequest(did: String, - privateKey: P256.Signing.PrivateKey, + secureKey: SecureKeyData, credentialOffer: CredentialOffer, codeVerifier: String, authServer: AuthorisationServerWellKnownConfiguration) async -> String? { @@ -173,7 +182,7 @@ public class IssueService: NSObject { let code = await processAuthorisationRequestUsingIdToken( did: did, - privateKey: privateKey, + secureKey: secureKey, authServerWellKnownConfig: authServer, redirectURI: redirectUri ?? "", nonce: nonce ?? "", @@ -185,7 +194,7 @@ public class IssueService: NSObject { private func processAuthorisationRequestUsingIdToken( did: String, - privateKey: P256.Signing.PrivateKey, + secureKey: SecureKeyData, authServerWellKnownConfig: AuthorisationServerWellKnownConfiguration, redirectURI: String, nonce: String, @@ -217,8 +226,8 @@ public class IssueService: NSObject { let headerData = Data(header.utf8) let payloadData = Data(payload.utf8) let unsignedToken = "\(headerData.base64URLEncodedString()).\(payloadData.base64URLEncodedString())" - let signatureData = try! privateKey.signature(for: unsignedToken.data(using: .utf8)!) - let signature = signatureData.rawRepresentation + + guard let signature = keyHandler.sign(data: unsignedToken.data(using: .utf8)!, withKey: secureKey.privateKey) else{return nil} let idToken = "\(unsignedToken).\(signature.base64URLEncodedString())" guard let urlComponents = URLComponents(string: redirectURI) else { return nil } @@ -288,7 +297,6 @@ public class IssueService: NSObject { public func processTokenRequest(authServerWellKnownConfig: AuthorisationServerWellKnownConfiguration, code: String, did: String, - privateKey: P256.Signing.PrivateKey, codeVerifier: String, isPreAuthorisedCodeFlow: Bool = false, preAuthCode: String, @@ -297,7 +305,7 @@ public class IssueService: NSObject { let codeVal = code.removingPercentEncoding ?? "" // Service call for access token and details if userPin == nil || userPin == "" { - let tokenResponse = await getAccessToken(didKeyIdentifier: did, codeVerifier: codeVerifier, authCode: codeVal, privateKey: privateKey, tokenEndpoint: authServerWellKnownConfig.tokenEndpoint ?? "") + let tokenResponse = await getAccessToken(didKeyIdentifier: did, codeVerifier: codeVerifier, authCode: codeVal, tokenEndpoint: authServerWellKnownConfig.tokenEndpoint ?? "") return tokenResponse } else { // Service call for access token and details @@ -310,6 +318,7 @@ public class IssueService: NSObject { /** - Parameters - did: The identifier for the DID key. + - secureKey: A wrapper object containing the public and private encryption keys - credentialOffer: The credential offer object containing offer details. - credentialEndpointUrlString: The URL string of the credential endpoint. - c_nonce: The nonce value for the credential request. @@ -319,7 +328,7 @@ public class IssueService: NSObject { */ public func processCredentialRequest( did: String, - privateKey: P256.Signing.PrivateKey, + secureKey: SecureKeyData, nonce: String, credentialOffer: CredentialOffer, issuerConfig: IssuerWellKnownConfiguration, @@ -357,8 +366,8 @@ public class IssueService: NSObject { let headerData = Data(header.utf8) let payloadData = Data(payload.utf8) let unsignedToken = "\(headerData.base64URLEncodedString()).\(payloadData.base64URLEncodedString())" - let signatureData = try! privateKey.signature(for: unsignedToken.data(using: .utf8)!) - let signature = signatureData.rawRepresentation + // sign the data to be encrypted and exchanged + guard let signature = keyHandler.sign(data: unsignedToken.data(using: .utf8)!, withKey: secureKey.privateKey) else{return nil} let idToken = "\(unsignedToken).\(signature.base64URLEncodedString())" let credentialTypes = credentialOffer.credentials?[0].types ?? [] @@ -513,7 +522,6 @@ public class IssueService: NSObject { didKeyIdentifier: String, codeVerifier: String, authCode: String, - privateKey: P256.Signing.PrivateKey, tokenEndpoint: String) async -> TokenResponse? { let jsonDecoder = JSONDecoder() diff --git a/Sources/eudiWalletOidcIos/Service/IssueServiceProtocol.swift b/Sources/eudiWalletOidcIos/Service/IssueServiceProtocol.swift index 1899283..66a4fb4 100644 --- a/Sources/eudiWalletOidcIos/Service/IssueServiceProtocol.swift +++ b/Sources/eudiWalletOidcIos/Service/IssueServiceProtocol.swift @@ -20,11 +20,12 @@ protocol IssueServiceProtocol { // To process the authorisation request, The authorisation request is to grant access to the credential endpoint. /// - Parameters: /// - did - DID created for the issuance + /// - secureKey: A wrapper object containing the public and private encryption keys /// - credentialOffer: The credential offer containing the necessary details for authorization. /// - authServer: The authorization server configuration. /// - codeVerifier - to build the authorisation request /// - Returns: code if successful; otherwise, nil. - func processAuthorisationRequest(did: String, privateKey: P256.Signing.PrivateKey, credentialOffer: CredentialOffer, codeVerifier: String, authServer: AuthorisationServerWellKnownConfiguration) async -> String? + func processAuthorisationRequest(did: String, secureKey: SecureKeyData, credentialOffer: CredentialOffer, codeVerifier: String, authServer: AuthorisationServerWellKnownConfiguration) async -> String? // Processes the token request to obtain the access token. /** - Parameters @@ -32,7 +33,6 @@ protocol IssueServiceProtocol { - code: If the credential offer is pre authorised, then use the pre authorised code from the credential offer else use the code from the previous function - processAuthorisationRequest - did: The identifier for the DID key. - - privateKey: - codeverifier: - isPreAuthorisedCodeFlow: A boolean indicating if it's a pre-authorized code flow. - preAuthCode: The pre-authorization code for the token request. @@ -40,13 +40,14 @@ protocol IssueServiceProtocol { - Returns: A `TokenResponse` object if the request is successful, otherwise `nil`. */ - func processTokenRequest(authServerWellKnownConfig: AuthorisationServerWellKnownConfiguration, code: String, did: String, privateKey: P256.Signing.PrivateKey, codeVerifier: String, isPreAuthorisedCodeFlow: Bool, preAuthCode: String, userPin: String?) async -> TokenResponse? + func processTokenRequest(authServerWellKnownConfig: AuthorisationServerWellKnownConfiguration, code: String, did: String, codeVerifier: String, isPreAuthorisedCodeFlow: Bool, preAuthCode: String, userPin: String?) async -> TokenResponse? // Processes a credential request to the specified credential endpoint. /** - Parameters - did: The identifier for the DID key. + - secureKey: A wrapper object containing the public and private encryption keys - credentialOffer: The credential offer object containing offer details. - credentialEndpointUrlString: The URL string of the credential endpoint. - c_nonce: The nonce value for the credential request. @@ -54,7 +55,7 @@ protocol IssueServiceProtocol { - Returns: A `CredentialResponse` object if the request is successful, otherwise `nil`. */ - func processCredentialRequest(did: String, privateKey: P256.Signing.PrivateKey, nonce: String, credentialOffer: CredentialOffer, issuerConfig: IssuerWellKnownConfiguration, accessToken: String, format: String) async -> CredentialResponse? + func processCredentialRequest(did: String, secureKey: SecureKeyData, nonce: String, credentialOffer: CredentialOffer, issuerConfig: IssuerWellKnownConfiguration, accessToken: String, format: String) async -> CredentialResponse? // Processes a deferred credential request to obtain the credential response in deffered manner. @@ -74,4 +75,5 @@ protocol IssueServiceProtocol { func getTypesFromIssuerConfig(issuerConfig: IssuerWellKnownConfiguration?, type: String?) -> Any? func getCryptoFromIssuerConfig(issuerConfig: IssuerWellKnownConfiguration?, type: String?) -> [String]? + } diff --git a/Sources/eudiWalletOidcIos/Service/SDJWTService.swift b/Sources/eudiWalletOidcIos/Service/SDJWTService.swift index e0dcba4..f31379d 100644 --- a/Sources/eudiWalletOidcIos/Service/SDJWTService.swift +++ b/Sources/eudiWalletOidcIos/Service/SDJWTService.swift @@ -50,7 +50,7 @@ public class SDJWTService { let processedCredentialWithRequiredDisclosures = try processDisclosuresWithPresentationDefinition( credential: credential, - presentationDefinition: VerificationService.shared.processPresentationDefinition(presentationRequest.presentationDefinition) + presentationDefinition: VerificationService.processPresentationDefinition(presentationRequest.presentationDefinition) ) // let iat = Date() diff --git a/Sources/eudiWalletOidcIos/Service/VerificationService.swift b/Sources/eudiWalletOidcIos/Service/VerificationService.swift index baf43f6..9e5360e 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationService.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationService.swift @@ -9,17 +9,25 @@ import CryptoKit import PresentationExchangeSdkiOS public class VerificationService: VerificationServiceProtocol { - public static var shared = VerificationService() - private init() {} + var keyHandler: SecureKeyProtocol + + // MARK: - A custom initialiser with dependency injection for encryption key generation handler + /// + /// - Parameters: + /// - keyhandler: A handler to encryption key generation class + /// - Returns: An `VerificationService` object + public required init(keyhandler: SecureKeyProtocol) { + keyHandler = keyhandler + } // MARK: - Sends a Verifiable Presentation (VP) token asynchronously. public func sendVPToken( did: String, - privateKey: P256.Signing.PrivateKey, + secureKey: SecureKeyData, presentationRequest: PresentationRequest?, credentialsList: [String]?) async -> Data? { - let jwk = generateJWKFromPrivateKey(privateKey: privateKey, did: did) + let jwk = generateJWKFromPrivateKey(secureKey: secureKey, did: did) // Generate JWT header let header = generateJWTHeader(jwk: jwk, did: did) @@ -28,7 +36,7 @@ public class VerificationService: VerificationServiceProtocol { let payload = generateJWTPayload(did: did, nonce: presentationRequest?.nonce ?? "", credentialsList: credentialsList ?? [], state: presentationRequest?.state ?? "", clientID: presentationRequest?.clientId ?? "") debugPrint("payload:\(payload)") - let vpToken = generateVPToken(header: header, payload: payload, privateKey: privateKey) + let vpToken = generateVPToken(header: header, payload: payload, secureKey: secureKey) // Presentation Submission model guard let presentationSubmission = preparePresentationSubmission() else { return nil } @@ -36,8 +44,8 @@ public class VerificationService: VerificationServiceProtocol { return await sendVPRequest(vpToken: vpToken, presentationSubmission: presentationSubmission, redirectURI: presentationRequest?.redirectUri ?? "", state: presentationRequest?.state ?? "") } - private func generateJWKFromPrivateKey(privateKey: P256.Signing.PrivateKey, did: String) -> [String: Any] { - let rawRepresentation = privateKey.publicKey.rawRepresentation + private func generateJWKFromPrivateKey(secureKey: SecureKeyData, did: String) -> [String: Any] { + let rawRepresentation = secureKey.publicKey let x = rawRepresentation[rawRepresentation.startIndex.. String { + private func generateVPToken(header: String, payload: String, secureKey: SecureKeyData) -> String { let headerData = Data(header.utf8) let payloadData = Data(payload.utf8) let unsignedToken = "\(headerData.base64URLEncodedString()).\(payloadData.base64URLEncodedString())" - let signatureData = try? privateKey.signature(for: unsignedToken.data(using: .utf8)!) - let signature = signatureData?.rawRepresentation - return "\(unsignedToken).\(signature?.base64URLEncodedString() ?? "")" + //let signatureData = try? privateKey.signature(for: unsignedToken.data(using: .utf8)!) + //let signature = signatureData?.rawRepresentation + guard let signature = keyHandler.sign(data: unsignedToken.data(using: .utf8)!, withKey: secureKey.privateKey) else{return ""} + return "\(unsignedToken).\(signature.base64URLEncodedString() ?? "")" } private func preparePresentationSubmission() -> PresentationSubmissionModel? { @@ -223,7 +232,7 @@ public class VerificationService: VerificationServiceProtocol { } - public func processPresentationDefinition(_ presentationDefinition: Any?) throws -> PresentationDefinitionModel { + public static func processPresentationDefinition(_ presentationDefinition: Any?) throws -> PresentationDefinitionModel { do { guard let presentationDefinition = presentationDefinition else { throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid presentation definition"]) diff --git a/Sources/eudiWalletOidcIos/Service/VerificationServiceProtocol.swift b/Sources/eudiWalletOidcIos/Service/VerificationServiceProtocol.swift index 57dfa19..fa673de 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationServiceProtocol.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationServiceProtocol.swift @@ -13,6 +13,7 @@ protocol VerificationServiceProtocol { Sends a verifiable presentation token (VP token) asynchronously. - Parameters: - did: The decentralized identifier (DID) of the entity issuing the token. + - secureKey: A wrapper object containing the public and private encryption keys - privateKey: The private key used for signing the token. - presentationRequest: The presentation request containing the details of the requested presentation, or nil if not applicable. - credentialsList: The list of credentials to be included in the token, or nil if not applicable. @@ -20,7 +21,7 @@ protocol VerificationServiceProtocol { */ func sendVPToken( did: String, - privateKey: P256.Signing.PrivateKey, + secureKey: SecureKeyData, presentationRequest: PresentationRequest?, credentialsList: [String]? ) async -> Data? @@ -38,7 +39,7 @@ protocol VerificationServiceProtocol { - Throws: An error if the presentation definition data is invalid or cannot be processed. - Returns: The PresentationDefinitionModel object representing the presentation definition. */ - func processPresentationDefinition(_ presentationDefinition: Any?) throws -> PresentationDefinitionModel + static func processPresentationDefinition(_ presentationDefinition: Any?) throws -> PresentationDefinitionModel /** Filters the provided list of credentials based on the given presentation definition. From 80edda91fab0c030e6319bb41b60fb170c038285 Mon Sep 17 00:00:00 2001 From: Joseph Milan Date: Fri, 5 Jul 2024 16:34:24 +0530 Subject: [PATCH 18/33] Fix: Error handling in verification Send VP token --- .../Helper/ErrorHandler.swift | 49 ++++++++++++ .../Model/WrappedVerificationResponse.swift | 18 +++++ .../Service/VerificationService.swift | 78 ++++++++++++------- .../Service/VerificationServiceProtocol.swift | 2 +- 4 files changed, 116 insertions(+), 31 deletions(-) create mode 100644 Sources/eudiWalletOidcIos/Helper/ErrorHandler.swift create mode 100644 Sources/eudiWalletOidcIos/Model/WrappedVerificationResponse.swift diff --git a/Sources/eudiWalletOidcIos/Helper/ErrorHandler.swift b/Sources/eudiWalletOidcIos/Helper/ErrorHandler.swift new file mode 100644 index 0000000..7a70959 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Helper/ErrorHandler.swift @@ -0,0 +1,49 @@ +// +// File.swift +// +// +// Created by oem on 05/07/24. +// + +import Foundation + +class ErrorHandler { + + static func processError(data: Data?) -> EUDIError? { + // Convert Data to String for initial check + guard let data = data, let dataString = String(data: data, encoding: .utf8) else { + return nil + } + + // Attempt to parse the data string as a JSON object + let jsonObject: [String: Any]? + do { + jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] + } catch { + jsonObject = nil + } + + // Determine the error response based on the content of the error message + let errorResponse: EUDIError? + if dataString.contains("Invalid Proof JWT: iss doesn't match the expected client_id") { + errorResponse = EUDIError(from: ErrorResponse(message:"DID is invalid", code: 1)) + } else if let jsonObject = jsonObject { + if let errorDescription = jsonObject["error_description"] as? String { + errorResponse = EUDIError(from: ErrorResponse(message:errorDescription, code: -1)) + } else if let errors = jsonObject["errors"] as? [[String: Any]], + let firstError = errors.first, + let message = firstError["message"] as? String { + errorResponse = EUDIError(from: ErrorResponse(message:message, code: -1)) + } else if let error = jsonObject["error"] as? String { + errorResponse = EUDIError(from: ErrorResponse(message:error, code: -1)) + } else if let error = jsonObject["detail"] as? String { + errorResponse = EUDIError(from: ErrorResponse(message:error, code: -1)) + } else { + errorResponse = nil + } + } else { + errorResponse = nil + } + return errorResponse + } +} diff --git a/Sources/eudiWalletOidcIos/Model/WrappedVerificationResponse.swift b/Sources/eudiWalletOidcIos/Model/WrappedVerificationResponse.swift new file mode 100644 index 0000000..d756fa8 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Model/WrappedVerificationResponse.swift @@ -0,0 +1,18 @@ +// +// File.swift +// +// +// Created by Milan on 05/07/24. +// + +import Foundation + +public struct WrappedVerificationResponse { + public var data: String? + public var error: EUDIError? + + enum CodingKeys: String, CodingKey { + case data = "data" + case error = "error" + } +} diff --git a/Sources/eudiWalletOidcIos/Service/VerificationService.swift b/Sources/eudiWalletOidcIos/Service/VerificationService.swift index 9e5360e..10192d4 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationService.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationService.swift @@ -7,7 +7,7 @@ import Foundation import CryptoKit import PresentationExchangeSdkiOS -public class VerificationService: VerificationServiceProtocol { +public class VerificationService: NSObject, VerificationServiceProtocol { var keyHandler: SecureKeyProtocol @@ -25,7 +25,7 @@ public class VerificationService: VerificationServiceProtocol { did: String, secureKey: SecureKeyData, presentationRequest: PresentationRequest?, - credentialsList: [String]?) async -> Data? { + credentialsList: [String]?) async -> WrappedVerificationResponse? { let jwk = generateJWKFromPrivateKey(secureKey: secureKey, did: did) @@ -39,7 +39,7 @@ public class VerificationService: VerificationServiceProtocol { let vpToken = generateVPToken(header: header, payload: payload, secureKey: secureKey) // Presentation Submission model - guard let presentationSubmission = preparePresentationSubmission() else { return nil } + guard let presentationSubmission = preparePresentationSubmission(presentationRequest: presentationRequest) else { return nil } return await sendVPRequest(vpToken: vpToken, presentationSubmission: presentationSubmission, redirectURI: presentationRequest?.redirectUri ?? "", state: presentationRequest?.state ?? "") } @@ -173,33 +173,31 @@ public class VerificationService: VerificationServiceProtocol { return "\(unsignedToken).\(signature.base64URLEncodedString() ?? "")" } - private func preparePresentationSubmission() -> PresentationSubmissionModel? { -// if !isVPExchange && !isPassportExchange { -// var descMaps = [DescriptorMap]() -// for i in 0..<(presentationDefinitionModel?.inputDescriptors?.count ?? 1) { -// descMaps.append(DescriptorMap(id: presentationDefinitionModel?.inputDescriptors?[i].id ?? "", path: "$", format: "jwt_vp", pathNested: DescriptorMap(id: presentationDefinitionModel?.inputDescriptors?[i].id ?? "", path: "$.verifiableCredential[\(i)]", format: "jwt_vc", pathNested: nil))) -// } -// return PresentationSubmissionModel(id: "a30e3b91-fb77-4d22-95fa-871689c322e2", definitionID: "holder-wallet-qualification-presentation", descriptorMap: descMaps) -// } else if isPassportExchange { -// let uuid = UUID().uuidString -// let component = nonce -// let dict = UIApplicationUtils.shared.convertToDictionary(text: component) -// guard let data = try? JSONSerialization.data(withJSONObject: dict ?? [:]) else { return nil } -// let elements = try? JSONDecoder().decode(PresentationDefinitionModel.self, from: data) -// -// let pathNested = DescriptorMap(id: elements?.inputDescriptors?[0].id ?? "", path: "$.verifiableCredential[0]", format: "jwt_vc", pathNested: nil) -// let descMap = [ -// DescriptorMap(id: elements?.inputDescriptors?[0].id ?? "", path: "$", format: "jwt_vp", pathNested: pathNested)] -// return PresentationSubmissionModel(id: uuid, definitionID: elements?.id ?? "", descriptorMap: descMap) -// } else { - let pathNested = DescriptorMap(id: "vp1", path: "$.verifiableCredential[0]", format: "jwt_vc", pathNested: nil) - let descMap = [ - DescriptorMap(id: "vp1", path: "$", format: "jwt_vp", pathNested: pathNested)] - return PresentationSubmissionModel(id: "essppda1", definitionID: "essppda1", descriptorMap: descMap) - // } + private func preparePresentationSubmission( + presentationRequest: PresentationRequest? + ) -> PresentationSubmissionModel? { + if presentationRequest == nil { return nil } + var descMap : [DescriptorMap] = [] + var presentationDefinition :PresentationDefinitionModel? = nil + do { + presentationDefinition = try VerificationService.processPresentationDefinition(presentationRequest?.presentationDefinition) + } catch { + presentationDefinition = nil + } + if let inputDescriptors = presentationDefinition?.inputDescriptors { + for index in 0.. Data? { + private func sendVPRequest(vpToken: String, presentationSubmission: PresentationSubmissionModel, redirectURI: String, state: String) async -> WrappedVerificationResponse? { let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase let data = try? encoder.encode(presentationSubmission) @@ -222,9 +220,22 @@ public class VerificationService: VerificationServiceProtocol { request.httpBody = paramsData // Performing the token request + var responseUrl = "" do { - let (data, _) = try await URLSession.shared.data(for: request) - return data + let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) + + let (data, response) = try await session.data(for: request) + + + let httpres = response as? HTTPURLResponse + if httpres?.statusCode == 302, let location = httpres?.value(forHTTPHeaderField: "Location"){ + responseUrl = location + return WrappedVerificationResponse(data: responseUrl, error: ErrorHandler.processError(data: data)) + } else if httpres?.statusCode ?? 400 >= 400 { + return WrappedVerificationResponse(data: nil, error: ErrorHandler.processError(data: data)) + } else{ + return WrappedVerificationResponse(data: "data", error: nil) + } } catch { debugPrint("JSON Serialization Error: \(error)") return nil @@ -357,3 +368,10 @@ func updatePath(in descriptor: InputDescriptor) -> InputDescriptor { return updatedDescriptor } } + +extension VerificationService: URLSessionDelegate, URLSessionTaskDelegate { + public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { + // Stops the redirection, and returns (internally) the response body. + completionHandler(nil) + } + } diff --git a/Sources/eudiWalletOidcIos/Service/VerificationServiceProtocol.swift b/Sources/eudiWalletOidcIos/Service/VerificationServiceProtocol.swift index fa673de..88234f4 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationServiceProtocol.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationServiceProtocol.swift @@ -24,7 +24,7 @@ protocol VerificationServiceProtocol { secureKey: SecureKeyData, presentationRequest: PresentationRequest?, credentialsList: [String]? - ) async -> Data? + ) async -> WrappedVerificationResponse? /** Processes an authorization request and extracts a PresentationRequest object asynchronously. From ba826f0a8b6b2cad027be2d73f0d4b40ed129d93 Mon Sep 17 00:00:00 2001 From: Joseph Milan Date: Mon, 8 Jul 2024 23:28:48 +0530 Subject: [PATCH 19/33] Fix: Added constructor in Issuer config --- .../Model/IssuerWellKnownConfiguration.swift | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift index e02825f..97c1a58 100644 --- a/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift +++ b/Sources/eudiWalletOidcIos/Model/IssuerWellKnownConfiguration.swift @@ -26,6 +26,17 @@ public struct Display{ backgroundColor = from.backgroundColor textColor = from.textColor } + + public init(mName: String, mLocation:String, mLocale: String?, mDescription: String, mCover: DisplayCover?, mLogo: DisplayCover?, mBackgroundColor: String?, mTextColor: String?) { + name = mName + location = mLocation + locale = mLocale + description = mDescription + cover = mCover + logo = mLogo + backgroundColor = mBackgroundColor + textColor = mTextColor + } } public struct TrustFrameworkInIssuer { public let name: String? @@ -157,6 +168,11 @@ public struct DisplayCover{ url = from.url altText = from.altText } + + public init(mUrl: String?, mAltText: String?) { + url = mUrl + altText = mAltText + } } @@ -194,6 +210,20 @@ public struct IssuerWellKnownConfiguration { } + public init(mCredentialIssuer: String?, + mAuthorizationServer: String?, + mCredentialEndpoint: String?, + mDeferredCredentialEndpoint: String?, + mDisplay: Display?) { + credentialIssuer = nil + authorizationServer = nil + credentialEndpoint = nil + deferredCredentialEndpoint = nil + display = mDisplay != nil ? [mDisplay!] : nil + credentialsSupported = nil + error = nil + } + init(from: EUDIError) { error = from credentialIssuer = nil @@ -203,7 +233,5 @@ public struct IssuerWellKnownConfiguration { display = nil credentialsSupported = nil } - - } From 51a6e706e73531d04936523d6f5f2ae4f2ff5eb3 Mon Sep 17 00:00:00 2001 From: Joseph Milan Date: Wed, 10 Jul 2024 18:19:27 +0530 Subject: [PATCH 20/33] Fix: Handle error when response is returned as string instead or json --- Sources/eudiWalletOidcIos/Helper/ErrorHandler.swift | 8 ++++---- .../eudiWalletOidcIos/Service/VerificationService.swift | 7 +++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Sources/eudiWalletOidcIos/Helper/ErrorHandler.swift b/Sources/eudiWalletOidcIos/Helper/ErrorHandler.swift index 7a70959..3844aef 100644 --- a/Sources/eudiWalletOidcIos/Helper/ErrorHandler.swift +++ b/Sources/eudiWalletOidcIos/Helper/ErrorHandler.swift @@ -12,7 +12,7 @@ class ErrorHandler { static func processError(data: Data?) -> EUDIError? { // Convert Data to String for initial check guard let data = data, let dataString = String(data: data, encoding: .utf8) else { - return nil + return EUDIError(from: ErrorResponse(message:"Unexpected error. Please try again.", code: -1)) } // Attempt to parse the data string as a JSON object @@ -20,7 +20,7 @@ class ErrorHandler { do { jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] } catch { - jsonObject = nil + return EUDIError(from: ErrorResponse(message: dataString, code: -1)) } // Determine the error response based on the content of the error message @@ -39,10 +39,10 @@ class ErrorHandler { } else if let error = jsonObject["detail"] as? String { errorResponse = EUDIError(from: ErrorResponse(message:error, code: -1)) } else { - errorResponse = nil + errorResponse = EUDIError(from: ErrorResponse(message:"Unexpected error. Please try again.", code: -1)) } } else { - errorResponse = nil + errorResponse = EUDIError(from: ErrorResponse(message:"Unexpected error. Please try again.", code: -1)) } return errorResponse } diff --git a/Sources/eudiWalletOidcIos/Service/VerificationService.swift b/Sources/eudiWalletOidcIos/Service/VerificationService.swift index 10192d4..0d6cf37 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationService.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationService.swift @@ -230,11 +230,14 @@ public class VerificationService: NSObject, VerificationServiceProtocol { let httpres = response as? HTTPURLResponse if httpres?.statusCode == 302, let location = httpres?.value(forHTTPHeaderField: "Location"){ responseUrl = location - return WrappedVerificationResponse(data: responseUrl, error: ErrorHandler.processError(data: data)) + return WrappedVerificationResponse(data: responseUrl, error: nil) } else if httpres?.statusCode ?? 400 >= 400 { return WrappedVerificationResponse(data: nil, error: ErrorHandler.processError(data: data)) } else{ - return WrappedVerificationResponse(data: "data", error: nil) + guard let dataString = String(data: data, encoding: .utf8) else { + return WrappedVerificationResponse(data: "data", error: nil) + } + return WrappedVerificationResponse(data: dataString, error: nil) } } catch { debugPrint("JSON Serialization Error: \(error)") From 711c3aa2aedce111cba4f864f1b2ca2ae0d69f53 Mon Sep 17 00:00:00 2001 From: ArunRajasekhar84 Date: Mon, 15 Jul 2024 12:59:30 +0530 Subject: [PATCH 21/33] support secure enclave * Changes for encryption key management Segregated the code for encryption key generation and data signing to separate class to enable scalability to other encryption key generation and storage techniques. * Changing the interface descriptions Changed the interface descriptions to match the new changes related to key generation handler * Cleaning up spare code Removing test code for secure enclave * Changes related to secure enclave integratio * Changes for secure enclave - Fixed issue with creating jwk - code cleanup and adding necessary comments --- Package.swift | 5 +- .../Crypto Kit/CryptoKitHandler.swift | 18 ++- .../Secure Enclave/SecureEnclaveHandler.swift | 95 +++++++++++++ .../Secure Enclave/SecureEnclaveHelper.swift | 132 ++++++++++++++++++ .../SecureKeyProtocol.swift | 24 +++- .../Service/DidService.swift | 38 +++-- .../Service/IssueService.swift | 26 ++-- .../Service/VerificationService.swift | 14 +- 8 files changed, 317 insertions(+), 35 deletions(-) create mode 100644 Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/Secure Enclave/SecureEnclaveHandler.swift create mode 100644 Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/Secure Enclave/SecureEnclaveHelper.swift diff --git a/Package.swift b/Package.swift index 85b6a7b..d4895ef 100644 --- a/Package.swift +++ b/Package.swift @@ -16,12 +16,13 @@ let package = Package( dependencies: [ .package(url: "https://github.com/keefertaylor/Base58Swift.git", branch: "master"), .package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "1.8.1"), - .package(url: "https://github.com/decentralised-dataexchange/PresentationExchangeSdkiOS.git", .upToNextMajor(from: "2024.3.1")) + .package(url: "https://github.com/decentralised-dataexchange/PresentationExchangeSdkiOS.git", .upToNextMajor(from: "2024.3.1")), + .package(url: "https://github.com/airsidemobile/JOSESwift.git", from: "2.3.0") ], targets: [ .target( name: "eudiWalletOidcIos", - dependencies: ["Base58Swift", "CryptoSwift", "PresentationExchangeSdkiOS"]), + dependencies: ["Base58Swift", "CryptoSwift", "PresentationExchangeSdkiOS", "JOSESwift"]), .testTarget( name: "eudi-wallet-oidc-iosTests", dependencies: ["eudiWalletOidcIos"]), diff --git a/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/Crypto Kit/CryptoKitHandler.swift b/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/Crypto Kit/CryptoKitHandler.swift index 3b1de95..b079ac6 100644 --- a/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/Crypto Kit/CryptoKitHandler.swift +++ b/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/Crypto Kit/CryptoKitHandler.swift @@ -10,17 +10,27 @@ import CryptoKit public class CryptoKitHandler:NSObject, SecureKeyProtocol{ + public var keyStorageType: SecureKeyTypes = .cryptoKit + public func generateSecureKey() -> SecureKeyData?{ let privateKey = P256.Signing.PrivateKey() return SecureKeyData(publicKey: privateKey.publicKey.rawRepresentation, privateKey: privateKey.rawRepresentation) } - public func sign(data: Data, withKey privateKey: Data?) -> Data?{ + + public func sign(payload: String, header: Data, withKey privateKey: Data?) -> String?{ if let privateKeyData = privateKey{ do{ - let privateKey = try P256.Signing.PrivateKey(rawRepresentation: privateKeyData) - let signedData = try privateKey.signature(for: data) - return signedData.rawRepresentation + + let payloadData = Data(payload.utf8) + let unsignedToken = "\(header.base64URLEncodedString()).\(payloadData.base64URLEncodedString())" + if let data = unsignedToken.data(using: .utf8){ + let privateKey = try P256.Signing.PrivateKey(rawRepresentation: privateKeyData) + let signedData = try privateKey.signature(for: data) + let idToken = "\(unsignedToken).\(signedData.rawRepresentation.base64URLEncodedString())" + return idToken + } + } catch{ return nil diff --git a/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/Secure Enclave/SecureEnclaveHandler.swift b/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/Secure Enclave/SecureEnclaveHandler.swift new file mode 100644 index 0000000..023f3ba --- /dev/null +++ b/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/Secure Enclave/SecureEnclaveHandler.swift @@ -0,0 +1,95 @@ +// +// File.swift +// +// +// Created by Arun Raj on 27/06/24. +// + +import Foundation + +public class SecureEnclaveHandler: NSObject, SecureKeyProtocol{ + + var privateKeyLabel = "" + var publicKeyLabel = "" + var organisationID = "" + var secureEnclaveHandler: SecureEnclave? + public var keyStorageType: SecureKeyTypes = .cryptoKit + + public init(organisationID: String) { + super.init() + self.organisationID = organisationID + self.keyStorageType = .secureEnclave + } + + //Creating secure enclave instance with unique identifer for the keys + private func createSecureEnclaveHandlerFor() -> Bool{ + if !organisationID.isEmpty{ + secureEnclaveHandler = nil + privateKeyLabel = "com.EudiWallet.\(organisationID).PrivateKey" + secureEnclaveHandler = SecureEnclave(privateKeyApplicationTag: privateKeyLabel) + return true + } else{ + // invalid organisation id + return false + } + } + + //Generate private and public keys from secure enclave and pass the public key back + //private key is stored securely within secure enclave is not accessible directly + public func generateSecureKey() -> SecureKeyData?{ + if createSecureEnclaveHandlerFor(){ + do{ + let anyExistingKey = try SecureEnclave.loadKeyPair(with: privateKeyLabel) + + if let publicKeyData = convertSecKeyToData(key: anyExistingKey.publicKey){ + return SecureKeyData(publicKey: publicKeyData, privateKey: nil) + } + } + catch{ + + do{ + let newKeys = try SecureEnclave.generateKeyPair(with: privateKeyLabel) + if let publicKeyData = convertSecKeyToData(key: newKeys.publicKey){ + return SecureKeyData(publicKey: publicKeyData, privateKey: nil) + } + } + catch{ + print("Error retrieving the key") + return nil + } + + } + + } + return nil + } + + func convertSecKeyToData(key: SecKey) -> Data?{ + var error: Unmanaged? + guard let publicKeydata = SecKeyCopyExternalRepresentation(key, &error) as? Data else { + return nil + } + return publicKeydata + } + + public func sign(payload: String, header: Data, withKey privateKey: Data?) -> String?{ + if createSecureEnclaveHandlerFor(){ + do{ + if let signedData = try secureEnclaveHandler?.sign(payload, header: header){ + + return signedData + } + } + catch{ + return nil + } + } + return nil + } + + public func getJWK(publicKey: Data) -> [String:Any]?{ + let jwk = secureEnclaveHandler?.getJWK(publicKey: publicKey) + return jwk + } + +} diff --git a/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/Secure Enclave/SecureEnclaveHelper.swift b/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/Secure Enclave/SecureEnclaveHelper.swift new file mode 100644 index 0000000..5b3dac6 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/Secure Enclave/SecureEnclaveHelper.swift @@ -0,0 +1,132 @@ +//Class implementing the high level key generation and signing of data +//using JOSESwift library for secure enclave + +import Foundation +import Security +import JOSESwift + + +enum SecureEnclaveError: Error { + case couldNotLoadKeyPair + case couldNotGenerateKeyPair(description: String) + case couldNotCreateSigner + case couldNotCreateVerifier +} + +class SecureEnclave { + let keyPair: KeyPair + + init(privateKeyApplicationTag: String) { + do { + keyPair = try SecureEnclave.loadKeyPair(with: privateKeyApplicationTag) + } catch { + keyPair = try! SecureEnclave.generateKeyPair(with: privateKeyApplicationTag) + } + } + + //signing of data securely with the private key + func sign(_ message: String, header: Data) throws -> String { + if let headerParams = JWSHeader(header){ + let payload = Payload(message.data(using: .utf8)!) + + guard let signer = Signer(signingAlgorithm: .ES256, privateKey: keyPair.privateKey) else { + throw SecureEnclaveError.couldNotCreateSigner + } + + return try JWS(header: headerParams, payload: payload, signer: signer).compactSerializedString + }else{ + throw SecureEnclaveError.couldNotCreateSigner + } + } + + func verify(_ compactSerialization: String) throws -> Bool { + guard let verifier = Verifier(verifyingAlgorithm: .ES256, publicKey: keyPair.publicKey) else { + throw SecureEnclaveError.couldNotCreateVerifier + } + + let jws = try JWS(compactSerialization: compactSerialization) + + return jws.isValid(for: verifier) + } + +} + +extension SecureEnclave { + typealias KeyPair = (privateKey: SecKey, publicKey: SecKey) + + //Load the private and public keys if already available in secure enclave + static func loadKeyPair(with applicationTag: String) throws -> KeyPair { + let query: [String: Any] = [ + kSecClass as String: kSecClassKey, + kSecAttrApplicationTag as String: applicationTag, + kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, + kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave, + kSecReturnRef as String: true + ] + + var item: CFTypeRef? + let status = SecItemCopyMatching(query as CFDictionary, &item) + guard status == errSecSuccess else { + throw SecureEnclaveError.couldNotLoadKeyPair + } + + let privateKey = item as! SecKey + let publicKey = SecKeyCopyPublicKey(privateKey)! + + return (privateKey, publicKey) + } + + //generate new pair of public and private keys from secure enclave + static func generateKeyPair(with applicationTag: String) throws -> KeyPair { + let access = SecAccessControlCreateWithFlags( + kCFAllocatorDefault, + kSecAttrAccessibleWhenUnlockedThisDeviceOnly, + .privateKeyUsage, + nil + )! + + let attributes: [String: Any] = [ + kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, + kSecAttrKeySizeInBits as String: 256, + kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave, + kSecPrivateKeyAttrs as String: [ + kSecAttrIsPermanent as String: true, + kSecAttrApplicationTag as String: applicationTag, + kSecAttrAccessControl as String: access + ] + ] + + var error: Unmanaged? + guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else { + throw SecureEnclaveError.couldNotGenerateKeyPair( + description: error!.takeRetainedValue().localizedDescription + ) + } + + let publicKey = SecKeyCopyPublicKey(privateKey)! + + return (privateKey, publicKey) + } + + //create jason web key for the given public key + func getJWK(publicKey:Data) -> [String:Any]?{ + let jwk = try! ECPublicKey(publicKey: publicKey) + if let jsonData = jwk.jsonData(){ + if let jwkDict = convertToDictionary(data: jsonData){ + return jwkDict + } + } + return nil + } + + func convertToDictionary(data: Data) -> [String: Any]? { + + do { + return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] + } catch { + print(error.localizedDescription) + } + + return nil + } +} diff --git a/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/SecureKeyProtocol.swift b/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/SecureKeyProtocol.swift index a49ff86..4c3adfa 100644 --- a/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/SecureKeyProtocol.swift +++ b/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/SecureKeyProtocol.swift @@ -1,12 +1,18 @@ // -// File.swift -// -// +// SecureKeyProtocol.swift +// A protocol class to be implemented by the encrytion key generation classes +// to provide standardised api to apps // Created by Arun Raj on 27/06/24. // import Foundation +//Enum to identify the type of key generation class used like CryptoKitHandler, SecureEnclaveHandler +public enum SecureKeyTypes{ + case cryptoKit + case secureEnclave +} + public struct SecureKeyData{ public var publicKey: Data public var privateKey: Data? @@ -18,7 +24,13 @@ public struct SecureKeyData{ } public protocol SecureKeyProtocol: NSObjectProtocol{ - func generateSecureKey() -> SecureKeyData? - func sign(data: Data, withKey privateKey: Data?) -> Data? - + var keyStorageType: SecureKeyTypes { get set } //value for storing the key generation type used + func generateSecureKey() -> SecureKeyData? //for generating new private & public keys + func sign(payload: String, header: Data, withKey privateKey: Data?) -> String? //sign data + func getJWK(publicKey:Data) -> [String:Any]? //get the json web key for did generation +} + +extension SecureKeyProtocol{ + public func getJWK(publicKey:Data) -> [String:Any]? {return nil} + } diff --git a/Sources/eudiWalletOidcIos/Service/DidService.swift b/Sources/eudiWalletOidcIos/Service/DidService.swift index 8e2ea60..2f9c00b 100644 --- a/Sources/eudiWalletOidcIos/Service/DidService.swift +++ b/Sources/eudiWalletOidcIos/Service/DidService.swift @@ -57,17 +57,35 @@ public class DidService { public func createJWK(keyHandler: SecureKeyProtocol) async -> ([String: Any], SecureKeyData)?{ if let keys = keyHandler.generateSecureKey(){ - let rawRepresentation = keys.publicKey - let x = rawRepresentation[rawRepresentation.startIndex.. String { let headerData = Data(header.utf8) - let payloadData = Data(payload.utf8) - let unsignedToken = "\(headerData.base64URLEncodedString()).\(payloadData.base64URLEncodedString())" + + //let payloadData = Data(payload.utf8) + //let unsignedToken = "\(headerData.base64URLEncodedString()).\(payloadData.base64URLEncodedString())" //let signatureData = try? privateKey.signature(for: unsignedToken.data(using: .utf8)!) //let signature = signatureData?.rawRepresentation - guard let signature = keyHandler.sign(data: unsignedToken.data(using: .utf8)!, withKey: secureKey.privateKey) else{return ""} - return "\(unsignedToken).\(signature.base64URLEncodedString() ?? "")" + guard let idToken = keyHandler.sign(payload: payload, header: headerData, withKey: secureKey.privateKey) else{return ""} + //guard let signature = keyHandler.sign(data: unsignedToken.data(using: .utf8)!, withKey: secureKey.privateKey) else{return ""} + return idToken//"\(unsignedToken).\(signature.base64URLEncodedString() ?? "")" + } private func preparePresentationSubmission( @@ -222,6 +226,7 @@ public class VerificationService: NSObject, VerificationServiceProtocol { // Performing the token request var responseUrl = "" do { + let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) let (data, response) = try await session.data(for: request) @@ -239,6 +244,7 @@ public class VerificationService: NSObject, VerificationServiceProtocol { } return WrappedVerificationResponse(data: dataString, error: nil) } + } catch { debugPrint("JSON Serialization Error: \(error)") return nil From 4bc6e9888c29d1e83884f0d52bb0b7e384302975 Mon Sep 17 00:00:00 2001 From: Joseph Milan Date: Tue, 16 Jul 2024 12:48:43 +0530 Subject: [PATCH 22/33] Fix #27: Updated filtering function to support combination of JWT and SDJWT --- .../Service/VerificationService.swift | 207 ++++++++++-------- 1 file changed, 110 insertions(+), 97 deletions(-) diff --git a/Sources/eudiWalletOidcIos/Service/VerificationService.swift b/Sources/eudiWalletOidcIos/Service/VerificationService.swift index b99ee19..bc1577b 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationService.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationService.swift @@ -26,24 +26,24 @@ public class VerificationService: NSObject, VerificationServiceProtocol { secureKey: SecureKeyData, presentationRequest: PresentationRequest?, credentialsList: [String]?) async -> WrappedVerificationResponse? { - - let jwk = generateJWKFromPrivateKey(secureKey: secureKey, did: did) - - // Generate JWT header - let header = generateJWTHeader(jwk: jwk, did: did) - - // Generate JWT payload - let payload = generateJWTPayload(did: did, nonce: presentationRequest?.nonce ?? "", credentialsList: credentialsList ?? [], state: presentationRequest?.state ?? "", clientID: presentationRequest?.clientId ?? "") - debugPrint("payload:\(payload)") - + + let jwk = generateJWKFromPrivateKey(secureKey: secureKey, did: did) + + // Generate JWT header + let header = generateJWTHeader(jwk: jwk, did: did) + + // Generate JWT payload + let payload = generateJWTPayload(did: did, nonce: presentationRequest?.nonce ?? "", credentialsList: credentialsList ?? [], state: presentationRequest?.state ?? "", clientID: presentationRequest?.clientId ?? "") + debugPrint("payload:\(payload)") + let vpToken = generateVPToken(header: header, payload: payload, secureKey: secureKey) - - // Presentation Submission model + + // Presentation Submission model guard let presentationSubmission = preparePresentationSubmission(presentationRequest: presentationRequest) else { return nil } - - guard let redirectURL = presentationRequest?.redirectUri else {return nil} - return await sendVPRequest(vpToken: vpToken, presentationSubmission: presentationSubmission, redirectURI: presentationRequest?.redirectUri ?? "", state: presentationRequest?.state ?? "") - } + + guard let redirectURL = presentationRequest?.redirectUri else {return nil} + return await sendVPRequest(vpToken: vpToken, presentationSubmission: presentationSubmission, redirectURI: presentationRequest?.redirectUri ?? "", state: presentationRequest?.state ?? "") + } private func generateJWKFromPrivateKey(secureKey: SecureKeyData, did: String) -> [String: Any] { let rawRepresentation = secureKey.publicKey @@ -74,19 +74,19 @@ public class VerificationService: NSObject, VerificationServiceProtocol { var presentationDefinition = URL(string: code)?.queryParameters?["presentation_definition"] ?? "" if presentationDefinition != "" { - - let presentationRequest = PresentationRequest(state: state, - clientId: clientID, - redirectUri: redirectUri, - responseUri: responseUri, - responseType: responseType, - responseMode: responseMode, - scope: scope, - nonce: nonce, - requestUri: requestUri, - presentationDefinition: presentationDefinition) - return presentationRequest - + + let presentationRequest = PresentationRequest(state: state, + clientId: clientID, + redirectUri: redirectUri, + responseUri: responseUri, + responseType: responseType, + responseMode: responseMode, + scope: scope, + nonce: nonce, + requestUri: requestUri, + presentationDefinition: presentationDefinition) + return presentationRequest + } else if requestUri != "" { var request = URLRequest(url: URL(string: requestUri)!) request.httpMethod = "GET" @@ -166,7 +166,7 @@ public class VerificationService: NSObject, VerificationServiceProtocol { private func generateVPToken(header: String, payload: String, secureKey: SecureKeyData) -> String { let headerData = Data(header.utf8) - + //let payloadData = Data(payload.utf8) //let unsignedToken = "\(headerData.base64URLEncodedString()).\(payloadData.base64URLEncodedString())" //let signatureData = try? privateKey.signature(for: unsignedToken.data(using: .utf8)!) @@ -174,7 +174,7 @@ public class VerificationService: NSObject, VerificationServiceProtocol { guard let idToken = keyHandler.sign(payload: payload, header: headerData, withKey: secureKey.privateKey) else{return ""} //guard let signature = keyHandler.sign(data: unsignedToken.data(using: .utf8)!, withKey: secureKey.privateKey) else{return ""} return idToken//"\(unsignedToken).\(signature.base64URLEncodedString() ?? "")" - + } private func preparePresentationSubmission( @@ -197,7 +197,7 @@ public class VerificationService: NSObject, VerificationServiceProtocol { } } - + return PresentationSubmissionModel(id: "essppda1", definitionID: presentationDefinition?.id ?? "", descriptorMap: descMap) } @@ -226,12 +226,12 @@ public class VerificationService: NSObject, VerificationServiceProtocol { // Performing the token request var responseUrl = "" do { - + let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) - + let (data, response) = try await session.data(for: request) - - + + let httpres = response as? HTTPURLResponse if httpres?.statusCode == 302, let location = httpres?.value(forHTTPHeaderField: "Location"){ responseUrl = location @@ -244,7 +244,7 @@ public class VerificationService: NSObject, VerificationServiceProtocol { } return WrappedVerificationResponse(data: dataString, error: nil) } - + } catch { debugPrint("JSON Serialization Error: \(error)") return nil @@ -252,74 +252,41 @@ public class VerificationService: NSObject, VerificationServiceProtocol { } - public static func processPresentationDefinition(_ presentationDefinition: Any?) throws -> PresentationDefinitionModel { - do { - guard let presentationDefinition = presentationDefinition else { - throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid presentation definition"]) - } - - if let presentationDefinition = presentationDefinition as? PresentationDefinitionModel { - return presentationDefinition - } else if let linkedTreeMap = presentationDefinition as? [AnyHashable: Any] { - let jsonData = try JSONSerialization.data(withJSONObject: linkedTreeMap) - let jsonString = String(data: jsonData, encoding: .utf8) ?? "" - return try JSONDecoder().decode(PresentationDefinitionModel.self, from: jsonString.data(using: .utf8) ?? Data()) - } else if let jsonString = presentationDefinition as? String { - let str = jsonString.replacingOccurrences(of: "+", with: "") - let data = str.data(using: .utf8) - let model = try JSONDecoder().decode(PresentationDefinitionModel.self, from: data!) - return model - } else { - throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid presentation definition format"]) - } - } catch { - throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Error processing presentation definition"]) - } - } - public func filterCredentials(credentialList: [String?], presentationDefinition: PresentationDefinitionModel) -> [[String]] { - var response: [[String]] = [] - - var tempCredentialList: [String?] = [] - for item in credentialList { - if let limitDisclosure = presentationDefinition.inputDescriptors?.first?.constraints?.limitDisclosure, - item?.contains("~") == true { - tempCredentialList.append(item) - } else if presentationDefinition.inputDescriptors?.first?.constraints?.limitDisclosure == nil, - item?.contains("~") == false { - tempCredentialList.append(item) - } - } - var processedCredentials = [String]() - for cred in tempCredentialList { - guard let cred = cred else { continue } - let split = cred.split(separator: ".") - - let jsonString: String - if (cred.split(separator: "~").count) > 0 { - jsonString = SDJWTService.shared.updateIssuerJwtWithDisclosures(credential: cred) ?? "" - } else if split.count > 1, - let base64Data = Data(base64Encoded: String(split[1]), options: .ignoreUnknownCharacters), - let decodedString = String(data: base64Data, encoding: .utf8) { - jsonString = decodedString - } else { - jsonString = "" + public static func processPresentationDefinition(_ presentationDefinition: Any?) throws -> PresentationDefinitionModel { + do { + guard let presentationDefinition = presentationDefinition else { + throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid presentation definition"]) } - let json = try? JSONSerialization.jsonObject(with: Data(jsonString.utf8), options: []) as? [String: Any] ?? [:] - - var vcString = "" - if let vc = json?["vc"] as? [String: Any] { - vcString = vc.toString() ?? "" + if let presentationDefinition = presentationDefinition as? PresentationDefinitionModel { + return presentationDefinition + } else if let linkedTreeMap = presentationDefinition as? [AnyHashable: Any] { + let jsonData = try JSONSerialization.data(withJSONObject: linkedTreeMap) + let jsonString = String(data: jsonData, encoding: .utf8) ?? "" + return try JSONDecoder().decode(PresentationDefinitionModel.self, from: jsonString.data(using: .utf8) ?? Data()) + } else if let jsonString = presentationDefinition as? String { + let str = jsonString.replacingOccurrences(of: "+", with: "") + let data = str.data(using: .utf8) + let model = try JSONDecoder().decode(PresentationDefinitionModel.self, from: data!) + return model } else { - vcString = jsonString + throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid presentation definition format"]) } - - processedCredentials.append(vcString) + } catch { + throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Error processing presentation definition"]) } + } + public func filterCredentials(credentialList: [String?], presentationDefinition: PresentationDefinitionModel) -> [[String]] { + var response: [[String]] = [] if let inputDescriptors = presentationDefinition.inputDescriptors { for inputDescriptor in inputDescriptors { - let updatedDescriptor = updatePath(in: inputDescriptor) + + let tempCredentialList = splitCredentialsBySdJWT(allCredentials: credentialList, isSdJwt: inputDescriptor.constraints?.limitDisclosure != nil) + + let processedCredentials = processCredentialsToJsonString(credentialList: tempCredentialList) + + let updatedDescriptor = updatePath(in: inputDescriptor) var filteredCredentialList: [String] = [] let jsonEncoder = JSONEncoder() @@ -352,6 +319,52 @@ public class VerificationService: NSObject, VerificationServiceProtocol { return response } + + private func splitCredentialsBySdJWT(allCredentials: [String?], isSdJwt: Bool) -> [String?] { + var filteredCredentials: [String?] = [] + for item in allCredentials { + if isSdJwt == true, + item?.contains("~") == true { + filteredCredentials.append(item) + } else if isSdJwt == false, + item?.contains("~") == false { + filteredCredentials.append(item) + } + } + return filteredCredentials + } + + private func processCredentialsToJsonString(credentialList: [String?]) -> [String] { + var processedCredentials = [String]() + for cred in credentialList { + guard let cred = cred else { continue } + let split = cred.split(separator: ".") + + let jsonString: String + if (cred.split(separator: "~").count) > 0 { + jsonString = SDJWTService.shared.updateIssuerJwtWithDisclosures(credential: cred) ?? "" + } else if split.count > 1, + let base64Data = Data(base64Encoded: String(split[1]), options: .ignoreUnknownCharacters), + let decodedString = String(data: base64Data, encoding: .utf8) { + jsonString = decodedString + } else { + jsonString = "" + } + + let json = try? JSONSerialization.jsonObject(with: Data(jsonString.utf8), options: []) as? [String: Any] ?? [:] + + var vcString = "" + if let vc = json?["vc"] as? [String: Any] { + vcString = vc.toString() ?? "" + } else { + vcString = jsonString + } + + processedCredentials.append(vcString) + } + return processedCredentials + } + func updatePath(in descriptor: InputDescriptor) -> InputDescriptor { var updatedDescriptor = descriptor guard var constraints = updatedDescriptor.constraints else { return updatedDescriptor } From 99e81bb31f7a9b9b8e84178a478e8d69d45ce00c Mon Sep 17 00:00:00 2001 From: ArunRajasekhar84 Date: Wed, 17 Jul 2024 19:54:39 +0530 Subject: [PATCH 23/33] Adding dependency for JOSE Swift --- Package.resolved | 9 +++++++++ .../Service/VerificationService.swift | 17 +++++++++-------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Package.resolved b/Package.resolved index 4bdcb48..486ded9 100644 --- a/Package.resolved +++ b/Package.resolved @@ -27,6 +27,15 @@ "version" : "1.8.2" } }, + { + "identity" : "joseswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/airsidemobile/JOSESwift.git", + "state" : { + "revision" : "10ed3b6736def7c26eb87135466b1cb46ea7e37f", + "version" : "2.4.0" + } + }, { "identity" : "presentationexchangesdkios", "kind" : "remoteSourceControl", diff --git a/Sources/eudiWalletOidcIos/Service/VerificationService.swift b/Sources/eudiWalletOidcIos/Service/VerificationService.swift index bc1577b..bcb1fba 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationService.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationService.swift @@ -40,10 +40,11 @@ public class VerificationService: NSObject, VerificationServiceProtocol { // Presentation Submission model guard let presentationSubmission = preparePresentationSubmission(presentationRequest: presentationRequest) else { return nil } - - guard let redirectURL = presentationRequest?.redirectUri else {return nil} - return await sendVPRequest(vpToken: vpToken, presentationSubmission: presentationSubmission, redirectURI: presentationRequest?.redirectUri ?? "", state: presentationRequest?.state ?? "") - } + + guard let redirectURL = presentationRequest?.redirectUri else {return nil} + return await sendVPRequest(vpToken: vpToken, presentationSubmission: presentationSubmission, redirectURI: presentationRequest?.redirectUri ?? "", state: presentationRequest?.state ?? "") + } + private func generateJWKFromPrivateKey(secureKey: SecureKeyData, did: String) -> [String: Any] { let rawRepresentation = secureKey.publicKey @@ -166,7 +167,7 @@ public class VerificationService: NSObject, VerificationServiceProtocol { private func generateVPToken(header: String, payload: String, secureKey: SecureKeyData) -> String { let headerData = Data(header.utf8) - + //let payloadData = Data(payload.utf8) //let unsignedToken = "\(headerData.base64URLEncodedString()).\(payloadData.base64URLEncodedString())" //let signatureData = try? privateKey.signature(for: unsignedToken.data(using: .utf8)!) @@ -174,7 +175,7 @@ public class VerificationService: NSObject, VerificationServiceProtocol { guard let idToken = keyHandler.sign(payload: payload, header: headerData, withKey: secureKey.privateKey) else{return ""} //guard let signature = keyHandler.sign(data: unsignedToken.data(using: .utf8)!, withKey: secureKey.privateKey) else{return ""} return idToken//"\(unsignedToken).\(signature.base64URLEncodedString() ?? "")" - + } private func preparePresentationSubmission( @@ -226,7 +227,7 @@ public class VerificationService: NSObject, VerificationServiceProtocol { // Performing the token request var responseUrl = "" do { - + let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) let (data, response) = try await session.data(for: request) @@ -244,7 +245,7 @@ public class VerificationService: NSObject, VerificationServiceProtocol { } return WrappedVerificationResponse(data: dataString, error: nil) } - + } catch { debugPrint("JSON Serialization Error: \(error)") return nil From ca2a86e53c697654aa75bb3f89ee746d3bdff792 Mon Sep 17 00:00:00 2001 From: Joseph Milan Date: Wed, 17 Jul 2024 20:06:15 +0530 Subject: [PATCH 24/33] Fix #29: Update access token request logic --- .../Service/IssueService.swift | 74 ++++++++++--------- .../Service/IssueServiceProtocol.swift | 2 +- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/Sources/eudiWalletOidcIos/Service/IssueService.swift b/Sources/eudiWalletOidcIos/Service/IssueService.swift index 86cf3b0..2aed917 100644 --- a/Sources/eudiWalletOidcIos/Service/IssueService.swift +++ b/Sources/eudiWalletOidcIos/Service/IssueService.swift @@ -169,10 +169,8 @@ public class IssueService: NSObject, IssueServiceProtocol { } - if responseUrl.contains("code=") { - let url = URL(string: responseUrl) - let code = url?.queryParameters?["code"] - return code + if responseUrl.contains("code=") || responseUrl.contains("error=") || responseUrl.contains("presentation_definition="){ + return responseUrl } else { // if 'code' is not present let url = URL(string: responseUrl) @@ -263,6 +261,7 @@ public class IssueService: NSObject, IssueServiceProtocol { let httpres = response as? HTTPURLResponse if httpres?.statusCode == 302, let location = httpres?.value(forHTTPHeaderField: "Location"){ responseUrl = location + return responseUrl } else{ let authorization_response = String.init(data: data, encoding: .utf8) ?? "" @@ -273,7 +272,7 @@ public class IssueService: NSObject, IssueServiceProtocol { guard let authorisation_url = URL(string: responseUrl) else { return nil } if let components = URLComponents(url: authorisation_url, resolvingAgainstBaseURL: false), let auth_code = components.queryItems?.first(where: { $0.name == "code" })?.value { - return auth_code + return responseUrl } else { return nil } @@ -297,26 +296,22 @@ public class IssueService: NSObject, IssueServiceProtocol { - Returns: A `TokenResponse` object if the request is successful, otherwise `nil`. */ - //FIX Me: Use isPreAuthorisedCodeFlow to check if its pre-auth or auth flow - //remove authServerWellKnownConfig and change it to a string to get the endpoint value alone - public func processTokenRequest(authServerWellKnownConfig: AuthorisationServerWellKnownConfiguration, - code: String, - did: String, - codeVerifier: String, - isPreAuthorisedCodeFlow: Bool = false, - preAuthCode: String, - userPin: String?) async -> TokenResponse? { + public func processTokenRequest( + did: String, + tokenEndPoint: String?, + code: String, + codeVerifier: String, + isPreAuthorisedCodeFlow: Bool = false, + userPin: String?) async -> TokenResponse? { - let codeVal = code.removingPercentEncoding ?? "" - // Service call for access token and details - if userPin == nil || userPin == "" { - let tokenResponse = await getAccessToken(didKeyIdentifier: did, codeVerifier: codeVerifier, authCode: codeVal, tokenEndpoint: authServerWellKnownConfig.tokenEndpoint ?? "") - return tokenResponse - } else { - // Service call for access token and details - let tokenResponse = await getAccessTokenForPreAuthCredential(preAuthCode: preAuthCode, otpVal: userPin ?? "", tokenEndpoint: authServerWellKnownConfig.tokenEndpoint ?? "") - return tokenResponse - } + if isPreAuthorisedCodeFlow { + let tokenResponse = await getAccessTokenForPreAuthCredential(preAuthCode: code, otpVal: userPin ?? "", tokenEndpoint: tokenEndPoint ?? "") + return tokenResponse + } else { + let codeVal = code.removingPercentEncoding ?? "" + let tokenResponse = await getAccessToken(didKeyIdentifier: did, codeVerifier: codeVerifier, authCode: codeVal, tokenEndpoint: tokenEndPoint ?? "") + return tokenResponse + } } // MARK: Processes a credential request to the specified credential endpoint. @@ -379,17 +374,30 @@ public class IssueService: NSObject, IssueServiceProtocol { let credentialTypes = credentialOffer.credentials?[0].types ?? [] + let types = getTypesFromIssuerConfig(issuerConfig: issuerConfig, type: credentialTypes.last ?? "") let formatT = getFormatFromIssuerConfig(issuerConfig: issuerConfig, type: credentialTypes.last) - var params: [String: Any] = [ - "credential_definition": [ - "type": credentialTypes - ], - "format": formatT, - "proof": [ - "proof_type": "jwt", - "jwt": idToken + var params: [String: Any] = [:] + if types is String { + params = [ + "vct": types ?? "", + "format": format ?? "jwt_vc", + "proof": [ + "proof_type": "jwt", + "jwt": idToken + ] + ] + }else{ + params = [ + "credential_definition": [ + "type": types + ], + "format": format ?? "jwt_vc", + "proof": [ + "proof_type": "jwt", + "jwt": idToken + ] ] - ] + } if credentialOffer.credentials?[0].trustFramework != nil { params = [ "types": credentialTypes, diff --git a/Sources/eudiWalletOidcIos/Service/IssueServiceProtocol.swift b/Sources/eudiWalletOidcIos/Service/IssueServiceProtocol.swift index 66a4fb4..bc1dee8 100644 --- a/Sources/eudiWalletOidcIos/Service/IssueServiceProtocol.swift +++ b/Sources/eudiWalletOidcIos/Service/IssueServiceProtocol.swift @@ -40,7 +40,7 @@ protocol IssueServiceProtocol { - Returns: A `TokenResponse` object if the request is successful, otherwise `nil`. */ - func processTokenRequest(authServerWellKnownConfig: AuthorisationServerWellKnownConfiguration, code: String, did: String, codeVerifier: String, isPreAuthorisedCodeFlow: Bool, preAuthCode: String, userPin: String?) async -> TokenResponse? + func processTokenRequest(did: String, tokenEndPoint: String?, code: String, codeVerifier: String, isPreAuthorisedCodeFlow: Bool, userPin: String?) async -> TokenResponse? // Processes a credential request to the specified credential endpoint. From 9e409b395c1f8b9dedb896b81ced80fd04ed1896 Mon Sep 17 00:00:00 2001 From: Joseph Milan Date: Wed, 24 Jul 2024 14:14:34 +0530 Subject: [PATCH 25/33] Fix #30: issue in presentation submission --- .../Service/VerificationService.swift | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/Sources/eudiWalletOidcIos/Service/VerificationService.swift b/Sources/eudiWalletOidcIos/Service/VerificationService.swift index bcb1fba..a368739 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationService.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationService.swift @@ -44,7 +44,6 @@ public class VerificationService: NSObject, VerificationServiceProtocol { guard let redirectURL = presentationRequest?.redirectUri else {return nil} return await sendVPRequest(vpToken: vpToken, presentationSubmission: presentationSubmission, redirectURI: presentationRequest?.redirectUri ?? "", state: presentationRequest?.state ?? "") } - private func generateJWKFromPrivateKey(secureKey: SecureKeyData, did: String) -> [String: Any] { let rawRepresentation = secureKey.publicKey @@ -58,7 +57,6 @@ public class VerificationService: NSObject, VerificationServiceProtocol { ] } - public func processAuthorisationRequest(data: String?) async -> PresentationRequest? { guard let _ = data else { return nil } @@ -138,11 +136,12 @@ public class VerificationService: NSObject, VerificationServiceProtocol { } private func generateJWTPayload(did: String, nonce: String, credentialsList: [String], state: String, clientID: String) -> String { + let uuid4 = UUID().uuidString let vp = ([ "@context": ["https://www.w3.org/2018/credentials/v1"], "holder": did, - "id": "urn:uuid:\(UUID().uuidString)", + "id": "urn:uuid:\(uuid4)", "type": [ "VerifiablePresentation" ], @@ -150,7 +149,6 @@ public class VerificationService: NSObject, VerificationServiceProtocol { ] as [String : Any]) let currentTime = Int(Date().timeIntervalSince1970) - let uuid4 = UUID().uuidString return ([ "aud": clientID, @@ -167,7 +165,6 @@ public class VerificationService: NSObject, VerificationServiceProtocol { private func generateVPToken(header: String, payload: String, secureKey: SecureKeyData) -> String { let headerData = Data(header.utf8) - //let payloadData = Data(payload.utf8) //let unsignedToken = "\(headerData.base64URLEncodedString()).\(payloadData.base64URLEncodedString())" //let signatureData = try? privateKey.signature(for: unsignedToken.data(using: .utf8)!) @@ -175,7 +172,6 @@ public class VerificationService: NSObject, VerificationServiceProtocol { guard let idToken = keyHandler.sign(payload: payload, header: headerData, withKey: secureKey.privateKey) else{return ""} //guard let signature = keyHandler.sign(data: unsignedToken.data(using: .utf8)!, withKey: secureKey.privateKey) else{return ""} return idToken//"\(unsignedToken).\(signature.base64URLEncodedString() ?? "")" - } private func preparePresentationSubmission( @@ -192,7 +188,7 @@ public class VerificationService: NSObject, VerificationServiceProtocol { if let inputDescriptors = presentationDefinition?.inputDescriptors { for index in 0.. InputDescriptor { return updatedDescriptor } } - extension VerificationService: URLSessionDelegate, URLSessionTaskDelegate { public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { // Stops the redirection, and returns (internally) the response body. From 88e2789a53a1c88d9b219bfb241e9e13b9d34bef Mon Sep 17 00:00:00 2001 From: Joseph Milan Date: Thu, 25 Jul 2024 19:09:00 +0530 Subject: [PATCH 26/33] Fix #33: Support for EdDSA cryptographic suit --- Package.resolved | 9 ++ Package.swift | 3 +- .../Helper/CryptographicAlgorithms.swift | 13 ++ .../EDDSA/EDDSAKeyHandler.swift | 41 +++++++ .../SecureKeyProtocol.swift | 1 + .../Service/DidService.swift | 111 +++++++++++++----- .../Service/DidServiceProtocol.swift | 30 ++++- 7 files changed, 179 insertions(+), 29 deletions(-) create mode 100644 Sources/eudiWalletOidcIos/Helper/CryptographicAlgorithms.swift create mode 100644 Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/EDDSA/EDDSAKeyHandler.swift diff --git a/Package.resolved b/Package.resolved index 486ded9..565e93a 100644 --- a/Package.resolved +++ b/Package.resolved @@ -44,6 +44,15 @@ "revision" : "7038d35f98505f364d4cc50b65ab81de6a3d336c", "version" : "2024.5.1" } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "46072478ca365fe48370993833cb22de9b41567f", + "version" : "3.5.2" + } } ], "version" : 2 diff --git a/Package.swift b/Package.swift index d4895ef..4fef61c 100644 --- a/Package.swift +++ b/Package.swift @@ -17,7 +17,8 @@ let package = Package( .package(url: "https://github.com/keefertaylor/Base58Swift.git", branch: "master"), .package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "1.8.1"), .package(url: "https://github.com/decentralised-dataexchange/PresentationExchangeSdkiOS.git", .upToNextMajor(from: "2024.3.1")), - .package(url: "https://github.com/airsidemobile/JOSESwift.git", from: "2.3.0") + .package(url: "https://github.com/airsidemobile/JOSESwift.git", from: "2.3.0"), + .package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "4.0.0") ], targets: [ .target( diff --git a/Sources/eudiWalletOidcIos/Helper/CryptographicAlgorithms.swift b/Sources/eudiWalletOidcIos/Helper/CryptographicAlgorithms.swift new file mode 100644 index 0000000..b61872d --- /dev/null +++ b/Sources/eudiWalletOidcIos/Helper/CryptographicAlgorithms.swift @@ -0,0 +1,13 @@ +// +// File.swift +// +// +// Created by Milan on 25/07/24. +// + +import Foundation + +public enum CryptographicAlgorithms: String { + case ES256 = "ES256" + case EdDSA = "EdDSA" +} diff --git a/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/EDDSA/EDDSAKeyHandler.swift b/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/EDDSA/EDDSAKeyHandler.swift new file mode 100644 index 0000000..f7a3ca2 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/EDDSA/EDDSAKeyHandler.swift @@ -0,0 +1,41 @@ +// +// File.swift +// +// +// Created by Milan on 25/07/24. +// + +import Foundation +import Crypto + +public class EDDSAKeyHandler:NSObject, SecureKeyProtocol{ + + public var keyStorageType: SecureKeyTypes = .eddsa + + public func generateSecureKey() -> SecureKeyData?{ + let privateKey = Curve25519.Signing.PrivateKey() + return SecureKeyData(publicKey: privateKey.publicKey.rawRepresentation, privateKey: privateKey.rawRepresentation) + } + + + public func sign(payload: String, header: Data, withKey privateKey: Data?) -> String?{ + if let privateKeyData = privateKey{ + do{ + + let payloadData = Data(payload.utf8) + let unsignedToken = "\(header.base64URLEncodedString()).\(payloadData.base64URLEncodedString())" + if let data = unsignedToken.data(using: .utf8){ + let privateKey = try Curve25519.Signing.PrivateKey(rawRepresentation: privateKeyData) + let signedData = try privateKey.signature(for: data) + let idToken = "\(unsignedToken).\(signedData.urlSafeBase64EncodedString()))" + return idToken + } + + } + catch{ + return nil + } + } + return nil + } +} diff --git a/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/SecureKeyProtocol.swift b/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/SecureKeyProtocol.swift index 4c3adfa..0417937 100644 --- a/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/SecureKeyProtocol.swift +++ b/Sources/eudiWalletOidcIos/Helper/EncryptionKeyHandler/SecureKeyProtocol.swift @@ -10,6 +10,7 @@ import Foundation //Enum to identify the type of key generation class used like CryptoKitHandler, SecureEnclaveHandler public enum SecureKeyTypes{ case cryptoKit + case eddsa case secureEnclave } diff --git a/Sources/eudiWalletOidcIos/Service/DidService.swift b/Sources/eudiWalletOidcIos/Service/DidService.swift index 2f9c00b..547cea7 100644 --- a/Sources/eudiWalletOidcIos/Service/DidService.swift +++ b/Sources/eudiWalletOidcIos/Service/DidService.swift @@ -17,7 +17,19 @@ public class DidService { /// /// - Parameter jwk: The JSON Web Key (JWK) used to create the DID. /// - Returns: The created DID string, or nil if an error occurs. - public func createDID(jwk: [String: Any]) async -> String? { + public func createDID(jwk: [String: Any], cryptographicAlgorithm: String? = CryptographicAlgorithms.ES256.rawValue) async -> String? { + switch cryptographicAlgorithm { + case CryptographicAlgorithms.ES256.rawValue: + return await createES256DID(jwk: jwk) + case CryptographicAlgorithms.EdDSA.rawValue: + return await createEdDSADID(jwk: jwk) + default: + return await createES256DID(jwk: jwk) + } + + } + + public func createES256DID(jwk: [String: Any]) async -> String? { do { // Step 1: Convert JWK to JSON string let jsonData = try JSONSerialization.data(withJSONObject: jwk, options: [.sortedKeys]) @@ -51,41 +63,86 @@ public class DidService { } } + public func createEdDSADID(jwk: [String: Any]) async -> String? { + do { + let decodedBytes = Base58.base58Decode(jwk["x"] as? String ?? "") + // unicode to utf8 "\xed\x01" = [5c 78 65 64 5c 78 30 31] + let multicodeByreArray: [UInt8] = [237, 1] + var hexWithMulticode = multicodeByreArray + hexWithMulticode.append(contentsOf: decodedBytes ?? []) + let encodedString = Base58.base58Encode(hexWithMulticode) + let finalString = "z" + encodedString + return "did:key:" + finalString + } catch { + print("Error: \(error)") + return nil + } + } + // MARK: - Exposed method to create a JSON Web Key (JWK) asynchronously. /// - Parameter keyHandler: A handler to encryption key generation class /// - Returns: A dictionary representing the JWK, or nil if an error occurs. public func createJWK(keyHandler: SecureKeyProtocol) async -> ([String: Any], SecureKeyData)?{ - + switch keyHandler.keyStorageType { + case .cryptoKit: + return await createES256JWK(keyHandler: keyHandler) + case .eddsa: + return await createEdDSAJWK(keyHandler: keyHandler) + default: + return await createSecureEnclaveJWK(keyHandler: keyHandler) + } + } + + public func createES256JWK(keyHandler: SecureKeyProtocol) async -> ([String: Any], SecureKeyData)?{ if let keys = keyHandler.generateSecureKey(){ - - if keyHandler.keyStorageType == .cryptoKit{ - let rawRepresentation = keys.publicKey - let x = rawRepresentation[rawRepresentation.startIndex.. ([String: Any], SecureKeyData)?{ + if let keys = keyHandler.generateSecureKey(){ + let rawRepresentation = keys.publicKey + let jwk: [String: Any] = [ + "kty": "OKP", + "crv": "Ed25519", + "x": rawRepresentation.urlSafeBase64EncodedString() + ] + + if let theJSONData = try? JSONSerialization.data( + withJSONObject: jwk, + options: []) { + let theJSONText = String(data: theJSONData, + encoding: .ascii) + print("JSON string = \(theJSONText!)") + } + return (jwk, SecureKeyData(publicKey: keys.publicKey, privateKey: keys.privateKey)) + } + return nil + } + public func createSecureEnclaveJWK(keyHandler: SecureKeyProtocol) async -> ([String: Any], SecureKeyData)?{ + if let keys = keyHandler.generateSecureKey(){ + if let jsonDict = keyHandler.getJWK(publicKey: keys.publicKey){ + return (jsonDict, SecureKeyData(publicKey: keys.publicKey, privateKey: keys.privateKey)) + } + } + return nil + } } diff --git a/Sources/eudiWalletOidcIos/Service/DidServiceProtocol.swift b/Sources/eudiWalletOidcIos/Service/DidServiceProtocol.swift index 19947d1..91eaf09 100644 --- a/Sources/eudiWalletOidcIos/Service/DidServiceProtocol.swift +++ b/Sources/eudiWalletOidcIos/Service/DidServiceProtocol.swift @@ -11,11 +11,39 @@ protocol DidServiceProtocol { // Creates a Decentralized Identifier (DID) asynchronously based on the provided JWK (JSON Web Key). /// /// - Parameter jwk: The JSON Web Key (JWK) used to create the DID. + /// - Parameter cryptographicAlgorithm: Alg for creating the DID. /// - Returns: The created DID string, or nil if an error occurs. - func createDID(jwk: [String: Any]) async -> String? + func createDID(jwk: [String: Any], cryptographicAlgorithm: String?) async -> String? + + // Creates a Decentralized Identifier (DID) asynchronously based on the provided JWK (JSON Web Key) for ES256. + /// + /// - Parameter jwk: The JSON Web Key (JWK) used to create the DID. + /// - Returns: The created DID string, or nil if an error occurs. + func createES256DID(jwk: [String: Any]) async -> String? + + // Creates a Decentralized Identifier (DID) asynchronously based on the provided JWK (JSON Web Key) for EdDSA. + /// + /// - Parameter jwk: The JSON Web Key (JWK) used to create the DID. + /// - Returns: The created DID string, or nil if an error occurs. + func createEdDSADID(jwk: [String: Any]) async -> String? // Exposed method to create a JSON Web Key (JWK) asynchronously. /// /// - Returns: A dictionary representing the JWK, or nil if an error occurs. func createJWK(keyHandler: SecureKeyProtocol) async -> ([String: Any], SecureKeyData)? + + // Exposed method to create a JSON Web Key (JWK) asynchronously for ES256. + /// + /// - Returns: A dictionary representing the JWK, or nil if an error occurs. + func createES256JWK(keyHandler: SecureKeyProtocol) async -> ([String: Any], SecureKeyData)? + + // Exposed method to create a JSON Web Key (JWK) asynchronously for EdDSA. + /// + /// - Returns: A dictionary representing the JWK, or nil if an error occurs. + func createEdDSAJWK(keyHandler: SecureKeyProtocol) async -> ([String: Any], SecureKeyData)? + + // Exposed method to create a JSON Web Key (JWK) asynchronously for Secure enclave. + /// + /// - Returns: A dictionary representing the JWK, or nil if an error occurs. + func createSecureEnclaveJWK(keyHandler: SecureKeyProtocol) async -> ([String: Any], SecureKeyData)? } From 2993db435476cada2eea0a7156f10edafdf65476 Mon Sep 17 00:00:00 2001 From: Joseph Milan Date: Thu, 25 Jul 2024 21:34:15 +0530 Subject: [PATCH 27/33] Fix #33: added swift-crypto to the packages --- Package.resolved | 4 ++-- Package.swift | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Package.resolved b/Package.resolved index 565e93a..39b2b0f 100644 --- a/Package.resolved +++ b/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/attaswift/BigInt.git", "state" : { - "revision" : "0ed110f7555c34ff468e72e1686e59721f2b0da6", - "version" : "5.3.0" + "revision" : "793a7fac0bfc318e85994bf6900652e827aef33e", + "version" : "5.4.1" } }, { diff --git a/Package.swift b/Package.swift index 4fef61c..f22c147 100644 --- a/Package.swift +++ b/Package.swift @@ -18,12 +18,13 @@ let package = Package( .package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "1.8.1"), .package(url: "https://github.com/decentralised-dataexchange/PresentationExchangeSdkiOS.git", .upToNextMajor(from: "2024.3.1")), .package(url: "https://github.com/airsidemobile/JOSESwift.git", from: "2.3.0"), - .package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "4.0.0") + .package(url: "https://github.com/apple/swift-crypto.git", from:"3.5.2") ], targets: [ .target( name: "eudiWalletOidcIos", - dependencies: ["Base58Swift", "CryptoSwift", "PresentationExchangeSdkiOS", "JOSESwift"]), + dependencies: ["Base58Swift", "CryptoSwift", "PresentationExchangeSdkiOS", "JOSESwift", .product(name: "Crypto", package: "swift-crypto")], + path: "Sources"), .testTarget( name: "eudi-wallet-oidc-iosTests", dependencies: ["eudiWalletOidcIos"]), From 98148a77e6fb12f9fa627e1a9a5774ece6412fd7 Mon Sep 17 00:00:00 2001 From: Joseph Milan Date: Fri, 26 Jul 2024 14:10:56 +0530 Subject: [PATCH 28/33] Fix #33 - updated did generation for EDDSA --- Sources/eudiWalletOidcIos/Service/DidService.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/eudiWalletOidcIos/Service/DidService.swift b/Sources/eudiWalletOidcIos/Service/DidService.swift index 547cea7..2ab6af2 100644 --- a/Sources/eudiWalletOidcIos/Service/DidService.swift +++ b/Sources/eudiWalletOidcIos/Service/DidService.swift @@ -65,11 +65,12 @@ public class DidService { public func createEdDSADID(jwk: [String: Any]) async -> String? { do { - let decodedBytes = Base58.base58Decode(jwk["x"] as? String ?? "") + let privateKeyX = Data(base64URLEncoded: jwk["x"] as? String ?? "") ?? Data() // unicode to utf8 "\xed\x01" = [5c 78 65 64 5c 78 30 31] + let privateKeyBytes = [UInt8](privateKeyX) let multicodeByreArray: [UInt8] = [237, 1] var hexWithMulticode = multicodeByreArray - hexWithMulticode.append(contentsOf: decodedBytes ?? []) + hexWithMulticode.append(contentsOf: privateKeyBytes ) let encodedString = Base58.base58Encode(hexWithMulticode) let finalString = "z" + encodedString return "did:key:" + finalString From 3b7fdc5ef1e3d45acf423522f0cdf8ad29a10534 Mon Sep 17 00:00:00 2001 From: Joseph Milan Date: Thu, 25 Jul 2024 15:43:55 +0530 Subject: [PATCH 29/33] Fix #32: Validation classes --- .../CredentialValidaorProtocol.swift | 12 ++ .../CredentialValidatorService.swift | 112 ++++++++++++++++++ .../ExpiryValidator.swift | 12 ++ .../SignatureValidator.swift | 12 ++ 4 files changed, 148 insertions(+) create mode 100644 Sources/eudiWalletOidcIos/Service/CredentialValidation/CredentialValidaorProtocol.swift create mode 100644 Sources/eudiWalletOidcIos/Service/CredentialValidation/CredentialValidatorService.swift create mode 100644 Sources/eudiWalletOidcIos/Service/CredentialValidation/ExpiryValidator.swift create mode 100644 Sources/eudiWalletOidcIos/Service/CredentialValidation/SignatureValidator.swift diff --git a/Sources/eudiWalletOidcIos/Service/CredentialValidation/CredentialValidaorProtocol.swift b/Sources/eudiWalletOidcIos/Service/CredentialValidation/CredentialValidaorProtocol.swift new file mode 100644 index 0000000..7d629d9 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Service/CredentialValidation/CredentialValidaorProtocol.swift @@ -0,0 +1,12 @@ +// +// File.swift +// +// +// Created by iGrant on 25/07/24. +// + +import Foundation + +protocol CredentialValidaorProtocol { + func validateCredential(jwt: String?) async throws +} diff --git a/Sources/eudiWalletOidcIos/Service/CredentialValidation/CredentialValidatorService.swift b/Sources/eudiWalletOidcIos/Service/CredentialValidation/CredentialValidatorService.swift new file mode 100644 index 0000000..99da883 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Service/CredentialValidation/CredentialValidatorService.swift @@ -0,0 +1,112 @@ +// +// File.swift +// +// +// Created by iGrant on 24/07/24. +// + +import Foundation +import Base58Swift + +public enum ValidationError: Error { + case JWTExpired + case signatureExpired +} + +public class CredentialValidatorService: CredentialValidaorProtocol { + public static var shared = CredentialValidatorService() + public init() {} + + public func validateCredential(jwt: String?) async throws { + let isJWTExpired = validateExpiryDate(jwt: jwt) ?? false + let isSignatureExpied = await validateSign(jwt: jwt) ?? false + if !isJWTExpired { + throw ValidationError.JWTExpired + } + if !isSignatureExpied { + throw ValidationError.signatureExpired + } + } + + public func validateExpiryDate(jwt: String?) -> Bool? { + guard let split = jwt?.split(separator: "."), + let jsonString = "\(split[1])".decodeBase64(), + let jsonObject = UIApplicationUtils.shared.convertStringToDictionary(text: jsonString) else { return false } + guard let vc = jsonObject["vc"] as? [String: Any], let expirationDate = vc["expirationDate"] as? String else { return true} + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" + guard let expiryDate = dateFormatter.date(from: expirationDate) else { return false} + let currentDate = Date() + if currentDate <= expiryDate { + return true + } else { + return false + } + } + + public func validateSign(jwt: String?) async -> Bool? { + guard let split = jwt?.split(separator: "."), + let jsonString = "\(split[0])".decodeBase64(), + let jsonObject = UIApplicationUtils.shared.convertStringToDictionary(text: jsonString) else { return false } + guard let kid = jsonObject["kid"] as? String else { return true} + var jwk: [String: Any] = [:] + if kid.hasPrefix("did:key:z") { + jwk = processJWKfromKid(did: kid) + } else if kid.hasPrefix("did:ebsi:z") { + jwk = await processJWKforEBSI(did: kid) + } else { + + } + return true + } + + + func processJWKfromKid(did: String?) -> [String: Any] { + do { + guard let did = did else { return [:]} + let components = did.split(separator: "#") + guard let didPart = components.first else { + return [:] + } + let multibaseString = String(didPart.dropFirst("did:key:z".count)) + + guard let decodedData = Base58.base58Decode(multibaseString) else { + print("Failed to decode Multibase string") + return [:] + } + + let multicodecPrefixLength = 3 + guard decodedData.count > multicodecPrefixLength else { + print("Invalid decoded data length") + return [:] + } + let jsonData = Data(decodedData.dropFirst(multicodecPrefixLength)) + + let jwk = try JSONSerialization.jsonObject(with: jsonData, options: []) + return jwk as? [String: Any] ?? [:] + } catch { + print("Error: \(error)") + return [:] + } + } + + func processJWKforEBSI(did: String?) async -> [String: Any]{ + guard let did = did else { return [:]} + let ebsiEndPoint = "https://api-conformance.ebsi.eu/did-registry/v5/identifiers/\(did)" + do { + guard let url = URL(string: ebsiEndPoint) else { return [:]} + let (data, response) = try await URLSession.shared.data(from: url) + guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { return [:]} + guard let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], let verificationMethods = jsonObject["verificationMethod"] as? [[String: Any]] else { return [:]} + for data in verificationMethods { + if let publicKeyJwk = data["publicKeyJwk"] as? [String: Any], let crv = publicKeyJwk["crv"] as? String, crv == "P-256" { + return publicKeyJwk + } + } + } catch { + print("error") + } + return [:] + } + +} diff --git a/Sources/eudiWalletOidcIos/Service/CredentialValidation/ExpiryValidator.swift b/Sources/eudiWalletOidcIos/Service/CredentialValidation/ExpiryValidator.swift new file mode 100644 index 0000000..329fbd6 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Service/CredentialValidation/ExpiryValidator.swift @@ -0,0 +1,12 @@ +// +// File.swift +// +// +// Created by iGrant on 25/07/24. +// + +import Foundation + +class ExpiryValidator { + +} diff --git a/Sources/eudiWalletOidcIos/Service/CredentialValidation/SignatureValidator.swift b/Sources/eudiWalletOidcIos/Service/CredentialValidation/SignatureValidator.swift new file mode 100644 index 0000000..06cb021 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Service/CredentialValidation/SignatureValidator.swift @@ -0,0 +1,12 @@ +// +// File.swift +// +// +// Created by iGrant on 25/07/24. +// + +import Foundation + +class SignatureValidator { + +} From 69ce1e4490cf52fdf358380b6374f3dc0f33275d Mon Sep 17 00:00:00 2001 From: Joseph Milan Date: Fri, 26 Jul 2024 21:10:08 +0530 Subject: [PATCH 30/33] Fix - Expiry and signature validation --- .../CredentialValidaorProtocol.swift | 2 +- .../CredentialValidatorService.swift | 89 +---------- .../ExpiryValidator.swift | 17 ++ .../SignatureValidator.swift | 148 ++++++++++++++++++ .../Service/DidService.swift | 25 +++ 5 files changed, 195 insertions(+), 86 deletions(-) diff --git a/Sources/eudiWalletOidcIos/Service/CredentialValidation/CredentialValidaorProtocol.swift b/Sources/eudiWalletOidcIos/Service/CredentialValidation/CredentialValidaorProtocol.swift index 7d629d9..74b2502 100644 --- a/Sources/eudiWalletOidcIos/Service/CredentialValidation/CredentialValidaorProtocol.swift +++ b/Sources/eudiWalletOidcIos/Service/CredentialValidation/CredentialValidaorProtocol.swift @@ -8,5 +8,5 @@ import Foundation protocol CredentialValidaorProtocol { - func validateCredential(jwt: String?) async throws + func validateCredential(jwt: String?, jwksURI: String?) async throws } diff --git a/Sources/eudiWalletOidcIos/Service/CredentialValidation/CredentialValidatorService.swift b/Sources/eudiWalletOidcIos/Service/CredentialValidation/CredentialValidatorService.swift index 99da883..3740d41 100644 --- a/Sources/eudiWalletOidcIos/Service/CredentialValidation/CredentialValidatorService.swift +++ b/Sources/eudiWalletOidcIos/Service/CredentialValidation/CredentialValidatorService.swift @@ -17,10 +17,10 @@ public class CredentialValidatorService: CredentialValidaorProtocol { public static var shared = CredentialValidatorService() public init() {} - public func validateCredential(jwt: String?) async throws { - let isJWTExpired = validateExpiryDate(jwt: jwt) ?? false - let isSignatureExpied = await validateSign(jwt: jwt) ?? false - if !isJWTExpired { + public func validateCredential(jwt: String?, jwksURI: String?) async throws { + let isJWTExpired = ExpiryValidator.validateExpiryDate(jwt: jwt) ?? false + let isSignatureExpied = await SignatureValidator.validateSign(jwt: jwt, jwksURI: jwksURI) ?? false + if isJWTExpired { throw ValidationError.JWTExpired } if !isSignatureExpied { @@ -28,85 +28,4 @@ public class CredentialValidatorService: CredentialValidaorProtocol { } } - public func validateExpiryDate(jwt: String?) -> Bool? { - guard let split = jwt?.split(separator: "."), - let jsonString = "\(split[1])".decodeBase64(), - let jsonObject = UIApplicationUtils.shared.convertStringToDictionary(text: jsonString) else { return false } - guard let vc = jsonObject["vc"] as? [String: Any], let expirationDate = vc["expirationDate"] as? String else { return true} - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" - guard let expiryDate = dateFormatter.date(from: expirationDate) else { return false} - let currentDate = Date() - if currentDate <= expiryDate { - return true - } else { - return false - } - } - - public func validateSign(jwt: String?) async -> Bool? { - guard let split = jwt?.split(separator: "."), - let jsonString = "\(split[0])".decodeBase64(), - let jsonObject = UIApplicationUtils.shared.convertStringToDictionary(text: jsonString) else { return false } - guard let kid = jsonObject["kid"] as? String else { return true} - var jwk: [String: Any] = [:] - if kid.hasPrefix("did:key:z") { - jwk = processJWKfromKid(did: kid) - } else if kid.hasPrefix("did:ebsi:z") { - jwk = await processJWKforEBSI(did: kid) - } else { - - } - return true - } - - - func processJWKfromKid(did: String?) -> [String: Any] { - do { - guard let did = did else { return [:]} - let components = did.split(separator: "#") - guard let didPart = components.first else { - return [:] - } - let multibaseString = String(didPart.dropFirst("did:key:z".count)) - - guard let decodedData = Base58.base58Decode(multibaseString) else { - print("Failed to decode Multibase string") - return [:] - } - - let multicodecPrefixLength = 3 - guard decodedData.count > multicodecPrefixLength else { - print("Invalid decoded data length") - return [:] - } - let jsonData = Data(decodedData.dropFirst(multicodecPrefixLength)) - - let jwk = try JSONSerialization.jsonObject(with: jsonData, options: []) - return jwk as? [String: Any] ?? [:] - } catch { - print("Error: \(error)") - return [:] - } - } - - func processJWKforEBSI(did: String?) async -> [String: Any]{ - guard let did = did else { return [:]} - let ebsiEndPoint = "https://api-conformance.ebsi.eu/did-registry/v5/identifiers/\(did)" - do { - guard let url = URL(string: ebsiEndPoint) else { return [:]} - let (data, response) = try await URLSession.shared.data(from: url) - guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { return [:]} - guard let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], let verificationMethods = jsonObject["verificationMethod"] as? [[String: Any]] else { return [:]} - for data in verificationMethods { - if let publicKeyJwk = data["publicKeyJwk"] as? [String: Any], let crv = publicKeyJwk["crv"] as? String, crv == "P-256" { - return publicKeyJwk - } - } - } catch { - print("error") - } - return [:] - } - } diff --git a/Sources/eudiWalletOidcIos/Service/CredentialValidation/ExpiryValidator.swift b/Sources/eudiWalletOidcIos/Service/CredentialValidation/ExpiryValidator.swift index 329fbd6..9e4da40 100644 --- a/Sources/eudiWalletOidcIos/Service/CredentialValidation/ExpiryValidator.swift +++ b/Sources/eudiWalletOidcIos/Service/CredentialValidation/ExpiryValidator.swift @@ -8,5 +8,22 @@ import Foundation class ExpiryValidator { + + static func validateExpiryDate(jwt: String?) -> Bool? { + guard let split = jwt?.split(separator: "."), + let jsonString = "\(split[1])".decodeBase64(), + let jsonObject = UIApplicationUtils.shared.convertStringToDictionary(text: jsonString) else { return false } + guard let vc = jsonObject["vc"] as? [String: Any], let expirationDate = vc["expirationDate"] as? String else { return false } + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'" + dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) + guard let expiryDate = dateFormatter.date(from: expirationDate) else { return false} + let currentDate = Date() + if currentDate <= expiryDate { + return false + } else { + return true + } + } } diff --git a/Sources/eudiWalletOidcIos/Service/CredentialValidation/SignatureValidator.swift b/Sources/eudiWalletOidcIos/Service/CredentialValidation/SignatureValidator.swift index 06cb021..e5fb0c2 100644 --- a/Sources/eudiWalletOidcIos/Service/CredentialValidation/SignatureValidator.swift +++ b/Sources/eudiWalletOidcIos/Service/CredentialValidation/SignatureValidator.swift @@ -6,7 +6,155 @@ // import Foundation +import Base58Swift +import Security +import CryptoKit class SignatureValidator { + static func validateSign(jwt: String?, jwksURI: String?) async -> Bool? { + var jwk: [String: Any] = [:] + guard let split = jwt?.split(separator: "."), + let jsonString = "\(split[0])".decodeBase64(), + let jsonObject = UIApplicationUtils.shared.convertStringToDictionary(text: jsonString) else { return false } + if let kid = jsonObject["kid"] as? String { + if kid.hasPrefix("did:key:z") { + jwk = processJWKfromKid(did: kid) + } else if kid.hasPrefix("did:ebsi:z") { + jwk = await processJWKforEBSI(did: kid) + } else { + jwk = await processJWKFromJwksURI2(kid: kid, jwksURI: jwksURI) + } + } else { + let kid = jsonObject["kid"] as? String + jwk = await processJWKFromJwksURI2(kid: kid, jwksURI: jwksURI) + } + return validateSignature(jwt: jwt, jwk: jwk) + } + + + static func processJWKfromKid(did: String?) -> [String: Any] { + guard let did = did else { return [:]} + let components = did.split(separator: "#") + guard let didPart = components.first else { + return [:] + } + return DidService.shared.createJWKfromDID(did: String(didPart)) + } + + static func processJWKforEBSI(did: String?) async -> [String: Any]{ + guard let did = did else { return [:]} + let ebsiEndPoint = "https://api-conformance.ebsi.eu/did-registry/v5/identifiers/\(did)" + do { + guard let url = URL(string: ebsiEndPoint) else { return [:]} + let (data, response) = try await URLSession.shared.data(from: url) + guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { return [:]} + guard let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], let verificationMethods = jsonObject["verificationMethod"] as? [[String: Any]] else { return [:]} + for data in verificationMethods { + if let publicKeyJwk = data["publicKeyJwk"] as? [String: Any], let crv = publicKeyJwk["crv"] as? String, crv == "P-256" { + return publicKeyJwk + } + } + } catch { + print("error") + } + return [:] + } + + static func processJWKFromJwksURI2(kid: String?, jwksURI: String?) async -> [String: Any] { + guard let jwksURI = jwksURI else {return [:]} + return await fetchJwkData(kid: kid, jwksUri: jwksURI) + } + + static func fetchJwkData(kid: String?, jwksUri: String)async -> [String: Any] { + guard let url = URL(string: jwksUri) else { + return [:] + } + do { + let (data, response) = try await URLSession.shared.data(from: url) + guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { return [:]} + guard let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], let keys = jsonObject["keys"] as? [[String: Any]] else { return [:]} + + var jwkKey: [String: Any]? = keys.first { $0["use"] as? String == "sig" } + + if jwkKey == nil, let kid = kid { + jwkKey = keys.first { $0["kid"] as? String == kid } + } + return jwkKey ?? [:] + + } catch { + print("error") + } + return [:] + } + + static private func validateSignature(jwt: String?, jwk: [String: Any]) -> Bool? { + let segments = jwt?.split(separator: ".") + guard segments?.count == 3 else { + return true + } + let headerData = String(segments?[0] ?? "") + let payloadData = String(segments?[1] ?? "") + var sigatureData = String(segments?[2] ?? "") + if sigatureData.contains("~") { + let splitData = sigatureData.split(separator: "~") + sigatureData = String(splitData[0]) + } + guard let headerEncoded = Data(base64URLEncoded: headerData) else { return false } + guard let signatureEncoded = Data(base64URLEncoded: sigatureData) else { return false } + guard let headerJson = try? JSONSerialization.jsonObject(with: headerEncoded, options: []) as? [String: Any], let alg = headerJson["alg"] as? String else { + return false + } + guard let crv = jwk["crv"] as? String else { + return false + } + let algToCrvMap: [String: String] = [ + "ES256": "P-256", + "ES384": "P-384", + "ES512": "P-521" + ] + if let expectedCrv = algToCrvMap[alg], expectedCrv != crv { + return false + } + guard let publicKey = extractPublicKey(from: jwk) else { + return false + } + + let signedData = "\(headerData).\(payloadData)".data(using: .utf8)! + let isVerified = verifySignature(signature: signatureEncoded, for: signedData, using: publicKey) + + return isVerified + } + + static private func extractPublicKey(from jwk: [String: Any]) -> P256.Signing.PublicKey? { + guard let crv = jwk["crv"] as? String, crv == "P-256", + let x = jwk["x"] as? String, + let y = jwk["y"] as? String, + let xData = Data(base64URLEncoded: x), + let yData = Data(base64URLEncoded: y) else { + return nil + } + + var publicKeyData = Data() + publicKeyData.append(0x04) + publicKeyData.append(xData) + publicKeyData.append(yData) + + do { + let publicKey = try P256.Signing.PublicKey(x963Representation: publicKeyData) + return publicKey + } catch { + print("Error creating public key: \(error)") + return nil + } + } + + static private func verifySignature(signature: Data, for data: Data, using publicKey: P256.Signing.PublicKey) -> Bool { + guard let ecdsaSignature = try? P256.Signing.ECDSASignature(rawRepresentation: signature) else { + print("Error converting signature to ECDSASignature") + return false + } + return publicKey.isValidSignature(ecdsaSignature, for: data) + } + } diff --git a/Sources/eudiWalletOidcIos/Service/DidService.swift b/Sources/eudiWalletOidcIos/Service/DidService.swift index 2ab6af2..9777097 100644 --- a/Sources/eudiWalletOidcIos/Service/DidService.swift +++ b/Sources/eudiWalletOidcIos/Service/DidService.swift @@ -146,4 +146,29 @@ public class DidService { } return nil } + + public func createJWKfromDID(did: String?) -> [String: Any] { + do { + guard let did = did else { return [:]} + let multibaseString = String(did.dropFirst("did:key:z".count)) + + guard let decodedData = Base58.base58Decode(multibaseString) else { + print("Failed to decode Multibase string") + return [:] + } + + let multicodecPrefixLength = 3 + guard decodedData.count > multicodecPrefixLength else { + print("Invalid decoded data length") + return [:] + } + let jsonData = Data(decodedData.dropFirst(multicodecPrefixLength)) + + let jwk = try JSONSerialization.jsonObject(with: jsonData, options: []) + return jwk as? [String: Any] ?? [:] + } catch { + print("Error: \(error)") + return [:] + } + } } From 4e445eeea630a40a1cbc607e8b6e8a3462fb6b4a Mon Sep 17 00:00:00 2001 From: Joseph Milan Date: Fri, 26 Jul 2024 21:15:54 +0530 Subject: [PATCH 31/33] Fix #36: Support value for reference in dynamic credential request --- Sources/eudiWalletOidcIos/Service/IssueService.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/eudiWalletOidcIos/Service/IssueService.swift b/Sources/eudiWalletOidcIos/Service/IssueService.swift index 2aed917..63225bf 100644 --- a/Sources/eudiWalletOidcIos/Service/IssueService.swift +++ b/Sources/eudiWalletOidcIos/Service/IssueService.swift @@ -169,7 +169,10 @@ public class IssueService: NSObject, IssueServiceProtocol { } - if responseUrl.contains("code=") || responseUrl.contains("error=") || responseUrl.contains("presentation_definition="){ + if responseUrl.contains("code=") || + responseUrl.contains("error=") || + responseUrl.contains("presentation_definition=") || + (responseUrl.contains("request_uri=") && !responseUrl.contains("response_type=") && !responseUrl.contains("state=")){ return responseUrl } else { // if 'code' is not present From cf17aec0b24e047e6dd3acc3be09f8b46bc58062 Mon Sep 17 00:00:00 2001 From: "Josep Milan K.A" Date: Fri, 9 Aug 2024 19:55:35 +0530 Subject: [PATCH 32/33] Fix #38 - Taking format from the presentation definition --- .../Service/VerificationService.swift | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/Sources/eudiWalletOidcIos/Service/VerificationService.swift b/Sources/eudiWalletOidcIos/Service/VerificationService.swift index a368739..ced041b 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationService.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationService.swift @@ -185,17 +185,21 @@ public class VerificationService: NSObject, VerificationServiceProtocol { } catch { presentationDefinition = nil } + //encoding is done because '+' was removed by the URL session + let formatKey = presentationDefinition?.format?.first(where: { key, _ in key.contains("vc") })?.key ?? "" + let format = formatKey == "vcsd-jwt" ? "vc+sd-jwt" : formatKey + let encodedFormat = format.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed.union(CharacterSet(charactersIn: "+")).subtracting(CharacterSet(charactersIn: "+")))?.replacingOccurrences(of: "+", with: "%2B") if let inputDescriptors = presentationDefinition?.inputDescriptors { for index in 0.. WrappedVerificationResponse? { @@ -229,9 +233,14 @@ public class VerificationService: NSObject, VerificationServiceProtocol { let httpres = response as? HTTPURLResponse - if httpres?.statusCode == 302, let location = httpres?.value(forHTTPHeaderField: "Location"){ - responseUrl = location - return WrappedVerificationResponse(data: responseUrl, error: nil) + + if httpres?.statusCode == 302 || httpres?.statusCode == 200 { + if let location = httpres?.value(forHTTPHeaderField: "Location") { + responseUrl = location + return WrappedVerificationResponse(data: responseUrl, error: nil) + } else { + return WrappedVerificationResponse(data: "https://www.example.com?code=1", error: nil) + } } else if httpres?.statusCode ?? 400 >= 400 { return WrappedVerificationResponse(data: nil, error: ErrorHandler.processError(data: data)) } else{ From 20e9b2b93a5f993225f49f90a766fa808c6ba7e4 Mon Sep 17 00:00:00 2001 From: "Josep Milan K.A" Date: Mon, 12 Aug 2024 11:09:10 +0530 Subject: [PATCH 33/33] Fix #40 : Update presentation exchange sdk --- Package.swift | 2 +- .../Service/VerificationService.swift | 17 ++++++----------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Package.swift b/Package.swift index f22c147..72c03ae 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ let package = Package( dependencies: [ .package(url: "https://github.com/keefertaylor/Base58Swift.git", branch: "master"), .package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "1.8.1"), - .package(url: "https://github.com/decentralised-dataexchange/PresentationExchangeSdkiOS.git", .upToNextMajor(from: "2024.3.1")), + .package(url: "https://github.com/decentralised-dataexchange/PresentationExchangeSdkiOS.git", .upToNextMajor(from: "2024.8.1")), .package(url: "https://github.com/airsidemobile/JOSESwift.git", from: "2.3.0"), .package(url: "https://github.com/apple/swift-crypto.git", from:"3.5.2") ], diff --git a/Sources/eudiWalletOidcIos/Service/VerificationService.swift b/Sources/eudiWalletOidcIos/Service/VerificationService.swift index ced041b..46c8f4d 100644 --- a/Sources/eudiWalletOidcIos/Service/VerificationService.swift +++ b/Sources/eudiWalletOidcIos/Service/VerificationService.swift @@ -304,19 +304,14 @@ public class VerificationService: NSObject, VerificationServiceProtocol { fatalError("Failed to convert dictionary to string") } - let matchesString = matchCredentials(inputDescriptorJson: inputDescriptorString, credentials: processedCredentials) - // Assuming `matchesString` contains a JSON array of matches - if let matchesData = matchesString.data(using: .utf8), - let matchesArray = try? JSONSerialization.jsonObject(with: matchesData) as? [String: Any], - let matchedCredentials = matchesArray["MatchedCredentials"] as? [[String: Any]] { - // Now you have access to the "MatchedCredentials" list - for index in 0..