diff --git a/Sources/eudiWalletOidcIos/Service/CredentialValidation/PublicKeyExtraction/ProcessEbsiJWKFromKID.swift b/Sources/eudiWalletOidcIos/Service/CredentialValidation/PublicKeyExtraction/ProcessEbsiJWKFromKID.swift new file mode 100644 index 0000000..31ea560 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Service/CredentialValidation/PublicKeyExtraction/ProcessEbsiJWKFromKID.swift @@ -0,0 +1,56 @@ +// +// File.swift +// +// +// Created by oem on 10/10/24. +// + +import Foundation + +class ProcessEbsiJWKFromKID { + + 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)" + let pilotEndpoint = "https://api-pilot.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 else { return [:] } + + if httpResponse.statusCode == 200 { + // Process the response from the first URL + return try processPublicKeyFromJWKList(data) + } else { + // Call the fallback URL if the status is not 200 + return try await fetchJWKListFromUrl(pilotEndpoint) + } + } catch { + print("Error fetching from primary URL: \(error)") + } + return [:] + } + + private static func processPublicKeyFromJWKList(_ data: Data) throws -> [String: Any] { + guard let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], + let verificationMethods = jsonObject["verificationMethod"] as? [[String: Any]] else { return [:] } + + for method in verificationMethods { + if let publicKeyJwk = method["publicKeyJwk"] as? [String: Any], + let crv = publicKeyJwk["crv"] as? String, crv == "P-256" { + return publicKeyJwk + } + } + return [:] + } + + private static func fetchJWKListFromUrl(_ fallbackURL: String) async throws -> [String: Any] { + guard let url = URL(string: fallbackURL) else { return [:] } + let (data, response) = try await URLSession.shared.data(from: url) + guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { return [:] } + + return try processPublicKeyFromJWKList(data) + } +} + diff --git a/Sources/eudiWalletOidcIos/Service/CredentialValidation/PublicKeyExtraction/ProcessJWKFromJwksUri.swift b/Sources/eudiWalletOidcIos/Service/CredentialValidation/PublicKeyExtraction/ProcessJWKFromJwksUri.swift new file mode 100644 index 0000000..067f727 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Service/CredentialValidation/PublicKeyExtraction/ProcessJWKFromJwksUri.swift @@ -0,0 +1,38 @@ +// +// File.swift +// +// +// Created by oem on 10/10/24. +// + +import Foundation + +class ProcessJWKFromJwksUri { + + 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 [:] + } +} diff --git a/Sources/eudiWalletOidcIos/Service/CredentialValidation/PublicKeyExtraction/ProcessJWKFromKID.swift b/Sources/eudiWalletOidcIos/Service/CredentialValidation/PublicKeyExtraction/ProcessJWKFromKID.swift new file mode 100644 index 0000000..ac621e6 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Service/CredentialValidation/PublicKeyExtraction/ProcessJWKFromKID.swift @@ -0,0 +1,26 @@ +// +// File.swift +// +// +// Created by oem on 10/10/24. +// + +import Foundation + +class ProcessJWKFromKID { + static func parseDIDJWK(_ didJwk: String) -> [String: Any]? { + guard didJwk.hasPrefix("did:jwk:") else { + return nil + } + + let base64UrlValue = didJwk.replacingOccurrences(of: "did:jwk:", with: "") + + guard let jsonString = base64UrlValue.decodeBase64(), + let jsonData = jsonString.data(using: .utf8), + let jwk = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] else { + return nil + } + + return jwk + } +} diff --git a/Sources/eudiWalletOidcIos/Service/CredentialValidation/PublicKeyExtraction/ProcessKeyJWKFromKID.swift b/Sources/eudiWalletOidcIos/Service/CredentialValidation/PublicKeyExtraction/ProcessKeyJWKFromKID.swift new file mode 100644 index 0000000..1d0a5f6 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Service/CredentialValidation/PublicKeyExtraction/ProcessKeyJWKFromKID.swift @@ -0,0 +1,19 @@ +// +// File.swift +// +// +// Created by oem on 10/10/24. +// + +import Foundation + +class ProcessKeyJWKFromKID { + 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)) + } +} diff --git a/Sources/eudiWalletOidcIos/Service/CredentialValidation/PublicKeyExtraction/ProcessWebJWKFromKID.swift b/Sources/eudiWalletOidcIos/Service/CredentialValidation/PublicKeyExtraction/ProcessWebJWKFromKID.swift new file mode 100644 index 0000000..9ab3358 --- /dev/null +++ b/Sources/eudiWalletOidcIos/Service/CredentialValidation/PublicKeyExtraction/ProcessWebJWKFromKID.swift @@ -0,0 +1,35 @@ +// +// File.swift +// +// +// Created by oem on 10/10/24. +// + +import Foundation + +class ProcessWebJWKFromKID { + static func fetchDIDDocument(did: String) async throws -> [String: Any]? { + guard did.hasPrefix("did:web:") else { return nil } + + let didWithoutPrefix = did.replacingOccurrences(of: "did:web:", with: "") + let didParts = didWithoutPrefix.split(separator: ":") + + guard didParts.count > 1 else { return nil } + + let host = didParts[0] + let path = didParts[1].split(separator: "#").first ?? "" + let didDocURLString = "https://\(host)/\(path)/did.json" + + guard let didDocURL = URL(string: didDocURLString) else { return nil } + + let (data, response) = try await URLSession.shared.data(from: didDocURL) + + guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { + return nil + } + + let didDoc = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] + + return didDoc + } +} diff --git a/Sources/eudiWalletOidcIos/Service/CredentialValidation/SignatureValidator.swift b/Sources/eudiWalletOidcIos/Service/CredentialValidation/SignatureValidator.swift index 9fd0fec..a350d23 100644 --- a/Sources/eudiWalletOidcIos/Service/CredentialValidation/SignatureValidator.swift +++ b/Sources/eudiWalletOidcIos/Service/CredentialValidation/SignatureValidator.swift @@ -14,107 +14,40 @@ class SignatureValidator { static func validateSign(jwt: String?, jwksURI: String?, format: String) async -> Bool? { var jwk: [String: Any] = [:] - if format == "mso_mdoc" { - return true + if format == "mso_mdoc" { + return true } else { guard let split = jwt?.split(separator: "."), split.count > 1 else { return true} guard 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) + let jsonObject = UIApplicationUtils.shared.convertStringToDictionary(text: jsonString) else { return false } + if var kid = jsonObject["kid"] as? String { + if kid.hasPrefix("did:jwk:") { + if let parsedJWK = ProcessJWKFromKID.parseDIDJWK(kid) { + jwk = parsedJWK + } + } else if kid.hasPrefix("did:key:z") { + jwk = ProcessKeyJWKFromKID.processJWKfromKid(did: kid) + } else if kid.hasPrefix("did:ebsi:z") { + jwk = await ProcessEbsiJWKFromKID.processJWKforEBSI(did: kid) + } else if kid.hasPrefix("did:web:") { + if let didDocument = try? await ProcessWebJWKFromKID.fetchDIDDocument(did: kid), + let verificationMethod = didDocument["verificationMethod"] as? [[String: Any]], + let publicKeyJwk = verificationMethod.first?["publicKeyJwk"] as? [String: Any] { + jwk = publicKeyJwk + } else { + print("Failed to fetch or parse DID document for did:web") + return false + } + + } else { + jwk = await ProcessJWKFromJwksUri.processJWKFromJwksURI2(kid: kid, jwksURI: jwksURI) + } } else { - jwk = await processJWKFromJwksURI2(kid: kid, jwksURI: jwksURI) + let kid = jsonObject["kid"] as? String + jwk = await ProcessJWKFromJwksUri.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) } - 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)" - let pilotEndpoint = "https://api-pilot.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 else { return [:] } - - if httpResponse.statusCode == 200 { - // Process the response from the first URL - return try processPublicKeyFromJWKList(data) - } else { - // Call the fallback URL if the status is not 200 - return try await fetchJWKListFromUrl(pilotEndpoint) - } - } catch { - print("Error fetching from primary URL: \(error)") - } - return [:] - } - - private static func processPublicKeyFromJWKList(_ data: Data) throws -> [String: Any] { - guard let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], - let verificationMethods = jsonObject["verificationMethod"] as? [[String: Any]] else { return [:] } - - for method in verificationMethods { - if let publicKeyJwk = method["publicKeyJwk"] as? [String: Any], - let crv = publicKeyJwk["crv"] as? String, crv == "P-256" { - return publicKeyJwk - } - } - return [:] - } - - private static func fetchJWKListFromUrl(_ fallbackURL: String) async throws -> [String: Any] { - guard let url = URL(string: fallbackURL) else { return [:] } - let (data, response) = try await URLSession.shared.data(from: url) - guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { return [:] } - - return try processPublicKeyFromJWKList(data) - } - - 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? { @@ -219,5 +152,4 @@ class SignatureValidator { return false } } - }