Skip to content

Commit

Permalink
Merge branch 'main' into 155-update-links-in-readmemd-to-point-to-hyp…
Browse files Browse the repository at this point in the history
…erledger-repos
  • Loading branch information
essbante-io authored Aug 26, 2024
2 parents 7439686 + 7e2737c commit ec9fb33
Show file tree
Hide file tree
Showing 25 changed files with 663 additions and 68 deletions.
14 changes: 14 additions & 0 deletions EdgeAgentSDK/Domain/Sources/Models/Errors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,12 @@ public enum PolluxError: KnownPrismError {
internalErrors: [Error]
)

/// An error case indicating that a credential cannot be verified..
case cannotVerifyCredential(
credential: String? = nil,
internalErrors: [Error]
)

/// An error case indicating that a specified input path was not found.
case inputPathNotFound(path: String)

Expand Down Expand Up @@ -874,6 +880,8 @@ public enum PolluxError: KnownPrismError {
return 78
case .credentialIsSuspended:
return 79
case .cannotVerifyCredential:
return 80
}
}

Expand Down Expand Up @@ -960,6 +968,12 @@ Cannot verify input descriptor field \(name.map { "with name: \($0)"} ?? ""), wi

case .credentialIsSuspended(let jwtString):
return "Credential (\(jwtString)) is suspended"
case .cannotVerifyCredential(let credential, let fieldErrors):
let errors = fieldErrors.map { " - \(errorMessage($0))" }.joined(separator: "\n")
return
"""
Cannot verify credential: \(credential.map { "with name: \($0)"} ?? ""), with errors: \n \(errors)
"""
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Proof.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ public extension EdgeAgent {
request: request.makeMessage(),
options: [
.exportableKey(exporting),
.subjectDID(subjectDID)
.subjectDID(subjectDID),
.disclosingClaims(claims: credential.claims.map(\.key))
]
)
default:
Expand Down
64 changes: 64 additions & 0 deletions EdgeAgentSDK/EdgeAgent/Tests/PresentationExchangeTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Builders
import Core
import Domain
import eudi_lib_sdjwt_swift
import Logging
import JSONWebSignature
import JSONWebToken
Expand Down Expand Up @@ -99,6 +100,44 @@ final class PresentationExchangeFlowTests: XCTestCase {
}
}

func testSDJWTPresentationRequest() async throws {
let prismDID = try await edgeAgent.createNewPrismDID()
let subjectDID = try await edgeAgent.createNewPrismDID()

let sdjwt = try await makeCredentialSDJWT(issuerDID: prismDID, subjectDID: subjectDID)
let credential = try SDJWTCredential(sdjwtString: sdjwt)

logger.info("Creating presentation request")
let message = try await edgeAgent.initiatePresentationRequest(
type: .jwt,
fromDID: DID(method: "test", methodId: "alice"),
toDID: DID(method: "test", methodId: "bob"),
claimFilters: [
.init(
paths: ["$.vc.credentialSubject.test"],
type: "string",
required: true,
pattern: "aliceTest"
)
]
)

try await edgeAgent.pluto.storeMessage(message: message.makeMessage(), direction: .sent).first().await()

let presentation = try await edgeAgent.createPresentationForRequestProof(
request: message,
credential: credential
)

let verification = try await edgeAgent.pollux.verifyPresentation(
message: presentation.makeMessage(),
options: []
)

logger.info(verification ? "Verification was successful" : "Verification failed")
XCTAssertTrue(verification)
}

private func makeCredentialJWT(issuerDID: DID, subjectDID: DID) async throws -> String {
let payload = MockCredentialClaim(
iss: issuerDID.string,
Expand All @@ -121,6 +160,31 @@ final class PresentationExchangeFlowTests: XCTestCase {
}
return try JWT.signed(payload: payload, protectedHeader: jwsHeader, key: jwkD.toJoseJWK()).jwtString
}

private func makeCredentialSDJWT(issuerDID: DID, subjectDID: DID) async throws -> String {
guard
let key = try await edgeAgent.pluto.getDIDPrivateKeys(did: issuerDID).first().await()?.first,
let jwkD = try await edgeAgent.apollo.restorePrivateKey(key).exporting?.jwk
else {
XCTFail()
fatalError()
}

let sdjwt = try SDJWTIssuer.issue(
issuersPrivateKey: try jwkD.toJoseJWK(),
header: DefaultJWSHeaderImpl(algorithm: .ES256K)
) {
ConstantClaims.iss(domain: issuerDID.string)
ConstantClaims.sub(subject: subjectDID.string)
ObjectClaim("vc") {
ObjectClaim("credentialSubject") {
FlatDisclosedClaim("test", "aliceTest")
}
}
}

return CompactSerialiser(signedSDJWT: sdjwt).serialised
}
}

private struct MockCredentialClaim: JWTRegisteredFieldsClaims, Codable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ extension JWTCredential: ProvableCredential {
let requestData = try JSONDecoder.didComm().decode(PresentationExchangeRequest.self, from: jsonData)
let payload: Data = try JWT.getPayload(jwtString: jwtString)
do {
try VerifyPresentationSubmission.verifyPresentationSubmissionClaims(
try VerifyPresentationSubmissionJWT.verifyPresentationSubmissionClaims(
request: requestData.presentationDefinition, credentials: [payload]
)
return true
Expand Down
4 changes: 2 additions & 2 deletions EdgeAgentSDK/Pollux/Sources/Models/JWT/JWTPresentation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,11 @@ struct JWTPresentation {
PresentationSubmission.Descriptor(
id: $0.id,
path: "$.verifiable_credential[0]",
format: "jwt_vp",
format: "jwt",
pathNested: .init(
id: $0.id,
path: "$.vp.verifiableCredential[0]",
format: "jwt_vc"
format: "jwt"
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ public struct PresentationDefinition: Codable {
public var ldpVp: LDPFormat?
/// Generic LDP format.
public var ldp: LDPFormat?
/// Generic SDJWT format..
public var sdJwt: JWTFormat?
}

/// Unique identifier for the presentation definition.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Foundation

public protocol PresentationExchangeClaimVerifier {
func verifyClaim(inputDescriptor: InputDescriptor) throws
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation

public protocol SubmissionDescriptorFormatParser {
var format: String { get }
func parse(path: String, presentationData: Data) async throws -> String
func parsePayload(path: String, presentationData: Data) async throws -> Data
func parseClaimVerifier(descriptor: PresentationSubmission.Descriptor, presentationData: Data) async throws -> PresentationExchangeClaimVerifier
}
6 changes: 6 additions & 0 deletions EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWT.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,9 @@ extension SDJWTCredential: Credential {
return "sd-jwt"
}
}

extension SDJWTCredential {
func getAlg() throws -> String? {
return sdjwt.jwt.protectedHeader.algorithm?.rawValue
}
}
60 changes: 60 additions & 0 deletions EdgeAgentSDK/Pollux/Sources/Models/SDJWT/SDJWTPresentation.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Core
import Domain
import eudi_lib_sdjwt_swift
import Foundation
Expand Down Expand Up @@ -50,6 +51,13 @@ struct SDJWTPresentation {
}

switch attachment.format {
case "dif/presentation-exchange/definitions@v1.0":
return try presentation(
credential: credential,
request: requestData,
disclosingClaims: disclosingClaims,
key: exportableKey
)
default:
return try vcPresentation(
credential: credential,
Expand All @@ -60,6 +68,58 @@ struct SDJWTPresentation {
}
}

private func presentation(
credential: SDJWTCredential,
request: Data,
disclosingClaims: [String],
key: ExportableKey
) throws -> String {
let presentationRequest = try JSONDecoder.didComm().decode(PresentationExchangeRequest.self, from: request)

guard
let jwtFormat = presentationRequest.presentationDefinition.format?.sdJwt,
try jwtFormat.supportedTypes.contains(where: { try $0 == credential.getAlg() })
else {
throw PolluxError.credentialIsNotOfPresentationDefinitionRequiredAlgorithm
}

let credentialSubject = try credential.sdjwt.recreateClaims().recreatedClaims.rawData()

try presentationRequest.presentationDefinition.inputDescriptors.forEach {
try $0.constraints.fields.forEach {
guard credentialSubject.query(values: $0.path) != nil else {
throw PolluxError.credentialDoesntProvideOneOrMoreInputDescriptors(path: $0.path)
}
}
}
let presentationDefinitions = presentationRequest.presentationDefinition.inputDescriptors.map {
PresentationSubmission.Descriptor(
id: $0.id,
path: "$.verifiable_credential[0]",
format: "sd_jwt"
)
}

let presentationSubmission = PresentationSubmission(
definitionId: presentationRequest.presentationDefinition.id,
descriptorMap: presentationDefinitions
)

let payload = try vcPresentation(
credential: credential,
request: request,
disclosingClaims: disclosingClaims,
key: key
)

let container = PresentationContainer(
presentationSubmission: presentationSubmission,
verifiableCredential: [AnyCodable(stringLiteral: payload)]
)

return try JSONEncoder.didComm().encode(container).tryToString()
}

private func vcPresentation(
credential: SDJWTCredential,
request: Data,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Domain
import Foundation
import JSONSchema
import JSONWebToken

struct JWTPresentationExchangeParser: SubmissionDescriptorFormatParser {
let format = "jwt"
let verifier: VerifyJWT

func parse(path: String, presentationData: Data) async throws -> String {
guard
let jwt = presentationData.query(string: path)
else {
throw PolluxError.credentialPathInvalid(path: path)
}

guard try await verifier.verifyJWT(jwtString: jwt) else {
throw PolluxError.cannotVerifyCredential(credential: jwt, internalErrors: [])
}

return jwt
}

func parsePayload(path: String, presentationData: Data) async throws -> Data {
let jwt = try await parse(path: path, presentationData: presentationData)
return try JWT.getPayload(jwtString: jwt)
}

func parseClaimVerifier(descriptor: PresentationSubmission.Descriptor, presentationData: Data) async throws -> PresentationExchangeClaimVerifier {
let jwt = try await parse(path: descriptor.path, presentationData: presentationData)
return JWTVerifierPresentationExchange(
castor: verifier.castor,
jwtString: jwt,
submissionDescriptor: descriptor
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Domain
import Foundation
import JSONSchema
import JSONWebToken

struct JWTVCPresentationExchangeParser: SubmissionDescriptorFormatParser {
let format = "jwt_vc"
let verifier: VerifyJWT

func parse(path: String, presentationData: Data) async throws -> String {
guard
let jwt = presentationData.query(string: path)
else {
throw PolluxError.credentialPathInvalid(path: path)
}

guard try await verifier.verifyJWT(jwtString: jwt) else {
throw PolluxError.cannotVerifyCredential(credential: jwt, internalErrors: [])
}

return jwt
}

func parsePayload(path: String, presentationData: Data) async throws -> Data {
let jwt = try await parse(path: path, presentationData: presentationData)
return try JWT.getPayload(jwtString: jwt)
}

func parseClaimVerifier(descriptor: PresentationSubmission.Descriptor, presentationData: Data) async throws -> PresentationExchangeClaimVerifier {
let jwt = try await parse(path: descriptor.path, presentationData: presentationData)
return JWTVerifierPresentationExchange(
castor: verifier.castor,
jwtString: jwt,
submissionDescriptor: descriptor
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Domain
import Foundation
import JSONSchema
import JSONWebToken

struct JWTVPPresentationExchangeParser: SubmissionDescriptorFormatParser {
let format = "jwt_vp"
let verifier: VerifyJWT

func parse(path: String, presentationData: Data) async throws -> String {
guard
let jwt = presentationData.query(string: path)
else {
throw PolluxError.credentialPathInvalid(path: path)
}

guard try await verifier.verifyJWT(jwtString: jwt) else {
throw PolluxError.cannotVerifyCredential(credential: jwt, internalErrors: [])
}

return jwt
}

func parsePayload(path: String, presentationData: Data) async throws -> Data {
let jwt = try await parse(path: path, presentationData: presentationData)
return try JWT.getPayload(jwtString: jwt)
}

func parseClaimVerifier(descriptor: PresentationSubmission.Descriptor, presentationData: Data) async throws -> PresentationExchangeClaimVerifier {
let jwt = try await parse(path: descriptor.path, presentationData: presentationData)
return JWTVerifierPresentationExchange(
castor: verifier.castor,
jwtString: jwt,
submissionDescriptor: descriptor
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Domain
import Foundation
import JSONWebToken

struct JWTVerifierPresentationExchange: PresentationExchangeClaimVerifier {
let castor: Castor
let jwtString: String
let submissionDescriptor: PresentationSubmission.Descriptor

func verifyClaim(inputDescriptor: InputDescriptor) throws {
let payload = try JWT.getPayload(jwtString: jwtString)
try VerifyJsonClaim.verify(inputDescriptor: inputDescriptor, jsonData: payload)
}
}
Loading

0 comments on commit ec9fb33

Please sign in to comment.