From a325ab6384f63dcd1149a5504c8324491a08abc9 Mon Sep 17 00:00:00 2001 From: sydneymorton Date: Mon, 9 Nov 2020 17:15:13 -0800 Subject: [PATCH] Add exchange model and formatter --- .../VCEntities.xcodeproj/project.pbxproj | 28 ++++++++ .../claims/ExchangeResponseClaims.swift | 65 +++++++++++++++++++ .../ExchangeResponseFormatter.swift | 62 ++++++++++++++++++ .../claims/IssuanceResponseClaims.swift | 2 +- .../claims/PresentationResponseClaims.swift | 2 +- VCEntities/VCEntities/util/Constants.swift | 8 +++ 6 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 VCEntities/VCEntities/exchange/claims/ExchangeResponseClaims.swift create mode 100644 VCEntities/VCEntities/formatters/ExchangeResponseFormatter.swift create mode 100644 VCEntities/VCEntities/util/Constants.swift diff --git a/VCEntities/VCEntities.xcodeproj/project.pbxproj b/VCEntities/VCEntities.xcodeproj/project.pbxproj index 79afea2..c39346a 100644 --- a/VCEntities/VCEntities.xcodeproj/project.pbxproj +++ b/VCEntities/VCEntities.xcodeproj/project.pbxproj @@ -60,6 +60,9 @@ 5577E86F251E8EB3005250BC /* IssuanceServiceResponseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5577E86E251E8EB3005250BC /* IssuanceServiceResponseTests.swift */; }; 5577E871251E8FE3005250BC /* OIDCClaimsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5577E870251E8FE3005250BC /* OIDCClaimsTests.swift */; }; 5577E873251E9007005250BC /* MockOIDCClaims.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5577E872251E9007005250BC /* MockOIDCClaims.swift */; }; + 55793BCB255A0497007F7599 /* ExchangeResponseClaims.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55793BCA255A0497007F7599 /* ExchangeResponseClaims.swift */; }; + 55793BCD255A070E007F7599 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55793BCC255A070E007F7599 /* Constants.swift */; }; + 55793BCF255A08FE007F7599 /* ExchangeResponseFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55793BCE255A08FE007F7599 /* ExchangeResponseFormatter.swift */; }; 557BFC46251E6058005B5B24 /* IssuanceResponseClaimsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 557BFC45251E6058005B5B24 /* IssuanceResponseClaimsTests.swift */; }; 557BFC53251E665D005B5B24 /* IssuanceResponseFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 557BFC51251E665D005B5B24 /* IssuanceResponseFormatter.swift */; }; 557BFC56251E6701005B5B24 /* IssuanceResponseContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 557BFC55251E6701005B5B24 /* IssuanceResponseContainer.swift */; }; @@ -150,6 +153,9 @@ 5577E86E251E8EB3005250BC /* IssuanceServiceResponseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssuanceServiceResponseTests.swift; sourceTree = ""; }; 5577E870251E8FE3005250BC /* OIDCClaimsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDCClaimsTests.swift; sourceTree = ""; }; 5577E872251E9007005250BC /* MockOIDCClaims.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockOIDCClaims.swift; sourceTree = ""; }; + 55793BCA255A0497007F7599 /* ExchangeResponseClaims.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExchangeResponseClaims.swift; sourceTree = ""; }; + 55793BCC255A070E007F7599 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 55793BCE255A08FE007F7599 /* ExchangeResponseFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExchangeResponseFormatter.swift; sourceTree = ""; }; 557BFC45251E6058005B5B24 /* IssuanceResponseClaimsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssuanceResponseClaimsTests.swift; sourceTree = ""; }; 557BFC51251E665D005B5B24 /* IssuanceResponseFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssuanceResponseFormatter.swift; sourceTree = ""; }; 557BFC55251E6701005B5B24 /* IssuanceResponseContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssuanceResponseContainer.swift; sourceTree = ""; }; @@ -248,6 +254,7 @@ children = ( 557BFC50251E665D005B5B24 /* formatters */, 5577E865251E830B005250BC /* identifier */, + 55793BC8255A0468007F7599 /* exchange */, 5577E862251E8284005250BC /* issuance */, 5584E49725255A8F00A9DE58 /* presentation */, 5577E864251E82B2005250BC /* verifiableCredential */, @@ -364,11 +371,28 @@ path = issuance; sourceTree = ""; }; + 55793BC8255A0468007F7599 /* exchange */ = { + isa = PBXGroup; + children = ( + 55793BC9255A0484007F7599 /* claims */, + ); + path = exchange; + sourceTree = ""; + }; + 55793BC9255A0484007F7599 /* claims */ = { + isa = PBXGroup; + children = ( + 55793BCA255A0497007F7599 /* ExchangeResponseClaims.swift */, + ); + path = claims; + sourceTree = ""; + }; 557BFC50251E665D005B5B24 /* formatters */ = { isa = PBXGroup; children = ( 557BFC51251E665D005B5B24 /* IssuanceResponseFormatter.swift */, 551F305A252E39540081D5E7 /* IdentifierFormatter.swift */, + 55793BCE255A08FE007F7599 /* ExchangeResponseFormatter.swift */, 5518CC7125264C6F00C7A21B /* PresentationResponseFormatter.swift */, 555CE08925267FE500C1C938 /* VerifiablePresentationFormatter.swift */, 551F30422527DC050081D5E7 /* JwsHeaderFormatter .swift */, @@ -433,6 +457,7 @@ 55AF7481252F6803006A8B25 /* util */ = { isa = PBXGroup; children = ( + 55793BCC255A070E007F7599 /* Constants.swift */, 55575762251BC6CF009979AB /* JSONCodingKeys.swift */, 551F3062252E6E230081D5E7 /* Multihash.swift */, ); @@ -603,6 +628,7 @@ 55575773251BC6CF009979AB /* VerifiableCredentialDescriptor.swift in Sources */, 555CE08F2526822300C1C938 /* VerifiablePresentationDescriptor.swift in Sources */, 55575770251BC6CF009979AB /* OIDCClaims.swift in Sources */, + 55793BCF255A08FE007F7599 /* ExchangeResponseFormatter.swift in Sources */, 5557576C251BC6CF009979AB /* CardDisplayDescriptor.swift in Sources */, 55AF748E252F6BE0006A8B25 /* Identifier.swift in Sources */, 55AF7486252F6B1F006A8B25 /* IdentifierDocumentServiceEndpoint.swift in Sources */, @@ -611,10 +637,12 @@ 551F305D252E4C1B0081D5E7 /* IdentifierDocument.swift in Sources */, 55575777251BC6CF009979AB /* IssuanceServiceResponse.swift in Sources */, 55309B762539DDC300AF15AA /* VerifiableCredential.swift in Sources */, + 55793BCB255A0497007F7599 /* ExchangeResponseClaims.swift in Sources */, 551F30432527DC050081D5E7 /* JwsHeaderFormatter .swift in Sources */, 55575765251BC6CF009979AB /* ConsentDisplayDescriptor.swift in Sources */, 5584E49C2525641600A9DE58 /* PresentationDefinition.swift in Sources */, 555CE08D2526810100C1C938 /* VerifiablePresentationClaims.swift in Sources */, + 55793BCD255A070E007F7599 /* Constants.swift in Sources */, 5584E4A2252565D900A9DE58 /* IssuanceMetadata.swift in Sources */, 5518CC68252645D000C7A21B /* PresentationSubmission.swift in Sources */, 5557576A251BC6CF009979AB /* Contract.swift in Sources */, diff --git a/VCEntities/VCEntities/exchange/claims/ExchangeResponseClaims.swift b/VCEntities/VCEntities/exchange/claims/ExchangeResponseClaims.swift new file mode 100644 index 0000000..d75102c --- /dev/null +++ b/VCEntities/VCEntities/exchange/claims/ExchangeResponseClaims.swift @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the MIT License. See License.txt in the project root for license information. +*--------------------------------------------------------------------------------------------*/ + +import VCJwt + +public struct ExchangeResponseClaims: OIDCClaims { + + public let issuer: String = Constants.SELF_ISSUED + + public let publicKeyThumbprint: String + + public let audience: String + + public let did: String + + public let publicJwk: ECPublicJwk? + + public let contract: String + + public let jti: String + + public let iat: Double? + + public let exp: Double? + + public let exchangeableVc: String + + public let recipientDid: String + + public init(publicKeyThumbprint: String = "", + audience: String = "", + did: String = "", + publicJwk: ECPublicJwk? = nil, + contract: String = "", + jti: String = "", + iat: Double? = nil, + exp: Double? = nil, + exchangeableVc: String = "", + recipientDid: String = "") { + self.publicKeyThumbprint = publicKeyThumbprint + self.audience = audience + self.did = did + self.publicJwk = publicJwk + self.contract = contract + self.jti = jti + self.iat = iat + self.exp = exp + self.exchangeableVc = exchangeableVc + self.recipientDid = recipientDid + } + + enum CodingKeys: String, CodingKey { + case issuer = "iss" + case publicKeyThumbprint = "sub" + case audience = "aud" + case publicJwk = "sub_jwk" + case exchangeableVc = "vc" + case recipientDid = "recipient" + case contract, jti, did, iat, exp + } +} + +public typealias ExchangeResponse = JwsToken diff --git a/VCEntities/VCEntities/formatters/ExchangeResponseFormatter.swift b/VCEntities/VCEntities/formatters/ExchangeResponseFormatter.swift new file mode 100644 index 0000000..3580f5f --- /dev/null +++ b/VCEntities/VCEntities/formatters/ExchangeResponseFormatter.swift @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import VCJwt + +public protocol ExchangeResponseFormatting { + func format(usingIdentifier identifier: Identifier, andExchangeableVc vc: VerifiableCredential) throws -> ExchangeResponse +} + +public class ExchangeResponseFormatter: ExchangeResponseFormatting { + + let signer: TokenSigning + let headerFormatter = JwsHeaderFormatter() + + public init(signer: TokenSigning = Secp256k1Signer()) { + self.signer = signer + } + + public func format(usingIdentifier identifier: Identifier, andExchangeableVc vc: VerifiableCredential) throws -> ExchangeResponse { + + guard let signingKey = identifier.didDocumentKeys.first else { + throw FormatterError.noSigningKeyFound + } + + return try createToken(usingIdentifier: identifier, andExchangeableVc: vc, andSignWith: signingKey) + } + + private func createToken(usingIdentifier identifier: Identifier, andExchangeableVc vc: VerifiableCredential, andSignWith signingKey: KeyContainer) throws -> ExchangeResponse { + + let headers = headerFormatter.formatHeaders(usingIdentifier: identifier, andSigningKey: signingKey) + let tokenContents = try formatClaims(usingIdentifier: identifier, andExchangeableVc: vc, andSigningKey: signingKey) + + guard var token = JwsToken(headers: headers, content: tokenContents) else { + throw FormatterError.unableToFormToken + } + + try token.sign(using: self.signer, withSecret: signingKey.keyReference) + return token + } + + private func formatClaims(usingIdentifier identifier: Identifier, andExchangeableVc vc: VerifiableCredential, andSigningKey key: KeyContainer) throws -> ExchangeResponseClaims { + + guard let audience = vc.token.content.vc.exchangeService?.id else { + throw FormatterError.noAudienceFoundInRequest + } + + let publicKey = try signer.getPublicJwk(from: key.keyReference, withKeyId: key.keyId) + let timeConstraints = TokenTimeConstraints(expiryInSeconds: 5) + + return ExchangeResponseClaims(publicKeyThumbprint: try publicKey.getThumbprint(), + audience: audience, + did: vc.token.content.sub, + publicJwk: publicKey, + jti: UUID().uuidString, + iat: timeConstraints.issuedAt, + exp: timeConstraints.expiration, + exchangeableVc: vc.raw, + recipientDid: identifier.longFormDid) + } +} diff --git a/VCEntities/VCEntities/issuance/claims/IssuanceResponseClaims.swift b/VCEntities/VCEntities/issuance/claims/IssuanceResponseClaims.swift index 466279f..46ef0ea 100644 --- a/VCEntities/VCEntities/issuance/claims/IssuanceResponseClaims.swift +++ b/VCEntities/VCEntities/issuance/claims/IssuanceResponseClaims.swift @@ -7,7 +7,7 @@ import VCJwt public struct IssuanceResponseClaims: OIDCClaims { - public let issuer: String = "https://self-issued.me" + public let issuer: String = Constants.SELF_ISSUED public let publicKeyThumbprint: String diff --git a/VCEntities/VCEntities/presentation/claims/PresentationResponseClaims.swift b/VCEntities/VCEntities/presentation/claims/PresentationResponseClaims.swift index b12dbc6..4c07873 100644 --- a/VCEntities/VCEntities/presentation/claims/PresentationResponseClaims.swift +++ b/VCEntities/VCEntities/presentation/claims/PresentationResponseClaims.swift @@ -7,7 +7,7 @@ import VCJwt public struct PresentationResponseClaims: OIDCClaims { - public let issuer: String = "https://self-issued.me" + public let issuer: String = Constants.SELF_ISSUED public let publicKeyThumbprint: String diff --git a/VCEntities/VCEntities/util/Constants.swift b/VCEntities/VCEntities/util/Constants.swift new file mode 100644 index 0000000..08d8410 --- /dev/null +++ b/VCEntities/VCEntities/util/Constants.swift @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the MIT License. See License.txt in the project root for license information. +*--------------------------------------------------------------------------------------------*/ + +struct Constants { + static let SELF_ISSUED = "https://self-issued.me" +}