@sphereon/ssi-types
Version:
SSI Common Types
146 lines (131 loc) • 5.17 kB
text/typescript
import type {
DocumentJson,
IssuerSignedItemJson,
IVerifiableCredential,
MdocDecodedPayload,
MdocDeviceResponse,
MdocDocument,
MdocIssuerSigned,
MdocOid4vpIssuerSigned,
MdocOid4vpMdocVpToken,
WrappedMdocCredential,
WrappedMdocPresentation,
WrappedVerifiableCredential,
WrappedVerifiablePresentation,
} from '../types'
import mdocPkg from '@sphereon/kmp-mdoc-core'
const { com } = mdocPkg
import { IProofPurpose, IProofType } from './did'
export function isWrappedMdocCredential(vc: WrappedVerifiableCredential): vc is WrappedMdocCredential {
return vc.format === 'mso_mdoc'
}
export function isWrappedMdocPresentation(vp: WrappedVerifiablePresentation): vp is WrappedMdocPresentation {
return vp.format === 'mso_mdoc'
}
export function getMdocDecodedPayload(mdoc: MdocDocument): MdocDecodedPayload {
const mdocJson = mdoc.toJson()
if (!mdocJson.issuerSigned.nameSpaces) {
throw Error(`Cannot access Issuer Signed items from the Mdoc`)
}
const issuerSignedJson = mdoc.issuerSigned.toJsonDTO()
const namespaces = issuerSignedJson.nameSpaces as unknown as Record<string, IssuerSignedItemJson[]>
const decodedPayload: MdocDecodedPayload = {}
for (const [namespace, items] of Object.entries(namespaces)) {
decodedPayload[namespace] = items.reduce(
(acc, item) => ({
...acc,
[item.key]: item.value.value,
}),
{},
)
}
return decodedPayload
}
/**
* Decode an Mdoc from its issuerSigned OID4VP Base64URL (string) to an object containing the disclosures,
* signed payload, decoded payload
*
*/
export function decodeMdocIssuerSigned(oid4vpIssuerSigned: MdocOid4vpIssuerSigned): MdocDocument {
// Issuer signed according to 18013-7 in base64url
const issuerSigned: MdocIssuerSigned = com.sphereon.mdoc.data.device.IssuerSignedCbor.Static.cborDecode(
com.sphereon.kmp.decodeFrom(oid4vpIssuerSigned, com.sphereon.kmp.Encoding.BASE64URL),
)
// Create an mdoc from it. // Validations need to be performed by the caller after this!
const holderMdoc: MdocDocument = issuerSigned.toDocument()
return holderMdoc
}
export function encodeMdocIssuerSigned(issuerSigned: MdocIssuerSigned, encoding: 'base64url' = 'base64url') {
return com.sphereon.kmp.encodeTo(issuerSigned.cborEncode(), com.sphereon.kmp.Encoding.BASE64URL)
}
/**
* Decode an Mdoc from its vp_token OID4VP Base64URL (string) to an object containing the disclosures,
* signed payload, decoded payload
*
*/
export function decodeMdocDeviceResponse(vpToken: MdocOid4vpMdocVpToken): MdocDeviceResponse {
const deviceResponse = com.sphereon.mdoc.data.device.DeviceResponseCbor.Static.cborDecode(
com.sphereon.kmp.decodeFrom(vpToken, com.sphereon.kmp.Encoding.BASE64URL),
)
return deviceResponse
}
// TODO naive implementation of mapping a mdoc onto a IVerifiableCredential. Needs some fixes and further implementation and needs to be moved out of ssi-types
export const mdocDecodedCredentialToUniformCredential = (
decoded: MdocDocument,
// @ts-ignore
opts?: { maxTimeSkewInMS?: number },
): IVerifiableCredential => {
const document = decoded.toJson()
const json = document.toJsonDTO<DocumentJson>()
const type = 'Personal Identification Data'
const MSO = document.MSO
if (!MSO || !json.issuerSigned?.nameSpaces) {
throw Error(`Cannot access Mobile Security Object or Issuer Signed items from the Mdoc`)
}
const nameSpaces = json.issuerSigned.nameSpaces as unknown as Record<string, IssuerSignedItemJson[]>
if (!('eu.europa.ec.eudi.pid.1' in nameSpaces)) {
throw Error(`Only PID supported at present`)
}
const items = nameSpaces['eu.europa.ec.eudi.pid.1']
if (!items || items.length === 0) {
throw Error(`No issuer signed items were found`)
}
type DisclosuresAccumulator = {
[key: string]: any
}
const credentialSubject = items.reduce((acc: DisclosuresAccumulator, item: IssuerSignedItemJson) => {
if (Array.isArray(item.value)) {
acc[item.key] = item.value.map((val) => val.value).join(', ')
} else {
acc[item.key] = item.value.value
}
return acc
}, {})
const validFrom = MSO.validityInfo.validFrom
const validUntil = MSO.validityInfo.validUntil
const docType = MSO.docType
const expirationDate = validUntil
let issuanceDateStr = validFrom
const issuanceDate = issuanceDateStr
if (!issuanceDate) {
throw Error(`JWT issuance date is required but was not present`)
}
const credential: Omit<IVerifiableCredential, 'issuer' | 'issuanceDate'> = {
type: [docType], // Mdoc not a W3C VC, so no VerifiableCredential
'@context': [], // Mdoc has no JSON-LD by default. Certainly not the VC DM1 default context for JSON-LD
credentialSubject: {
type,
...credentialSubject,
},
issuanceDate,
expirationDate,
proof: {
type: IProofType.MdocProof2024,
created: issuanceDate,
proofPurpose: IProofPurpose.authentication,
verificationMethod: json.issuerSigned.issuerAuth.payload,
mso_mdoc: com.sphereon.kmp.encodeTo(decoded.cborEncode(), com.sphereon.kmp.Encoding.BASE64URL),
},
}
return credential as IVerifiableCredential
}