This commit is contained in:
Sydney Morton 2022-02-09 18:07:29 -08:00
Родитель e482e9bde9
Коммит b84f2596fa
14 изменённых файлов: 87 добавлений и 129 удалений

Просмотреть файл

@ -5,6 +5,7 @@
enum FormatterError: Error {
case noSigningKeyFound
case noStateInRequest
case unableToFormToken
case unableToGetRawValueOfVerifiableCredential
}

Просмотреть файл

@ -20,10 +20,10 @@ class IssuanceVPFormatter {
}
func format(toWrap vc: VerifiableCredential,
withAudience audience: String,
withExpiryInSeconds exp: Int,
usingIdentifier identifier: Identifier,
andSignWith key: KeyContainer) throws -> VerifiablePresentation {
withAudience audience: String,
withExpiryInSeconds exp: Int,
usingIdentifier identifier: Identifier,
andSignWith key: KeyContainer) throws -> VerifiablePresentation {
let headers = headerFormatter.formatHeaders(usingIdentifier: identifier, andSigningKey: identifier.didDocumentKeys.first!)
let timeConstraints = TokenTimeConstraints(expiryInSeconds: exp)

Просмотреть файл

@ -37,12 +37,16 @@ public class PresentationResponseFormatter: PresentationResponseFormatting {
throw FormatterError.noSigningKeyFound
}
guard let state = response.request?.content.state else {
throw FormatterError.noStateInRequest
}
let idToken = try createIdToken(from: response, usingIdentifier: identifier, andSignWith: signingKey)
let vpToken = try createVpToken(from: response, usingIdentifier: identifier, andSignWith: signingKey)
return PresentationResponse(idToken: idToken,
vpToken: vpToken,
state: response.request.content.state)
state: state)
}
private func createIdToken(from response: PresentationResponseContainer,

Просмотреть файл

@ -13,21 +13,21 @@ public struct RegistrationClaims: Codable, Equatable {
/// The name of the requester.
public let clientName: String?
/// The purpose of the request.
public let clientPurpose: String?
/// Optional logo uri of the requester.
public let logoURI: String?
/// The identifier types supported to use to respond to request (ex. did).
public let subjectIdentifierTypesSupported: [String]?
/// The decentralized identity methods supported to use to respond to request (ex. ion).
public let didMethodsSupported: [String]?
/// The supported Verfiable Presentation Formats and Algorithms to respond to request.
public let vpFormats: SupportedVerifiablePresentationFormats?
enum CodingKeys: String, CodingKey {
case clientName = "client_name"
case didMethodsSupported = "did_methods_supported"
case clientPurpose = "client_purpose"
case logoURI = "logo_uri"
case subjectIdentifierTypesSupported = "subject_syntax_types_supported"
case vpFormats = "vp_formats"

Просмотреть файл

@ -11,7 +11,7 @@ enum PresentationResponseError: Error {
public struct PresentationResponseContainer: ResponseContaining {
let request: PresentationRequest
let request: PresentationRequest?
let expiryInSeconds: Int
@ -50,4 +50,18 @@ public struct PresentationResponseContainer: ResponseContaining {
self.request = presentationRequest
self.expiryInSeconds = exp
}
public init(audienceUrl: String,
audienceDid: String,
nonce: String,
expiryInSeconds: Int,
presentationDefinitionId: String)
{
self.audienceDid = audienceDid
self.audienceUrl = audienceUrl
self.expiryInSeconds = expiryInSeconds
self.nonce = nonce
self.presentationDefinitionId = presentationDefinitionId
self.request = nil
}
}

Просмотреть файл

@ -13,7 +13,8 @@ enum PresentationRequestValidatorError: Error {
case invalidSignature
case noExpirationPresent
case noRegistrationPresent
case responseSigningAlgorithmNotSupported(for: String, supportedAlgorithms: [String]?)
case responseSigningAlgorithmNotSupportedForVCs
case responseSigningAlgorithmNotSupportedForVPs
case subjectIdentifierTypeNotSupported
case tokenExpired
}
@ -70,11 +71,6 @@ public struct PresentationRequestValidator: RequestValidating {
}
private func validate(registration: RegistrationClaims) throws {
if let isDidMethodSupported = registration.didMethodsSupported?.contains(VCEntitiesConstants.DID_METHODS_SUPPORTED),
!isDidMethodSupported {
throw PresentationRequestValidatorError.didMethodNotSupported
}
if let isSubjectIdentifierTypeSupported = registration.subjectIdentifierTypesSupported?.contains(VCEntitiesConstants.SUBJECT_IDENTIFIER_TYPE_DID),
!isSubjectIdentifierTypeSupported {
throw PresentationRequestValidatorError.subjectIdentifierTypeNotSupported
@ -82,14 +78,12 @@ public struct PresentationRequestValidator: RequestValidating {
if let isAlgorithmSupportedInVp = registration.vpFormats?.jwtVP?.algorithms?.contains(VCEntitiesConstants.ALGORITHM_SUPPORTED_IN_VP),
!isAlgorithmSupportedInVp {
throw PresentationRequestValidatorError.responseSigningAlgorithmNotSupported(for: "Verifiable Presentations",
supportedAlgorithms: registration.vpFormats?.jwtVP?.algorithms)
throw PresentationRequestValidatorError.responseSigningAlgorithmNotSupportedForVPs
}
if let isAlgorithmSupportedInVp = registration.vpFormats?.jwtVC?.algorithms?.contains(VCEntitiesConstants.ALGORITHM_SUPPORTED_IN_VP),
!isAlgorithmSupportedInVp {
throw PresentationRequestValidatorError.responseSigningAlgorithmNotSupported(for: "Verifiable Credentials",
supportedAlgorithms: registration.vpFormats?.jwtVC?.algorithms)
throw PresentationRequestValidatorError.responseSigningAlgorithmNotSupportedForVCs
}
}

Просмотреть файл

@ -6,21 +6,21 @@
import VCToken
public struct VerifiablePresentationClaims: OIDCClaims {
let vpId: String
public let vpId: String
let verifiablePresentation: VerifiablePresentationDescriptor
public let verifiablePresentation: VerifiablePresentationDescriptor
let issuerOfVp: String
public let issuerOfVp: String
let audience: String
public let audience: String
let iat: Double
public let iat: Double
let nbf: Double
public let nbf: Double
let exp: Double
public let exp: Double
let nonce: String?
public let nonce: String?
enum CodingKeys: String, CodingKey {
case issuerOfVp = "iss"
@ -29,6 +29,24 @@ public struct VerifiablePresentationClaims: OIDCClaims {
case verifiablePresentation = "vp"
case iat, exp, nonce, nbf
}
public init(vpId: String = "",
verifiablePresentation: VerifiablePresentationDescriptor?,
issuerOfVp: String = "",
audience: String = "",
iat: Double = 0,
nbf: Double = 0,
exp: Double = 0,
nonce: String? = "") {
self.vpId = vpId
self.verifiablePresentation = verifiablePresentation ?? VerifiablePresentationDescriptor(context: [], type: [], verifiableCredential: [])
self.issuerOfVp = issuerOfVp
self.audience = audience
self.iat = iat
self.nbf = nbf
self.exp = exp
self.nonce = nonce
}
}
public typealias VerifiablePresentation = JwsToken<VerifiablePresentationClaims>

Просмотреть файл

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
struct VerifiablePresentationDescriptor: Codable {
public struct VerifiablePresentationDescriptor: Codable {
let context: [String]

Просмотреть файл

@ -41,11 +41,10 @@ class PresentationResponseFormatterTests: XCTestCase {
let formattedResponse = try formatter.format(response: self.mockResponse, usingIdentifier: self.mockIdentifier)
XCTAssertEqual(formattedResponse.idToken.content.did, self.mockIdentifier.longFormDid)
XCTAssertNotNil(formattedResponse.idToken.content.exp)
XCTAssertNotNil(formattedResponse.idToken.content.iat)
XCTAssertNotNil(formattedResponse.idToken.content.jti)
XCTAssertEqual(formattedResponse.idToken.content.audience, self.mockResponse.audienceUrl)
XCTAssertEqual(formattedResponse.idToken.content.subject, self.mockIdentifier.longFormDid)
XCTAssertEqual(formattedResponse.idToken.content.audience, self.mockResponse.audienceDid)
XCTAssert(MockTokenSigner.wasSignCalled)
XCTAssert(MockTokenSigner.wasGetPublicJwkCalled)
}
@ -53,12 +52,10 @@ class PresentationResponseFormatterTests: XCTestCase {
func testFormatTokenNoVcs() throws {
let formattedResponse = try formatter.format(response: self.mockResponse, usingIdentifier: self.mockIdentifier)
XCTAssertEqual(formattedResponse.idToken.content.did, self.mockIdentifier.longFormDid)
XCTAssertEqual(formattedResponse.idToken.content.subject, self.mockIdentifier.longFormDid)
XCTAssertNotNil(formattedResponse.idToken.content.exp)
XCTAssertNotNil(formattedResponse.idToken.content.iat)
XCTAssertNotNil(formattedResponse.idToken.content.jti)
XCTAssertEqual(formattedResponse.idToken.content.audience, self.mockResponse.audienceUrl)
XCTAssertNil(formattedResponse.idToken.content.presentationSubmission)
XCTAssertEqual(formattedResponse.idToken.content.audience, self.mockResponse.audienceDid)
XCTAssert(MockTokenSigner.wasSignCalled)
XCTAssert(MockTokenSigner.wasGetPublicJwkCalled)
}

Просмотреть файл

@ -101,25 +101,25 @@ class PresentationRequestValidatorTests: XCTestCase {
}
}
func testDidMethodNotSupportedError() throws {
func testSigningAlgorithmNotSupportedForVCError() throws {
let validator = PresentationRequestValidator(verifier: verifier)
let mockInvalidRegistration = PresentationRequestValidatorTests.createRegistration(expectedDidMethodSupported: "wrongValue")
let mockInvalidRegistration = PresentationRequestValidatorTests.createRegistration(expectedSupportedAlgorithmForVC: "wrongValue")
let mockRequestClaims = createMockPresentationRequestClaims(registration: mockInvalidRegistration)
if let mockRequest = PresentationRequestToken(headers: Header(),content: mockRequestClaims) {
XCTAssertThrowsError(try validator.validate(request: mockRequest, usingKeys: [mockDidPublicKey])) { error in
XCTAssertEqual(error as? PresentationRequestValidatorError, PresentationRequestValidatorError.didMethodNotSupported)
XCTAssertEqual(error as? PresentationRequestValidatorError, PresentationRequestValidatorError.responseSigningAlgorithmNotSupportedForVCs)
}
XCTAssertTrue(MockTokenVerifier.wasVerifyCalled)
}
}
func testSigningAlgorithmNotSupportedError() throws {
func testSigningAlgorithmNotSupportedForVPError() throws {
let validator = PresentationRequestValidator(verifier: verifier)
let mockInvalidRegistration = PresentationRequestValidatorTests.createRegistration(expectedSupportedAlgorithm: "wrongValue")
let mockInvalidRegistration = PresentationRequestValidatorTests.createRegistration(expectedSupportedAlgorithmForVP: "wrongValue")
let mockRequestClaims = createMockPresentationRequestClaims(registration: mockInvalidRegistration)
if let mockRequest = PresentationRequestToken(headers: Header(),content: mockRequestClaims) {
XCTAssertThrowsError(try validator.validate(request: mockRequest, usingKeys: [mockDidPublicKey])) { error in
XCTAssertEqual(error as? PresentationRequestValidatorError, PresentationRequestValidatorError.signingAlgorithmNotSupported)
XCTAssertEqual(error as? PresentationRequestValidatorError, PresentationRequestValidatorError.responseSigningAlgorithmNotSupportedForVPs)
}
XCTAssertTrue(MockTokenVerifier.wasVerifyCalled)
}
@ -130,8 +130,8 @@ class PresentationRequestValidatorTests: XCTestCase {
registration: RegistrationClaims = PresentationRequestValidatorTests.createRegistration(),
scope: String = VCEntitiesConstants.SCOPE,
timeConstraints: TokenTimeConstraints = TokenTimeConstraints(expiryInSeconds: 300)) -> PresentationRequestClaims {
return PresentationRequestClaims(clientID: "clientID",
issuer: "issuer",
return PresentationRequestClaims(jti: "testId",
clientID: "clientID",
redirectURI: "redirectURI",
responseMode: responseMode,
responseType: responseType,
@ -146,16 +146,18 @@ class PresentationRequestValidatorTests: XCTestCase {
exp: timeConstraints.expiration)
}
private static func createRegistration(expectedSubjectIdentifierType: String = "did",
expectedDidMethodSupported: String = "ion",
expectedSupportedAlgorithm: String = "ES256K") -> RegistrationClaims {
private static func createRegistration(expectedSubjectIdentifierType: String = "did:ion",
expectedSupportedAlgorithmForVC: String = "ES256K",
expectedSupportedAlgorithmForVP: String = "ES256K") -> RegistrationClaims {
let supportedAlgorithmsForVP = AllowedAlgorithms(algorithms: [expectedSupportedAlgorithmForVP])
let supportedAlgorithmsForVC = AllowedAlgorithms(algorithms: [expectedSupportedAlgorithmForVC])
return RegistrationClaims(clientName: "clientName",
clientPurpose: "clientPurpose",
tosURI: "tosURI",
logoURI: "logoURI",
subjectIdentifierTypesSupported: [expectedSubjectIdentifierType],
didMethodsSupported: [expectedDidMethodSupported],
vpFormats: SupportedVerifiablePresentationFormats(jwtVP: AllowedAlgorithms(algorithms: [expectedSupportedAlgorithm])))
vpFormats: SupportedVerifiablePresentationFormats(jwtVP: supportedAlgorithmsForVP, jwtVC: supportedAlgorithmsForVC))
}
}

Просмотреть файл

@ -19,10 +19,11 @@ class PostPresentionResponseOperationTests: XCTestCase {
override func setUpWithError() throws {
let header = Header(type: "type", algorithm: "alg", jsonWebKey: "key", keyId: "kid")
let claims = PresentationResponseClaims(state: "state",
nonce: "nonce")
let claims = PresentationResponseClaims(nonce: "nonce")
let idToken = PresentationResponseToken(headers: header, content: claims)!
expectedPresentationResponse = PresentationResponse(idToken: idToken, vpToken: nil)
let vpClaims = VerifiablePresentationClaims(verifiablePresentation: nil)
let vpToken = VerifiablePresentation(headers: header, content: vpClaims)!
expectedPresentationResponse = PresentationResponse(idToken: idToken, vpToken: vpToken, state: "state")
let configuration = URLSessionConfiguration.default
configuration.protocolClasses = [UrlProtocolMock.self]
@ -41,7 +42,7 @@ class PostPresentionResponseOperationTests: XCTestCase {
let encodedBody = try encoder.encode(value: expectedPresentationResponse)
XCTAssertEqual(postOperation.urlRequest.httpBody!, encodedBody)
XCTAssertEqual(postOperation.urlRequest.httpMethod!, Constants.POST)
XCTAssertEqual(postOperation.urlRequest.value(forHTTPHeaderField: Constants.CONTENT_TYPE)!, Constants.JSON)
XCTAssertEqual(postOperation.urlRequest.value(forHTTPHeaderField: Constants.CONTENT_TYPE)!, Constants.FORM_URLENCODED)
}
func testInvalidUrlInit() {

Просмотреть файл

@ -7,7 +7,6 @@
objects = {
/* Begin PBXBuildFile section */
550D4184256474D70078CD97 /* PairwiseServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550D4183256474D70078CD97 /* PairwiseServiceTests.swift */; };
550F1E3F25101759009AF467 /* VCServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 550F1E3525101758009AF467 /* VCServices.framework */; };
550F1E4425101759009AF467 /* IssuanceServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550F1E4325101759009AF467 /* IssuanceServiceTests.swift */; };
550F1E4625101759009AF467 /* VCUseCase.h in Headers */ = {isa = PBXBuildFile; fileRef = 550F1E3825101758009AF467 /* VCUseCase.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -72,7 +71,6 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
550D4183256474D70078CD97 /* PairwiseServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairwiseServiceTests.swift; sourceTree = "<group>"; };
550F1E3525101758009AF467 /* VCServices.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = VCServices.framework; sourceTree = BUILT_PRODUCTS_DIR; };
550F1E3825101758009AF467 /* VCUseCase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VCUseCase.h; sourceTree = "<group>"; };
550F1E3925101758009AF467 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -200,7 +198,6 @@
550F1E4325101759009AF467 /* IssuanceServiceTests.swift */,
55793BDC255C65DA007F7599 /* ExchangeServiceTests.swift */,
559FC9F025C9F2B700737F14 /* LinkedDomainServiceTests.swift */,
550D4183256474D70078CD97 /* PairwiseServiceTests.swift */,
551F304E2527EAF40081D5E7 /* PresentationServiceTests.swift */,
555BDAEF2530E4B9001E7A18 /* CoreDataManagerTests.swift */,
555BDB122530EA79001E7A18 /* IdentifierDatabaseTests.swift */,
@ -440,7 +437,6 @@
55793BDB255C654E007F7599 /* MockExchangeRequestFormatter.swift in Sources */,
555BDAF02530E4B9001E7A18 /* CoreDataManagerTests.swift in Sources */,
55DA76F525BE3858009C32E0 /* MockExchangeApiCalls.swift in Sources */,
550D4184256474D70078CD97 /* PairwiseServiceTests.swift in Sources */,
551F30512527EDBC0081D5E7 /* MockPresentationResponseFormatter.swift in Sources */,
559FC9F925C9F35A00737F14 /* MockWellKnownConfigDocumentApiCalls.swift in Sources */,
55793BDD255C65DA007F7599 /* ExchangeServiceTests.swift in Sources */,

Просмотреть файл

@ -1,68 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import XCTest
import VCEntities
import VCCrypto
@testable import VCServices
class PairwiseServiceTests: XCTestCase {
var service: PairwiseService!
var mockPresentationResponse: ResponseContaining!
override func setUpWithError() throws {
let formatter = MockExchangeRequestFormatter(shouldSucceed: true)
let exchangeService = ExchangeService(formatter: formatter, apiCalls: MockExchangeApiCalls())
service = PairwiseService(exchangeService: exchangeService,
identifierService: IdentifierService())
let requestToken = PresentationRequestToken(from: TestData.presentationRequest.rawValue)!
let request = PresentationRequest(from: requestToken, linkedDomainResult: .linkedDomainVerified(domainUrl: "test.com"))
self.mockPresentationResponse = try PresentationResponseContainer(from: request, expiryInSeconds: 5)
let mockVc = VerifiableCredential(from: TestData.verifiableCredential.rawValue)!
self.mockPresentationResponse.requestVCMap.append(RequestedVerifiableCredentialMapping(type: "test",
verifiableCredential: mockVc))
MockIssuanceResponseFormatter.wasFormatCalled = false
try self.storeMockOwnerIdentifier()
}
override func tearDownWithError() throws {
try CoreDataManager.sharedInstance.deleteAllIdentifiers()
}
func testSendResponse() throws {
let expec = self.expectation(description: "Fire")
service.createPairwiseResponse(response: mockPresentationResponse).done {
response in
print(response)
XCTFail()
expec.fulfill()
}.catch { error in
print(error)
XCTAssert(MockExchangeRequestFormatter.wasFormatCalled)
XCTAssert(MockExchangeApiCalls.wasPostCalled)
XCTAssert(error is MockExchangeNetworkingError)
expec.fulfill()
}
wait(for: [expec], timeout: 20)
}
private func storeMockOwnerIdentifier() throws {
let identifierDB = IdentifierDatabase()
let mockDid = "did:ion:EiAmVAdVaxoi6uxjUdGw-J2xbbsQE8SLFrtt3oz9CKEGgQ?-ion-initial-state=eyJkZWx0YV9oYXNoIjoiRWlBaE9zbHBNanZ6WFhfQS1ONEd6Szh6WVB4cE9XakJIakpMY252Si11aDJ0dyIsInJlY292ZXJ5X2NvbW1pdG1lbnQiOiJFaUFtT1JVelQtZGNlT3lOT0Vrc0Z6Wm9ZVV85Mm9yNl81cWN4N3UyMmtNa25BIn0.eyJ1cGRhdGVfY29tbWl0bWVudCI6IkVpQW1PUlV6VC1kY2VPeU5PRWtzRnpab1lVXzkyb3I2XzVxY3g3dTIya01rbkEiLCJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljX2tleXMiOlt7ImlkIjoieTZnX3NpZ25feE02Zy1vQllfMSIsInR5cGUiOiJFY2RzYVNlY3AyNTZrMVZlcmlmaWNhdGlvbktleTIwMTkiLCJqd2siOnsia3R5IjoiRUMiLCJjcnYiOiJzZWNwMjU2azEiLCJ4IjoicDI5emFHNktJT1otX3hxZkdBR3VjcDNCVm5BQ28xMDhsaXlVMWpGSlJmbyIsInkiOiI0X3EzWHlPUHJrWlRwUWhRaTdEM2N6RWM4OGd4VlpOOFNaZU5EQzZlUHJjIn0sInB1cnBvc2UiOlsiYXV0aCIsImdlbmVyYWwiXX1dfX1dfQ"
let operations = CryptoOperations()
let key = try operations.generateKey()
let keyContainer = KeyContainer(keyReference: key, keyId: "test")
let mockOwnerIdentifier = Identifier(longFormDid: mockDid, didDocumentKeys: [keyContainer], updateKey: keyContainer, recoveryKey: keyContainer, alias: "test")
try identifierDB.saveIdentifier(identifier: mockOwnerIdentifier)
}
}

Просмотреть файл

@ -26,10 +26,9 @@ class MockPresentationResponseFormatter: PresentationResponseFormatting {
Self.wasFormatCalled = true
if (shouldSucceed) {
let header = Header(type: "type", algorithm: "alg", jsonWebKey: "key", keyId: "kid")
let claims = PresentationResponseClaims(state: "state",
nonce: "nonce")
let claims = PresentationResponseClaims(nonce: "nonce")
let idToken = PresentationResponseToken(headers: header, content: claims)!
return PresentationResponse(idToken: idToken, vpToken: nil)
return PresentationResponse(idToken: idToken, vpToken: nil, state: "state")
} else {
throw MockIssuanceResponseFormatterError.doNotWantToResolveRealObject
}