diff --git a/EdgeAgentSDK/Domain/Sources/BBs/Pollux.swift b/EdgeAgentSDK/Domain/Sources/BBs/Pollux.swift index 2567a194..0c4f5827 100644 --- a/EdgeAgentSDK/Domain/Sources/BBs/Pollux.swift +++ b/EdgeAgentSDK/Domain/Sources/BBs/Pollux.swift @@ -14,6 +14,8 @@ public enum CredentialOperationsOptions { 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 thid(String) + case presentationRequestId(String) case custom(key: String, data: Data) // Any custom data. } @@ -23,8 +25,18 @@ public protocol Pollux { /// - Parameter data: The encoded item to parse. /// - Throws: An error if the item cannot be parsed or decoded. /// - Returns: An object representing the parsed item. + @available(*, deprecated, message: "Please use the new method for parseCredential(type: String, credentialPayload: Data, options: [CredentialOperationsOptions])") func parseCredential(issuedCredential: Message, options: [CredentialOperationsOptions]) async throws -> Credential + /// Parses an encoded item and returns an object representing the parsed item. + /// - Parameters: + /// - type: The type of the credential, (`jwt`, `prism/jwt`, `vc+sd-jwt`, `anoncreds`, `anoncreds/credential@v1.0`) + /// - credentialPayload: The encoded credential to parse. + /// - options: Options required for some types of credentials. + /// - Throws: An error if the item cannot be parsed or decoded. + /// - Returns: An object representing the parsed item. + func parseCredential(type: String, credentialPayload: Data, options: [CredentialOperationsOptions]) async throws -> Credential + /// Restores a previously stored item using the provided restoration identifier and data. /// - Parameters: /// - restorationIdentifier: The identifier to use when restoring the item. @@ -39,11 +51,25 @@ public protocol Pollux { /// - options: The options to use when processing the request. /// - Throws: An error if the request cannot be processed. /// - Returns: A string representing the result of the request process. + @available(*, deprecated, message: "Please use the new method for processCredentialRequest(type: String, offerPayload: Data, options: [CredentialOperationsOptions])") func processCredentialRequest( offerMessage: Message, options: [CredentialOperationsOptions] ) async throws -> String + /// Processes a request based on a provided offer message and options. + /// - Parameters: + /// - type: The type of the credential, (`jwt`, `prism/jwt`, `vc+sd-jwt`, `anoncreds`, `anoncreds/credential-offer@v1.0`) + /// - offerMessage: The offer message that contains the details of the request. + /// - options: The options to use when processing the request. + /// - Throws: An error if the request cannot be processed. + /// - Returns: A string representing the result of the request process. + func processCredentialRequest( + type: String, + offerPayload: Data, + options: [CredentialOperationsOptions] + ) async throws -> String + /// Creates a presentation request for credentials of a specified type, directed to a specific DID, with additional metadata and filtering options. /// /// - Parameters: @@ -69,10 +95,25 @@ public protocol Pollux { /// - options: An array of options that influence how the presentation verification is conducted. /// - Returns: A Boolean value indicating whether the presentation is valid (`true`) or not (`false`). /// - Throws: An error if there is a problem verifying the presentation. + @available(*, deprecated, message: "Please use the new method for verifyPresentation(type: String, presentationPayload: Data, options: [CredentialOperationsOptions])") func verifyPresentation( message: Message, options: [CredentialOperationsOptions] ) async throws -> Bool + + /// Verifies the validity of a presentation contained within a message, using specified options. + /// + /// - Parameters: + /// - type: The type of the credential, (`jwt`, `prism/jwt`, `vc+sd-jwt`, `anoncreds`, `anoncreds/credential-presentation@v1.0`) + /// - presentationPayload: The message containing the presentation to be verified. + /// - options: An array of options that influence how the presentation verification is conducted. + /// - Returns: A Boolean value indicating whether the presentation is valid (`true`) or not (`false`). + /// - Throws: An error if there is a problem verifying the presentation. + func verifyPresentation( + type: String, + presentationPayload: Data, + options: [CredentialOperationsOptions] + ) async throws -> Bool } public extension Pollux { diff --git a/EdgeAgentSDK/Domain/Sources/Models/Credentials/ProvableCredential.swift b/EdgeAgentSDK/Domain/Sources/Models/Credentials/ProvableCredential.swift index 4826a5dc..db985a00 100644 --- a/EdgeAgentSDK/Domain/Sources/Models/Credentials/ProvableCredential.swift +++ b/EdgeAgentSDK/Domain/Sources/Models/Credentials/ProvableCredential.swift @@ -9,8 +9,18 @@ public protocol ProvableCredential { /// - options: The options to use when creating the proof. /// - Returns: The proof as a `String`. /// - Throws: If there is an error creating the proof. + @available(*, deprecated, message: "Please use the new method for presentation(type: requestPayload: options:)") func presentation(request: Message, options: [CredentialOperationsOptions]) throws -> String + /// Creates a presentation proof for a request message with the given options. + /// + /// - Parameters: + /// - request: The request message for which the proof needs to be created. + /// - options: The options to use when creating the proof. + /// - Returns: The proof as a `String`. + /// - Throws: If there is an error creating the proof. + func presentation(type: String, requestPayload: Data, options: [CredentialOperationsOptions]) throws -> String + /// Validates if the credential can be used for the given presentation request, using the specified options. /// /// - Parameters: @@ -18,7 +28,17 @@ public protocol ProvableCredential { /// - options: Options that may influence the validation process. /// - Returns: A Boolean indicating whether the credential is valid for the presentation (`true`) or not (`false`). /// - Throws: If there is an error during the validation process. + @available(*, deprecated, message: "Please use the new method for isValidForPresentation(type: requestPayload: options:)") func isValidForPresentation(request: Message, options: [CredentialOperationsOptions]) throws -> Bool + + /// Validates if the credential can be used for the given presentation request, using the specified options. + /// + /// - Parameters: + /// - request: The presentation request message to be validated against. + /// - options: Options that may influence the validation process. + /// - Returns: A Boolean indicating whether the credential is valid for the presentation (`true`) or not (`false`). + /// - Throws: If there is an error during the validation process. + func isValidForPresentation(type: String, requestPayload: Data, options: [CredentialOperationsOptions]) throws -> Bool } public extension Credential { diff --git a/EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent+Credentials.swift b/EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent+Credentials.swift new file mode 100644 index 00000000..baebb148 --- /dev/null +++ b/EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent+Credentials.swift @@ -0,0 +1,282 @@ +import Core +import Combine +import Domain +import Foundation +import Logging +import JSONWebToken + +public extension DIDCommAgent { + + /// This function initiates a presentation request for a specific type of credential, specifying the sender's and receiver's DIDs, and any claim filters applicable. + /// + /// - Parameters: + /// - type: The type of the credential for which the presentation is requested. + /// - fromDID: The decentralized identifier (DID) of the entity initiating the request. + /// - toDID: The decentralized identifier (DID) of the entity to which the request is being sent. + /// - claimFilters: A collection of filters specifying the claims required in the credential. + /// - Returns: The initiated request for presentation. + /// - Throws: EdgeAgentError, if there is a problem initiating the presentation request. + func initiatePresentationRequest( + type: CredentialType, + fromDID: DID, + toDID: DID, + claimFilters: [ClaimFilter] + ) async throws -> RequestPresentation { + let rqstStr = try await edgeAgent.initiatePresentationRequest( + type: type, + fromDID: fromDID, + toDID: toDID, + claimFilters: claimFilters + ) + let attachment: AttachmentDescriptor + switch type { + case .jwt: + let data = try AttachmentBase64(base64: rqstStr.tryToData().base64URLEncoded()) + attachment = AttachmentDescriptor( + mediaType: "application/json", + data: data, + format: "dif/presentation-exchange/definitions@v1.0" + ) + case .anoncred: + let data = try AttachmentBase64(base64: rqstStr.tryToData().base64URLEncoded()) + attachment = AttachmentDescriptor( + mediaType: "application/json", + data: data, + format: "anoncreds/proof-request@v1.0" + ) + } + + return RequestPresentation( + body: .init( + proofTypes: [ProofTypes( + schema: "", + requiredFields: claimFilters.flatMap(\.paths), + trustIssuers: nil + )] + ), + attachments: [attachment], + thid: nil, + from: fromDID, + to: toDID + ) + } + + /// This function verifies the presentation contained within a message. + /// + /// - Parameters: + /// - message: The message containing the presentation to be verified. + /// - Returns: A Boolean value indicating whether the presentation is valid (`true`) or not (`false`). + /// - Throws: EdgeAgentError, if there is a problem verifying the presentation. + + func verifyPresentation(message: Message) async throws -> Bool { + do { + let downloader = DownloadDataWithResolver(castor: castor) + guard + let attachment = message.attachments.first, + let requestId = message.thid + else { + throw PolluxError.couldNotFindPresentationInAttachments + } + + let jsonData: Data + switch attachment.data { + case let attchedData as AttachmentBase64: + guard let decoded = Data(fromBase64URL: attchedData.base64) else { + throw CommonError.invalidCoding(message: "Invalid base64 url attachment") + } + jsonData = decoded + case let attchedData as AttachmentJsonData: + jsonData = attchedData.data + default: + throw EdgeAgentError.invalidAttachmentFormat(nil) + } + + guard let format = attachment.format else { + throw EdgeAgentError.invalidAttachmentFormat(nil) + } + + return try await pollux.verifyPresentation( + type: format, + presentationPayload: jsonData, + options: [ + .presentationRequestId(requestId), + .credentialDefinitionDownloader(downloader: downloader), + .schemaDownloader(downloader: downloader) + ]) + } catch { + logger.error(error: error) + throw error + } + } + + /// This function parses an issued credential message, stores and returns the verifiable credential. + /// + /// - Parameters: + /// - message: Issue credential Message. + /// - Returns: The parsed verifiable credential. + /// - Throws: EdgeAgentError, if there is a problem parsing the credential. + func processIssuedCredentialMessage(message: IssueCredential3_0) async throws -> Credential { + guard + let linkSecret = try await pluto.getLinkSecret().first().await() + else { throw EdgeAgentError.cannotFindDIDKeyPairIndex } + + let restored = try await self.apollo.restoreKey(linkSecret) + guard + let linkSecretString = String(data: restored.raw, encoding: .utf8) + else { throw EdgeAgentError.cannotFindDIDKeyPairIndex } + + let downloader = DownloadDataWithResolver(castor: castor) + guard + let attachment = message.attachments.first, + let format = attachment.format + else { + throw PolluxError.unsupportedIssuedMessage + } + + let jsonData: Data + switch attachment.data { + case let attchedData as AttachmentBase64: + guard let decoded = Data(fromBase64URL: attchedData.base64) else { + throw CommonError.invalidCoding(message: "Invalid base64 url attachment") + } + jsonData = decoded + case let attchedData as AttachmentJsonData: + jsonData = attchedData.data + default: + throw EdgeAgentError.invalidAttachmentFormat(nil) + } + + let credential = try await pollux.parseCredential( + type: format, + credentialPayload: jsonData, + options: [ + .linkSecret(id: "", secret: linkSecretString), + .credentialDefinitionDownloader(downloader: downloader), + .schemaDownloader(downloader: downloader) + ] + ) + + guard let storableCredential = credential.storable else { + return credential + } + try await pluto + .storeCredential(credential: storableCredential) + .first() + .await() + return credential + } + + /// This function prepares a request credential from an offer given the subject DID. + /// + /// - Parameters: + /// - did: Subject DID. + /// - did: Received offer credential. + /// - Returns: Created request credential + /// - Throws: EdgeAgentError, if there is a problem creating the request credential. + func prepareRequestCredentialWithIssuer(did: DID, offer: OfferCredential3_0) async throws -> RequestCredential3_0? { + guard did.method == "prism" else { throw PolluxError.invalidPrismDID } + let didInfo = try await pluto + .getDIDInfo(did: did) + .first() + .await() + + guard let storedPrivateKey = didInfo?.privateKeys.first else { throw EdgeAgentError.cannotFindDIDKeyPairIndex } + + let privateKey = try await apollo.restorePrivateKey(storedPrivateKey) + + guard + let exporting = privateKey.exporting, + let linkSecret = try await pluto.getLinkSecret().first().await() + else { throw EdgeAgentError.cannotFindDIDKeyPairIndex } + + let restored = try await self.apollo.restoreKey(linkSecret) + guard + let linkSecretString = String(data: restored.raw, encoding: .utf8) + else { throw EdgeAgentError.cannotFindDIDKeyPairIndex } + + let downloader = DownloadDataWithResolver(castor: castor) + guard + let attachment = offer.attachments.first, + let offerFormat = attachment.format + else { + throw PolluxError.unsupportedIssuedMessage + } + + let jsonData: Data + switch attachment.data { + case let attchedData as AttachmentBase64: + guard let decoded = Data(fromBase64URL: attchedData.base64) else { + throw CommonError.invalidCoding(message: "Invalid base64 url attachment") + } + jsonData = decoded + case let attchedData as AttachmentJsonData: + jsonData = attchedData.data + default: + throw EdgeAgentError.invalidAttachmentFormat(nil) + } + let requestString = try await pollux.processCredentialRequest( + type: offerFormat, + offerPayload: jsonData, + options: [ + .exportableKey(exporting), + .subjectDID(did), + .linkSecret(id: did.string, secret: linkSecretString), + .credentialDefinitionDownloader(downloader: downloader), + .schemaDownloader(downloader: downloader) + ] + ) + + guard + let base64String = requestString.data(using: .utf8)?.base64EncodedString() + else { + throw CommonError.invalidCoding(message: "Could not encode to base64") + } + guard + let offerPiuri = ProtocolTypes(rawValue: offer.type) + else { + throw EdgeAgentError.invalidMessageType( + type: offer.type, + shouldBe: [ + ProtocolTypes.didcommOfferCredential3_0.rawValue + ] + ) + } + let format: String + 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: + throw EdgeAgentError.invalidMessageType( + type: offerFormat, + shouldBe: [ + "prism/jwt", + "anoncreds/credential-offer@v1.0" + ] + ) + } + + let type = offerPiuri == .didcommOfferCredential ? + ProtocolTypes.didcommRequestCredential : + ProtocolTypes.didcommRequestCredential3_0 + + let requestCredential = RequestCredential3_0( + body: .init( + goalCode: offer.body.goalCode, + comment: offer.body.comment + ), + type: type.rawValue, + attachments: [.init( + data: AttachmentBase64(base64: base64String), + format: format + )], + thid: offer.thid, + from: offer.to, + to: offer.from + ) + return requestCredential + } +} diff --git a/EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent+DIDs.swift b/EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent+DIDs.swift new file mode 100644 index 00000000..fb0a8a0e --- /dev/null +++ b/EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent+DIDs.swift @@ -0,0 +1,165 @@ +import Combine +import Domain +import Foundation + +// MARK: DID High Level functionalities +public extension DIDCommAgent { + + /// This method create a new Prism DID, that can be used to identify the agent and interact with other agents. + /// - Parameters: + /// - keyPathIndex: key path index used to identify the DID + /// - alias: An alias that can be used to identify the DID + /// - services: an array of services associated to the DID + /// - Returns: The new created DID + func createNewPrismDID( + keyPathIndex: Int? = nil, + alias: String? = nil, + services: [DIDDocument.Service] = [] + ) async throws -> DID { + try await edgeAgent.createNewPrismDID( + keyPathIndex: keyPathIndex, + alias: alias, + services: services + ) + } + + /// This method registers a Prism DID, that can be used to identify the agent and interact with other agents. + /// - Parameters: + /// - did: the DID which will be registered. + /// - keyPathIndex: key path index used to identify the DID + /// - alias: An alias that can be used to identify the DID + /// - Returns: The new created DID + func registerPrismDID( + did: DID, + privateKey: PrivateKey, + alias: String? = nil + ) async throws { + try await edgeAgent.registerPrismDID( + did: did, + privateKey: privateKey, + alias: alias + ) + } + /// This function creates a new Peer DID, stores it in pluto database and updates the mediator if requested. + /// + /// - Parameters: + /// - services: The services associated to the new DID. + /// - updateMediator: Indicates if the new DID should be added to the mediator's list. It will as well add the mediator service. + /// - Returns: A new DID + /// - Throws: EdgeAgentError, if updateMediator is true and there is no mediator available or if storing the new DID failed + func createNewPeerDID( + services: [DIDDocument.Service] = [], + alias: String? = "", + updateMediator: Bool + ) async throws -> DID { + var keyAgreementPrivateKey = try apollo.createPrivateKey(parameters: [ + KeyProperties.type.rawValue: "EC", + KeyProperties.curve.rawValue: KnownKeyCurves.x25519.rawValue + ]) + + var authenticationPrivateKey = try apollo.createPrivateKey(parameters: [ + KeyProperties.type.rawValue: "EC", + KeyProperties.curve.rawValue: KnownKeyCurves.ed25519.rawValue + ]) + + let withServices: [DIDDocument.Service] + if updateMediator, let routingDID = mediatorRoutingDID?.string { + withServices = services + [.init( + id: "#didcomm-1", + type: ["DIDCommMessaging"], + serviceEndpoint: [.init( + uri: routingDID + )])] + } else { + withServices = services + } + + let newDID = try castor.createPeerDID( + keyAgreementPublicKey: keyAgreementPrivateKey.publicKey(), + authenticationPublicKey: authenticationPrivateKey.publicKey(), + services: withServices + ) + + let didDocument = try await castor.resolveDID(did: newDID) + didDocument.authenticate.first.map { authenticationPrivateKey.identifier = $0.id.string } + didDocument.keyAgreement.first.map { keyAgreementPrivateKey.identifier = $0.id.string } + + logger.debug(message: "Created new Peer DID", metadata: [ + .maskedMetadataByLevel(key: "DID", value: newDID.string, level: .debug) + ]) + + try await registerPeerDID( + did: newDID, + privateKeys: [ + keyAgreementPrivateKey, + authenticationPrivateKey + ], + alias: alias, + updateMediator: updateMediator + ) + + return newDID + } + + /// This function registers a Peer DID, stores it and his private key in pluto database and updates the mediator if requested. + /// + /// - Parameters: + /// - services: The services associated to the new DID. + /// - updateMediator: Indicates if the new DID should be added to the mediator's list. + /// - Returns: A new DID + /// - Throws: EdgeAgentError, if updateMediator is true and there is no mediator available or if storing the new DID failed + func registerPeerDID( + did: DID, + privateKeys: [PrivateKey], + alias: String?, + updateMediator: Bool + ) async throws { + if updateMediator { + try await updateMediatorWithDID(did: did) + } + logger.debug(message: "Register of DID in storage", metadata: [ + .maskedMetadataByLevel(key: "DID", value: did.string, level: .debug) + ]) + + let storablePrivateKeys = try privateKeys + .map { + guard let storablePrivateKey = $0 as? (PrivateKey & StorableKey) else { + throw KeyError.keyRequiresConformation(conformations: ["PrivateKey", "StorableKey"]) + } + return storablePrivateKey + } + + try await pluto + .storePeerDID( + did: did, + privateKeys: storablePrivateKeys, + alias: alias + ) + .first() + .await() + } + + /// This function updates the mediator key list with a new DID. + /// + /// - Parameters: + /// - services: The services associated to the new DID. + /// - updateMediator: Indicates if the new DID should be added to the mediator's list. + /// - Returns: A new DID + /// - Throws: EdgeAgentError, if updateMediator is true and there is no mediator available + func updateMediatorWithDID(did: DID) async throws { + logger.debug(message: "Update mediator key list with DID", metadata: [ + .maskedMetadataByLevel(key: "DID", value: did.string, level: .debug) + ]) + + try await mediationHandler?.updateKeyListWithDIDs(dids: [did]) + } + + /// This function gets all the DID peers from the `pluto` store + /// + /// - Returns: A publisher that emits an array of tuples (`DID`, `String?`) objects, or an error if there was a problem getting the dids + func getAllRegisteredPeerDIDs() -> AnyPublisher<[(did: DID, alias: String?)], Error> { + pluto.getAllPeerDIDs() + .map { $0.map { ($0.did, $0.alias) } } + .eraseToAnyPublisher() + } +} diff --git a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Invitations.swift b/EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent+Invitations.swift similarity index 98% rename from EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Invitations.swift rename to EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent+Invitations.swift index ff2aefb0..b263a050 100644 --- a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Invitations.swift +++ b/EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent+Invitations.swift @@ -1,8 +1,7 @@ import Domain import Foundation -// MARK: Invitation funcionalities -public extension EdgeAgent { +public extension DIDCommAgent { /// Enumeration representing the type of invitation enum InvitationType { /// Struct representing a Prism Onboarding invitation @@ -21,6 +20,50 @@ public extension EdgeAgent { case onboardingDIDComm(OutOfBandInvitation) } + /// Parses the given string as an Out-of-Band invitation + /// - Parameter url: The string to parse + /// - Returns: The parsed Out-of-Band invitation + /// - Throws: `EdgeAgentError` if the string is not a valid URL + func parseOOBInvitation(url: String) throws -> OutOfBandInvitation { + guard let url = URL(string: url) else { throw CommonError.invalidURLError(url: url) } + return try parseOOBInvitation(url: url) + } + + /// Parses the given URL as an Out-of-Band invitation + /// - Parameter url: The URL to parse + /// - Returns: The parsed Out-of-Band invitation + /// - Throws: `EdgeAgentError` if the URL is not a valid Out-of-Band invitation + func parseOOBInvitation(url: URL) throws -> OutOfBandInvitation { + return try DIDCommInvitationRunner(url: url).run() + } + + /// Accepts a Prism Onboarding invitation and performs the onboarding process + /// - Parameter invitation: The Prism Onboarding invitation to accept + /// - Throws: `EdgeAgentError` if the onboarding process fails + func acceptPrismInvitation(invitation: InvitationType.PrismOnboarding) async throws { + struct SendDID: Encodable { + let did: String + } + var request = URLRequest(url: invitation.endpoint) + request.httpMethod = "POST" + request.httpBody = try JSONEncoder().encode(SendDID(did: invitation.ownDID.string)) + request.setValue("application/json", forHTTPHeaderField: "content-type") + do { + let response = try await URLSession.shared.data(for: request) + guard let urlResponse = response.1 as? HTTPURLResponse else { + throw CommonError.invalidCoding( + message: "This should not happen cannot convert URLResponse to HTTPURLResponse" + ) + } + guard urlResponse.statusCode == 200 else { + throw CommonError.httpError( + code: urlResponse.statusCode, + message: String(data: response.0, encoding: .utf8) ?? "" + ) + } + } + } + /// Parses the given string as an invitation /// - Parameter str: The string to parse /// - Returns: The parsed invitation @@ -64,23 +107,6 @@ public extension EdgeAgent { ) } - /// Parses the given string as an Out-of-Band invitation - /// - Parameter url: The string to parse - /// - Returns: The parsed Out-of-Band invitation - /// - Throws: `EdgeAgentError` if the string is not a valid URL - func parseOOBInvitation(url: String) throws -> OutOfBandInvitation { - guard let url = URL(string: url) else { throw CommonError.invalidURLError(url: url) } - return try parseOOBInvitation(url: url) - } - - /// Parses the given URL as an Out-of-Band invitation - /// - Parameter url: The URL to parse - /// - Returns: The parsed Out-of-Band invitation - /// - Throws: `EdgeAgentError` if the URL is not a valid Out-of-Band invitation - func parseOOBInvitation(url: URL) throws -> OutOfBandInvitation { - return try DIDCommInvitationRunner(url: url).run() - } - /// Accepts an Out-of-Band (DIDComm) invitation and establishes a new connection /// - Parameter invitation: The Out-of-Band invitation to accept /// - Throws: `EdgeAgentError` if there is no mediator available or other errors occur during the acceptance process @@ -101,31 +127,4 @@ public extension EdgeAgent { ).run() try await connectionManager.addConnection(pair) } - - /// Accepts a Prism Onboarding invitation and performs the onboarding process - /// - Parameter invitation: The Prism Onboarding invitation to accept - /// - Throws: `EdgeAgentError` if the onboarding process fails - func acceptPrismInvitation(invitation: InvitationType.PrismOnboarding) async throws { - struct SendDID: Encodable { - let did: String - } - var request = URLRequest(url: invitation.endpoint) - request.httpMethod = "POST" - request.httpBody = try JSONEncoder().encode(SendDID(did: invitation.ownDID.string)) - request.setValue("application/json", forHTTPHeaderField: "content-type") - do { - let response = try await URLSession.shared.data(for: request) - guard let urlResponse = response.1 as? HTTPURLResponse else { - throw CommonError.invalidCoding( - message: "This should not happen cannot convert URLResponse to HTTPURLResponse" - ) - } - guard urlResponse.statusCode == 200 else { - throw CommonError.httpError( - code: urlResponse.statusCode, - message: String(data: response.0, encoding: .utf8) ?? "" - ) - } - } - } } diff --git a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+MessageEvents.swift b/EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent+Messages.swift similarity index 92% rename from EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+MessageEvents.swift rename to EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent+Messages.swift index c4f6834a..f5651e4e 100644 --- a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+MessageEvents.swift +++ b/EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent+Messages.swift @@ -2,8 +2,7 @@ import Combine import Domain import Foundation -// MARK: Messaging events funcionalities -public extension EdgeAgent { +public extension DIDCommAgent { /// Start fetching the messages from the mediator func startFetchingMessages(timeBetweenRequests: Int = 5) { let timeInterval = max(timeBetweenRequests, 5) @@ -26,10 +25,6 @@ public extension EdgeAgent { logger.error(error: error) } sleep(UInt32(timeInterval)) - - if (messagesStreamTask?.isCancelled == true) { - break - } } } } diff --git a/EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent+Proof.swift b/EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent+Proof.swift new file mode 100644 index 00000000..8a7d1b3a --- /dev/null +++ b/EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent+Proof.swift @@ -0,0 +1,100 @@ +import Core +import Combine +import Domain +import Foundation +import Logging +import JSONWebToken + +public extension DIDCommAgent { + + /// This function creates a Presentation from a request verfication. + /// + /// - Parameters: + /// - request: Request message received. + /// - credential: Verifiable Credential to present. + /// - Returns: Presentation message prepared to send. + /// - Throws: EdgeAgentError, if there is a problem creating the presentation. + func createPresentationForRequestProof( + request: RequestPresentation, + credential: Credential, + options: [CredentialOperationsOptions] = [] + ) async throws -> Presentation { + guard let proofableCredential = credential.proof else { + throw EdgeAgentError.credentialCannotIssuePresentations + } + + guard let requestType = request.attachments.first?.format else { + throw EdgeAgentError.invalidAttachmentFormat(nil) + } + let presentationString: String + let format: String + switch requestType { + case "anoncreds/proof-request@v1.0": + guard + let linkSecret = try await pluto.getLinkSecret().first().await() + else { throw EdgeAgentError.cannotFindDIDKeyPairIndex } + + let restored = try await self.apollo.restoreKey(linkSecret) + guard + let linkSecretString = String(data: restored.raw, encoding: .utf8) + else { throw EdgeAgentError.cannotFindDIDKeyPairIndex } + format = "anoncreds/proof@v1.0" + presentationString = try proofableCredential.presentation( + request: request.makeMessage(), + options: options + [ + .linkSecret(id: "", secret: linkSecretString) + ] + ) + case "prism/jwt", "vc+sd-jwt", "dif/presentation-exchange/definitions@v1.0": + guard + let subjectDIDString = credential.subject + else { + throw PolluxError.invalidPrismDID + } + + let subjectDID = try DID(string: subjectDIDString) + + let privateKeys = try await pluto.getDIDPrivateKeys(did: subjectDID).first().await() + + guard + let storedPrivateKey = privateKeys?.first + else { throw EdgeAgentError.cannotFindDIDKeyPairIndex } + + let privateKey = try await apollo.restorePrivateKey(storedPrivateKey) + + guard + let exporting = privateKey.exporting + else { throw EdgeAgentError.cannotFindDIDKeyPairIndex } + + format = requestType == "prism/jwt" ? "prism/jwt" : "dif/presentation-exchange/submission@v1.0" + + presentationString = try proofableCredential.presentation( + request: request.makeMessage(), + options: options + [ + .exportableKey(exporting), + .subjectDID(subjectDID) + ] + ) + default: + throw EdgeAgentError.invalidAttachmentFormat(requestType) + } + + Logger(label: "").log(level: .info, "Presentation: \(presentationString)") + + let base64String = try presentationString.tryToData().base64URLEncoded() + + return Presentation( + body: .init( + goalCode: request.body.goalCode, + comment: request.body.comment + ), + attachments: [.init( + data: AttachmentBase64(base64: base64String), + format: format + )], + thid: request.thid ?? request.id, + from: request.to, + to: request.from + ) + } +} diff --git a/EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent.swift b/EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent.swift new file mode 100644 index 00000000..9b38b182 --- /dev/null +++ b/EdgeAgentSDK/EdgeAgent/Sources/DIDCommAgent/DIDCommAgent.swift @@ -0,0 +1,221 @@ +import Builders +import Combine +import Core +import Domain +import Foundation + +public class DIDCommAgent { + /// Enumeration representing the current state of the agent. + public enum State: String { + case stoped + case starting + case running + case stoping + } + + /// Represents the current state of the agent. + public private(set) var state = State.stoped + + /// The mediator routing DID if one is currently registered. + public var mediatorRoutingDID: DID? { + connectionManager?.mediationHandler.mediator?.routingDID + } + + public let mercury: Mercury + public let edgeAgent: EdgeAgent + public var apollo: Apollo & KeyRestoration { edgeAgent.apollo } + public var castor: Castor { edgeAgent.castor } + public var pluto: Pluto { edgeAgent.pluto } + public var pollux: Pollux { edgeAgent.pollux } + var logger: SDKLogger { edgeAgent.logger } + + var mediationHandler: MediatorHandler? + + var connectionManager: ConnectionsManagerImpl? + var cancellables = [AnyCancellable]() + // Not a "stream" + var messagesStreamTask: Task? + + /// Initializes a EdgeAgent with the given dependency objects and seed data. + /// + /// - Parameters: + /// - apollo: An instance of Apollo. + /// - castor: An instance of Castor. + /// - pluto: An instance of Pluto. + /// - pollux: An instance of Pollux. + /// - mercury: An instance of Mercury. + /// - seed: A unique seed used to generate the unique DID. + /// - mediatorServiceEnpoint: The endpoint of the Mediator service to use. + public init( + edgeAgent: EdgeAgent, + mercury: Mercury, + mediationHandler: MediatorHandler? = nil + ) { + self.edgeAgent = edgeAgent + self.mercury = mercury + self.mediationHandler = mediationHandler + mediationHandler.map { + self.connectionManager = ConnectionsManagerImpl( + castor: edgeAgent.castor, + mercury: mercury, + pluto: edgeAgent.pluto, + mediationHandler: $0, + pairings: [] + ) + } + } + + /** + Convenience initializer for `EdgeAgent` that allows for optional initialization of seed data and mediator service endpoint. + + - Parameters: + - seedData: Optional seed data for creating a new seed. If not provided, a random seed will be generated. + - mediatorServiceEnpoint: Optional DID representing the service endpoint of the mediator. If not provided, the default Prism mediator endpoint will be used. + */ + public convenience init( + seedData: Data? = nil, + mediatorDID: DID + ) { + let edgeAgent = EdgeAgent(seedData: seedData) + let secretsStream = createSecretsStream( + keyRestoration: edgeAgent.apollo, + pluto: edgeAgent.pluto, + castor: edgeAgent.castor + ) + + let mercury = MercuryBuilder( + castor: edgeAgent.castor, + secretsStream: secretsStream + ).build() + + self.init( + edgeAgent: edgeAgent, + mercury: mercury, + mediationHandler: BasicMediatorHandler( + mediatorDID: mediatorDID, + mercury: mercury, + store: BasicMediatorHandler.PlutoMediatorStoreImpl(pluto: edgeAgent.pluto) + ) + ) + } + + public func setupMediatorHandler(mediationHandler: MediatorHandler) async throws { + try await stop() + self.mediationHandler = mediationHandler + self.connectionManager = ConnectionsManagerImpl( + castor: castor, + mercury: mercury, + pluto: pluto, + mediationHandler: mediationHandler, + pairings: [] + ) + } + + public func setupMediatorDID(did: DID) async throws { + let mediatorHandler = BasicMediatorHandler( + mediatorDID: did, + mercury: mercury, + store: BasicMediatorHandler.PlutoMediatorStoreImpl(pluto: pluto) + ) + try await setupMediatorHandler(mediationHandler: mediatorHandler) + } + + /** + Start the EdgeAgent and Mediator services + + - Throws: EdgeAgentError.noMediatorAvailableError if no mediator is available, + as well as any error thrown by `createNewPeerDID` and `connectionManager.registerMediator` + */ + public func start() async throws { + guard + let connectionManager, + state == .stoped + else { return } + logger.info(message: "Starting agent") + state = .starting + do { + try await connectionManager.startMediator() + } catch EdgeAgentError.noMediatorAvailableError { + let hostDID = try await createNewPeerDID(updateMediator: false) + try await connectionManager.registerMediator(hostDID: hostDID) + } + try await edgeAgent.firstLinkSecretSetup() + state = .running + logger.info(message: "Mediation Achieved", metadata: [ + .publicMetadata(key: "Routing DID", value: mediatorRoutingDID?.string ?? "") + ]) + logger.info(message: "Agent running") + } + + /** + This function is used to stop the EdgeAgent. + The function sets the state of EdgeAgent to .stoping. + All ongoing events that was created by the EdgeAgent are stopped. + After all the events are stopped the state of the EdgeAgent is set to .stoped. + + - Throws: If the current state is not running throws error. + */ + public func stop() async throws { + guard state == .running else { return } + logger.info(message: "Stoping agent") + state = .stoping + cancellables.forEach { $0.cancel() } + connectionManager?.stopAllEvents() + state = .stoped + logger.info(message: "Agent not running") + } +} + +private func createSecretsStream( + keyRestoration: KeyRestoration, + pluto: Pluto, + castor: Castor +) -> AnyPublisher<[Secret], Error> { + pluto.getAllKeys() + .first() + .flatMap { keys in + Future { + let privateKeys = await keys.asyncMap { + try? await keyRestoration.restorePrivateKey($0) + }.compactMap { $0 } + return try parsePrivateKeys( + privateKeys: privateKeys, + castor: castor + ) + } + } + .eraseToAnyPublisher() +} + +private func parsePrivateKeys( + privateKeys: [PrivateKey], + castor: Castor +) throws -> [Domain.Secret] { + return try privateKeys + .map { $0 as? (PrivateKey & ExportableKey & StorableKey) } + .compactMap { $0 } + .map { privateKey in + return privateKey + } + .map { privateKey in + try parseToSecret( + privateKey: privateKey, + identifier: privateKey.identifier + ) + } +} + +private func parseToSecret(privateKey: PrivateKey & ExportableKey, identifier: String) throws -> Domain.Secret { + let jwk = privateKey.jwk + guard + let dataJson = try? JSONEncoder().encode(jwk), + let stringJson = String(data: dataJson, encoding: .utf8) + else { + throw CommonError.invalidCoding(message: "Could not encode privateKey.jwk") + } + return .init( + id: identifier, + type: .jsonWebKey2020, + secretMaterial: .jwk(value: stringJson) + ) +} diff --git a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Credentials.swift b/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Credentials.swift index 5ecd2e1d..d6e88e79 100644 --- a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Credentials.swift +++ b/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Credentials.swift @@ -38,7 +38,7 @@ public extension EdgeAgent { fromDID: DID, toDID: DID, claimFilters: [ClaimFilter] - ) async throws -> RequestPresentation { + ) async throws -> String { let request = try self.pollux.createPresentationRequest( type: type, toDID: toDID, @@ -49,37 +49,7 @@ public extension EdgeAgent { let rqstStr = try request.tryToString() logger.debug(message: "Request: \(rqstStr)") - let attachment: AttachmentDescriptor - switch type { - case .jwt: - let data = AttachmentBase64(base64: request.base64URLEncoded()) - attachment = AttachmentDescriptor( - mediaType: "application/json", - data: data, - format: "dif/presentation-exchange/definitions@v1.0" - ) - case .anoncred: - let data = AttachmentBase64(base64: request.base64URLEncoded()) - attachment = AttachmentDescriptor( - mediaType: "application/json", - data: data, - format: "anoncreds/proof-request@v1.0" - ) - } - - return RequestPresentation( - body: .init( - proofTypes: [ProofTypes( - schema: "", - requiredFields: claimFilters.flatMap(\.paths), - trustIssuers: nil - )] - ), - attachments: [attachment], - thid: nil, - from: fromDID, - to: toDID - ) + return rqstStr } /// This function verifies the presentation contained within a message. @@ -89,13 +59,22 @@ public extension EdgeAgent { /// - Returns: A Boolean value indicating whether the presentation is valid (`true`) or not (`false`). /// - Throws: EdgeAgentError, if there is a problem verifying the presentation. - func verifyPresentation(message: Message) async throws -> Bool { + func verifyPresentation( + type: String, + presentationPayload: Data, + requestId: String + ) async throws -> Bool { do { let downloader = DownloadDataWithResolver(castor: castor) - return try await pollux.verifyPresentation(message: message, options: [ - .credentialDefinitionDownloader(downloader: downloader), - .schemaDownloader(downloader: downloader) - ]) + return try await pollux.verifyPresentation( + type: type, + presentationPayload: presentationPayload, + options: [ + .presentationRequestId(requestId), + .credentialDefinitionDownloader(downloader: downloader), + .schemaDownloader(downloader: downloader) + ] + ) } catch { logger.error(error: error) throw error @@ -108,7 +87,7 @@ public extension EdgeAgent { /// - message: Issue credential Message. /// - Returns: The parsed verifiable credential. /// - Throws: EdgeAgentError, if there is a problem parsing the credential. - func processIssuedCredentialMessage(message: IssueCredential3_0) async throws -> Credential { + func processIssuedCredential(type: String, issuedCredentialPayload: Data) async throws -> Credential { guard let linkSecret = try await pluto.getLinkSecret().first().await() else { throw EdgeAgentError.cannotFindDIDKeyPairIndex } @@ -120,7 +99,8 @@ public extension EdgeAgent { let downloader = DownloadDataWithResolver(castor: castor) let credential = try await pollux.parseCredential( - issuedCredential: message.makeMessage(), + type: type, + credentialPayload: issuedCredentialPayload, options: [ .linkSecret(id: "", secret: linkSecretString), .credentialDefinitionDownloader(downloader: downloader), @@ -145,7 +125,11 @@ public extension EdgeAgent { /// - did: Received offer credential. /// - Returns: Created request credential /// - Throws: EdgeAgentError, if there is a problem creating the request credential. - func prepareRequestCredentialWithIssuer(did: DID, offer: OfferCredential3_0) async throws -> RequestCredential3_0? { + func prepareRequestCredentialWithIssuer( + did: DID, + type: String, + offerPayload: Data + ) async throws -> String { guard did.method == "prism" else { throw PolluxError.invalidPrismDID } let didInfo = try await pluto .getDIDInfo(did: did) @@ -167,8 +151,9 @@ public extension EdgeAgent { else { throw EdgeAgentError.cannotFindDIDKeyPairIndex } let downloader = DownloadDataWithResolver(castor: castor) - let requestString = try await pollux.processCredentialRequest( - offerMessage: offer.makeMessage(), + return try await pollux.processCredentialRequest( + type: type, + offerPayload: offerPayload, options: [ .exportableKey(exporting), .subjectDID(did), @@ -177,59 +162,5 @@ public extension EdgeAgent { .schemaDownloader(downloader: downloader) ] ) - - guard - let offerFormat = offer.attachments.first?.format, - let base64String = requestString.data(using: .utf8)?.base64EncodedString() - else { - throw CommonError.invalidCoding(message: "Could not encode to base64") - } - guard - let offerPiuri = ProtocolTypes(rawValue: offer.type) - else { - throw EdgeAgentError.invalidMessageType( - type: offer.type, - shouldBe: [ - ProtocolTypes.didcommOfferCredential3_0.rawValue - ] - ) - } - let format: String - 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: - throw EdgeAgentError.invalidMessageType( - type: offerFormat, - shouldBe: [ - "prism/jwt", - "anoncreds/credential-offer@v1.0" - ] - ) - } - - let type = offerPiuri == .didcommOfferCredential ? - ProtocolTypes.didcommRequestCredential : - ProtocolTypes.didcommRequestCredential3_0 - - let requestCredential = RequestCredential3_0( - body: .init( - goalCode: offer.body.goalCode, - comment: offer.body.comment - ), - type: type.rawValue, - attachments: [.init( - data: AttachmentBase64(base64: base64String), - format: format - )], - thid: offer.thid, - from: offer.to, - to: offer.from - ) - return requestCredential } } diff --git a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+DIDHigherFucntions.swift b/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+DIDHigherFucntions.swift index 45a65724..7c785270 100644 --- a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+DIDHigherFucntions.swift +++ b/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+DIDHigherFucntions.swift @@ -132,120 +132,6 @@ Could not find key in storage please use Castor instead and provide the private .await() } - /// This function creates a new Peer DID, stores it in pluto database and updates the mediator if requested. - /// - /// - Parameters: - /// - services: The services associated to the new DID. - /// - updateMediator: Indicates if the new DID should be added to the mediator's list. It will as well add the mediator service. - /// - Returns: A new DID - /// - Throws: EdgeAgentError, if updateMediator is true and there is no mediator available or if storing the new DID failed - func createNewPeerDID( - services: [DIDDocument.Service] = [], - alias: String? = "", - updateMediator: Bool - ) async throws -> DID { - var keyAgreementPrivateKey = try apollo.createPrivateKey(parameters: [ - KeyProperties.type.rawValue: "EC", - KeyProperties.curve.rawValue: KnownKeyCurves.x25519.rawValue - ]) - - var authenticationPrivateKey = try apollo.createPrivateKey(parameters: [ - KeyProperties.type.rawValue: "EC", - KeyProperties.curve.rawValue: KnownKeyCurves.ed25519.rawValue - ]) - - let withServices: [DIDDocument.Service] - if updateMediator, let routingDID = mediatorRoutingDID?.string { - withServices = services + [.init( - id: "#didcomm-1", - type: ["DIDCommMessaging"], - serviceEndpoint: [.init( - uri: routingDID - )])] - } else { - withServices = services - } - - let newDID = try castor.createPeerDID( - keyAgreementPublicKey: keyAgreementPrivateKey.publicKey(), - authenticationPublicKey: authenticationPrivateKey.publicKey(), - services: withServices - ) - - let didDocument = try await castor.resolveDID(did: newDID) - didDocument.authenticate.first.map { authenticationPrivateKey.identifier = $0.id.string } - didDocument.keyAgreement.first.map { keyAgreementPrivateKey.identifier = $0.id.string } - - logger.debug(message: "Created new Peer DID", metadata: [ - .maskedMetadataByLevel(key: "DID", value: newDID.string, level: .debug) - ]) - - try await registerPeerDID( - did: newDID, - privateKeys: [ - keyAgreementPrivateKey, - authenticationPrivateKey - ], - alias: alias, - updateMediator: updateMediator - ) - - return newDID - } - - /// This function registers a Peer DID, stores it and his private key in pluto database and updates the mediator if requested. - /// - /// - Parameters: - /// - services: The services associated to the new DID. - /// - updateMediator: Indicates if the new DID should be added to the mediator's list. - /// - Returns: A new DID - /// - Throws: EdgeAgentError, if updateMediator is true and there is no mediator available or if storing the new DID failed - func registerPeerDID( - did: DID, - privateKeys: [PrivateKey], - alias: String?, - updateMediator: Bool - ) async throws { - if updateMediator { - try await updateMediatorWithDID(did: did) - } - logger.debug(message: "Register of DID in storage", metadata: [ - .maskedMetadataByLevel(key: "DID", value: did.string, level: .debug) - ]) - - let storablePrivateKeys = try privateKeys - .map { - guard let storablePrivateKey = $0 as? (PrivateKey & StorableKey) else { - throw KeyError.keyRequiresConformation(conformations: ["PrivateKey", "StorableKey"]) - } - return storablePrivateKey - } - - try await pluto - .storePeerDID( - did: did, - privateKeys: storablePrivateKeys, - alias: alias - ) - .first() - .await() - } - - /// This function updates the mediator key list with a new DID. - /// - /// - Parameters: - /// - services: The services associated to the new DID. - /// - updateMediator: Indicates if the new DID should be added to the mediator's list. - /// - Returns: A new DID - /// - Throws: EdgeAgentError, if updateMediator is true and there is no mediator available - func updateMediatorWithDID(did: DID) async throws { - logger.debug(message: "Update mediator key list with DID", metadata: [ - .maskedMetadataByLevel(key: "DID", value: did.string, level: .debug) - ]) - - try await mediationHandler?.updateKeyListWithDIDs(dids: [did]) - } - /// This function gets the DID information (alias) for a given DID /// /// - Parameter did: The DID for which the information is requested diff --git a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent.swift b/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent.swift index a2f28f6d..4acaf83f 100644 --- a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent.swift +++ b/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent.swift @@ -7,42 +7,14 @@ import Foundation /// EdgeAgent class is responsible for handling the connection to other agents in the network using /// a provided Mediator Service Endpoint and seed data. public class EdgeAgent { - /// Enumeration representing the current state of the agent. - public enum State: String { - case stoped - case starting - case running - case stoping - } - /// Represents the seed data used to create a unique DID. public let seed: Seed - /// Represents the current state of the agent. - public private(set) var state = State.stoped - - // TODO: This is to be deleted - public private(set) var requestedPresentations: CurrentValueSubject< - [(RequestPresentation, Bool)], Never - > = .init([]) - - /// The mediator routing DID if one is currently registered. - public var mediatorRoutingDID: DID? { - connectionManager?.mediationHandler.mediator?.routingDID - } - let logger = SDKLogger(category: .edgeAgent) 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? - var cancellables = [AnyCancellable]() - // Not a "stream" - var messagesStreamTask: Task? public static func setupLogging(logLevels: [LogComponent: LogLevel]) { SDKLogger.logLevels = logLevels @@ -63,26 +35,13 @@ public class EdgeAgent { castor: Castor, pluto: Pluto, pollux: Pollux & CredentialImporter, - mercury: Mercury, - mediationHandler: MediatorHandler? = nil, seed: Seed? = nil ) { self.apollo = apollo self.castor = castor self.pluto = pluto self.pollux = pollux - self.mercury = mercury self.seed = seed ?? apollo.createRandomSeed().seed - self.mediationHandler = mediationHandler - mediationHandler.map { - self.connectionManager = ConnectionsManagerImpl( - castor: castor, - mercury: mercury, - pluto: pluto, - mediationHandler: $0, - pairings: [] - ) - } } /** @@ -92,109 +51,23 @@ public class EdgeAgent { - seedData: Optional seed data for creating a new seed. If not provided, a random seed will be generated. - mediatorServiceEnpoint: Optional DID representing the service endpoint of the mediator. If not provided, the default Prism mediator endpoint will be used. */ - public convenience init( - seedData: Data? = nil, - mediatorDID: DID - ) { + public convenience init(seedData: Data? = nil) { let apollo = ApolloBuilder().build() let castor = CastorBuilder(apollo: apollo).build() let pluto = PlutoBuilder().build() let pollux = PolluxBuilder(pluto: pluto, castor: castor).build() - let secretsStream = createSecretsStream( - keyRestoration: apollo, - pluto: pluto, - castor: castor - ) - - let mercury = MercuryBuilder( - castor: castor, - secretsStream: secretsStream - ).build() - let seed = seedData.map { Seed(value: $0) } ?? apollo.createRandomSeed().seed self.init( apollo: apollo, castor: castor, pluto: pluto, pollux: pollux, - mercury: mercury, - mediationHandler: BasicMediatorHandler( - mediatorDID: mediatorDID, - mercury: mercury, - store: BasicMediatorHandler.PlutoMediatorStoreImpl(pluto: pluto) - ), seed: seed ) } - public func setupMediatorHandler(mediationHandler: MediatorHandler) async throws { - try await stop() - self.mediationHandler = mediationHandler - self.connectionManager = ConnectionsManagerImpl( - castor: castor, - mercury: mercury, - pluto: pluto, - mediationHandler: mediationHandler, - pairings: [] - ) - } - - public func setupMediatorDID(did: DID) async throws { - let mediatorHandler = BasicMediatorHandler( - mediatorDID: did, - mercury: mercury, - store: BasicMediatorHandler.PlutoMediatorStoreImpl(pluto: pluto) - ) - try await setupMediatorHandler(mediationHandler: mediatorHandler) - } - - /** - Start the EdgeAgent and Mediator services - - - Throws: EdgeAgentError.noMediatorAvailableError if no mediator is available, - as well as any error thrown by `createNewPeerDID` and `connectionManager.registerMediator` - */ - public func start() async throws { - guard - let connectionManager, - state == .stoped - else { return } - logger.info(message: "Starting agent") - state = .starting - do { - try await connectionManager.startMediator() - } catch EdgeAgentError.noMediatorAvailableError { - let hostDID = try await createNewPeerDID(updateMediator: false) - try await connectionManager.registerMediator(hostDID: hostDID) - } - try await firstLinkSecretSetup() - state = .running - logger.info(message: "Mediation Achieved", metadata: [ - .publicMetadata(key: "Routing DID", value: mediatorRoutingDID?.string ?? "") - ]) - logger.info(message: "Agent running") - } - - /** - This function is used to stop the EdgeAgent. - The function sets the state of EdgeAgent to .stoping. - All ongoing events that was created by the EdgeAgent are stopped. - After all the events are stopped the state of the EdgeAgent is set to .stoped. - - - Throws: If the current state is not running throws error. - */ - public func stop() async throws { - guard state == .running else { return } - logger.info(message: "Stoping agent") - state = .stoping - cancellables.forEach { $0.cancel() } - connectionManager?.stopAllEvents() - state = .stoped - logger.info(message: "Agent not running") - } - - private func firstLinkSecretSetup() async throws { + func firstLinkSecretSetup() async throws { if try await pluto.getLinkSecret().first().await() == nil { let secret = try apollo.createNewLinkSecret() guard let storableSecret = secret.storable else { @@ -213,57 +86,3 @@ extension DID { return str } } - -private func createSecretsStream( - keyRestoration: KeyRestoration, - pluto: Pluto, - castor: Castor -) -> AnyPublisher<[Secret], Error> { - pluto.getAllKeys() - .first() - .flatMap { keys in - Future { - let privateKeys = await keys.asyncMap { - try? await keyRestoration.restorePrivateKey($0) - }.compactMap { $0 } - return try parsePrivateKeys( - privateKeys: privateKeys, - castor: castor - ) - } - } - .eraseToAnyPublisher() -} - -private func parsePrivateKeys( - privateKeys: [PrivateKey], - castor: Castor -) throws -> [Domain.Secret] { - return try privateKeys - .map { $0 as? (PrivateKey & ExportableKey & StorableKey) } - .compactMap { $0 } - .map { privateKey in - return privateKey - } - .map { privateKey in - try parseToSecret( - privateKey: privateKey, - identifier: privateKey.identifier - ) - } -} - -private func parseToSecret(privateKey: PrivateKey & ExportableKey, identifier: String) throws -> Domain.Secret { - let jwk = privateKey.jwk - guard - let dataJson = try? JSONEncoder().encode(jwk), - let stringJson = String(data: dataJson, encoding: .utf8) - else { - throw CommonError.invalidCoding(message: "Could not encode privateKey.jwk") - } - return .init( - id: identifier, - type: .jsonWebKey2020, - secretMaterial: .jwk(value: stringJson) - ) -} diff --git a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgentErrors.swift b/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgentErrors.swift index 2a7d51be..f8894a66 100644 --- a/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgentErrors.swift +++ b/EdgeAgentSDK/EdgeAgent/Sources/EdgeAgentErrors.swift @@ -9,6 +9,67 @@ import Domain */ public enum EdgeAgentError: KnownPrismError { + public enum OIDCError: KnownPrismError { + /// An error case representing that a `CredentialOffer` doesnt contain grants. + case noGrantProvided + + /// An error case representing that a flow is not supported. + case flowNotSupported + + /// An error case representing an internal error normally originating from a library + case internalError(error: Error) + + /// An error case representing an invalid callback url + case invalidCallbackURL + + /// An error case representing that some parameters are missing from the callback url + case missingQueryParameters(parameters: [String]) + + /// An error case representing that a credential request cannot be of deferred type + case crendentialResponseDeferredNotSupported + + /// An error case representing that an invalid proof was submited + case invalidProof(errorDescription: String?) + + public var code: Int { + switch self { + case .noGrantProvided: + return 1001 + case .flowNotSupported: + return 1002 + case .internalError: + return 1003 + case .invalidCallbackURL: + return 1004 + case .missingQueryParameters: + return 1005 + case .crendentialResponseDeferredNotSupported: + return 1006 + case .invalidProof: + return 1007 + } + } + + public var message: String { + switch self { + case .noGrantProvided: + return "No grant was provided" + case .flowNotSupported: + return "The flow is not supported" + case .internalError(error: let error): + return "An internal error occurred: \(error)" + case .invalidCallbackURL: + return "The callback url is invalid" + case .missingQueryParameters(parameters: let parameters): + return "The following query parameters are missing on the callback url: \(parameters)" + case .crendentialResponseDeferredNotSupported: + return "Credential response deferred is not supported" + case .invalidProof(errorDescription: let errorDescription): + return "The proof is invalid: \(errorDescription ?? "")" + } + } + } + /// An error case representing that a DID key pair index could not be found. case cannotFindDIDKeyPairIndex diff --git a/EdgeAgentSDK/EdgeAgent/Sources/OIDCAgent/OIDCAgent+DIDs.swift b/EdgeAgentSDK/EdgeAgent/Sources/OIDCAgent/OIDCAgent+DIDs.swift new file mode 100644 index 00000000..c8f693ac --- /dev/null +++ b/EdgeAgentSDK/EdgeAgent/Sources/OIDCAgent/OIDCAgent+DIDs.swift @@ -0,0 +1,43 @@ +import Combine +import Domain +import Foundation + +// MARK: DID High Level functionalities +public extension OIDCAgent { + + /// This method create a new Prism DID, that can be used to identify the agent and interact with other agents. + /// - Parameters: + /// - keyPathIndex: key path index used to identify the DID + /// - alias: An alias that can be used to identify the DID + /// - services: an array of services associated to the DID + /// - Returns: The new created DID + func createNewPrismDID( + keyPathIndex: Int? = nil, + alias: String? = nil, + services: [DIDDocument.Service] = [] + ) async throws -> DID { + try await edgeAgent.createNewPrismDID( + keyPathIndex: keyPathIndex, + alias: alias, + services: services + ) + } + + /// This method registers a Prism DID, that can be used to identify the agent and interact with other agents. + /// - Parameters: + /// - did: the DID which will be registered. + /// - keyPathIndex: key path index used to identify the DID + /// - alias: An alias that can be used to identify the DID + /// - Returns: The new created DID + func registerPrismDID( + did: DID, + privateKey: PrivateKey, + alias: String? = nil + ) async throws { + try await edgeAgent.registerPrismDID( + did: did, + privateKey: privateKey, + alias: alias + ) + } +} diff --git a/EdgeAgentSDK/EdgeAgent/Sources/OIDCAgent/OIDCAgent.swift b/EdgeAgentSDK/EdgeAgent/Sources/OIDCAgent/OIDCAgent.swift new file mode 100644 index 00000000..f9cf5f06 --- /dev/null +++ b/EdgeAgentSDK/EdgeAgent/Sources/OIDCAgent/OIDCAgent.swift @@ -0,0 +1,198 @@ +import Core +import Domain +import Foundation +import OpenID4VCI +import JSONWebKey + +public class OIDCAgent { + public let edgeAgent: EdgeAgent + public var apollo: Apollo & KeyRestoration { edgeAgent.apollo } + public var castor: Castor { edgeAgent.castor } + public var pluto: Pluto { edgeAgent.pluto } + public var pollux: Pollux & CredentialImporter { edgeAgent.pollux } + var logger: SDKLogger { edgeAgent.logger } + + /// Initializes a EdgeAgent with the given dependency objects and seed data. + /// + /// - Parameters: + /// - apollo: An instance of Apollo. + /// - castor: An instance of Castor. + /// - pluto: An instance of Pluto. + /// - pollux: An instance of Pollux. + /// - mercury: An instance of Mercury. + /// - seed: A unique seed used to generate the unique DID. + /// - mediatorServiceEnpoint: The endpoint of the Mediator service to use. + public init( + edgeAgent: EdgeAgent + ) { + self.edgeAgent = edgeAgent + } + + /** + Convenience initializer for `EdgeAgent` that allows for optional initialization of seed data and mediator service endpoint. + + - Parameters: + - seedData: Optional seed data for creating a new seed. If not provided, a random seed will be generated. + - mediatorServiceEnpoint: Optional DID representing the service endpoint of the mediator. If not provided, the default Prism mediator endpoint will be used. + */ + public convenience init( + seedData: Data? = nil + ) { + let edgeAgent = EdgeAgent(seedData: seedData) + + self.init(edgeAgent: edgeAgent) + } + + public func parseCredentialOffer( + offerUri: String + ) async throws -> CredentialOffer { + let result = try await CredentialOfferRequestResolver() + .resolve(source: .init(urlString: offerUri)) + switch result { + case .success(let success): + return success + case .failure(let failure): + throw failure + } + } + + public func createAuthorizationRequest( + clientId: String, + redirectUri: URL, + offer: CredentialOffer + ) async throws -> (Issuer, UnauthorizedRequest) { + let config = OpenId4VCIConfig( + clientId: clientId, + authFlowRedirectionURI: redirectUri, + authorizeIssuanceConfig: .favorScopes, + usePAR: false + ) + + guard let grants = offer.grants else { + throw EdgeAgentError.OIDCError.noGrantProvided + } + + let issuer = try Issuer( + authorizationServerMetadata: offer.authorizationServerMetadata, + issuerMetadata: offer.credentialIssuerMetadata, + config: config + ) + + switch grants { + case .authorizationCode: + let parPlaced = try await issuer.pushAuthorizationCodeRequest( + credentialOffer: offer + ) + switch parPlaced { + case .success(let success): + return (issuer, success) + case .failure(let failure): + throw EdgeAgentError.OIDCError.internalError(error: failure) + } + default: + throw EdgeAgentError.OIDCError.flowNotSupported + } + } + + public func handleTokenRequest( + request: UnauthorizedRequest, + issuer: Issuer, + callbackUrl: URL + ) async throws -> (Issuer, AuthorizedRequest){ + guard let components = URLComponents(url: callbackUrl, resolvingAgainstBaseURL: false) else { + throw EdgeAgentError.OIDCError.invalidCallbackURL + } + guard + let queryItems = components.queryItems, + let code = queryItems.first(where: { $0.name == "code" })?.value + else { + throw EdgeAgentError.OIDCError.missingQueryParameters(parameters: ["code"]) + } + + let issuanceAuthorization: IssuanceAuthorization = .authorizationCode(authorizationCode: code) + let unAuthorized = await issuer.handleAuthorizationCode( + parRequested: request, + authorizationCode: issuanceAuthorization + ) + + switch unAuthorized { + case .success(let request): + let authorizedRequest = await issuer.requestAccessToken(authorizationCode: request) + + if case let .success(authorized) = authorizedRequest { + return (issuer, authorized) + } + + case .failure(let error): + throw EdgeAgentError.OIDCError.internalError(error: error) + } + + throw UnknownError.somethingWentWrongError(customMessage: "OIDC Flow did not complete successfully", underlyingErrors: nil) + + } + + public func credentialRequest( + issuer: Issuer, + offer: CredentialOffer, + request: AuthorizedRequest + ) async throws -> Credential { + let payload: IssuanceRequestPayload = .configurationBased( + credentialConfigurationIdentifier: offer.credentialConfigurationIdentifiers.first!, + claimSet: nil + ) + + let did = try await createNewPrismDID() + + guard + let keys = try await pluto.getDIDPrivateKeys(did: did).first().await(), + let key = try await keys.first.asyncMap({ try await apollo.restorePrivateKey($0) }), + let exported = key.exporting?.jwkWithKid(kid: did.string + "#authentication0") + else { + throw EdgeAgentError.cannotFindDIDKeyPairIndex + } + + let privateJwk = try exported.toJoseJWK() + let result = try await issuer.requestSingle( + proofRequest: request, + bindingKey: .jwk( + algorithm: .init(name: "ES256K"), + jwk: privateJwk.publicKey, + privateKey: privateJwk, + issuer: did.string + ), + requestPayload: payload) { issuerResponseEncryptionMetadata in + Issuer.createResponseEncryptionSpec(issuerResponseEncryptionMetadata) + } + + switch result { + case .success(let success): + switch success { + case .success(let response): + guard let credential = response.credentialResponses.first else { + throw UnknownError.somethingWentWrongError(customMessage: nil, underlyingErrors: nil) + } + switch credential { + case .deferred: + throw EdgeAgentError.OIDCError.crendentialResponseDeferredNotSupported + case .issued(let credential, _): + let parsedCredential = try await pollux.importCredential( + credentialData: credential.tryToData(), + restorationType: "jwt", + options: [] + ) + if let storableCredential = parsedCredential.storable { + try await pluto.storeCredential(credential: storableCredential).first().await() + } + + return parsedCredential + } + case .failed(let error): + throw EdgeAgentError.OIDCError.internalError(error: error) + case .invalidProof(_, let errorDescription): + throw EdgeAgentError.OIDCError.invalidProof(errorDescription: errorDescription) + } + case .failure(let failure): + throw EdgeAgentError.OIDCError.internalError(error: failure) + } + } +} diff --git a/EdgeAgentSDK/EdgeAgent/Tests/AnoncredsPresentationFlowTest.swift b/EdgeAgentSDK/EdgeAgent/Tests/AnoncredsPresentationFlowTest.swift index 592ad376..a32cb71c 100644 --- a/EdgeAgentSDK/EdgeAgent/Tests/AnoncredsPresentationFlowTest.swift +++ b/EdgeAgentSDK/EdgeAgent/Tests/AnoncredsPresentationFlowTest.swift @@ -13,19 +13,19 @@ final class AnoncredsPresentationFlowTest: XCTestCase { var castor: Castor! var pollux: (Pollux & CredentialImporter)! var mercury = MercuryStub() - var edgeAgent: EdgeAgent! + var edgeAgent: DIDCommAgent! var linkSecret: Key! override func setUp() async throws { castor = CastorBuilder(apollo: apollo).build() pollux = PolluxBuilder(pluto: pluto, castor: castor).build() - edgeAgent = EdgeAgent( + let edgeAgent = EdgeAgent( apollo: apollo, castor: castor, pluto: pluto, - pollux: pollux, - mercury: mercury + pollux: pollux ) + self.edgeAgent = DIDCommAgent(edgeAgent: edgeAgent, mercury: mercury) linkSecret = try apollo.createNewLinkSecret() } @@ -51,7 +51,8 @@ final class AnoncredsPresentationFlowTest: XCTestCase { let presentation = try await edgeAgent.createPresentationForRequestProof( request: presentationRequest, - credential: credential + credential: credential, + options: [.disclosingClaims(claims: ["test"])] ) let verification = try await edgeAgent.pollux.verifyPresentation( diff --git a/EdgeAgentSDK/EdgeAgent/Tests/BackupWalletTests.swift b/EdgeAgentSDK/EdgeAgent/Tests/BackupWalletTests.swift index af4f4931..bc74b39b 100644 --- a/EdgeAgentSDK/EdgeAgent/Tests/BackupWalletTests.swift +++ b/EdgeAgentSDK/EdgeAgent/Tests/BackupWalletTests.swift @@ -23,7 +23,6 @@ final class BackupWalletTests: XCTestCase { castor: castor, pluto: pluto, pollux: pollux, - mercury: MercuryStub(), seed: seed ) return (agent, pluto) @@ -39,7 +38,6 @@ final class BackupWalletTests: XCTestCase { castor: castor, pluto: pluto, pollux: pollux, - mercury: MercuryStub(), seed: seed ) return (agent, pluto) @@ -47,7 +45,7 @@ final class BackupWalletTests: XCTestCase { func testBackup() async throws { let (backupAgent, backupPluto) = try createAgent() - _ = try await backupAgent.createNewPeerDID(updateMediator: false) + _ = try await backupAgent.createNewPrismDID() _ = try await backupAgent.createNewPrismDID() backupPluto.didPairs = [ diff --git a/EdgeAgentSDK/EdgeAgent/Tests/CheckTest.swift b/EdgeAgentSDK/EdgeAgent/Tests/CheckTest.swift index ad583a2b..838a3b44 100644 --- a/EdgeAgentSDK/EdgeAgent/Tests/CheckTest.swift +++ b/EdgeAgentSDK/EdgeAgent/Tests/CheckTest.swift @@ -29,7 +29,7 @@ final class CheckTests: XCTestCase { func testOOB() async throws { let oob = "https://mediator.rootsid.cloud?_oob=eyJ0eXBlIjoiaHR0cHM6Ly9kaWRjb21tLm9yZy9vdXQtb2YtYmFuZC8yLjAvaW52aXRhdGlvbiIsImlkIjoiNzk0Mjc4MzctY2MwNi00ODUzLWJiMzktNjg2ZWFjM2U2YjlhIiwiZnJvbSI6ImRpZDpwZWVyOjIuRXo2TFNtczU1NVloRnRobjFXVjhjaURCcFptODZoSzl0cDgzV29qSlVteFBHazFoWi5WejZNa21kQmpNeUI0VFM1VWJiUXc1NHN6bTh5dk1NZjFmdEdWMnNRVllBeGFlV2hFLlNleUpwWkNJNkltNWxkeTFwWkNJc0luUWlPaUprYlNJc0luTWlPaUpvZEhSd2N6b3ZMMjFsWkdsaGRHOXlMbkp2YjNSemFXUXVZMnh2ZFdRaUxDSmhJanBiSW1ScFpHTnZiVzB2ZGpJaVhYMCIsImJvZHkiOnsiZ29hbF9jb2RlIjoicmVxdWVzdC1tZWRpYXRlIiwiZ29hbCI6IlJlcXVlc3RNZWRpYXRlIiwibGFiZWwiOiJNZWRpYXRvciIsImFjY2VwdCI6WyJkaWRjb21tL3YyIl19fQ" - let agent = EdgeAgent(mediatorDID: DID(method: "peer", methodId: "123")) + let agent = DIDCommAgent(mediatorDID: DID(method: "peer", methodId: "123")) let parsed = try await agent.parseInvitation(str: oob) print(parsed) diff --git a/EdgeAgentSDK/EdgeAgent/Tests/Helper/MockPollux.swift b/EdgeAgentSDK/EdgeAgent/Tests/Helper/MockPollux.swift index 3fd0f109..46cd3e35 100644 --- a/EdgeAgentSDK/EdgeAgent/Tests/Helper/MockPollux.swift +++ b/EdgeAgentSDK/EdgeAgent/Tests/Helper/MockPollux.swift @@ -25,6 +25,30 @@ struct MockCredential: Credential, StorableCredential, ExportableCredential { } struct MockPollux: Pollux & CredentialImporter { + func parseCredential( + type: String, + credentialPayload: Data, + options: [Domain.CredentialOperationsOptions] + ) async throws -> any Domain.Credential { + return MockCredential(exporting: Data(count: 5), restorationType: "mocked") + } + + func processCredentialRequest( + type: String, + offerPayload: Data, + options: [Domain.CredentialOperationsOptions] + ) async throws -> String { + "" + } + + func verifyPresentation( + type: String, + presentationPayload: Data, + options: [Domain.CredentialOperationsOptions] + ) async throws -> Bool { + false + } + func importCredential( credentialData: Data, restorationType: String, diff --git a/EdgeAgentSDK/EdgeAgent/Tests/PresentationExchangeTests.swift b/EdgeAgentSDK/EdgeAgent/Tests/PresentationExchangeTests.swift index 546961cb..80c28cb7 100644 --- a/EdgeAgentSDK/EdgeAgent/Tests/PresentationExchangeTests.swift +++ b/EdgeAgentSDK/EdgeAgent/Tests/PresentationExchangeTests.swift @@ -15,19 +15,19 @@ final class PresentationExchangeFlowTests: XCTestCase { var castor: Castor! var pollux: (Pollux & CredentialImporter)! var mercury = MercuryStub() - var edgeAgent: EdgeAgent! + var edgeAgent: DIDCommAgent! let logger = Logger(label: "presentation_exchange_test") override func setUp() async throws { castor = CastorBuilder(apollo: apollo).build() pollux = PolluxBuilder(pluto: pluto, castor: castor).build() - edgeAgent = EdgeAgent( + let edgeAgent = EdgeAgent( apollo: apollo, castor: castor, pluto: pluto, - pollux: pollux, - mercury: mercury + pollux: pollux ) + self.edgeAgent = DIDCommAgent(edgeAgent: edgeAgent, mercury: mercury) } func testJWTPresentationRequest() async throws { @@ -126,7 +126,8 @@ final class PresentationExchangeFlowTests: XCTestCase { let presentation = try await edgeAgent.createPresentationForRequestProof( request: message, - credential: credential + credential: credential, + options: [.disclosingClaims(claims: ["test"])] ) let verification = try await edgeAgent.pollux.verifyPresentation( diff --git a/EdgeAgentSDK/Pollux/Sources/Models/AnonCreds/AnoncredsCredentialStack+ProvableCredential.swift b/EdgeAgentSDK/Pollux/Sources/Models/AnonCreds/AnoncredsCredentialStack+ProvableCredential.swift index 1ec94d64..6e6af434 100644 --- a/EdgeAgentSDK/Pollux/Sources/Models/AnonCreds/AnoncredsCredentialStack+ProvableCredential.swift +++ b/EdgeAgentSDK/Pollux/Sources/Models/AnonCreds/AnoncredsCredentialStack+ProvableCredential.swift @@ -57,6 +57,43 @@ extension AnoncredsCredentialStack: ProvableCredential { } } + func presentation(type: String, requestPayload: Data, options: [CredentialOperationsOptions]) throws -> String { + let requestStr = try requestPayload.tryToString() + guard + let linkSecretOption = options.first(where: { + if case .linkSecret = $0 { return true } + return false + }), + case let CredentialOperationsOptions.linkSecret(_, secret: linkSecret) = linkSecretOption + else { + throw PolluxError.missingAndIsRequiredForOperation(type: "LinkSecret") + } + + if + let zkpParameters = options.first(where: { + if case .zkpPresentationParams = $0 { return true } + return false + }), + case let CredentialOperationsOptions.zkpPresentationParams(attributes, predicates) = zkpParameters + { + return try AnoncredsPresentation().createPresentation( + stack: self, + request: requestStr, + linkSecret: linkSecret, + attributes: attributes, + predicates: predicates + ) + } else { + return try AnoncredsPresentation().createPresentation( + stack: self, + request: requestStr, + linkSecret: linkSecret, + attributes: try computeAttributes(requestJson: requestStr), + predicates: try computePredicates(requestJson: requestStr) + ) + } + } + func isValidForPresentation(request: Message, options: [CredentialOperationsOptions]) throws -> Bool { guard let attachment = request.attachments.first @@ -71,6 +108,19 @@ extension AnoncredsCredentialStack: ProvableCredential { return false } } + + func isValidForPresentation( + type: String, + requestPayload: Data, + options: [CredentialOperationsOptions] + ) throws -> Bool { + switch type { + case "anoncreds/proof-request@v1.0": + return true + default: + return false + } + } } private func computeAttributes(requestJson: String) throws -> [String: Bool] { diff --git a/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTCredential+ProofableCredential.swift b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTCredential+ProofableCredential.swift index 934125e5..1d225cfd 100644 --- a/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTCredential+ProofableCredential.swift +++ b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTCredential+ProofableCredential.swift @@ -4,9 +4,39 @@ import JSONWebToken extension JWTCredential: ProvableCredential { public func presentation(request: Message, options: [CredentialOperationsOptions]) throws -> String { + guard + let attachment = request.attachments.first, + let format = attachment.format, + 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 + } + return try JWTPresentation().createPresentation( + credential: self, + type: format, + requestData: requestData, + options: options + ) + } + + public func presentation( + type: String, + requestPayload: Data, + options: [CredentialOperationsOptions] + ) throws -> String { try JWTPresentation().createPresentation( credential: self, - request: request, + type: type, + requestData: requestPayload, options: options ) } @@ -46,4 +76,33 @@ extension JWTCredential: ProvableCredential { throw PolluxError.unsupportedAttachmentFormat(attachment.format) } } + + public func isValidForPresentation( + type: String, + requestPayload: Data, + options: [CredentialOperationsOptions] + ) throws -> Bool { + switch type { + case "dif/presentation-exchange/definitions@v1.0": + let requestData = try JSONDecoder.didComm().decode(PresentationExchangeRequest.self, from: requestPayload) + let payload: Data = try JWT.getPayload(jwtString: jwtString) + guard + let format = requestData.presentationDefinition.format?.jwt + else { + return false + } + do { + try requestData.presentationDefinition.inputDescriptors.forEach { + try VerifyJsonClaim.verify(inputDescriptor: $0, jsonData: payload) + } + return true + } catch { + return false + } + case "prism/jwt", "jwt": + return true + default: + throw PolluxError.unsupportedAttachmentFormat(type) + } + } } diff --git a/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTCredential.swift b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTCredential.swift index 3e617878..527a5a88 100644 --- a/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTCredential.swift +++ b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTCredential.swift @@ -40,12 +40,25 @@ extension JWTCredential: Credential { public var claims: [Claim] { guard - let dic = jwtVerifiableCredential.verifiableCredential.credentialSubject.value as? [String: String] + let dic = jwtVerifiableCredential.verifiableCredential.credentialSubject.value as? [String: Any] else { return [] } - return dic.map { - Claim(key: $0, value: .string($1)) + return dic.compactMap { + switch $1 { + case let value as Date: + Claim(key: $0, value: .date(value)) + case let value as Data: + Claim(key: $0, value: .data(value)) + case let value as Bool: + Claim(key: $0, value: .bool(value)) + case let value as String: + Claim(key: $0, value: .string(value)) + case let value as NSNumber: + Claim(key: $0, value: .number(value.doubleValue)) + default: + nil + } } } diff --git a/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPresentation.swift b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPresentation.swift index 47f48225..87b72441 100644 --- a/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPresentation.swift +++ b/EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPresentation.swift @@ -38,7 +38,8 @@ struct JWTPresentation { func createPresentation( credential: JWTCredential, - request: Message, + type: String, + requestData: Data, options: [CredentialOperationsOptions] ) throws -> String { guard @@ -61,23 +62,7 @@ struct JWTPresentation { throw PolluxError.requiresExportableKeyForOperation(operation: "Create Presentation JWT Credential") } - 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 { + switch type { case "dif/presentation-exchange/definitions@v1.0": return try presentation( credential: credential, diff --git a/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT+ProvableCredential.swift b/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT+ProvableCredential.swift index 900cbf73..479f58a3 100644 --- a/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT+ProvableCredential.swift +++ b/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT+ProvableCredential.swift @@ -3,14 +3,52 @@ import Foundation extension SDJWTCredential: ProvableCredential { func presentation(request: Domain.Message, options: [Domain.CredentialOperationsOptions]) throws -> String { + guard + let attachment = request.attachments.first, + let format = attachment.format, + 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 + } + return try SDJWTPresentation().createPresentation( + credential: self, + type: format, + requestData: requestData, + options: options + ) + } + + func presentation( + type: String, + requestPayload: Data, + options: [CredentialOperationsOptions] + ) throws -> String { try SDJWTPresentation().createPresentation( credential: self, - request: request, + type: type, + requestData: requestPayload, options: options ) } - + func isValidForPresentation(request: Domain.Message, options: [Domain.CredentialOperationsOptions]) throws -> Bool { request.attachments.first.map { $0.format == "vc+sd-jwt"} ?? true } + + func isValidForPresentation( + type: String, + requestPayload: Data, + options: [CredentialOperationsOptions] + ) throws -> Bool { + type == "vc+sd-jwt" + } } diff --git a/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWTPresentation.swift b/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWTPresentation.swift index 8f40590b..e2e6d3ce 100644 --- a/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWTPresentation.swift +++ b/EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWTPresentation.swift @@ -7,7 +7,8 @@ import JSONWebKey struct SDJWTPresentation { func createPresentation( credential: SDJWTCredential, - request: Message, + type: String, + requestData: Data, options: [CredentialOperationsOptions] ) throws -> String{ guard @@ -34,23 +35,7 @@ struct SDJWTPresentation { 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 { + switch type { case "dif/presentation-exchange/definitions@v1.0": return try presentation( credential: credential, diff --git a/EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialRequest.swift b/EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialRequest.swift index 9fc7b695..1f5f3bc1 100644 --- a/EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialRequest.swift +++ b/EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialRequest.swift @@ -64,6 +64,37 @@ extension PolluxImpl { throw PolluxError.invalidCredentialError } + public func processCredentialRequest( + type: String, + offerPayload: Data, + options: [CredentialOperationsOptions] + ) async throws -> String { + switch type { + case "jwt", "prism/jwt": + return try await processJWTCredentialRequest(offerData: offerPayload, options: options) + case "vc+sd-jwt": + return try await processSDJWTCredentialRequest(offerData: offerPayload, options: options) + case "anoncreds/credential-offer@v1.0": + guard + let thidOption = options.first(where: { + if case .thid = $0 { return true } + return false + }), + case let CredentialOperationsOptions.thid(thid) = thidOption + else { + throw PolluxError.missingAndIsRequiredForOperation(type: "thid") + } + return try await processAnoncredsCredentialRequest( + offerData: offerPayload, + thid: thid, + options: options + ) + default: + break + } + throw PolluxError.invalidCredentialError + } + private func processJWTCredentialRequest(offerData: Data, options: [CredentialOperationsOptions]) async throws -> String { guard let subjectDIDOption = options.first(where: { diff --git a/EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialVerification.swift b/EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialVerification.swift index bbd3a051..9eb26aba 100644 --- a/EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialVerification.swift +++ b/EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialVerification.swift @@ -59,6 +59,38 @@ extension PolluxImpl { } } +//<<<<<<< HEAD +// private func getDefinition(id: String) async throws -> PresentationExchangeRequest { +//======= + public func verifyPresentation( + type: String, + presentationPayload: Data, + options: [CredentialOperationsOptions] + ) async throws -> Bool { + guard + let requestIdOption = options.first(where: { + if case .presentationRequestId = $0 { return true } + return false + }), + case let CredentialOperationsOptions.presentationRequestId(requestId) = requestIdOption + else { + throw PolluxError.invalidPrismDID + } + + switch type { + case "dif/presentation-exchange/submission@v1.0": + return try await verifyPresentationSubmission(json: presentationPayload, requestId: requestId) + case "anoncreds/proof@v1.0": + return try await verifyAnoncreds( + presentation: presentationPayload, + requestId: requestId, + options: options + ) + default: + throw PolluxError.unsupportedAttachmentFormat(type) + } + } + private func getDefinition(id: String) async throws -> PresentationExchangeRequest { guard let request = try await pluto.getMessage(id: id).first().await(), @@ -80,54 +112,20 @@ extension PolluxImpl { return try JSONDecoder.didComm().decode(PresentationExchangeRequest.self, from: json) } - private func verifyJWTs(credentials: [String]) async throws { - var errors = [Error]() - await credentials - .asyncForEach { - do { - try await verifyJWT(jwtString: $0) - } catch { - errors.append(error) - } - } - guard errors.isEmpty else { - throw PolluxError.cannotVerifyPresentationInputs(errors: errors) - } - } - - private func verifyJWT(jwtString: String) async throws -> Bool { - try await verifyJWTCredentialRevocation(jwtString: jwtString) - let payload: DefaultJWTClaimsImpl = try JWT.getPayload(jwtString: jwtString) - guard let issuer = payload.iss else { - throw PolluxError.requiresThatIssuerExistsAndIsAPrismDID + private func verifyPresentationSubmission(json: Data, requestId: String) async throws -> Bool { + let presentationContainer = try JSONDecoder.didComm().decode(PresentationContainer.self, from: json) + let presentationRequest = try await getDefinition(id: requestId) + guard let submission = presentationContainer.presentationSubmission else { + throw PolluxError.presentationSubmissionNotAvailable } - let issuerDID = try DID(string: issuer) - let issuerKeys = try await castor.getDIDPublicKeys(did: issuerDID) - - ES256KVerifier.bouncyCastleFailSafe = true - - let validations = issuerKeys - .compactMap(\.exporting) - .compactMap { - try? JWT.verify(jwtString: jwtString, senderKey: $0.jwk.toJoseJWK()) - } - ES256KVerifier.bouncyCastleFailSafe = false - return !validations.isEmpty - } - - private func verifyJWTCredentialRevocation(jwtString: String) async throws { - guard let credential = try? JWTCredential(data: jwtString.tryToData()) else { - return - } - let isRevoked = try await credential.isRevoked - let isSuspended = try await credential.isSuspended - guard !isRevoked else { - throw PolluxError.credentialIsRevoked(jwtString: jwtString) - } - guard !isSuspended else { - throw PolluxError.credentialIsSuspended(jwtString: jwtString) - } + return try await VerifyPresentationSubmission( + castor: castor, + parsers: presentationExchangeParsers + ).verifyPresentationSubmission( + json: json, + presentationRequest: presentationRequest + ) } private func verifyAnoncreds( diff --git a/EdgeAgentSDK/Pollux/Sources/PolluxImpl+ParseCredential.swift b/EdgeAgentSDK/Pollux/Sources/PolluxImpl+ParseCredential.swift index 7d359733..1538c7e7 100644 --- a/EdgeAgentSDK/Pollux/Sources/PolluxImpl+ParseCredential.swift +++ b/EdgeAgentSDK/Pollux/Sources/PolluxImpl+ParseCredential.swift @@ -86,4 +86,63 @@ extension PolluxImpl { throw PolluxError.invalidCredentialError } } + + public func parseCredential(type: String, credentialPayload: Data, options: [CredentialOperationsOptions]) async throws -> Credential { + switch type { + case "jwt", "", "prism/jwt": + return try ParseJWTCredentialFromMessage.parse(issuerCredentialData: credentialPayload) + case "vc+sd-jwt": + return try SDJWTCredential(sdjwtString: credentialPayload.tryToString()) + case "anoncreds", "prism/anoncreds", "anoncreds/credential@v1.0": + guard + let thidOption = options.first(where: { + if case .thid = $0 { return true } + return false + }), + case let CredentialOperationsOptions.thid(thid) = thidOption + else { + throw PolluxError.missingAndIsRequiredForOperation(type: "thid") + } + guard + let linkSecretOption = options.first(where: { + if case .linkSecret = $0 { return true } + return false + }), + case let CredentialOperationsOptions.linkSecret(_, secret: linkSecret) = linkSecretOption + else { + throw PolluxError.missingAndIsRequiredForOperation(type: "linkSecret") + } + + guard + let credDefinitionDownloaderOption = options.first(where: { + if case .credentialDefinitionDownloader = $0 { return true } + return false + }), + case let CredentialOperationsOptions.credentialDefinitionDownloader(definitionDownloader) = credDefinitionDownloaderOption + else { + throw PolluxError.missingAndIsRequiredForOperation(type: "credentialDefinitionDownloader") + } + + guard + let schemaDownloaderOption = options.first(where: { + if case .schemaDownloader = $0 { return true } + return false + }), + case let CredentialOperationsOptions.schemaDownloader(schemaDownloader) = schemaDownloaderOption + else { + throw PolluxError.missingAndIsRequiredForOperation(type: "schemaDownloader") + } + + return try await ParseAnoncredsCredentialFromMessage.parse( + issuerCredentialData: credentialPayload, + linkSecret: linkSecret, + credentialDefinitionDownloader: definitionDownloader, + schemaDownloader: schemaDownloader, + thid: thid, + pluto: self.pluto + ) + default: + throw PolluxError.invalidCredentialError + } + } } diff --git a/Package.swift b/Package.swift index 3782109f..542c1fb9 100644 --- a/Package.swift +++ b/Package.swift @@ -56,15 +56,16 @@ let package = Package( from: "1.4.4" ), .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.7.0"), - .package(url: "https://github.com/beatt83/didcomm-swift.git", from: "0.1.8"), - .package(url: "https://github.com/beatt83/jose-swift.git", from: "3.2.0"), + .package(url: "https://github.com/beatt83/didcomm-swift.git", from: "0.1.10"), + .package(url: "https://github.com/beatt83/jose-swift.git", from: "3.3.1"), .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/hyperledger/identus-apollo.git", exact: "1.4.2"), .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/goncalo-frade-iohk/eudi-lib-sdjwt-swift.git", from: "0.0.2"), - .package(url: "https://github.com/1024jp/GzipSwift.git", exact: "6.0.0") + .package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-sdjwt-swift.git", from: "0.1.0"), + .package(url: "https://github.com/1024jp/GzipSwift.git", exact: "6.0.0"), + .package(url: "https://github.com/goncalo-frade-iohk/eudi-lib-ios-openid4vci-swift.git", branch: "feature/add-w3cvc-support") ], targets: [ .target( @@ -171,7 +172,8 @@ let package = Package( dependencies: [ "Domain", "Builders", - "Core" + "Core", + .product(name: "OpenID4VCI", package: "eudi-lib-ios-openid4vci-swift") ], path: "EdgeAgentSDK/EdgeAgent/Sources" ), diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo.xcodeproj/project.pbxproj b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo.xcodeproj/project.pbxproj index f9fe724d..f1fc6c9c 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo.xcodeproj/project.pbxproj +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo.xcodeproj/project.pbxproj @@ -34,6 +34,7 @@ EE418AF72BCFD926008766A6 /* CreatePresentationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE418AEB2BCFD925008766A6 /* CreatePresentationView.swift */; }; EE549F472ACC1F5E0038ED1D /* CredentialDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE549F462ACC1F5E0038ED1D /* CredentialDetailView.swift */; }; EE549F492ACC1F7D0038ED1D /* CredentialDetailViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE549F482ACC1F7D0038ED1D /* CredentialDetailViewState.swift */; }; + EE566EDF2C9C4708004C4051 /* DeepLinkWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE566EDE2C9C4708004C4051 /* DeepLinkWebView.swift */; }; EE6C38DC294626E1006CD2D3 /* String+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE6C38DB294626E1006CD2D3 /* String+extensions.swift */; }; EE6C38E3294627B2006CD2D3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = EE6C38E2294627B2006CD2D3 /* Localizable.strings */; }; EE6C38E529462822006CD2D3 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = EE6C38E429462822006CD2D3 /* Localizable.stringsdict */; }; @@ -165,6 +166,7 @@ EE418AEB2BCFD925008766A6 /* CreatePresentationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreatePresentationView.swift; sourceTree = ""; }; EE549F462ACC1F5E0038ED1D /* CredentialDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialDetailView.swift; sourceTree = ""; }; EE549F482ACC1F7D0038ED1D /* CredentialDetailViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialDetailViewState.swift; sourceTree = ""; }; + EE566EDE2C9C4708004C4051 /* DeepLinkWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkWebView.swift; sourceTree = ""; }; EE6C38DB294626E1006CD2D3 /* String+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+extensions.swift"; sourceTree = ""; }; EE6C38E2294627B2006CD2D3 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; EE6C38E429462822006CD2D3 /* Localizable.stringsdict */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = ""; }; @@ -512,6 +514,7 @@ EE92C3FA2B7E1CA200FC0B6E /* Environment */, EE92C3FD2B7E1CA200FC0B6E /* PreferenceHelpers */, EE92C4002B7E1CA200FC0B6E /* NavigationUtils.swift */, + EE566EDE2C9C4708004C4051 /* DeepLinkWebView.swift */, ); path = AtalaSwiftUIComponents; sourceTree = ""; @@ -931,6 +934,7 @@ EE92C40C2B7E1CA200FC0B6E /* ClearFullCoverModifier.swift in Sources */, EE92C41B2B7E1CA200FC0B6E /* WordTagGrid.swift in Sources */, EE418AED2BCFD926008766A6 /* PresentationDetailViewModel.swift in Sources */, + EE566EDF2C9C4708004C4051 /* DeepLinkWebView.swift in Sources */, EE75147E29C376E700FFFAA4 /* DIDDetailView.swift in Sources */, EEE61FE02937CEAA0053AE52 /* SeedViewModel.swift in Sources */, EE92C41E2B7E1CA200FC0B6E /* WebView.swift in Sources */, @@ -1164,6 +1168,7 @@ INFOPLIST_FILE = AtalaPrismWalletDemo/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Atala PRISM Wallet Demo"; INFOPLIST_KEY_NSCameraUsageDescription = NSCameraUsageDescription; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; @@ -1196,6 +1201,7 @@ INFOPLIST_FILE = AtalaPrismWalletDemo/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Atala PRISM Wallet Demo"; INFOPLIST_KEY_NSCameraUsageDescription = NSCameraUsageDescription; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Helper/AtalaSwiftUIComponents/DeepLinkWebView.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Helper/AtalaSwiftUIComponents/DeepLinkWebView.swift new file mode 100644 index 00000000..d8e1d498 --- /dev/null +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Helper/AtalaSwiftUIComponents/DeepLinkWebView.swift @@ -0,0 +1,50 @@ +import SwiftUI +import WebKit + +struct DeepLinkWebView: UIViewRepresentable { + let url: URL + @Binding var deepLinkUrl: URL? + @Binding var shouldDismiss: Bool + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + func makeUIView(context: Context) -> WKWebView { + let webView = WKWebView() + webView.navigationDelegate = context.coordinator + + let request = URLRequest(url: url) + webView.load(request) + + return webView + } + + func updateUIView(_ uiView: WKWebView, context: Context) { + // No need to update the view + } + + class Coordinator: NSObject, WKNavigationDelegate { + var parent: DeepLinkWebView + + init(_ parent: DeepLinkWebView) { + self.parent = parent + } + + // Intercept URL changes + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + + if let url = navigationAction.request.url { + // Check if the URL matches your deep link scheme + if url.scheme == "edgeagentsdk" { + parent.deepLinkUrl = url + parent.shouldDismiss = false + decisionHandler(.cancel) + return + } + } + decisionHandler(.allow) + } + } +} diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Info.plist b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Info.plist index 67e5f5ef..770900cf 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Info.plist +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Info.plist @@ -14,18 +14,21 @@ walletX + + CFBundleTypeRole + Editor + CFBundleURLName + com.identus.oidc + CFBundleURLSchemes + + edgeagentsdk + + NSAppTransportSecurity NSAllowsArbitraryLoads - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/DID/DIDFuncionalitiesViewModel.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/DID/DIDFuncionalitiesViewModel.swift index f7117125..52b7a7b0 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/DID/DIDFuncionalitiesViewModel.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/DID/DIDFuncionalitiesViewModel.swift @@ -5,13 +5,13 @@ import EdgeAgent final class DIDFuncionalitiesViewModel: ObservableObject { private let castor: Castor - private let agent: EdgeAgent + private let agent: DIDCommAgent init() { self.castor = CastorBuilder( apollo: ApolloBuilder().build() ).build() - self.agent = EdgeAgent(mediatorDID: DID(method: "peer", methodId: "123")) + self.agent = DIDCommAgent(mediatorDID: DID(method: "peer", methodId: "123")) } @Published var createdDID: DID? diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/SetupPrismAgent/SetupPrismAgentViewModel.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/SetupPrismAgent/SetupPrismAgentViewModel.swift index 8cb3b190..d952c3e5 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/SetupPrismAgent/SetupPrismAgentViewModel.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/SetupPrismAgent/SetupPrismAgentViewModel.swift @@ -10,13 +10,13 @@ final class SetupEdgeAgentViewModelImpl: ObservableObject, SetupEdgeAgentViewMod @Published var status: String = "" @Published var error: String? - private let agent: EdgeAgent + private let agent: DIDCommAgent private var cancellables = [AnyCancellable]() init() { let did = try! DID(string: "did:peer:2.Ez6LSms555YhFthn1WV8ciDBpZm86hK9tp83WojJUmxPGk1hZ.Vz6MkmdBjMyB4TS5UbbQw54szm8yvMMf1ftGV2sQVYAxaeWhE.SeyJpZCI6Im5ldy1pZCIsInQiOiJkbSIsInMiOiJodHRwczovL21lZGlhdG9yLnJvb3RzaWQuY2xvdWQiLCJhIjpbImRpZGNvbW0vdjIiXX0") - self.agent = EdgeAgent(mediatorDID: did) + self.agent = DIDCommAgent(mediatorDID: did) status = agent.state.rawValue } diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/SigningVerification/SigningVerificationViewModel.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/SigningVerification/SigningVerificationViewModel.swift index faf7d303..8656931d 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/SigningVerification/SigningVerificationViewModel.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/SigningVerification/SigningVerificationViewModel.swift @@ -5,13 +5,13 @@ import EdgeAgent final class SigningVerificationViewModel: ObservableObject { private let castor: Castor - private let agent: EdgeAgent + private let agent: DIDCommAgent init() { self.castor = CastorBuilder( apollo: ApolloBuilder().build() ).build() - self.agent = EdgeAgent(mediatorDID: DID(method: "peer", methodId: "1234")) + self.agent = DIDCommAgent(mediatorDID: DID(method: "peer", methodId: "1234")) } @Published var createdDID: DID? @@ -47,7 +47,7 @@ final class SigningVerificationViewModel: ObservableObject { else { return } // Signs with a valid DID that was created by the agent - let signature = try? await agent.signWith(did: did, message: messageData) + let signature = try? await agent.edgeAgent.signWith(did: did, message: messageData) await MainActor.run { self.signedMessage = signature } diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/Verifier/Main/MainVerifierRouter.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/Verifier/Main/MainVerifierRouter.swift index 223806f2..601d23fd 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/Verifier/Main/MainVerifierRouter.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/Verifier/Main/MainVerifierRouter.swift @@ -20,26 +20,28 @@ final class MainVerifierRouterImpl: MainVerifierViewRouter { castor: castor ) ).build() - let agent = EdgeAgent( + let edgeAgent = EdgeAgent( apollo: apollo, castor: castor, pluto: pluto, - pollux: pollux, - mercury: mercury + pollux: pollux ) + let didcommAgent = DIDCommAgent(edgeAgent: edgeAgent, mercury: mercury) + let oidcAgent = OIDCAgent(edgeAgent: edgeAgent) container.register(type: Apollo.self, component: apollo) container.register(type: Castor.self, component: castor) container.register(type: Pluto.self, component: pluto) container.register(type: Pollux.self, component: pollux) container.register(type: Mercury.self, component: mercury) - container.register(type: EdgeAgent.self, component: agent) + container.register(type: DIDCommAgent.self, component: didcommAgent) + container.register(type: OIDCAgent.self, component: oidcAgent) } func routeToMediator() -> some View { let viewModel = MediatorViewModelImpl( castor: container.resolve(type: Castor.self)!, pluto: container.resolve(type: Pluto.self)!, - agent: container.resolve(type: EdgeAgent.self)! + agent: container.resolve(type: DIDCommAgent.self)! ) return MediatorPageView(viewModel: viewModel) } @@ -47,7 +49,7 @@ final class MainVerifierRouterImpl: MainVerifierViewRouter { func routeToDids() -> some View { let viewModel = DIDListViewModelImpl( pluto: container.resolve(type: Pluto.self)!, - agent: container.resolve(type: EdgeAgent.self)! + agent: container.resolve(type: DIDCommAgent.self)! ) return DIDListView(viewModel: viewModel) @@ -57,7 +59,7 @@ final class MainVerifierRouterImpl: MainVerifierViewRouter { let viewModel = ConnectionsListViewModelImpl( castor: container.resolve(type: Castor.self)!, pluto: container.resolve(type: Pluto.self)!, - agent: container.resolve(type: EdgeAgent.self)! + agent: container.resolve(type: DIDCommAgent.self)! ) return ConnectionsListView( @@ -68,7 +70,7 @@ final class MainVerifierRouterImpl: MainVerifierViewRouter { func routeToMessages() -> some View { let viewModel = MessagesListViewModelImpl( - agent: container.resolve(type: EdgeAgent.self)! + agent: container.resolve(type: DIDCommAgent.self)! ) return MessagesListView( @@ -88,7 +90,7 @@ final class MainVerifierRouterImpl: MainVerifierViewRouter { func routeToCredentials() -> some View { let viewModel = CredentialListViewModelImpl( - agent: container.resolve(type: EdgeAgent.self)!, + agent: container.resolve(type: DIDCommAgent.self)!, apollo: container.resolve(type: Apollo.self)! as! Apollo & KeyRestoration, pluto: container.resolve(type: Pluto.self)! ) diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/Verifier/PresentationCreate/CreatePresentationViewModel.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/Verifier/PresentationCreate/CreatePresentationViewModel.swift index 40e6f062..24b24289 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/Verifier/PresentationCreate/CreatePresentationViewModel.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/Verifier/PresentationCreate/CreatePresentationViewModel.swift @@ -10,9 +10,9 @@ class CreatePresentationViewModelImpl: CreatePresentationViewModel { @Published var selectedCredentialType: CreatePresentationViewState.CredentialType = .jwt @Published var jwtClaims: [CreatePresentationViewState.JWTClaim] = [] @Published var anoncredsClaims: [CreatePresentationViewState.AnoncredsClaim] = [] - private let agent: EdgeAgent + private let agent: DIDCommAgent - init(edgeAgent: EdgeAgent) { + init(edgeAgent: DIDCommAgent) { self.agent = edgeAgent bind() @@ -20,6 +20,7 @@ class CreatePresentationViewModelImpl: CreatePresentationViewModel { func bind() { agent + .edgeAgent .getAllDIDPairs() .map { $0.map { diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/Verifier/Presentations/PresentationsRouter.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/Verifier/Presentations/PresentationsRouter.swift index afb0ee5b..5c45f9be 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/Verifier/Presentations/PresentationsRouter.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/Verifier/Presentations/PresentationsRouter.swift @@ -6,7 +6,7 @@ struct PresentationsViewRouterImpl: PresentationsViewRouter { let container: DIContainer func routeToCreate() -> some View { - let viewModel = CreatePresentationViewModelImpl(edgeAgent: container.resolve(type: EdgeAgent.self)!) + let viewModel = CreatePresentationViewModelImpl(edgeAgent: container.resolve(type: DIDCommAgent.self)!) return CreatePresentationView(viewModel: viewModel) } @@ -14,7 +14,7 @@ struct PresentationsViewRouterImpl: PresentationsViewRouter { func routeToDetail(id: String) -> some View { let viewModel = PresentationDetailViewModelImpl( id: id, - agent: container.resolve(type: EdgeAgent.self)!, + agent: container.resolve(type: DIDCommAgent.self)!, pluto: container.resolve(type: Pluto.self)! ) diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/Verifier/PresentationsDetail/PresentationDetailViewModel.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/Verifier/PresentationsDetail/PresentationDetailViewModel.swift index 0bcb3c45..27d95879 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/Verifier/PresentationsDetail/PresentationDetailViewModel.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/Verifier/PresentationsDetail/PresentationDetailViewModel.swift @@ -15,12 +15,12 @@ class PresentationDetailViewModelImpl: PresentationDetailViewModel { @Published var receivedPresentations: [PresentationDetailViewState.ReceivedPresentation] = [] @Published var isVerified = false - private let agent: EdgeAgent + private let agent: DIDCommAgent private let pluto: Pluto init( id: String, - agent: EdgeAgent, + agent: DIDCommAgent, pluto: Pluto ) { self.agent = agent diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/AddNewContact/AddNewContactBuilder.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/AddNewContact/AddNewContactBuilder.swift index 7eb8c64c..091336e2 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/AddNewContact/AddNewContactBuilder.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/AddNewContact/AddNewContactBuilder.swift @@ -12,7 +12,8 @@ struct AddNewContactBuilder: Builder { let viewModel = getViewModel(component: component) { AddNewContactViewModelImpl( token: component.token ?? "", - agent: component.container.resolve(type: EdgeAgent.self)!, + agent: component.container.resolve(type: DIDCommAgent.self)!, + oidcAgent: component.container.resolve(type: OIDCAgent.self)!, pluto: component.container.resolve(type: Pluto.self)! ) } diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/AddNewContact/AddNewContactView.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/AddNewContact/AddNewContactView.swift index 482269f0..1bb80d4a 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/AddNewContact/AddNewContactView.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/AddNewContact/AddNewContactView.swift @@ -7,11 +7,16 @@ protocol AddNewContactViewModel: ObservableObject { var dismiss: Bool { get } var dismissRoot: Bool { get } var code: String { get set } + var url: URL? { get } + var hasUrl: Bool { get set } func isContactAlreadyAdded() func addContact() + func handleLinkCallback(url: URL) async throws } struct AddNewContactView: View { + @State var shouldDismissWebView = false + @State var deepLinkUrl: URL? @StateObject var viewModel: ViewModel @Environment(\.presentationMode) var presentationMode @Environment(\.rootPresentationMode) var modalPresentation @@ -85,6 +90,28 @@ struct AddNewContactView: View { .onChange(of: viewModel.dismissRoot, perform: { value in self.modalPresentation.wrappedValue = value }) + .sheet( + isPresented: $viewModel.hasUrl, + onDismiss: { + if let deepLinkUrl { + Task { + try await self.viewModel.handleLinkCallback(url: deepLinkUrl) + } + } + }, + content: { + DeepLinkWebView( + url: viewModel.url!, + deepLinkUrl: $deepLinkUrl, + shouldDismiss: $viewModel.hasUrl + ) + }) + .onOpenURL(perform: { url in + Task { + print("App was opened via URL: \(url)") + try await self.viewModel.handleLinkCallback(url: url) + } + }) } } @@ -95,6 +122,10 @@ struct AddNewContactView_Previews: PreviewProvider { } private class MockViewModel: AddNewContactViewModel { + var url: URL? = nil + var hasUrl: Bool = false + + func handleLinkCallback(url: URL) async throws {} var contactInfo: AddNewContactState.Contact? var flowStep: AddNewContactState.AddContacFlowStep = .getCode var loading = false diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/AddNewContact/AddNewContactViewModel.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/AddNewContact/AddNewContactViewModel.swift index c0907dce..4f2c65e2 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/AddNewContact/AddNewContactViewModel.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/AddNewContact/AddNewContactViewModel.swift @@ -2,6 +2,7 @@ import Combine import Domain import Foundation import EdgeAgent +import OpenID4VCI final class AddNewContactViewModelImpl: AddNewContactViewModel { @Published var flowStep: AddNewContactState.AddContacFlowStep @@ -10,18 +11,26 @@ final class AddNewContactViewModelImpl: AddNewContactViewModel { @Published var dismissRoot = false @Published var loading = false @Published var contactInfo: AddNewContactState.Contact? + @Published var url: URL? + @Published var hasUrl: Bool = false private let pluto: Pluto - private let agent: EdgeAgent + private let agent: DIDCommAgent + private let oidcAgent: OIDCAgent private var cancellables = Set() + private var issuer: Issuer? + private var offer: CredentialOffer? + private var request: UnauthorizedRequest? init( token: String = "", - agent: EdgeAgent, + agent: DIDCommAgent, + oidcAgent: OIDCAgent, pluto: Pluto ) { code = token self.agent = agent + self.oidcAgent = oidcAgent self.pluto = pluto flowStep = token.isEmpty ? .getCode : .checkDuplication } @@ -36,7 +45,7 @@ final class AddNewContactViewModelImpl: AddNewContactViewModel { do { if let recipientDID = try? DID(string: self.code) { - let didPairs = try await agent.getAllDIDPairs().first().await() + let didPairs = try await agent.edgeAgent.getAllDIDPairs().first().await() await MainActor.run { [weak self] in guard didPairs.first(where: { $0.other.string == recipientDID.string }) == nil else { @@ -50,9 +59,26 @@ final class AddNewContactViewModelImpl: AddNewContactViewModel { self?.loading = false } + } else if self.code.contains("openid-credential-offer"){ + let offer = try await oidcAgent.parseCredentialOffer(offerUri: self.code) + self.offer = offer + let prePreparedRequest = try await oidcAgent.createAuthorizationRequest( + clientId: "alice-wallet", + redirectUri: URL(string: "edgeagentsdk://oidc")!, + offer: offer + ) + self.issuer = prePreparedRequest.0 + self.request = prePreparedRequest.1 + switch self.request { + case .par(let parRequested): + self.url = parRequested.getAuthorizationCodeURL.url + self.hasUrl = true + default: + throw UnknownError.somethingWentWrongError(customMessage: nil, underlyingErrors: nil) + } } else { let connection = try agent.parseOOBInvitation(url: self.code) - let didPairs = try await agent.getAllDIDPairs().first().await() + let didPairs = try await agent.edgeAgent.getAllDIDPairs().first().await() await MainActor.run { [weak self] in guard didPairs.first(where: { $0.other.string == connection.from }) == nil else { @@ -75,6 +101,23 @@ final class AddNewContactViewModelImpl: AddNewContactViewModel { } } + func handleLinkCallback(url: URL) async throws { + hasUrl = false + let response = try await oidcAgent.handleTokenRequest( + request: request!, + issuer: issuer!, + callbackUrl: url + ) + let credential = try await oidcAgent.credentialRequest( + issuer: response.0, + offer: offer!, + request: response.1 + ) + loading = false + dismiss = true + print(credential) + } + func addContact() { guard contactInfo != nil, !loading else { return } loading = true diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Backup/BackupViewBuilder.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Backup/BackupViewBuilder.swift index 16286dae..10183a27 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Backup/BackupViewBuilder.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Backup/BackupViewBuilder.swift @@ -8,7 +8,7 @@ struct BackupComponent: ComponentContainer { struct BackupBuilder: Builder { func build(component: BackupComponent) -> some View { let viewModel = getViewModel(component: component) { - BackupViewModelImpl(agent: component.container.resolve(type: EdgeAgent.self)!) + BackupViewModelImpl(agent: component.container.resolve(type: DIDCommAgent.self)!) } return BackupView(viewModel: viewModel) } diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Backup/BackupViewModel.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Backup/BackupViewModel.swift index c33eddb2..a505a518 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Backup/BackupViewModel.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Backup/BackupViewModel.swift @@ -5,14 +5,14 @@ import Foundation final class BackupViewModelImpl: BackupViewModel { @Published var newJWE: String? = nil - private let agent: EdgeAgent + private let agent: DIDCommAgent - init(agent: EdgeAgent) { + init(agent: DIDCommAgent) { self.agent = agent } func createNewJWE() async throws { - let jwe = try await agent.backupWallet() + let jwe = try await agent.edgeAgent.backupWallet() await MainActor.run { self.newJWE = jwe @@ -21,7 +21,7 @@ final class BackupViewModelImpl: BackupViewModel { func backupWith(_ jwe: String) async throws { do { - try await agent.recoverWallet(encrypted: jwe) + try await agent.edgeAgent.recoverWallet(encrypted: jwe) } catch { print(error) print() diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Connections/ConnectionsListViewModel.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Connections/ConnectionsListViewModel.swift index 56b0730c..a6edc0e0 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Connections/ConnectionsListViewModel.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Connections/ConnectionsListViewModel.swift @@ -10,9 +10,9 @@ final class ConnectionsListViewModelImpl: ConnectionsListViewModel { private var cancellables = Set() private let castor: Castor private let pluto: Pluto - private let agent: EdgeAgent + private let agent: DIDCommAgent - init(castor: Castor, pluto: Pluto, agent: EdgeAgent) { + init(castor: Castor, pluto: Pluto, agent: DIDCommAgent) { self.castor = castor self.pluto = pluto self.agent = agent @@ -22,6 +22,7 @@ final class ConnectionsListViewModelImpl: ConnectionsListViewModel { func bind() { agent + .edgeAgent .getAllDIDPairs() .map { $0.map { diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Credentials/CredentialDetail/CredentialDetailViewModel.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Credentials/CredentialDetail/CredentialDetailViewModel.swift index 62e584d6..29b12ca8 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Credentials/CredentialDetail/CredentialDetailViewModel.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Credentials/CredentialDetail/CredentialDetailViewModel.swift @@ -11,10 +11,10 @@ final class CredentialDetailViewModelImpl: CredentialDetailViewModel { schemaId: nil ) - private let agent: EdgeAgent + private let agent: DIDCommAgent private let credentialId: String - init(agent: EdgeAgent, credentialId: String) { + init(agent: DIDCommAgent, credentialId: String) { self.agent = agent self.credentialId = credentialId bind() @@ -22,7 +22,7 @@ final class CredentialDetailViewModelImpl: CredentialDetailViewModel { private func bind() { let credentialId = self.credentialId - return self.agent + return self.agent.edgeAgent .verifiableCredentials() .map { if let credential = $0.first(where: { $0.id == credentialId }) { diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Credentials/CredentialsList/CredentialListRouter.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Credentials/CredentialsList/CredentialListRouter.swift index 84eb8f7f..e239edf9 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Credentials/CredentialsList/CredentialListRouter.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Credentials/CredentialsList/CredentialListRouter.swift @@ -6,7 +6,7 @@ struct CredentialListRouterImpl: CredentialListRouter { func routeToCredentialDetail(id: String) -> some View { CredentialDetailView(viewModel: CredentialDetailViewModelImpl( - agent: container.resolve(type: EdgeAgent.self)!, + agent: container.resolve(type: DIDCommAgent.self)!, credentialId: id )) } diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Credentials/CredentialsList/CredentialListViewModel.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Credentials/CredentialsList/CredentialListViewModel.swift index 42d30534..72ff60af 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Credentials/CredentialsList/CredentialListViewModel.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Credentials/CredentialsList/CredentialListViewModel.swift @@ -16,12 +16,12 @@ final class CredentialListViewModelImpl: CredentialListViewModel { @Published var invalidCredentials = [CredentialListViewState.Credential]() @Published var requestId: String? = nil - private let agent: EdgeAgent + private let agent: DIDCommAgent private let pluto: Pluto private let apollo: Apollo & KeyRestoration init( - agent: EdgeAgent, + agent: DIDCommAgent, apollo: Apollo & KeyRestoration, pluto: Pluto ) { @@ -32,7 +32,7 @@ final class CredentialListViewModelImpl: CredentialListViewModel { } private func bind() { - self.agent.verifiableCredentials().map { + self.agent.edgeAgent.verifiableCredentials().map { $0.map { CredentialListViewState.Credential( id: $0.id, @@ -52,6 +52,7 @@ final class CredentialListViewModelImpl: CredentialListViewModel { .dropNil() .flatMap { message in self.agent + .edgeAgent .verifiableCredentials() .map { $0.filter { (try? $0.proof?.isValidForPresentation(request: message, options: [])) ?? false} @@ -77,6 +78,7 @@ final class CredentialListViewModelImpl: CredentialListViewModel { .dropNil() .flatMap { message in self.agent + .edgeAgent .verifiableCredentials() .map { $0.filter { !((try? $0.proof?.isValidForPresentation(request: message, options: [])) ?? false)} @@ -100,7 +102,7 @@ final class CredentialListViewModelImpl: CredentialListViewModel { finalThreadFlowRequests() Task { - let credentials = try await self.agent.verifiableCredentials().first().await() + let credentials = try await self.agent.edgeAgent.verifiableCredentials().first().await() let linkSecret = try await self.agent.pluto.getLinkSecret().first().await() guard credentials.isEmpty, linkSecret != nil else { return @@ -146,7 +148,7 @@ final class CredentialListViewModelImpl: CredentialListViewModel { _ = try await self.agent.sendMessage(message: try requestCredential.makeMessage()) case ProtocolTypes.didcommRequestPresentation.rawValue: - let credential = try await self.agent.verifiableCredentials() + let credential = try await self.agent.edgeAgent.verifiableCredentials() .map { $0.compactMap { $0 as? Credential & ProvableCredential} } .map { $0.first { $0.id == credentialId } } .first() diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/DIDs/DIDList/DIDListViewModel.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/DIDs/DIDList/DIDListViewModel.swift index a83888f6..76806284 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/DIDs/DIDList/DIDListViewModel.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/DIDs/DIDList/DIDListViewModel.swift @@ -9,9 +9,9 @@ final class DIDListViewModelImpl: DIDListViewModel { @Published var error: FancyToast? private let pluto: Pluto - private let agent: EdgeAgent + private let agent: DIDCommAgent - init(pluto: Pluto, agent: EdgeAgent) { + init(pluto: Pluto, agent: DIDCommAgent) { self.pluto = pluto self.agent = agent diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Main/Main2Router.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Main/Main2Router.swift index b110bbe4..8cb1552e 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Main/Main2Router.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Main/Main2Router.swift @@ -34,22 +34,24 @@ final class Main2RouterImpl: Main2ViewRouter { castor: castor, pluto: pluto, pollux: pollux, - mercury: mercury, seed: seed ) + let didcommAgent = DIDCommAgent(edgeAgent: agent, mercury: mercury) + let oidcAgent = OIDCAgent(edgeAgent: agent) container.register(type: Apollo.self, component: apollo) container.register(type: Castor.self, component: castor) container.register(type: Pluto.self, component: pluto) container.register(type: Pollux.self, component: pollux) container.register(type: Mercury.self, component: mercury) - container.register(type: EdgeAgent.self, component: agent) + container.register(type: DIDCommAgent.self, component: didcommAgent) + container.register(type: OIDCAgent.self, component: oidcAgent) } func routeToMediator() -> some View { let viewModel = MediatorViewModelImpl( castor: container.resolve(type: Castor.self)!, pluto: container.resolve(type: Pluto.self)!, - agent: container.resolve(type: EdgeAgent.self)! + agent: container.resolve(type: DIDCommAgent.self)! ) return MediatorPageView(viewModel: viewModel) } @@ -57,7 +59,7 @@ final class Main2RouterImpl: Main2ViewRouter { func routeToDids() -> some View { let viewModel = DIDListViewModelImpl( pluto: container.resolve(type: Pluto.self)!, - agent: container.resolve(type: EdgeAgent.self)! + agent: container.resolve(type: DIDCommAgent.self)! ) return DIDListView(viewModel: viewModel) @@ -67,7 +69,7 @@ final class Main2RouterImpl: Main2ViewRouter { let viewModel = ConnectionsListViewModelImpl( castor: container.resolve(type: Castor.self)!, pluto: container.resolve(type: Pluto.self)!, - agent: container.resolve(type: EdgeAgent.self)! + agent: container.resolve(type: DIDCommAgent.self)! ) return ConnectionsListView( @@ -78,7 +80,7 @@ final class Main2RouterImpl: Main2ViewRouter { func routeToMessages() -> some View { let viewModel = MessagesListViewModelImpl( - agent: container.resolve(type: EdgeAgent.self)! + agent: container.resolve(type: DIDCommAgent.self)! ) return MessagesListView( @@ -89,7 +91,7 @@ final class Main2RouterImpl: Main2ViewRouter { func routeToCredentials() -> some View { let viewModel = CredentialListViewModelImpl( - agent: container.resolve(type: EdgeAgent.self)!, + agent: container.resolve(type: DIDCommAgent.self)!, apollo: container.resolve(type: Apollo.self)! as! Apollo & KeyRestoration, pluto: container.resolve(type: Pluto.self)! ) diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Mediator/MediatorPage/MediatorPageViewModel.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Mediator/MediatorPage/MediatorPageViewModel.swift index 2fdff8a3..68630cea 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Mediator/MediatorPage/MediatorPageViewModel.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Mediator/MediatorPage/MediatorPageViewModel.swift @@ -11,9 +11,9 @@ final class MediatorViewModelImpl: MediatorPageViewModel { @Published var error: FancyToast? private let castor: Castor private let pluto: Pluto - private let agent: EdgeAgent + private let agent: DIDCommAgent - init(castor: Castor, pluto: Pluto, agent: EdgeAgent) { + init(castor: Castor, pluto: Pluto, agent: DIDCommAgent) { self.castor = castor self.pluto = pluto self.agent = agent diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Messages/MessageDetail/MessageDetailViewModel.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Messages/MessageDetail/MessageDetailViewModel.swift index 61f7d2cb..657443ab 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Messages/MessageDetail/MessageDetailViewModel.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Messages/MessageDetail/MessageDetailViewModel.swift @@ -25,11 +25,11 @@ final class MessageDetailViewModelImpl: MessageDetailViewModel { private let messageId: String private let pluto: Pluto - private let agent: EdgeAgent + private let agent: DIDCommAgent private var message: Message? private var cancellables = Set() - init(messageId: String, pluto: Pluto, agent: EdgeAgent) { + init(messageId: String, pluto: Pluto, agent: DIDCommAgent) { self.messageId = messageId self.pluto = pluto self.agent = agent @@ -80,7 +80,7 @@ final class MessageDetailViewModelImpl: MessageDetailViewModel { case .didcommPresentation: let presentation = try Presentation(fromMessage: message) case .didcommRequestPresentation: - let credential = try await agent.verifiableCredentials().map { $0.first }.first().await() + let credential = try await agent.edgeAgent.verifiableCredentials().map { $0.first }.first().await() guard let credential else { throw UnknownError.somethingWentWrongError() } diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Messages/MessagesList/MessagesListRouter.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Messages/MessagesList/MessagesListRouter.swift index aa7fbb83..84387343 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Messages/MessagesList/MessagesListRouter.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Messages/MessagesList/MessagesListRouter.swift @@ -9,7 +9,7 @@ struct MessageListRouterImpl: MessageListRouter { let viewModel = MessageDetailViewModelImpl( messageId: messageId, pluto: container.resolve(type: Pluto.self)!, - agent: container.resolve(type: EdgeAgent.self)! + agent: container.resolve(type: DIDCommAgent.self)! ) return MessageDetailView(viewModel: viewModel) diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Messages/MessagesList/MessagesListViewModel.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Messages/MessagesList/MessagesListViewModel.swift index ab4cce9b..29889127 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Messages/MessagesList/MessagesListViewModel.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Messages/MessagesList/MessagesListViewModel.swift @@ -7,7 +7,7 @@ final class MessagesListViewModelImpl: MessageListViewModel { @Published var messages = [MessagesListViewState.Message]() @Published var error: FancyToast? - private let agent: EdgeAgent + private let agent: DIDCommAgent private var messagesDomain = Set() { didSet { messages = messagesDomain @@ -25,7 +25,7 @@ final class MessagesListViewModelImpl: MessageListViewModel { } private var cancellables = Set() - init(agent: EdgeAgent) { + init(agent: DIDCommAgent) { self.agent = agent bind() } @@ -123,7 +123,7 @@ final class MessagesListViewModelImpl: MessageListViewModel { let to = message.to else { return } - try await agent.registerDIDPair(pair: .init( + try await agent.edgeAgent.registerDIDPair(pair: .init( holder: from, other: to, name: nil diff --git a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Settings/SettingsViewRouter.swift b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Settings/SettingsViewRouter.swift index b892e7bb..9cec520c 100644 --- a/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Settings/SettingsViewRouter.swift +++ b/Sample/AtalaPrismWalletDemo/AtalaPrismWalletDemo/Modules/WalletDemo2/Settings/SettingsViewRouter.swift @@ -8,7 +8,7 @@ struct SettingsViewRouterImpl: SettingsViewRouter { func routeToDIDs() -> some View { let viewModel = DIDListViewModelImpl( pluto: container.resolve(type: Pluto.self)!, - agent: container.resolve(type: EdgeAgent.self)! + agent: container.resolve(type: DIDCommAgent.self)! ) return DIDListView(viewModel: viewModel) @@ -18,7 +18,7 @@ struct SettingsViewRouterImpl: SettingsViewRouter { let viewModel = MediatorViewModelImpl( castor: container.resolve(type: Castor.self)!, pluto: container.resolve(type: Pluto.self)!, - agent: container.resolve(type: EdgeAgent.self)! + agent: container.resolve(type: DIDCommAgent.self)! ) return MediatorPageView(viewModel: viewModel) }