Skip to content

Commit

Permalink
Merge pull request #143 from niscy-eudiw/main
Browse files Browse the repository at this point in the history
Enhance OpenId4VpService with inputDescriptorMap for presentation definition parsing
  • Loading branch information
phisakel authored Jan 16, 2025
2 parents 6046ffd + 74532be commit 293f42f
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 20 deletions.
26 changes: 16 additions & 10 deletions Sources/EudiWalletKit/Services/OpenId4VpService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ public final class OpenId4VpService: @unchecked Sendable, PresentationService {
var iaca: [SecCertificate]!
// map of docType to data format (formats requested)
var formatsRequested: [String: DocDataFormat]!
/// map of inputDescriptor-id to docType
var inputDescriptorMap: [String: String]!
var dauthMethod: DeviceAuthMethod
var devicePrivateKeys: [String: CoseKeyPrivate]!
var logger = Logger(label: "OpenId4VpService")
Expand Down Expand Up @@ -122,8 +124,8 @@ public final class OpenId4VpService: @unchecked Sendable, PresentationService {
responseUri: responseUri, nonce: vp.nonce, mdocGeneratedNonce: mdocGeneratedNonce)
logger.info("Session Transcript: \(sessionTranscript.encode().toHexString()), for clientId: \(vp.client.id), responseUri: \(responseUri), nonce: \(vp.nonce), mdocGeneratedNonce: \(mdocGeneratedNonce!)")
self.presentationDefinition = vp.presentationDefinition
let (items, fmtsReq) = try Openid4VpUtils.parsePresentationDefinition(vp.presentationDefinition, idsToDocTypes: idsToDocTypes, dataFormats: dataFormats, docDisplayNames: docDisplayNames, logger: logger)
self.formatsRequested = fmtsReq
let (items, fmtsReq, imap) = try Openid4VpUtils.parsePresentationDefinition(vp.presentationDefinition, idsToDocTypes: idsToDocTypes, dataFormats: dataFormats, docDisplayNames: docDisplayNames, logger: logger)
self.formatsRequested = fmtsReq; self.inputDescriptorMap = imap
guard let items else { throw PresentationSession.makeError(str: "Invalid presentation definition") }
var result = UserRequestInfo(docDataFormats: fmtsReq, validItemsRequested: items)
logger.info("Verifer requested items: \(items.mapValues { $0.mapValues { ar in ar.map(\.elementIdentifier) } })")
Expand Down Expand Up @@ -165,25 +167,26 @@ public final class OpenId4VpService: @unchecked Sendable, PresentationService {
}
logger.info("Openid4vp request items: \(itemsToSend.mapValues { $0.mapValues { ar in ar.map(\.elementIdentifier) } })")
if unlockData == nil { _ = try await startQrEngagement(secureAreaName: nil, crv: .P256) }
if formatsRequested.allSatisfy({ (key: String, value: DocDataFormat) in value == .cbor }) {
if formatsRequested.count == 1, formatsRequested.allSatisfy({ (key: String, value: DocDataFormat) in value == .cbor }) {
makeCborDocs()
let vpToken = try await generateCborVpToken(itemsToSend: itemsToSend)
try await SendVpToken([vpToken], pd, resolved, onSuccess)
try await SendVpToken([(pd.inputDescriptors.first!.id, vpToken)], pd, resolved, onSuccess)
} else {
if formatsRequested.first(where: { (key: String, value: DocDataFormat) in value == .cbor }) != nil {
makeCborDocs()
}
let parser = CompactParser()
let docStrings = docs.filter { k,v in Self.filterFormat(dataFormats[k]!, fmt: .sdjwt)}.compactMapValues { String(data: $0, encoding: .utf8) }
docsSdJwt = docStrings.compactMapValues { try? parser.getSignedSdJwt(serialisedString: $0) }
var presentations = [VpToken.VerifiablePresentation]()
var inputToPresentations = [(String, VpToken.VerifiablePresentation)]()
// support sd-jwt documents
for (docId, nsItems) in itemsToSend {
guard let docType = idsToDocTypes[docId], let inputDescrId = inputDescriptorMap[docType] else { continue }
if dataFormats[docId] == .cbor {
if docsCbor == nil { makeCborDocs() }
let itemsToSend1 = Dictionary(uniqueKeysWithValues: [(docId, nsItems)])
let vpToken = try await generateCborVpToken(itemsToSend: itemsToSend1)
presentations.append(vpToken)
inputToPresentations.append((inputDescrId, vpToken))
} else if dataFormats[docId] == .sdjwt {
let docSigned = docsSdJwt[docId]; let dpk = devicePrivateKeys[docId]
guard let docSigned, let dpk, let items = nsItems.first?.value.map(\.elementIdentifier) else { continue }
Expand All @@ -195,18 +198,21 @@ public final class OpenId4VpService: @unchecked Sendable, PresentationService {
guard let presented = try await Openid4VpUtils.getSdJwtPresentation(docSigned, hashingAlg: hai.hashingAlgorithm(), signer: signer, signAlg: signAlg, requestItems: items, nonce: vpNonce, aud: vpClientId) else {
continue
}
presentations.append(VpToken.VerifiablePresentation.generic(presented.serialisation))
inputToPresentations.append((inputDescrId, VpToken.VerifiablePresentation.generic(presented.serialisation)))
}
}
try await SendVpToken(presentations, pd, resolved, onSuccess)
try await SendVpToken(inputToPresentations, pd, resolved, onSuccess)
}
}
/// Filter document accordind to the raw format value
static func filterFormat(_ df: DocDataFormat, fmt: DocDataFormat) -> Bool { df == fmt }

fileprivate func SendVpToken(_ vpTokens: [VpToken.VerifiablePresentation]?, _ pd: PresentationDefinition, _ resolved: ResolvedRequestData, _ onSuccess: ((URL?) -> Void)?) async throws {
fileprivate func SendVpToken(_ vpTokens: [(String, VpToken.VerifiablePresentation)]?, _ pd: PresentationDefinition, _ resolved: ResolvedRequestData, _ onSuccess: ((URL?) -> Void)?) async throws {
let consent: ClientConsent = if let vpTokens {
.vpToken(vpToken: .init(apu: mdocGeneratedNonce.base64urlEncode, verifiablePresentations: vpTokens), presentationSubmission: .init(id: UUID().uuidString, definitionID: pd.id, descriptorMap: pd.inputDescriptors.enumerated().map { i,d in DescriptorMap(id: d.id, format: d.formatContainer?.formats.first?["designation"].string ?? "", path: "$[\(i)]")}))
.vpToken(vpToken: .init(apu: mdocGeneratedNonce.base64urlEncode, verifiablePresentations: vpTokens.map(\.1)), presentationSubmission: .init(id: UUID().uuidString, definitionID: pd.id, descriptorMap: vpTokens.enumerated().map { i,v in
let descr = pd.inputDescriptors.first(where: { $0.id == v.0 })!
return DescriptorMap(id: descr.id, format: descr.formatContainer?.formats.first?["designation"].string ?? "", path: vpTokens.count == 1 ? "$" : "$[\(i)]")
}))
} else { .negative(message: "Rejected") }
// Generate a direct post authorisation response
let response = try AuthorizationResponse(resolvedRequest: resolved, consent: consent, walletOpenId4VPConfig: getWalletConf(verifierApiUrl: openId4VpVerifierApiUri, verifierLegalName: openId4VpVerifierLegalName))
Expand Down
17 changes: 7 additions & 10 deletions Sources/EudiWalletKit/Services/Openid4VpUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,10 @@ class Openid4VpUtils {
}

/// Parse mDoc request from presentation definition (Presentation Exchange 2.0.0 protocol)
static func parsePresentationDefinition(_ presentationDefinition: PresentationDefinition, idsToDocTypes: [String: String], dataFormats: [String: DocDataFormat], docDisplayNames: [String: [String: [String: String]]?], logger: Logger? = nil) throws -> (RequestItems?, [String: DocDataFormat]) {
var res = RequestItems()
var formats = [String: DocDataFormat]()
static func parsePresentationDefinition(_ presentationDefinition: PresentationDefinition, idsToDocTypes: [String: String], dataFormats: [String: DocDataFormat], docDisplayNames: [String: [String: [String: String]]?], logger: Logger? = nil) throws -> (RequestItems?, [String: DocDataFormat], [String: String]) {
var inputDescriptorMap = [String: String]()
var requestItems = RequestItems()
var formatsRequested = [String: DocDataFormat]()
for inputDescriptor in presentationDefinition.inputDescriptors {
let formatRequested: DocDataFormat = inputDescriptor.formatContainer?.formats.contains(where: { $0["designation"].string?.lowercased() == "mso_mdoc" }) ?? false ? .cbor : .sdjwt
let filterValue = inputDescriptor.constraints.fields.first { $0.filter?["const"].string != nil }?.filter?["const"].string
Expand All @@ -109,10 +110,9 @@ class Openid4VpUtils {
if nsItems[pair.0] == nil { nsItems[pair.0] = [] }
if !nsItems[pair.0]!.contains(pair.1) { nsItems[pair.0]!.append(pair.1) }
}
formats[docType] = formatRequested
if !nsItems.isEmpty { res[docType] = nsItems }
if !nsItems.isEmpty { inputDescriptorMap[docType] = inputDescriptor.id; requestItems[docType] = nsItems; formatsRequested[docType] = formatRequested }
}
return (res, formats)
return (requestItems, formatsRequested, inputDescriptorMap)
}

/// parse field and return (namespace, RequestItem) pair
Expand Down Expand Up @@ -152,10 +152,7 @@ class Openid4VpUtils {
let presentedSdJwt = try await sdJwt.present(query: query)
guard let presentedSdJwt else { return nil }
let digestCreator = DigestCreator(hashingAlgorithm: hashingAlg)
let sdHash = digestCreator.hashAndBase64Encode(
input: CompactSerialiser(signedSDJWT: presentedSdJwt).serialised
)!

guard let sdHash = digestCreator.hashAndBase64Encode(input: CompactSerialiser(signedSDJWT: presentedSdJwt).serialised) else { return nil }
let kbJwt: KBJWT = try KBJWT(header: DefaultJWSHeaderImpl(algorithm: signAlg),
kbJwtPayload: .init([Keys.nonce.rawValue: nonce, Keys.aud.rawValue: aud, Keys.iat.rawValue: Int(Date().timeIntervalSince1970.rounded()), Keys.sdHash.rawValue: sdHash]))
let holderPresentation = try await SDJWTIssuer.presentation(
Expand Down

0 comments on commit 293f42f

Please sign in to comment.