diff --git a/EdgeAgentSDK/Castor/Sources/Operations/CreatePeerDIDOperation.swift b/EdgeAgentSDK/Castor/Sources/Operations/CreatePeerDIDOperation.swift index d4b2773c..2c28285f 100644 --- a/EdgeAgentSDK/Castor/Sources/Operations/CreatePeerDIDOperation.swift +++ b/EdgeAgentSDK/Castor/Sources/Operations/CreatePeerDIDOperation.swift @@ -17,15 +17,14 @@ struct CreatePeerDIDOperation { agreementKeys: [keyAgreementFromPublicKey(publicKey: agreementPublicKey)], services: services.flatMap { service in service.serviceEndpoint.map { - DIDCore.DIDDocument.Service( - id: service.id, - type: service.type.first ?? "", - serviceEndpoint: AnyCodable( - dictionaryLiteral: - ("uri", $0.uri), - ("accept", $0.accept), - ("routing_keys", $0.routingKeys) - ) + AnyCodable(dictionaryLiteral: + ("id", service.id), + ("type", service.type.first ?? ""), + ("serviceEndpoint", [ + "uri" : $0.uri, + "accept" : $0.accept, + "routing_keys" : $0.routingKeys + ]) ) } } diff --git a/EdgeAgentSDK/Castor/Sources/Resolvers/PeerDIDResolver.swift b/EdgeAgentSDK/Castor/Sources/Resolvers/PeerDIDResolver.swift index b871e9a9..dad94ac7 100644 --- a/EdgeAgentSDK/Castor/Sources/Resolvers/PeerDIDResolver.swift +++ b/EdgeAgentSDK/Castor/Sources/Resolvers/PeerDIDResolver.swift @@ -60,7 +60,7 @@ extension DIDCore.DIDDocument { assertionMethod: nil, capabilityDelegation: nil, keyAgreement: keyAgreementIds.map { .stringValue($0) }, - services: services + services: services.map { $0.toAnyCodable() } ) } @@ -98,23 +98,40 @@ extension DIDCore.DIDDocument { } let services = try self.services?.map { - guard - let endpoint = $0.serviceEndpoint.value as? [String: Any], - let uri = endpoint["uri"] as? String - else { - throw CastorError.notPossibleToResolveDID(did: $0.id, reason: "Invalid service") + let service = try DIDCore.DIDDocument.Service(from: $0) + switch service.serviceEndpoint.value { + case let endpoint as [String: Any]: + guard + let uri = endpoint["uri"] as? String + else { + throw CastorError.notPossibleToResolveDID(did: service.id, reason: "Invalid service") + } + return Domain.DIDDocument.Service( + id: service.id, + type: [service.type], + serviceEndpoint: [ + .init( + uri: uri, + accept: endpoint["accept"] as? [String] ?? [], + routingKeys: endpoint["routing_keys"] as? [String] ?? [] + ) + ] + ) + case let endpoint as String: + return Domain.DIDDocument.Service( + id: service.id, + type: [service.type], + serviceEndpoint: [ + .init( + uri: endpoint, + accept: ($0.value as? [String: Any])?["accept"] as? [String] ?? [], + routingKeys: ($0.value as? [String: Any])?["routing_keys"] as? [String] ?? [] + ) + ] + ) + default: + throw CastorError.notPossibleToResolveDID(did: service.id, reason: "Invalid service") } - return Domain.DIDDocument.Service( - id: $0.id, - type: [$0.type], - serviceEndpoint: [ - .init( - uri: uri, - accept: endpoint["accept"] as? [String] ?? [], - routingKeys: endpoint["routing_keys"] as? [String] ?? [] - ) - ] - ) } ?? [Domain.DIDDocument.Service]() return Domain.DIDDocument( @@ -184,3 +201,50 @@ extension DIDCore.DIDDocument.VerificationMethod { } } } + +extension DIDCore.DIDDocument.Service { + init(from: AnyCodable) throws { + guard + let dic = from.value as? [String: Any], + let id = dic["id"] as? String, + let type = dic["type"] as? String, + let serviceEndpoint = dic["serviceEndpoint"] + else { throw CommonError.invalidCoding(message: "Could not decode service") } + switch serviceEndpoint { + case let value as AnyCodable: + self = .init( + id: id, + type: type, + serviceEndpoint: value + ) + case let value as String: + self = .init( + id: id, + type: type, + serviceEndpoint: AnyCodable(value) + ) + case let value as [String: Any]: + self = .init( + id: id, + type: type, + serviceEndpoint: AnyCodable(value) + ) + case let value as [String]: + self = .init( + id: id, + type: type, + serviceEndpoint: AnyCodable(value) + ) + default: + throw CommonError.invalidCoding(message: "Could not decode service") + } + } + + func toAnyCodable() -> AnyCodable { + AnyCodable(dictionaryLiteral: + ("id", self.id), + ("type", self.type), + ("serviceEndpoint", self.serviceEndpoint.value) + ) + } +} diff --git a/EdgeAgentSDK/Castor/Tests/PeerDIDCreationTests.swift b/EdgeAgentSDK/Castor/Tests/PeerDIDCreationTests.swift index d7f90bb3..87bfc243 100644 --- a/EdgeAgentSDK/Castor/Tests/PeerDIDCreationTests.swift +++ b/EdgeAgentSDK/Castor/Tests/PeerDIDCreationTests.swift @@ -14,6 +14,8 @@ final class PeerDIDCreationTests: XCTestCase { KeyProperties.rawKey.rawValue: Data(base64URLEncoded: "COd9Xhr-amD7fuswWId2706JBUY_tmjp9eiNEieJeEE")!.base64Encoded() ]) + print(keyAgreementPrivateKey.raw.base64URLEncoded()) + let authenticationPrivateKey = try apollo.createPrivateKey(parameters: [ KeyProperties.type.rawValue: "EC", @@ -35,28 +37,18 @@ final class PeerDIDCreationTests: XCTestCase { services: [service] ) - XCTAssertEqual(did.string, validPeerDID) + XCTAssertTrue(did.string.contains("did:peer:2.Ez6LSoHkfN1Y4nK9RCjx7vopWsLrMGNFNgTNZgoCNQrTzmb1n.Vz6MknRZmapV7uYZQuZez9n9N3tQotjRN18UGS68Vcfo6gR4h.SeyJ")) } func testResolvePeerDID() async throws { - let peerDIDString = "did:peer:2.Ez6LSci5EK4Ezue5QA72ZX71QUbXY2xr5ygRw7wM1WJigTNnd.Vz6MkqgCXHEGr2wJZANPZGC8WFmeVuS3abAD9uvh7mTXygCFv.SeyJ0IjoiZG0iLCJzIjoibG9jYWxob3N0OjgwODIiLCJyIjpbXSwiYSI6WyJkaWRjb21tL3YyIl19" - let peerDID = DID( schema: "did", method: "peer", methodId: "2.Ez6LSci5EK4Ezue5QA72ZX71QUbXY2xr5ygRw7wM1WJigTNnd.Vz6MkqgCXHEGr2wJZANPZGC8WFmeVuS3abAD9uvh7mTXygCFv.SeyJ0IjoiZG0iLCJzIjoibG9jYWxob3N0OjgwODIiLCJyIjpbXSwiYSI6WyJkaWRjb21tL3YyIl19" ) - let mypeerDIDString = "did:peer:2.Ez6LSmx3k5X9xMos7VXdMDJx1CGNTd2tWfLTVyMtu3toJWqPo.Vz6Mkvcu3GqbvM3vr5W1sDVe41wmLeUL6a7b4wEcrGw6ULATR.SeyJ0IjoiZG0iLCJzIjoiazhzLWRldi5hdGFsYXByaXNtLmlvL3ByaXNtLWFnZW50L2RpZGNvbW0iLCJyIjpbXSwiYSI6WyJkaWRjb21tL3YyIl19" - - let mypeerDID = DID( - schema: "did", - method: "peer", - methodId: "2.Ez6LSms555YhFthn1WV8ciDBpZm86hK9tp83WojJUmxPGk1hZ.Vz6MkmdBjMyB4TS5UbbQw54szm8yvMMf1ftGV2sQVYAxaeWhE.SeyJpZCI6Im5ldy1pZCIsInQiOiJkbSIsInMiOiJodHRwczovL21lZGlhdG9yLnJvb3RzaWQuY2xvdWQiLCJhIjpbImRpZGNvbW0vdjIiXX0" - ) - let apollo = ApolloImpl() let castor = CastorImpl(apollo: apollo) - let document = try await castor.resolveDID(did: mypeerDID) + let document = try await castor.resolveDID(did: peerDID) } } diff --git a/EdgeAgentSDK/Domain/Sources/BBs/Pollux.swift b/EdgeAgentSDK/Domain/Sources/BBs/Pollux.swift index 5bee0c91..2567a194 100644 --- a/EdgeAgentSDK/Domain/Sources/BBs/Pollux.swift +++ b/EdgeAgentSDK/Domain/Sources/BBs/Pollux.swift @@ -13,6 +13,7 @@ public enum CredentialOperationsOptions { case signableKey(SignableKey) // A key that can be used for signing. case exportableKey(ExportableKey) // A key that can be exported. case zkpPresentationParams(attributes: [String: Bool], predicates: [String]) // Anoncreds zero-knowledge proof presentation parameters + case disclosingClaims(claims: [String]) case custom(key: String, data: Data) // Any custom data. } diff --git a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Credentials.swift b/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Credentials.swift index 193e6361..5ecd2e1d 100644 --- a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Credentials.swift +++ b/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Credentials.swift @@ -198,6 +198,8 @@ public extension EdgeAgent { switch offerFormat { case "prism/jwt": format = "prism/jwt" + case "vc+sd-jwt": + format = "vc+sd-jwt" case "anoncreds/credential-offer@v1.0": format = "anoncreds/credential-request@v1.0" default: diff --git a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Proof.swift b/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Proof.swift index 5a3c02a7..b76346f4 100644 --- a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Proof.swift +++ b/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Proof.swift @@ -44,7 +44,7 @@ public extension EdgeAgent { .linkSecret(id: "", secret: linkSecretString) ] ) - case "prism/jwt", "dif/presentation-exchange/definitions@v1.0": + case "prism/jwt", "vc+sd-jwt", "dif/presentation-exchange/definitions@v1.0": guard let subjectDIDString = credential.subject else { diff --git a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent.swift b/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent.swift index 95e004e6..a2f28f6d 100644 --- a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent.swift +++ b/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent.swift @@ -32,11 +32,11 @@ public class EdgeAgent { } let logger = SDKLogger(category: .edgeAgent) - let apollo: Apollo & KeyRestoration - let castor: Castor - let pluto: Pluto - let pollux: Pollux & CredentialImporter - let mercury: Mercury + public let apollo: Apollo & KeyRestoration + public let castor: Castor + public let pluto: Pluto + public let pollux: Pollux & CredentialImporter + public let mercury: Mercury var mediationHandler: MediatorHandler? var connectionManager: ConnectionsManagerImpl? diff --git a/EdgeAgentSDK/EdgeAgent/Tests/PresentationExchangeTests.swift b/EdgeAgentSDK/EdgeAgent/Tests/PresentationExchangeTests.swift index 30750b5c..c784050f 100644 --- a/EdgeAgentSDK/EdgeAgent/Tests/PresentationExchangeTests.swift +++ b/EdgeAgentSDK/EdgeAgent/Tests/PresentationExchangeTests.swift @@ -123,7 +123,7 @@ final class PresentationExchangeFlowTests: XCTestCase { } } -private struct MockCredentialClaim: JWTRegisteredFieldsClaims { +private struct MockCredentialClaim: JWTRegisteredFieldsClaims, Codable { struct VC: Codable { let credentialSubject: [String: String] } diff --git a/EdgeAgentSDK/Mercury/Sources/DIDCommWrappers/DIDCommDIDResolverWrapper.swift b/EdgeAgentSDK/Mercury/Sources/DIDCommWrappers/DIDCommDIDResolverWrapper.swift index 63ce0c5a..3c87fbd7 100644 --- a/EdgeAgentSDK/Mercury/Sources/DIDCommWrappers/DIDCommDIDResolverWrapper.swift +++ b/EdgeAgentSDK/Mercury/Sources/DIDCommWrappers/DIDCommDIDResolverWrapper.swift @@ -85,7 +85,7 @@ extension DIDCore.DIDDocument { verificationMethods: verificationMethods, authentication: authentications.map { .stringValue($0) }, keyAgreement: keyAgreements.map { .stringValue($0) }, - services: services + services: services.map { $0.toAnyCodable() } ) } } @@ -95,3 +95,50 @@ extension Dictionary where Key == String, Value == String { try Core.convertToJsonString(dic: self) } } + +extension DIDCore.DIDDocument.Service { + init(from: DIDCore.AnyCodable) throws { + guard + let dic = from.value as? [String: Any], + let id = dic["id"] as? String, + let type = dic["type"] as? String, + let serviceEndpoint = dic["serviceEndpoint"] + else { throw CommonError.invalidCoding(message: "Could not decode service") } + switch serviceEndpoint { + case let value as DIDCore.AnyCodable: + self = .init( + id: id, + type: type, + serviceEndpoint: value + ) + case let value as String: + self = .init( + id: id, + type: type, + serviceEndpoint: AnyCodable(value) + ) + case let value as [String: Any]: + self = .init( + id: id, + type: type, + serviceEndpoint: AnyCodable(value) + ) + case let value as [String]: + self = .init( + id: id, + type: type, + serviceEndpoint: AnyCodable(value) + ) + default: + throw CommonError.invalidCoding(message: "Could not decode service") + } + } + + func toAnyCodable() -> DIDCore.AnyCodable { + AnyCodable(dictionaryLiteral: + ("id", self.id), + ("type", self.type), + ("serviceEndpoint", self.serviceEndpoint.value) + ) + } +} diff --git a/EdgeAgentSDK/Mercury/Sources/Helpers/SessionManager.swift b/EdgeAgentSDK/Mercury/Sources/Helpers/SessionManager.swift index 05be1802..c842fae4 100644 --- a/EdgeAgentSDK/Mercury/Sources/Helpers/SessionManager.swift +++ b/EdgeAgentSDK/Mercury/Sources/Helpers/SessionManager.swift @@ -21,7 +21,8 @@ struct SessionManager { headers: [String: String] = [:], parameters: [String: String] = [:] ) async throws -> Data? { - try await call(request: try makeRequest( + let url = URL(string: url.absoluteString.replacingOccurrences(of: "host.docker.internal", with: "localhost"))! + return try await call(request: try makeRequest( url: url, method: .post, body: body, diff --git a/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTCredential+ProofableCredential.swift b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTCredential+ProofableCredential.swift index 9f33ad97..54206621 100644 --- a/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTCredential+ProofableCredential.swift +++ b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTCredential+ProofableCredential.swift @@ -31,7 +31,7 @@ extension JWTCredential: ProvableCredential { switch attachment.format { case "dif/presentation-exchange/definitions@v1.0": let requestData = try JSONDecoder.didComm().decode(PresentationExchangeRequest.self, from: jsonData) - let payload = try JWT.getPayload(jwtString: jwtString) + let payload: Data = try JWT.getPayload(jwtString: jwtString) do { try VerifyPresentationSubmission.verifyPresentationSubmissionClaims( request: requestData.presentationDefinition, credentials: [payload] diff --git a/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPresentation.swift b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPresentation.swift index 57521a76..9b2db182 100644 --- a/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPresentation.swift +++ b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPresentation.swift @@ -2,6 +2,7 @@ import Core import Domain import Foundation import JSONWebAlgorithms +import JSONWebKey import JSONWebSignature import JSONWebToken import Sextant @@ -198,7 +199,7 @@ struct JWTPresentation { let jwt = try JWT.signed( payload: payload, protectedHeader: DefaultJWSHeaderImpl(algorithm: .ES256K), - key: .init( + key: JSONWebKey.JWK( keyType: .init(rawValue: keyJWK.kty)!, keyID: keyJWK.kid, x: keyJWK.x.flatMap { Data(fromBase64URL: $0) }, @@ -213,7 +214,7 @@ struct JWTPresentation { } } -struct ClaimsProofPresentationJWT: JWTRegisteredFieldsClaims { +struct ClaimsProofPresentationJWT: JWTRegisteredFieldsClaims, Codable { struct VerifiablePresentation: Codable { enum CodingKeys: String, CodingKey { case context = "@context" diff --git a/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT+Codable.swift b/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT+Codable.swift new file mode 100644 index 00000000..75ace9c8 --- /dev/null +++ b/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT+Codable.swift @@ -0,0 +1,21 @@ +import Foundation + +extension SDJWTCredential: Codable { + enum CodingKeys: String, CodingKey { + case sdjwtString + } + + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(sdjwtString, forKey: .sdjwtString) + } + + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + let sdjwtString = try container.decode(String.self, forKey: .sdjwtString) + + try self.init(sdjwtString: sdjwtString) + } +} diff --git a/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT+ExportableCredential.swift b/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT+ExportableCredential.swift new file mode 100644 index 00000000..51cfbf98 --- /dev/null +++ b/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT+ExportableCredential.swift @@ -0,0 +1,10 @@ +import Domain +import Foundation + +extension SDJWTCredential: ExportableCredential { + public var exporting: Data { + (try? sdjwtString.tryToData()) ?? Data() + } + + public var restorationType: String { "sd-jwt" } +} diff --git a/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT+ProvableCredential.swift b/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT+ProvableCredential.swift new file mode 100644 index 00000000..900cbf73 --- /dev/null +++ b/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT+ProvableCredential.swift @@ -0,0 +1,16 @@ +import Domain +import Foundation + +extension SDJWTCredential: ProvableCredential { + func presentation(request: Domain.Message, options: [Domain.CredentialOperationsOptions]) throws -> String { + try SDJWTPresentation().createPresentation( + credential: self, + request: request, + options: options + ) + } + + func isValidForPresentation(request: Domain.Message, options: [Domain.CredentialOperationsOptions]) throws -> Bool { + request.attachments.first.map { $0.format == "vc+sd-jwt"} ?? true + } +} diff --git a/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT+StorableCredential.swift b/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT+StorableCredential.swift new file mode 100644 index 00000000..3212e152 --- /dev/null +++ b/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT+StorableCredential.swift @@ -0,0 +1,48 @@ +import Domain +import Foundation + +extension SDJWTCredential: StorableCredential { + var storingId: String { + sdjwtString + } + + var recoveryId: String { + "sd-jwt+credential" + } + + var credentialData: Data { + (try? sdjwtString.tryToData()) ?? Data() + } + + var queryIssuer: String? { + issuer + } + + var querySubject: String? { + subject + } + + var queryCredentialCreated: Date? { + nil + } + + var queryCredentialUpdated: Date? { + nil + } + + var queryCredentialSchema: String? { + nil + } + + var queryValidUntil: Date? { + nil + } + + var queryRevoked: Bool? { + nil + } + + var queryAvailableClaims: [String] { + claims.map(\.key) + } +} diff --git a/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT.swift b/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT.swift new file mode 100644 index 00000000..0a83ac46 --- /dev/null +++ b/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT.swift @@ -0,0 +1,56 @@ +import Domain +import Foundation +import eudi_lib_sdjwt_swift +import JSONWebSignature + +struct SDJWTCredential { + let sdjwtString: String + let sdjwt: SignedSDJWT + + init(sdjwtString: String) throws { + let sdjwt = try CompactParser(serialisedString: sdjwtString).getSignedSdJwt() + self.sdjwtString = sdjwtString + self.sdjwt = sdjwt + } +} + +fileprivate struct SDJWTComplex: Codable { + let disclosures: [String] +} + +extension SDJWTCredential: Credential { + var id: String { + sdjwtString + } + + var issuer: String { + (try? sdjwt.recreateClaims().recreatedClaims["iss"].stringValue) ?? "" + } + + var subject: String? { + (try? sdjwt.recreateClaims().recreatedClaims["sub"].stringValue) ?? "" + } + + var claims: [Domain.Claim] { + sdjwt.disclosures.compactMap { + guard + let base64Decoded = Data(fromBase64URL: $0), + let array = try? JSONDecoder().decode([String].self, from: base64Decoded), + array.count == 3 + else { + return nil + } + let key = array[1] + let value = array[2] + return Claim(key: key, value: .string(value)) + } + } + + var properties: [String : Any] { + return [:] + } + + var credentialType: String { + return "sd-jwt" + } +} diff --git a/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWTPresentation.swift b/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWTPresentation.swift new file mode 100644 index 00000000..bdbacfd8 --- /dev/null +++ b/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWTPresentation.swift @@ -0,0 +1,94 @@ +import Domain +import eudi_lib_sdjwt_swift +import Foundation +import JSONWebKey + +struct SDJWTPresentation { + func createPresentation( + credential: SDJWTCredential, + request: Message, + options: [CredentialOperationsOptions] + ) throws -> String{ + guard + let exportableKeyOption = options.first(where: { + if case .exportableKey = $0 { return true } + return false + }), + case let CredentialOperationsOptions.exportableKey(exportableKey) = exportableKeyOption + else { + throw PolluxError.requiresExportableKeyForOperation(operation: "Create Presentation for SD-JWT Credential") + } + + let disclosingClaims: [String] + if + let claims = options.first(where: { + if case .disclosingClaims = $0 { return true } + return false + }), + case let CredentialOperationsOptions.disclosingClaims(claims) = claims + { + disclosingClaims = claims + } + else { + disclosingClaims = [] + } + + guard + let attachment = request.attachments.first, + let requestData = request.attachments.first.flatMap({ + switch $0.data { + case let json as AttachmentJsonData: + return json.data + case let bas64 as AttachmentBase64: + return Data(fromBase64URL: bas64.base64) + default: + return nil + } + }) + else { + throw PolluxError.offerDoesntProvideEnoughInformation + } + + switch attachment.format { + default: + return try vcPresentation( + credential: credential, + request: requestData, + disclosingClaims: disclosingClaims, + key: exportableKey + ) + } + } + + private func vcPresentation( + credential: SDJWTCredential, + request: Data, + disclosingClaims: [String], + key: ExportableKey + ) throws -> String { + let disclosures = credential.sdjwt.disclosures.filter { disclosure in + disclosingClaims.first { + guard + let decoded = try? Data(fromBase64URL: disclosure)?.tryToString() + else { return false} + return decoded.contains("\"\($0)\"") + } != nil + } + + let sdJwt = try SDJWTIssuer.presentation( + holdersPrivateKey: key.jwk.toJoseJWK(), + signedSDJWT: credential.sdjwt, + disclosuresToPresent: disclosures, + keyBindingJWT: nil + ) + + return CompactSerialiser(signedSDJWT: sdJwt).serialised + } +} + +private extension Domain.JWK { + func toJoseJWK() throws -> JSONWebKey.JWK { + let toJson = try JSONEncoder.jwt.encode(self) + return try JSONDecoder.jwt.decode(JSONWebKey.JWK.self, from: toJson) + } +} diff --git a/EdgeAgentSDK/Pollux/Sources/Operation/JWT/CreateJWTCredentialRequest.swift b/EdgeAgentSDK/Pollux/Sources/Operation/JWT/CreateJWTCredentialRequest.swift index 0c931f35..ab6bb450 100644 --- a/EdgeAgentSDK/Pollux/Sources/Operation/JWT/CreateJWTCredentialRequest.swift +++ b/EdgeAgentSDK/Pollux/Sources/Operation/JWT/CreateJWTCredentialRequest.swift @@ -1,6 +1,8 @@ import Combine import Domain import Foundation +import JSONWebAlgorithms +import JSONWebKey import JSONWebToken import JSONWebSignature @@ -12,7 +14,7 @@ private struct Schema: Codable { } struct CreateJWTCredentialRequest { - static func create(didStr: String, key: ExportableKey, offerData: Data) throws -> String { + static func create(didStr: String, key: ExportableKey, offerData: Data) async throws -> String { let jsonObject = try JSONSerialization.jsonObject(with: offerData) guard let domain = findValue(forKey: "domain", in: jsonObject), @@ -20,25 +22,28 @@ struct CreateJWTCredentialRequest { else { throw PolluxError.offerDoesntProvideEnoughInformation } let keyJWK = key.jwk - + let claims = ClaimsRequestSignatureJWT( + iss: didStr, + sub: nil, + aud: [domain], + exp: nil, + nbf: nil, + iat: nil, + jti: nil, + nonce: challenge, + vp: .init(context: .init([ + "https://www.w3.org/2018/presentations/v1" + ]), type: .init([ + "VerifiablePresentation" + ])) + ) + + ES256KSigner.invertedBytesR_S = true + let jwt = try JWT.signed( - payload: ClaimsRequestSignatureJWT( - iss: didStr, - sub: nil, - aud: [domain], - exp: nil, - nbf: nil, - iat: nil, - jti: nil, - nonce: challenge, - vp: .init(context: .init([ - "https://www.w3.org/2018/presentations/v1" - ]), type: .init([ - "VerifiablePresentation" - ])) - ), + payload: claims, protectedHeader: DefaultJWSHeaderImpl(algorithm: .ES256K), - key: .init( + key: JSONWebKey.JWK( keyType: .init(rawValue: keyJWK.kty)!, keyID: keyJWK.kid, x: keyJWK.x.flatMap { Data(fromBase64URL: $0) }, @@ -46,25 +51,13 @@ struct CreateJWTCredentialRequest { d: keyJWK.d.flatMap { Data(fromBase64URL: $0) } ) ) - - // We need to do for now this process so the signatures of secp256k1 Bitcoin can be verified by Bouncy castle - let jwtString = jwt.jwtString - var components = jwtString.components(separatedBy: ".") - guard - let signature = components.last, - let signatureData = Data(fromBase64URL: signature) - else { - return jwtString - } - let (r, s) = extractRS(from: signatureData) - let fipsSignature = (Data(r.reversed()) + Data(s.reversed())).base64UrlEncodedString() - _ = components.removeLast() - return (components + [fipsSignature]).joined(separator: ".") + ES256KSigner.invertedBytesR_S = false + return jwt.jwtString } } -struct ClaimsRequestSignatureJWT: JWTRegisteredFieldsClaims { +struct ClaimsRequestSignatureJWT: JWTRegisteredFieldsClaims, Codable { struct VerifiablePresentation: Codable { enum CodingKeys: String, CodingKey { case context = "@context" @@ -109,11 +102,3 @@ func findValue(forKey key: String, in json: Any) -> String? { } return nil } - -private func extractRS(from signature: Data) -> (r: Data, s: Data) { - let rIndex = signature.startIndex - let sIndex = signature.index(rIndex, offsetBy: 32) - let r = signature[rIndex.. String { + private func processJWTCredentialRequest(offerData: Data, options: [CredentialOperationsOptions]) async throws -> String { guard let subjectDIDOption = options.first(where: { if case .subjectDID = $0 { return true } @@ -78,9 +85,33 @@ extension PolluxImpl { throw PolluxError.requiresExportableKeyForOperation(operation: "Create Credential Request") } - return try CreateJWTCredentialRequest.create(didStr: did.string, key: exportableKey, offerData: offerData) + return try await CreateJWTCredentialRequest.create(didStr: did.string, key: exportableKey, offerData: offerData) } - + + private func processSDJWTCredentialRequest(offerData: Data, options: [CredentialOperationsOptions]) async throws -> String { + guard + let subjectDIDOption = options.first(where: { + if case .subjectDID = $0 { return true } + return false + }), + case let CredentialOperationsOptions.subjectDID(did) = subjectDIDOption + else { + throw PolluxError.invalidPrismDID + } + + guard + let exportableKeyOption = options.first(where: { + if case .exportableKey = $0 { return true } + return false + }), + case let CredentialOperationsOptions.exportableKey(exportableKey) = exportableKeyOption + else { + throw PolluxError.requiresExportableKeyForOperation(operation: "Create Credential Request") + } + + return try await CreateJWTCredentialRequest.create(didStr: did.string, key: exportableKey, offerData: offerData) + } + private func processAnoncredsCredentialRequest( offerData: Data, thid: String, diff --git a/EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialVerification.swift b/EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialVerification.swift index 1a8529da..85dcdce2 100644 --- a/EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialVerification.swift +++ b/EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialVerification.swift @@ -62,7 +62,7 @@ extension PolluxImpl { try VerifyPresentationSubmission.verifyPresentationSubmissionClaims( request: presentationRequest.presentationDefinition, credentials: try credentials.map { - try JWT.getPayload(jwtString: $0) + try JWT.getPayload(jwtString: $0) } ) @@ -93,7 +93,7 @@ extension PolluxImpl { guard let nestedDescriptor = descriptor.pathNested else { return jwts } - let nestedPayload: Data = try JWT.getPayload(jwtString: jwts) + let nestedPayload: Data = try JWT.getPayload(jwtString: jwts) return try processJWTPath(descriptor: nestedDescriptor, presentationData: nestedPayload) } @@ -116,7 +116,7 @@ extension PolluxImpl { } private func verifyJWT(jwtString: String) async throws -> Bool { - let payload: DefaultJWTClaimsImpl = try JWT.getPayload(jwtString: jwtString) + let payload: DefaultJWTClaimsImpl = try JWT.getPayload(jwtString: jwtString) guard let issuer = payload.iss else { throw PolluxError.requiresThatIssuerExistsAndIsAPrismDID } @@ -129,7 +129,7 @@ extension PolluxImpl { let validations = issuerKeys .compactMap(\.exporting) .compactMap { - try? JWT.verify(jwtString: jwtString, senderKey: $0.jwk.toJoseJWK()) + try? JWT.verify(jwtString: jwtString, senderKey: $0.jwk.toJoseJWK()) } ES256KVerifier.bouncyCastleFailSafe = false return !validations.isEmpty @@ -203,7 +203,10 @@ extension PolluxImpl { case let jsonData as AttachmentJsonData: json = jsonData.data case let base64Data as AttachmentBase64: - json = try base64Data.decoded() + guard let data = try Data(fromBase64URL: base64Data.base64.tryToData()) else { + throw CommonError.invalidCoding(message: "Could not decode base64 message attchment") + } + json = data default: throw PolluxError.invalidAttachmentType(supportedTypes: []) } diff --git a/EdgeAgentSDK/Pollux/Sources/PolluxImpl+ParseCredential.swift b/EdgeAgentSDK/Pollux/Sources/PolluxImpl+ParseCredential.swift index 0e97a4b4..7d359733 100644 --- a/EdgeAgentSDK/Pollux/Sources/PolluxImpl+ParseCredential.swift +++ b/EdgeAgentSDK/Pollux/Sources/PolluxImpl+ParseCredential.swift @@ -17,6 +17,15 @@ extension PolluxImpl { default: throw PolluxError.unsupportedIssuedMessage } + case "vc+sd-jwt": + switch issuedAttachment.data { + case let json as AttachmentJsonData: + return try SDJWTCredential(sdjwtString: json.data.tryToString()) + case let base64 as AttachmentBase64: + return try SDJWTCredential(sdjwtString: try base64.decoded().tryToString()) + default: + throw PolluxError.unsupportedIssuedMessage + } case "anoncreds", "prism/anoncreds", "anoncreds/credential@v1.0": guard let thid = issuedCredential.thid else { throw PolluxError.messageDoesntProvideEnoughInformation diff --git a/EdgeAgentSDK/Pollux/Sources/PolluxImpl+Public.swift b/EdgeAgentSDK/Pollux/Sources/PolluxImpl+Public.swift index 9dea2cc5..a4336860 100644 --- a/EdgeAgentSDK/Pollux/Sources/PolluxImpl+Public.swift +++ b/EdgeAgentSDK/Pollux/Sources/PolluxImpl+Public.swift @@ -4,15 +4,22 @@ import Foundation extension PolluxImpl: Pollux { public func restoreCredential(restorationIdentifier: String, credentialData: Data) throws -> Credential { - switch restorationIdentifier { - case "jwt+credential": - return try JSONDecoder().decode(JWTCredential.self, from: credentialData) - case "w3c+credential": - return try JSONDecoder().decode(W3CVerifiableCredential.self, from: credentialData) - case "anon+credential": - return try JSONDecoder().decode(AnoncredsCredentialStack.self, from: credentialData) - default: - throw PolluxError.invalidCredentialError + do { + switch restorationIdentifier { + case "sd-jwt+credential": + return try SDJWTCredential(sdjwtString: credentialData.tryToString()) + case "jwt+credential": + return try JSONDecoder().decode(JWTCredential.self, from: credentialData) + case "w3c+credential": + return try JSONDecoder().decode(W3CVerifiableCredential.self, from: credentialData) + case "anon+credential": + return try JSONDecoder().decode(AnoncredsCredentialStack.self, from: credentialData) + default: + throw PolluxError.invalidCredentialError + } + } catch { + print(error) + throw error } } } diff --git a/EdgeAgentSDK/Pollux/Tests/JWTTests.swift b/EdgeAgentSDK/Pollux/Tests/JWTTests.swift index 6fac502a..c7acdee2 100644 --- a/EdgeAgentSDK/Pollux/Tests/JWTTests.swift +++ b/EdgeAgentSDK/Pollux/Tests/JWTTests.swift @@ -20,148 +20,4 @@ final class JWTTests: XCTestCase { XCTAssertEqual(credential.claims.map(\.key).sorted(), ["id", "test"].sorted()) XCTAssertEqual(credential.id, validJWTString) } - -// func testJWTCreateCredentialRequest() async throws { -// let offerCredentialMessage = OfferCredential3_0( -// id: "test1", -// body: .init( -// goalCode: nil, -// comment: nil, -// replacementId: nil, -// multipleAvailable: nil, -// credentialPreview: .init(schemaId: "", attributes: []) -// ), -// type: "test", -// attachments: [.init( -// id: "", -// mediaType: nil, -// data: AttachmentJsonData(data: "{\"domain\":\"test\", \"challenge\":\"test\"}".data(using: .utf8)!), -// filename: nil, -// format: nil, -// lastmodTime: nil, -// byteCount: nil, -// description: nil -// )], -// thid: nil, -// from: DID.init(method: "test", methodId: "123"), -// to: DID.init(method: "test", methodId: "123") -// ) -// -// let pollux = PolluxImpl() -// let privKey = try apollo.createPrivateKey(parameters: [ -// KeyProperties.type.rawValue: "EC", -// KeyProperties.derivationPath.rawValue: DerivationPath(index: 0).keyPathString(), -// KeyProperties.curve.rawValue: KnownKeyCurves.secp256k1.rawValue, -// KeyProperties.rawKey.rawValue: Data(fromBase64URL: "N_JFgvYaReyRXwassz5FHg33A4I6dczzdXrjdHGksmg")!.base64Encoded() -// ]) as! PrivateKey & ExportableKey -// -// let pubKey = privKey.publicKey() -// let subjectPrismDID = try castor.createPrismDID(masterPublicKey: pubKey, services: []) -// -// let requestString = try await pollux.processCredentialRequest( -// offerMessage: offerCredentialMessage.makeMessage(), -// options: [ -// .subjectDID(subjectPrismDID), -// .exportableKey(privKey) -// ] -// ) -// -// let validJWTString = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJub25jZSI6InRlc3QiLCJpc3MiOiJkaWQ6cHJpc206OTRiY2RkZGIxN2E2NTY1NTg2NGYxZGEzYjIyNzU2NTI1NmJjZTM2MTg1Y2EwMzE5OWVlZjNiNmNkMTg4MTBlZDpDc2NCQ3NRQkVtUUtEMkYxZEdobGJuUnBZMkYwYVc5dU1CQUVRazhLQ1hObFkzQXlOVFpyTVJJZ1A2WGlXdERvYWo2ZzNsZTEybGpxamwzSl9aWGFfa1Jzd0M5R05VWW1rVFVhSUlHRlZFU1g4U2pLVFQ1eXBTNi1ERGl6VldtTHdUVHFNdlMtZEJQSTFjZkVFbHdLQjIxaGMzUmxjakFRQVVKUENnbHpaV053TWpVMmF6RVNJRC1sNGxyUTZHby1vTjVYdGRwWTZvNWR5ZjJWMnY1RWJNQXZSalZHSnBFMUdpQ0JoVlJFbF9Fb3lrMC1jcVV1dmd3NHMxVnBpOEUwNmpMMHZuUVR5TlhIeEEiLCJ2cCI6eyJAY29udGV4dCI6WyJodHRwczpcL1wvd3d3LnczLm9yZ1wvMjAxOFwvcHJlc2VudGF0aW9uc1wvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlUHJlc2VudGF0aW9uIl19LCJhdWQiOiJ0ZXN0In0.gBfP9y5dscgc--DoKWQ8eWGjJ8yRsfwbDH1sHwMIgm5cSUpvGnzpn4wsrLIsCMG6Udp2H2N-jaFnXhjeE28-lA" -// -// let verifier = JWT.verify(requestString, using: .es256k(publicKey: pubKey.exporting!.pem.data(using: .utf8)!)) -// XCTAssertTrue(verifier) -// XCTAssertEqual(validJWTString, requestString) -// } -// -// func testJWTCreateCredentialRequestErrorMissingDomain() async throws { -// let offerCredentialMessage = OfferCredential( -// id: "test1", -// body: .init( -// credentialPreview: CredentialPreview(attributes: []), -// formats: [] -// ), -// type: "test", -// attachments: [.init( -// id: "", -// mediaType: nil, -// data: AttachmentJsonData(data: "{\"challenge\":\"test\"}".data(using: .utf8)!), -// filename: nil, -// format: nil, -// lastmodTime: nil, -// byteCount: nil, -// description: nil -// )], -// thid: nil, -// from: DID.init(method: "test", methodId: "123"), -// to: DID.init(method: "test", methodId: "123") -// ) -// -// let pollux = PolluxImpl() -// let privKey = try apollo.createPrivateKey(parameters: [ -// KeyProperties.type.rawValue: "EC", -// KeyProperties.curve.rawValue: KnownKeyCurves.secp256k1.rawValue, -// KeyProperties.rawKey.rawValue: Data(fromBase64URL: "N_JFgvYaReyRXwassz5FHg33A4I6dczzdXrjdHGksmg")!.base64Encoded() -// ]) as! PrivateKey & ExportableKey -// -// let pubKey = privKey.publicKey() -// let subjectPrismDID = try castor.createPrismDID(masterPublicKey: pubKey, services: []) -// do { -// try await pollux.processCredentialRequest( -// offerMessage: offerCredentialMessage.makeMessage(), -// options: [ -// .subjectDID(subjectPrismDID), -// .exportableKey(privKey) -// ] -// ) -// XCTFail("Should throw an error") -// } catch { -// -// } -// } -// -// func testJWTPresentationSignature() async throws { -// let requestPresentation = RequestPresentation( -// body: .init(proofTypes: []), -// attachments: [.init( -// id: "", -// mediaType: nil, -// data: AttachmentJsonData(data: "{\"domain\":\"test\", \"challenge\":\"test\"}".data(using: .utf8)!), -// filename: nil, -// format: nil, -// lastmodTime: nil, -// byteCount: nil, -// description: nil -// )], -// thid: nil, -// from: DID.init(method: "test", methodId: "123"), -// to: DID.init(method: "test", methodId: "123") -// ) -// -// let cred = createJWTCredential() -// let privKey = try apollo.createPrivateKey(parameters: [ -// KeyProperties.type.rawValue: "EC", -// KeyProperties.curve.rawValue: KnownKeyCurves.secp256k1.rawValue, -// KeyProperties.rawKey.rawValue: Data(fromBase64URL: "N_JFgvYaReyRXwassz5FHg33A4I6dczzdXrjdHGksmg")!.base64Encoded() -// ]) as! PrivateKey & ExportableKey -// -// let pubKey = privKey.publicKey() -// let subjectPrismDID = try castor.createPrismDID(masterPublicKey: pubKey, services: []) -// let resultString = try cred.proof?.presentation( -// request: requestPresentation.makeMessage(), -// options: [ -// .subjectDID(subjectPrismDID), -// .exportableKey(privKey) -// ] -// ) -// -// let validResultString = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJub25jZSI6InRlc3QiLCJpc3MiOiJkaWQ6cHJpc206OTRiY2RkZGIxN2E2NTY1NTg2NGYxZGEzYjIyNzU2NTI1NmJjZTM2MTg1Y2EwMzE5OWVlZjNiNmNkMTg4MTBlZDpDc2NCQ3NRQkVtUUtEMkYxZEdobGJuUnBZMkYwYVc5dU1CQUVRazhLQ1hObFkzQXlOVFpyTVJJZ1A2WGlXdERvYWo2ZzNsZTEybGpxamwzSl9aWGFfa1Jzd0M5R05VWW1rVFVhSUlHRlZFU1g4U2pLVFQ1eXBTNi1ERGl6VldtTHdUVHFNdlMtZEJQSTFjZkVFbHdLQjIxaGMzUmxjakFRQVVKUENnbHpaV053TWpVMmF6RVNJRC1sNGxyUTZHby1vTjVYdGRwWTZvNWR5ZjJWMnY1RWJNQXZSalZHSnBFMUdpQ0JoVlJFbF9Fb3lrMC1jcVV1dmd3NHMxVnBpOEUwNmpMMHZuUVR5TlhIeEEiLCJ2cCI6eyJAY29udGV4dCI6WyJodHRwczpcL1wvd3d3LnczLm9yZ1wvMjAxOFwvcHJlc2VudGF0aW9uc1wvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlUHJlc2VudGF0aW9uIl0sInZlcmlmaWFibGVDcmVkZW50aWFsIjpbImV5SmhiR2NpT2lKRlV6STFOa3NpZlEuZXlKcGMzTWlPaUprYVdRNmNISnBjMjA2TW1VME1HWmtOamt5WWpnell6RTVaamxoTlRVek5qUmpNbU5oTldKbU5qa3lPR0k0T0RVMU5HRTFZbVl4TVRjMFlUYzRaalk0TkRrNFpEZ3daR1pqTmpwRGNtTkNRM0pSUWtWcWEwdENWM1JzWlZNd2VFVkJTa3RNWjI5S1l6SldhbU5FU1RGT2JYTjRSV2xGUTFwRGJEVjRhVVJFYjNac1ZGbE5OVlZTZVhkSE9EWlBXamMyUldOVFkzTmpTRXBsYUhSbmJXTktUbEZUVDJkdlIxbFlWakJoUXpCNFJVRlNTMHhuYjBwak1sWnFZMFJKTVU1dGMzaEZhVVZEUlVNelRVTlBhazR4YjFsTlpqVTJaVlZCYVRBM05reEdYMmhSWkRSd2JGRmliM0pLY25Ca09IZEhZMU5QZDI5SVlsZEdlbVJIVm5sTlFrRkNVMmswUzBOWVRteFpNMEY1VGxSYWNrMVNTV2hCZVRWcVZrYzRVVFJXT0hSWVYwUm9VV052YjJ4UFRtRklkVFpIYVc1b2NrSjZTRXRmUlhZeVNXOXlOU0lzSW5OMVlpSTZJbVJwWkRwd2NtbHpiVG80T0RZd04yWTRZakUzWldKaFptTmhPRGd3TkRkbVpEUTBZVE15WlRFNE5HSTFNR1l3TTJReU5XWmhaV1ExWkdSaVlXUXlaR1JqTkdZeVpqZzVZV1l6T2tOelkwSkRjMUZDUlcxUlMwUXlSakZrUjJoc1ltNVNjRmt5UmpCaFZ6bDFUVUpCUlZGck9FdERXRTVzV1ROQmVVNVVXbkpOVWtsbmNuRkRNVmhhTjJac09VcExTakJOVDNwVGEyaFNaRmhFU0hwblNWUXpUR0oxTWxOTGRUSnZaV3hLVldGSlQzZ3hTekZ2WTJORFJHMTRTUzA1Wm05alJtODRlbWhwVG01QllYQlBVR0ZYUVhZMFVHZzBhelpqV2tWc2QwdENNakZvWXpOU2JHTnFRVkZCVlVwUVEyZHNlbHBYVG5kTmFsVXlZWHBGVTBsTE5tZDBWakpsTXpWbVUxTnBaRVJFY3pCd1NWVllWbmQ0T0RSRFJUbDVNamQwYTJseWRIRkljRk5XUjJsRWMyUlRkR0ZJU0VGbk5YTlRVSFpZTmtoQ1lWQk5ORmxxV25kSGNWUnFNbXhuVEMxRU5HVktUMjVIVVNJc0ltNWlaaUk2TVRZNE9EQTFPRGN5Tnl3aVpYaHdJam94TmpnNE1EWXlNekkzTENKMll5STZleUpqY21Wa1pXNTBhV0ZzVTJOb1pXMWhJanA3SW1sa0lqb2lhSFIwY0hNNlhDOWNMMnM0Y3kxa1pYWXVZWFJoYkdGd2NtbHpiUzVwYjF3dmNISnBjMjB0WVdkbGJuUmNMM05qYUdWdFlTMXlaV2RwYzNSeWVWd3ZjMk5vWlcxaGMxd3ZNREl3TVRZNU0ySXROR1EyWkMwek5tVmpMV0V6TjJRdE9ERmtPRGhsT0RjeU5UTTVJaXdpZEhsd1pTSTZJa055WldSbGJuUnBZV3hUWTJobGJXRXlNREl5SW4wc0ltTnlaV1JsYm5ScFlXeFRkV0pxWldOMElqcDdJblJsYzNRaU9pSlVaWE4wTVNJc0ltbGtJam9pWkdsa09uQnlhWE50T2pnNE5qQTNaamhpTVRkbFltRm1ZMkU0T0RBME4yWmtORFJoTXpKbE1UZzBZalV3WmpBelpESTFabUZsWkRWa1pHSmhaREprWkdNMFpqSm1PRGxoWmpNNlEzTmpRa056VVVKRmJWRkxSREpHTVdSSGFHeGlibEp3V1RKR01HRlhPWFZOUWtGRlVXczRTME5ZVG14Wk0wRjVUbFJhY2sxU1NXZHljVU14V0ZvM1ptdzVTa3RLTUUxUGVsTnJhRkprV0VSSWVtZEpWRE5NWW5VeVUwdDFNbTlsYkVwVllVbFBlREZMTVc5alkwTkViWGhKTFRsbWIyTkdiemg2YUdsT2JrRmhjRTlRWVZkQmRqUlFhRFJyTm1OYVJXeDNTMEl5TVdoak0xSnNZMnBCVVVGVlNsQkRaMng2V2xkT2QwMXFWVEpoZWtWVFNVczJaM1JXTW1Vek5XWlRVMmxrUkVSek1IQkpWVmhXZDNnNE5FTkZPWGt5TjNScmFYSjBjVWh3VTFaSGFVUnpaRk4wWVVoSVFXYzFjMU5RZGxnMlNFSmhVRTAwV1dwYWQwZHhWR295YkdkTUxVUTBaVXBQYmtkUkluMHNJblI1Y0dVaU9sc2lWbVZ5YVdacFlXSnNaVU55WldSbGJuUnBZV3dpWFN3aVFHTnZiblJsZUhRaU9sc2lhSFIwY0hNNlhDOWNMM2QzZHk1M015NXZjbWRjTHpJd01UaGNMMk55WldSbGJuUnBZV3h6WEM5Mk1TSmRmWDAuSlpCcUFyVkZ2V2dqMlcwYjd2VlBTS1IzbVNIX1gtVk9DLVlRX2p5TFpTT0VZVWtvcnRrUkdpNDF4d0E3U1BGU3FQZFNDSGw0aWFncEJpcjF0WU1CT3ciXX0sImF1ZCI6InRlc3QifQ.vEUJnRmJsp7H7IKgQfDBSn12HPBXXVyEySI8sXHtwPRFTGOCvOpB6PImrn7N4I4ENmV6vLadJf5ZbO2n9hBwDw" -// -// XCTAssertEqual(validResultString, resultString) -// } -// -// private func createJWTCredential() -> Credential { -// let jwtString = "eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6cHJpc206MmU0MGZkNjkyYjgzYzE5ZjlhNTUzNjRjMmNhNWJmNjkyOGI4ODU1NGE1YmYxMTc0YTc4ZjY4NDk4ZDgwZGZjNjpDcmNCQ3JRQkVqa0tCV3RsZVMweEVBSktMZ29KYzJWamNESTFObXN4RWlFQ1pDbDV4aUREb3ZsVFlNNVVSeXdHODZPWjc2RWNTY3NjSEplaHRnbWNKTlFTT2dvR1lYVjBhQzB4RUFSS0xnb0pjMlZqY0RJMU5tc3hFaUVDRUMzTUNPak4xb1lNZjU2ZVVBaTA3NkxGX2hRZDRwbFFib3JKcnBkOHdHY1NPd29IYldGemRHVnlNQkFCU2k0S0NYTmxZM0F5TlRack1SSWhBeTVqVkc4UTRWOHRYV0RoUWNvb2xPTmFIdTZHaW5ockJ6SEtfRXYySW9yNSIsInN1YiI6ImRpZDpwcmlzbTo4ODYwN2Y4YjE3ZWJhZmNhODgwNDdmZDQ0YTMyZTE4NGI1MGYwM2QyNWZhZWQ1ZGRiYWQyZGRjNGYyZjg5YWYzOkNzY0JDc1FCRW1RS0QyRjFkR2hsYm5ScFkyRjBhVzl1TUJBRVFrOEtDWE5sWTNBeU5UWnJNUklncnFDMVhaN2ZsOUpLSjBNT3pTa2hSZFhESHpnSVQzTGJ1MlNLdTJvZWxKVWFJT3gxSzFvY2NDRG14SS05Zm9jRm84emhpTm5BYXBPUGFXQXY0UGg0azZjWkVsd0tCMjFoYzNSbGNqQVFBVUpQQ2dselpXTndNalUyYXpFU0lLNmd0VjJlMzVmU1NpZEREczBwSVVYVnd4ODRDRTl5Mjd0a2lydHFIcFNWR2lEc2RTdGFISEFnNXNTUHZYNkhCYVBNNFlqWndHcVRqMmxnTC1ENGVKT25HUSIsIm5iZiI6MTY4ODA1ODcyNywiZXhwIjoxNjg4MDYyMzI3LCJ2YyI6eyJjcmVkZW50aWFsU2NoZW1hIjp7ImlkIjoiaHR0cHM6XC9cL2s4cy1kZXYuYXRhbGFwcmlzbS5pb1wvcHJpc20tYWdlbnRcL3NjaGVtYS1yZWdpc3RyeVwvc2NoZW1hc1wvMDIwMTY5M2ItNGQ2ZC0zNmVjLWEzN2QtODFkODhlODcyNTM5IiwidHlwZSI6IkNyZWRlbnRpYWxTY2hlbWEyMDIyIn0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7InRlc3QiOiJUZXN0MSIsImlkIjoiZGlkOnByaXNtOjg4NjA3ZjhiMTdlYmFmY2E4ODA0N2ZkNDRhMzJlMTg0YjUwZjAzZDI1ZmFlZDVkZGJhZDJkZGM0ZjJmODlhZjM6Q3NjQkNzUUJFbVFLRDJGMWRHaGxiblJwWTJGMGFXOXVNQkFFUWs4S0NYTmxZM0F5TlRack1SSWdycUMxWFo3Zmw5SktKME1PelNraFJkWERIemdJVDNMYnUyU0t1Mm9lbEpVYUlPeDFLMW9jY0NEbXhJLTlmb2NGbzh6aGlObkFhcE9QYVdBdjRQaDRrNmNaRWx3S0IyMWhjM1JsY2pBUUFVSlBDZ2x6WldOd01qVTJhekVTSUs2Z3RWMmUzNWZTU2lkRERzMHBJVVhWd3g4NENFOXkyN3RraXJ0cUhwU1ZHaURzZFN0YUhIQWc1c1NQdlg2SEJhUE00WWpad0dxVGoybGdMLUQ0ZUpPbkdRIn0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiXSwiQGNvbnRleHQiOlsiaHR0cHM6XC9cL3d3dy53My5vcmdcLzIwMThcL2NyZWRlbnRpYWxzXC92MSJdfX0.JZBqArVFvWgj2W0b7vVPSKR3mSH_X-VOC-YQ_jyLZSOEYUkortkRGi41xwA7SPFSqPdSCHl4iagpBir1tYMBOw" -// -// return try! JWTCredential(data: jwtString.data(using: .utf8)!) -// } } diff --git a/EdgeAgentSDK/Pollux/Tests/SDJWTTests.swift b/EdgeAgentSDK/Pollux/Tests/SDJWTTests.swift new file mode 100644 index 00000000..f57ddbc9 --- /dev/null +++ b/EdgeAgentSDK/Pollux/Tests/SDJWTTests.swift @@ -0,0 +1,20 @@ +import Apollo +import Castor +import Domain +import EdgeAgent +@testable import Pollux +import XCTest + +final class SDJWTTests: XCTestCase { + + lazy var apollo = ApolloImpl() + lazy var castor = CastorImpl(apollo: apollo) + + func testParseSDJWTCredential() throws { + let validJWTString = "eyJ0eXAiOiJzZCtqd3QiLCJhbGciOiJFUzI1NiJ9.eyJpZCI6IjEyMzQiLCJfc2QiOlsiYkRUUnZtNS1Zbi1IRzdjcXBWUjVPVlJJWHNTYUJrNTdKZ2lPcV9qMVZJNCIsImV0M1VmUnlsd1ZyZlhkUEt6Zzc5aGNqRDFJdHpvUTlvQm9YUkd0TW9zRmsiLCJ6V2ZaTlMxOUF0YlJTVGJvN3NKUm4wQlpRdldSZGNob0M3VVphYkZyalk4Il0sIl9zZF9hbGciOiJzaGEtMjU2In0.n27NCtnuwytlBYtUNjgkesDP_7gN7bhaLhWNL4SWT6MaHsOjZ2ZMp987GgQRL6ZkLbJ7Cd3hlePHS84GBXPuvg~WyI1ZWI4Yzg2MjM0MDJjZjJlIiwiZmlyc3RuYW1lIiwiSm9obiJd~WyJjNWMzMWY2ZWYzNTg4MWJjIiwibGFzdG5hbWUiLCJEb2UiXQ~WyJmYTlkYTUzZWJjOTk3OThlIiwic3NuIiwiMTIzLTQ1LTY3ODkiXQ~" + + let credential = try SDJWTCredential(sdjwtString: validJWTString) + XCTAssertEqual(credential.claims.map(\.key).sorted(), ["firstname", "lastname", "ssn"].sorted()) + XCTAssertEqual(credential.id, validJWTString) + } +} diff --git a/Package.swift b/Package.swift index 85eede98..02cc8f03 100644 --- a/Package.swift +++ b/Package.swift @@ -56,13 +56,14 @@ let package = Package( from: "1.4.4" ), .package(url: "git@github.com:apple/swift-protobuf.git", from: "1.7.0"), - .package(url: "https://github.com/beatt83/didcomm-swift.git", from: "0.1.5"), - .package(url: "https://github.com/beatt83/jose-swift.git", from: "2.2.2"), - .package(url: "https://github.com/beatt83/peerdid-swift.git", from: "2.0.2"), + .package(url: "https://github.com/beatt83/didcomm-swift.git", from: "0.1.8"), + .package(url: "https://github.com/beatt83/jose-swift.git", from: "3.1.0"), + .package(url: "https://github.com/beatt83/peerdid-swift.git", from: "3.0.1"), .package(url: "https://github.com/input-output-hk/anoncreds-rs.git", exact: "0.4.1"), .package(url: "https://github.com/input-output-hk/atala-prism-apollo.git", exact: "1.3.3"), .package(url: "https://github.com/KittyMac/Sextant.git", exact: "0.4.31"), - .package(url: "https://github.com/kylef/JSONSchema.swift.git", exact: "0.6.0") + .package(url: "https://github.com/kylef/JSONSchema.swift.git", exact: "0.6.0"), + .package(url: "https://github.com/goncalo-frade-iohk/eudi-lib-sdjwt-swift.git", from: "0.0.2") ], targets: [ .target( @@ -119,6 +120,7 @@ let package = Package( "Core", "jose-swift", "Sextant", + "eudi-lib-sdjwt-swift", .product(name: "AnoncredsSwift", package: "anoncreds-rs"), .product(name: "JSONSchema", package: "JSONSchema.swift") ], diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo.xcodeproj/project.pbxproj b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo.xcodeproj/project.pbxproj index f253b30c..b4b8076d 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo.xcodeproj/project.pbxproj +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo.xcodeproj/project.pbxproj @@ -1148,6 +1148,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1179,6 +1180,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Credentials/CredentialsList/CredentialListViewModel.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Credentials/CredentialsList/CredentialListViewModel.swift index e10bc2e5..42d30534 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Credentials/CredentialsList/CredentialListViewModel.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Credentials/CredentialsList/CredentialListViewModel.swift @@ -276,7 +276,7 @@ private func getResponses(messages: [Message]) -> [CredentialListViewState.Respo } } -private struct MockCredentialClaim: JWTRegisteredFieldsClaims { +private struct MockCredentialClaim: JWTRegisteredFieldsClaims, Codable { struct VC: Codable { let credentialSubject: [String: String] }