diff --git a/Docs/InternalArchitecture.md b/Docs/InternalArchitecture.md index 66f3d287..1e8109d9 100644 --- a/Docs/InternalArchitecture.md +++ b/Docs/InternalArchitecture.md @@ -4,31 +4,42 @@ ## Creating a Verified Id Request from Input ```mermaid classDiagram -RequestHandler ..> VerifiedIdRequestInput: uses -RequestResolver ..> AdditionalRequestParams: uses -RequestResolver ..|> RawRequest: creates -RequestResolver ..|> VerifiedIdRequestInput: uses -RequestHandler ..> RequestResolver: uses -RequestProcessor ..|> VerifiedIdRequest: creates -AdditionalRequestParams <.. RequestProcessor: uses -RawRequest <.. RequestProcessor: uses +class RequestHandlerFactory { + - requestHandlers: [RequestHandler] + ~getHandler(from resolver: RequestResolver) RequestHandler +} +RequestHandlerFactory o-- RequestHandler +RequestHandlerFactory ..> RequestResolver: uses +RequestHandlerFactory ..> RequestHandler: creates + +RawRequest ..> RequestHandler: uses +RequestResolver ..> RawRequest: creates +RequestResolver ..> VerifiedIdRequestInput: uses +RequestProcessor ..> VerifiedIdRequest: creates +RequestHandler ..> VerifiedIdRequest: creates +RawRequest ..> RequestProcessor: uses <> RequestResolver class RequestResolver { ~canResolve(using: RequestHandler) Bool ~canResolve(from: VerifiedIdClientInput) Bool - ~resolve(input: VerifiedIdClientInput, using: [AdditionalRequestParams]) RawRequest + ~resolve(input: VerifiedIdClientInput) RawRequest } class RequestHandler{ - ~handle(input: VerifiedIdRequestInput, using: RequestResolver) VerifiedIdRequest + ~handle(input: RawRequest) VerifiedIdRequest } <> VerifiedIdRequestInput +class RequestProcessorFactory { + - requestProcessors: [RequestProcessor] + ~getRequestProcessor(for: RawRequest) RequestProcessor +} <> RequestProcessor class RequestProcessor { - -requestParams: AdditionalRequestParams ~canProcess(raw: RawRequest) Bool ~process(raw: RawRequest) VerifiedIdRequest } -<> AdditionalRequestParams +RequestProcessorFactory o-- RequestProcessor +RequestHandler ..> RequestProcessor: uses +RequestHandler *-- RequestProcessorFactory <> RawRequest <> VerifiedIdRequest class VerifiedIdRequest { @@ -44,14 +55,14 @@ A Request Resolver resolves a raw request from a request input and additional pa Ex: An `OpenIdURLRequestResolver` would know how to resolve a raw open id request token. The additional params would be OpenId specific to be used to send any additional information needed to resolve the request (what version of openid is supported, for example). The result would be a `OpenIdRawRequest` that has not been processed or validated yet. ### Request Handler -A request handler is used to handle an input where the request can be resolved by a request resolver and then processed, validated and mapped to a Verified Id Request. A request handler is protocol specific (e.g. `OpenIdRequestHandler`). It does not need to know to resolve the input as that logic is handles by the resolver, but it can inject any additional parameters into the resolver using Additional Params. +A request handler is used to handle an input where the request can be resolved by a request resolver and then processed, validated and mapped to a Verified Id Request. A request handler is protocol specific (e.g. `OpenIdRequestHandler`). It does not need to know to resolve the input as that logic is handles by the resolver. -Ex: An `OpenIdRequestHandler` would take in any type of input as long as the resolver that is also passed in knows how to resolve the input into an OpenIdRawRequest. The `OpenIdRequestHandler` would contain a list of `OpenIdRequestProcessors` that know how to process different types of openid version (e.g. jwt 0.1, jwt 0.2, json-ld, etc). The handler would use the `OpenIdAdditionalRequestParams` that are passed into the resolver to tell the resolver what version of open-id are supported. +Ex: An `OpenIdRequestHandler` would take in any type of input as long as the resolver that is also passed in knows how to resolve the input into an OpenIdRawRequest. The `OpenIdRequestHandler` would contain a list of `OpenIdRequestProcessors` that know how to process different types of openid version or extensions (e.g. jwt 0.1, jwt 0.2, json-ld, etc). `OpenIdRequestHandler` can parse the base OpenID request, then use `OpenIDRequestProcessors` for verified ID logic to form responses, then serialize and send the response according to openID protocol. ### Request Processor -A request processor is used to process a Raw Request and return a Verified Id Request. A request processor is protocol-version specific. +A request processor is used to process a Raw Request and return a Verified Id Request. A request processor is protocol-version specific logic. -Ex. A `JWTV1RequestProcessor` takes in a `OpenIdRawRequest` and processes, validates, and maps it to a Verified Id Request. +Ex. A `JWTV1RequestProcessor` takes in a `OpenIdRawRequest` and processes, validates, and maps it to a Verified Id Request. ## Configuring the Request Handler ```mermaid diff --git a/Docs/PresentationExchange.md b/Docs/PresentationExchange.md new file mode 100644 index 00000000..a493519d --- /dev/null +++ b/Docs/PresentationExchange.md @@ -0,0 +1,114 @@ +# OpenID Presentation Exchange Request Processor +[Presentation Exchange](https://identity.foundation/presentation-exchange/) is a data format defining the exchange of credentials. Combined with the [OpenID Connect Verifiable Presentations](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) extension creates a complete verified id request and response flow. + +```mermaid +classDiagram + +class OpenIdRawRequest { + - type: RequestType + - raw: Data? + - promptValueForIssuance: String +} + +RawRequest --|> OpenIdRawRequest + +RawRequest ..> RequestHandling: uses +RawRequest ..> RequestProcessor: uses + +<> RequestHandling +class RequestHandling { + ~handle(input: RawRequest) VerifiedIdRequest +} + +class OpenIdRequestHandler { + - configuration: LibraryConfiguration + - openIdResponder: OpenIdResponder + - manifestResolver: ManifestResolver + - verifiedIdRequester: VerifiedIdRequester + ~handleRequest(request: RawRequest): VerifiedIdRequest +} + +class OpenIdPresentationRequest { + - rawRequest: OpenIdRawRequest + - responder: OpenIdResponder + - configuration: LibraryConfiguration +} +OpenIdPresentationRequest ..> PresentationResponseContainer: uses + +class VerifiedIdRequirement { + - encrypted: Bool + - required: Bool + - types: [String] + - purpose: String? + - issuanceOptions: [VerifiedIdRequestInput] + - id: String? + - constraint: VerifiedIdConstraint + - selectedVerifiedId: VerifiedId? + - exclusivePlacement: [String] + ~getMatches(verifeidIds: [VerifiedId]): [VerifiedId] + ~fulfill(verifiedId: VerifiedId): VerifiedIdResult + + ~exclusive(ids: [String]) +} + +class PresentationResponseContainer { + - request: PresentationRequest + - expiryInSeconds: Int + - audienceUrl: String + - audienceDid: String + - nonce: String + - requestedIdTokenMap: [:] + - requestedSelfAttestedClaimMap: [:] + - requestedVCMap: [:] + ~addVerifiableCredential(id: String, vc: VerifiableCredential) + ~add(requirement: Requirement) + +} +PresentationResponseContainer ..> VerifiedIdRequirement: uses + +OpenIdPresentationRequest o-- VerifiedIdRequirement + +VerifiedIdRequest --|> OpenIdPresentationRequest + +RequestHandling --|> OpenIdRequestHandler +OpenIdRawRequest ..> OpenIdRequestHandler: uses + +class RequestProcessorFactory { + - requestProcessors: [RequestProcessor] + ~getRequestProcessor(for: RawRequest) RequestProcessor +} +<> RequestProcessor +class RequestProcessor { + ~canProcess(raw: RawRequest) Bool + ~process(raw: RawRequest) VerifiedIdRequest +} + +class OpenIdPresentationExchangeRequestProcessor { + ~canProcess(raw: RawRequest) Bool + ~process(raw: RawRequest) VerifiedIdRequest +} + +RequestProcessor --|> OpenIdPresentationExchangeRequestProcessor + +RequestProcessorFactory o-- OpenIdPresentationExchangeRequestProcessor +OpenIdPresentationExchangeRequestProcessor ..> OpenIdPresentationRequest: creates +OpenIdRequestHandler *-- RequestProcessorFactory +<> RawRequest +<> VerifiedIdRequest +class VerifiedIdRequest { + - style: RequesterStyle + - requirement: Requirement + - rootOfTrust: RootOfTrust + ~isSatisfied(): Bool + ~complete(): VerifiedIdResult + ~cancel(message: String?): VerifiedIdResult +} +``` + +`OpenIdPresentationExchangeRequestProcessor` can be called by `OpenIdRequestHandler` with the request. If the request is an `OpenIdRawRequest`, the processor will parse the `raw` value for a definition and form the corresponding `VerifiedIdRequest`. + +In parsing, it converts `input_definition`s into `VerifiedIdRequirement`s. `VerifiedIdRequirement` is fulfilled by supplying a `VerifiedId` that matches constraints. + +Once all `VerifiedIdRequirement`s have been satisfied, the OpenIdPresentationRequest can `complete()`. This will create a `PresentationResponseContainer` and `add` each requirement to the container. Once added, the container can be serialized, signed, and send according to the request's parameters. + +**Proposed Change**: `exclusivePlacement` can be added to `VerifiedIdRequirement` with `exclusive(with: [definition_id])` to ensure input definitions / requirements do not share the same verifiable presentation. `PresentationResponseContainer` will note these exclusions and any subject conflicts to create and map as many verifiable presentations as required. \ No newline at end of file diff --git a/WalletLibrary/Submodules/VerifiableCredential-SDK-iOS/VCEntities/VCEntities/oidc/RequestedClaims.swift b/WalletLibrary/Submodules/VerifiableCredential-SDK-iOS/VCEntities/VCEntities/oidc/RequestedClaims.swift index 2d2f37de..254e12c8 100644 --- a/WalletLibrary/Submodules/VerifiableCredential-SDK-iOS/VCEntities/VCEntities/oidc/RequestedClaims.swift +++ b/WalletLibrary/Submodules/VerifiableCredential-SDK-iOS/VCEntities/VCEntities/oidc/RequestedClaims.swift @@ -7,24 +7,22 @@ struct RequestedClaims: Codable, Equatable { /// Request Verifiable Presentation Tokens. - let vpToken: [RequestedVPToken] + let vpToken: RequestedVPToken enum CodingKeys: String, CodingKey { case vpToken = "vp_token" } - init(vpToken: [RequestedVPToken]) { + init(vpToken: RequestedVPToken) { self.vpToken = vpToken } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - if let vpTokens = try? container.decode([RequestedVPToken].self, forKey: .vpToken) { + if let vpTokens = try? container.decode(RequestedVPToken.self, forKey: .vpToken) { self.vpToken = vpTokens - } else if let vpToken = try? container.decode(RequestedVPToken.self, forKey: .vpToken) { - self.vpToken = [vpToken] - } else { + } else { throw DecodingError.unableToDecodeToken } } diff --git a/WalletLibrary/Submodules/VerifiableCredential-SDK-iOS/VCEntities/VCEntities/presentation/PresentationResponseContainer.swift b/WalletLibrary/Submodules/VerifiableCredential-SDK-iOS/VCEntities/VCEntities/presentation/PresentationResponseContainer.swift index 94e50dcb..b05e0974 100644 --- a/WalletLibrary/Submodules/VerifiableCredential-SDK-iOS/VCEntities/VCEntities/presentation/PresentationResponseContainer.swift +++ b/WalletLibrary/Submodules/VerifiableCredential-SDK-iOS/VCEntities/VCEntities/presentation/PresentationResponseContainer.swift @@ -64,29 +64,25 @@ struct PresentationResponseContainer: ResponseContaining { mutating func addVerifiableCredential(id: String, vc: VerifiableCredential) throws { - guard let vpTokenRequests = request?.content.claims?.vpToken, - !vpTokenRequests.isEmpty else { + guard let vpTokenRequest = request?.content.claims?.vpToken else { throw PresentationResponseError.noVerifiablePresentationRequestsInRequest } - for vpTokenRequest in vpTokenRequests { + guard let presentationDefinition = vpTokenRequest.presentationDefinition, + let presentationDefinitionId = presentationDefinition.id else { + throw PresentationResponseError.noPresentationDefinitionInVerifiablePresentationRequest + } + + if let inputDescriptors = presentationDefinition.inputDescriptors, + inputDescriptors.contains(where: { $0.id == id }) { + + let mapping = RequestedVerifiableCredentialMapping(id: id, verifiableCredential: vc) - guard let presentationDefinition = vpTokenRequest.presentationDefinition, - let presentationDefinitionId = presentationDefinition.id else { - throw PresentationResponseError.noPresentationDefinitionInVerifiablePresentationRequest - } + var mappings = requestVCMap[presentationDefinitionId] ?? [] + mappings.append(mapping) - if let inputDescriptors = presentationDefinition.inputDescriptors, - inputDescriptors.contains(where: { $0.id == id }) { - - let mapping = RequestedVerifiableCredentialMapping(id: id, verifiableCredential: vc) - - var mappings = requestVCMap[presentationDefinitionId] ?? [] - mappings.append(mapping) - - requestVCMap.updateValue(mappings, forKey: presentationDefinitionId) - return - } + requestVCMap.updateValue(mappings, forKey: presentationDefinitionId) + return } throw PresentationResponseError.noInputDescriptorMatchesGivenId diff --git a/WalletLibrary/WalletLibrary/Extensions/Mappings/VCSDK/Presentation/RequestedClaims+Mappable.swift b/WalletLibrary/WalletLibrary/Extensions/Mappings/VCSDK/Presentation/RequestedClaims+Mappable.swift index b472f780..5fb86f3e 100644 --- a/WalletLibrary/WalletLibrary/Extensions/Mappings/VCSDK/Presentation/RequestedClaims+Mappable.swift +++ b/WalletLibrary/WalletLibrary/Extensions/Mappings/VCSDK/Presentation/RequestedClaims+Mappable.swift @@ -14,12 +14,9 @@ extension RequestedClaims: Mappable { var requirements: [Requirement] = [] - for vpTokenRequest in vpToken + if let presentationDefinition = vpToken.presentationDefinition { - if let presentationDefinition = vpTokenRequest.presentationDefinition - { - requirements.append(contentsOf: try mapper.map(presentationDefinition)) - } + requirements.append(contentsOf: try mapper.map(presentationDefinition)) } if requirements.isEmpty