-
Notifications
You must be signed in to change notification settings - Fork 204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: openid4vp-mdoc #2080
feat: openid4vp-mdoc #2080
Changes from 1 commit
e26d833
9044e81
3ab4c12
912f311
3ff4af7
2bcedab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,16 +35,17 @@ | |
"@peculiar/asn1-schema": "^2.3.8", | ||
"@peculiar/asn1-x509": "^2.3.8", | ||
"@peculiar/x509": "^1.11.0", | ||
"@protokoll/mdoc-client": "0.2.35", | ||
"@protokoll/mdoc-client": "0.2.36", | ||
"@sd-jwt/core": "^0.7.0", | ||
"@sd-jwt/decode": "^0.7.0", | ||
"@sd-jwt/jwt-status-list": "^0.7.0", | ||
"@sd-jwt/sd-jwt-vc": "^0.7.0", | ||
"@sd-jwt/types": "^0.7.0", | ||
"@sd-jwt/utils": "^0.7.0", | ||
"@sphereon/pex": "5.0.0-unstable.18", | ||
"@sphereon/kmp-mdl-mdoc": "0.2.0-SNAPSHOT.22", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need this right? |
||
"@sphereon/pex-models": "^2.3.1", | ||
"@sphereon/ssi-types": "^0.30.1", | ||
"@sphereon/ssi-types": "0.30.2-next.129", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And this one to |
||
"@stablelib/ed25519": "^1.0.2", | ||
"@types/ws": "^8.5.4", | ||
"abort-controller": "^3.0.0", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ import type { IPresentationDefinition, SelectResults, SubmissionRequirementMatch | |
import type { InputDescriptorV1, InputDescriptorV2, SubmissionRequirement } from '@sphereon/pex-models' | ||
|
||
import { decodeSdJwtSync, getClaimsSync } from '@sd-jwt/decode' | ||
import { Status } from '@sphereon/pex' | ||
import { SubmissionRequirementMatchType } from '@sphereon/pex/dist/main/lib/evaluation/core' | ||
import { Rules } from '@sphereon/pex-models' | ||
import { default as jp } from 'jsonpath' | ||
|
@@ -41,6 +42,11 @@ export async function getCredentialsForRequest( | |
|
||
const selectResults: CredentialRecordSelectResults = { | ||
...selectResultsRaw, | ||
areRequiredCredentialsPresent: | ||
nonMdocPresentationDefinition.input_descriptors.length === 0 && | ||
mdocPresentationDefinition.input_descriptors.length > 0 | ||
? Status.INFO | ||
: selectResultsRaw.areRequiredCredentialsPresent, | ||
Comment on lines
+46
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i don't understand this? mdoc also handle mdoc selecting now right? And what if the mdoc is not available, it should'nt be info the nright? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we set it to error below when we also check if all the mdoc credentials are available. |
||
// Map the encoded credential to their respective w3c credential record | ||
verifiableCredential: selectResultsRaw.verifiableCredential?.map((selectedEncoded): SubmissionEntryCredential => { | ||
const credentialRecordIndex = encodedCredentials.findIndex((encoded) => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,9 +8,11 @@ import type { | |
W3CVerifiablePresentation as SphereonW3CVerifiablePresentation, | ||
} from '@sphereon/ssi-types' | ||
|
||
import { CredoError } from '../../../error' | ||
import { JsonTransformer } from '../../../utils' | ||
import { MdocVerifiablePresentation } from '../../mdoc/MdocVerifiablePresentation' | ||
import { com } from '@sphereon/kmp-mdl-mdoc' | ||
|
||
import { Jwt } from '../../../crypto' | ||
import { JsonTransformer, TypedArrayEncoder } from '../../../utils' | ||
import { MdocDeviceResponse } from '../../mdoc' | ||
import { SdJwtVcApi } from '../../sd-jwt-vc' | ||
import { W3cCredentialRecord, W3cJsonLdVerifiablePresentation, W3cJwtVerifiablePresentation } from '../../vc' | ||
|
||
|
@@ -32,8 +34,10 @@ export function getSphereonOriginalVerifiablePresentation( | |
verifiablePresentation instanceof W3cJsonLdVerifiablePresentation | ||
) { | ||
return verifiablePresentation.encoded as SphereonOriginalVerifiablePresentation | ||
} else if (verifiablePresentation instanceof MdocVerifiablePresentation) { | ||
throw new CredoError('Mdoc verifiable presentation is not yet supported by Sphereon.') | ||
} else if (verifiablePresentation instanceof MdocDeviceResponse) { | ||
return com.sphereon.mdoc.data.device.DeviceResponseCbor.Static.cborDecode( | ||
new Int8Array(TypedArrayEncoder.fromBase64(verifiablePresentation.base64Url)) | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. original can just be a string for PEX / ssi-types now. That way we do'nt have to depend on / import the sphereon mdl lib |
||
} else { | ||
return verifiablePresentation.compact | ||
} | ||
|
@@ -47,12 +51,11 @@ export function getVerifiablePresentationFromEncoded( | |
if (typeof encodedVerifiablePresentation === 'string' && encodedVerifiablePresentation.includes('~')) { | ||
const sdJwtVcApi = agentContext.dependencyManager.resolve(SdJwtVcApi) | ||
return sdJwtVcApi.fromCompact(encodedVerifiablePresentation) | ||
} else if (typeof encodedVerifiablePresentation === 'string') { | ||
} else if (typeof encodedVerifiablePresentation === 'string' && Jwt.format.test(encodedVerifiablePresentation)) { | ||
return W3cJwtVerifiablePresentation.fromSerializedJwt(encodedVerifiablePresentation) | ||
} else if (typeof encodedVerifiablePresentation === 'object' && '@context' in encodedVerifiablePresentation) { | ||
return JsonTransformer.fromJSON(encodedVerifiablePresentation, W3cJsonLdVerifiablePresentation) | ||
} else { | ||
// TODO: WE NEED TO ADD SUPPORT FOR MDOC VERIFIABLE PRESENTATION | ||
throw new CredoError('Unsupported verifiable presentation format') | ||
return MdocDeviceResponse.fromBase64Url(encodedVerifiablePresentation) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -13,6 +13,7 @@ import { | |||||||||||||||||||||||||||||||
Verifier, | ||||||||||||||||||||||||||||||||
MDocStatus, | ||||||||||||||||||||||||||||||||
cborEncode, | ||||||||||||||||||||||||||||||||
parseDeviceResponse, | ||||||||||||||||||||||||||||||||
} from '@protokoll/mdoc-client' | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
import { CredoError } from '../../error' | ||||||||||||||||||||||||||||||||
|
@@ -26,7 +27,41 @@ import { getMdocContext } from './MdocContext' | |||||||||||||||||||||||||||||||
import { MdocError } from './MdocError' | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
export class MdocDeviceResponse { | ||||||||||||||||||||||||||||||||
public constructor() {} | ||||||||||||||||||||||||||||||||
private constructor(public base64Url: string, public documents: Mdoc[]) {} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
public static isBase64DeviceResponse(base64Url: string) { | ||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||
const parsed = parseDeviceResponse(TypedArrayEncoder.fromBase64(base64Url)) | ||||||||||||||||||||||||||||||||
if (parsed.status === MDocStatus.OK) return true | ||||||||||||||||||||||||||||||||
return false | ||||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||||
// no-op | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
return false | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also seems quite expensive. That we try to parse. But maybe the best way. Are we doing in places:
Because in that case a |
||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
public static fromBase64Url(base64Url: string) { | ||||||||||||||||||||||||||||||||
const parsed = parseDeviceResponse(TypedArrayEncoder.fromBase64(base64Url)) | ||||||||||||||||||||||||||||||||
if (parsed.status !== MDocStatus.OK) { | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no extra error provided in parsed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, not really atm |
||||||||||||||||||||||||||||||||
throw new MdocError(`Parsing Mdoc Device Response failed.`) | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
const documents = parsed.documents.map((doc) => { | ||||||||||||||||||||||||||||||||
const prepared = doc.prepare() | ||||||||||||||||||||||||||||||||
const docType = prepared.get('docType') as string | ||||||||||||||||||||||||||||||||
const issuerSigned = cborEncode(prepared.get('issuerSigned')) | ||||||||||||||||||||||||||||||||
const deviceSigned = cborEncode(prepared.get('deviceSigned')) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
return Mdoc.fromIssuerSignedDocument( | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fromIssuerSigned and then pasing deviceSigned is a bit weird maybe? |
||||||||||||||||||||||||||||||||
TypedArrayEncoder.toBase64URL(issuerSigned), | ||||||||||||||||||||||||||||||||
TypedArrayEncoder.toBase64URL(deviceSigned), | ||||||||||||||||||||||||||||||||
docType | ||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
return new MdocDeviceResponse(base64Url, documents) | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private static assertMdocInputDescriptor(inputDescriptor: InputDescriptorV2) { | ||||||||||||||||||||||||||||||||
if (!inputDescriptor.format || !inputDescriptor.format.mso_mdoc) { | ||||||||||||||||||||||||||||||||
|
@@ -113,7 +148,18 @@ export class MdocDeviceResponse { | |||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
const inputDescriptor = this.assertMdocInputDescriptor(options.inputDescriptor) | ||||||||||||||||||||||||||||||||
const _mdoc = parseIssuerSigned(TypedArrayEncoder.fromBase64(mdoc.base64Url), mdoc.docType) | ||||||||||||||||||||||||||||||||
return mdocLimitDisclosureToId({ mdoc: _mdoc, inputDescriptor }) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
const disclosure = mdocLimitDisclosureToId(_mdoc, inputDescriptor) | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Id is maybe a bit offputting as |
||||||||||||||||||||||||||||||||
const disclosedPayloadAsRecord = Object.fromEntries( | ||||||||||||||||||||||||||||||||
Object.entries(disclosure).map(([namespace, issuerSignedItem]) => { | ||||||||||||||||||||||||||||||||
return [ | ||||||||||||||||||||||||||||||||
namespace, | ||||||||||||||||||||||||||||||||
Object.fromEntries(issuerSignedItem.map((item) => [item.elementIdentifier, item.elementValue])), | ||||||||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
return disclosedPayloadAsRecord | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
public static async createOpenId4VpDeviceResponse( | ||||||||||||||||||||||||||||||||
|
@@ -160,32 +206,32 @@ export class MdocDeviceResponse { | |||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
public static async verify(agentContext: AgentContext, options: MdocDeviceResponseVerifyOptions) { | ||||||||||||||||||||||||||||||||
public async verify(agentContext: AgentContext, options: Omit<MdocDeviceResponseVerifyOptions, 'deviceResponse'>) { | ||||||||||||||||||||||||||||||||
const verifier = new Verifier() | ||||||||||||||||||||||||||||||||
const mdocContext = getMdocContext(agentContext) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
let trustedCerts: [string, ...string[]] | undefined | ||||||||||||||||||||||||||||||||
if (options?.trustedCertificates) { | ||||||||||||||||||||||||||||||||
trustedCerts = options.trustedCertificates | ||||||||||||||||||||||||||||||||
} else if (options?.verificationContext) { | ||||||||||||||||||||||||||||||||
agentContext.dependencyManager.resolve(X509ModuleConfig).getTrustedCertificatesForVerification | ||||||||||||||||||||||||||||||||
trustedCerts = await agentContext.dependencyManager | ||||||||||||||||||||||||||||||||
.resolve(X509ModuleConfig) | ||||||||||||||||||||||||||||||||
.getTrustedCertificatesForVerification?.(agentContext, options.verificationContext) | ||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||
trustedCerts = agentContext.dependencyManager.resolve(X509ModuleConfig).trustedCertificates | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
const x509ModuleConfig = agentContext.dependencyManager.isRegistered(X509ModuleConfig) | ||||||||||||||||||||||||||||||||
? agentContext.dependencyManager.resolve(X509ModuleConfig) | ||||||||||||||||||||||||||||||||
: undefined | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should always be configured, are you getting issues when you just reslve it directly? |
||||||||||||||||||||||||||||||||
const getTrustedCertificatesForVerification = x509ModuleConfig?.getTrustedCertificatesForVerification | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
if (!trustedCerts) { | ||||||||||||||||||||||||||||||||
const trustedCertificates = | ||||||||||||||||||||||||||||||||
options.trustedCertificates ?? | ||||||||||||||||||||||||||||||||
(await getTrustedCertificatesForVerification?.(agentContext, options.verificationContext)) ?? | ||||||||||||||||||||||||||||||||
x509ModuleConfig?.trustedCertificates | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
if (!trustedCertificates) { | ||||||||||||||||||||||||||||||||
throw new MdocError('No trusted certificates found. Cannot verify mdoc.') | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
const result = await verifier.verifyDeviceResponse( | ||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||
encodedDeviceResponse: TypedArrayEncoder.fromBase64(options.deviceResponse), | ||||||||||||||||||||||||||||||||
encodedDeviceResponse: TypedArrayEncoder.fromBase64(this.base64Url), | ||||||||||||||||||||||||||||||||
//ephemeralReaderKey: options.verifierKey ? getJwkFromKey(options.verifierKey).toJson() : undefined, | ||||||||||||||||||||||||||||||||
encodedSessionTranscript: DeviceResponse.calculateSessionTranscriptForOID4VP(options.sessionTranscriptOptions), | ||||||||||||||||||||||||||||||||
trustedCertificates: trustedCerts.map((cert) => X509Certificate.fromEncodedCertificate(cert).rawCertificate), | ||||||||||||||||||||||||||||||||
trustedCertificates: trustedCertificates.map( | ||||||||||||||||||||||||||||||||
(cert) => X509Certificate.fromEncodedCertificate(cert).rawCertificate | ||||||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||||||
now: options.now, | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
mdocContext | ||||||||||||||||||||||||||||||||
|
@@ -199,17 +245,6 @@ export class MdocDeviceResponse { | |||||||||||||||||||||||||||||||
throw new MdocError('Device response verification failed. An unknown error occurred.') | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
return result.documents.map((doc) => { | ||||||||||||||||||||||||||||||||
const prepared = doc.prepare() | ||||||||||||||||||||||||||||||||
const docType = prepared.get('docType') as string | ||||||||||||||||||||||||||||||||
const issuerSigned = cborEncode(prepared.get('issuerSigned')) | ||||||||||||||||||||||||||||||||
const deviceSigned = cborEncode(prepared.get('deviceSigned')) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
return Mdoc.fromIssuerSignedDocument( | ||||||||||||||||||||||||||||||||
TypedArrayEncoder.toBase64URL(issuerSigned), | ||||||||||||||||||||||||||||||||
TypedArrayEncoder.toBase64URL(deviceSigned), | ||||||||||||||||||||||||||||||||
docType | ||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||
return this.documents | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,12 +27,13 @@ | |
}, | ||
"dependencies": { | ||
"@credo-ts/core": "workspace:*", | ||
"@sphereon/did-auth-siop": "0.16.1-next.168", | ||
"@sphereon/oid4vc-common": "0.16.1-next.168", | ||
"@sphereon/oid4vci-client": "0.16.1-next.168", | ||
"@sphereon/oid4vci-common": "0.16.1-next.168", | ||
"@sphereon/oid4vci-issuer": "0.16.1-next.168", | ||
"@sphereon/ssi-types": "^0.30.1", | ||
"@sphereon/did-auth-siop": "0.16.1-fix.173", | ||
"@sphereon/kmp-mdl-mdoc": "0.2.0-SNAPSHOT.22", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. again, would be good to not depend on this. |
||
"@sphereon/oid4vc-common": "0.16.1-fix.173", | ||
"@sphereon/oid4vci-client": "0.16.1-fix.173", | ||
"@sphereon/oid4vci-common": "0.16.1-fix.173", | ||
"@sphereon/oid4vci-issuer": "0.16.1-fix.173", | ||
"@sphereon/ssi-types": "0.30.2-next.129", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also update this one |
||
"class-transformer": "^0.5.1", | ||
"rxjs": "^7.8.0" | ||
}, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you update this to unstable
5.0.0-unstable.25