From 7e67975b94ced7cff35f87d99d8f0e0845130f73 Mon Sep 17 00:00:00 2001 From: Niels Klomp Date: Tue, 26 Nov 2024 00:02:43 +0100 Subject: [PATCH] chore: mdoc signing --- packages/mdl-mdoc/src/functions/index.ts | 84 +++++++++++++++++------- 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/packages/mdl-mdoc/src/functions/index.ts b/packages/mdl-mdoc/src/functions/index.ts index 64aee031..b14e526b 100644 --- a/packages/mdl-mdoc/src/functions/index.ts +++ b/packages/mdl-mdoc/src/functions/index.ts @@ -5,11 +5,11 @@ import { getSubjectDN, pemOrDerToX509Certificate, validateX509CertificateChain, - X509ValidationResult, + X509ValidationResult } from '@sphereon/ssi-sdk-ext.x509-utils' import * as crypto from 'crypto' import { Certificate, CryptoEngine, setEngine } from 'pkijs' -import { VerifyCertificateChainArgs } from '../types/ImDLMdoc' +import { IRequiredContext, VerifyCertificateChainArgs } from '../types/ImDLMdoc' import CoseKeyCbor = com.sphereon.crypto.cose.CoseKeyCbor import CoseSign1Cbor = com.sphereon.crypto.cose.CoseSign1Cbor import ICoseKeyCbor = com.sphereon.crypto.cose.ICoseKeyCbor @@ -25,24 +25,60 @@ import IX509ServiceJS = com.sphereon.crypto.IX509ServiceJS import IX509VerificationResult = com.sphereon.crypto.IX509VerificationResult import Jwk = com.sphereon.crypto.jose.Jwk import KeyInfo = com.sphereon.crypto.KeyInfo +import ResolvedKeyInfo = com.sphereon.crypto.ResolvedKeyInfo import X509VerificationProfile = com.sphereon.crypto.X509VerificationProfile +import DateTimeUtils = com.sphereon.kmp.DateTimeUtils import decodeFrom = com.sphereon.kmp.decodeFrom +import encodeTo = com.sphereon.kmp.encodeTo import Encoding = com.sphereon.kmp.Encoding import LocalDateTimeKMP = com.sphereon.kmp.LocalDateTimeKMP -import DateTimeUtils = com.sphereon.kmp.DateTimeUtils export class CoseCryptoService implements ICoseCryptoCallbackJS { - async signAsync(input: ToBeSignedCbor, requireX5Chain: Nullable): Promise { - throw new Error('Method not implemented.') + + constructor(private context?: IRequiredContext) { + } + + setContext(context: IRequiredContext) { + this.context = context + } + + async signAsync(input: ToBeSignedCbor, requireX5Chain: Nullable): Promise { + if (!this.context) { + throw Error('No context provided. Please provide a context with the setContext method or constructor') + } + const { keyInfo, alg, value } = input + let kmsKeyRef = keyInfo.kmsKeyRef ?? undefined + if (!kmsKeyRef) { + const key = keyInfo.key.copy(true) + if (key == null) { + return Promise.reject(Error('No key present in keyInfo. This implementation cannot sign without a key!')) + } + key.getJwkThumbprint() + const resolvedKeyInfo = ResolvedKeyInfo.Static.fromKeyInfo(keyInfo, key) + const jwkKeyInfo: ResolvedKeyInfo = CoseJoseKeyMappingService.toResolvedJwkKeyInfo(resolvedKeyInfo) + const kid = jwkKeyInfo.kid ?? undefined + if (!kid) { + return Promise.reject(Error('No kid present')) + } + kmsKeyRef = kid + } + const result = await this.context.agent.keyManagerSign({ + algorithm: alg.jose!!.value, + data: encodeTo(value, Encoding.UTF8), + encoding: 'utf-8', + keyRef: kmsKeyRef!! + }) + return decodeFrom(result, Encoding.UTF8) + } async verify1Async( input: CoseSign1Cbor, keyInfo: IKeyInfo, - requireX5Chain: Nullable, + requireX5Chain: Nullable ): Promise> { const getCertAndKey = async ( - x5c: Nullable>, + x5c: Nullable> ): Promise<{ issuerCert?: Certificate issuerPublicKey: CryptoKey @@ -97,14 +133,14 @@ export class CoseCryptoService implements ICoseCryptoCallbackJS { crv, ...(jwk.x5c && { x5c: jwk.x5c }), ...(jwk.x && { x: jwk.x }), - ...(jwk.y && { y: jwk.y }), + ...(jwk.y && { y: jwk.y }) } satisfies JsonWebKey, { name: keyAlg.value === 'EC' ? 'ECDSA' : keyAlg.value, - namedCurve: crv, + namedCurve: crv }, true, - ['verify'], + ['verify'] ) } @@ -116,11 +152,11 @@ export class CoseCryptoService implements ICoseCryptoCallbackJS { const valid = await crypto.subtle.verify( { ...issuerPublicKey.algorithm, - hash: crv?.includes('-') ? `SHA-${crv.split('-')[1]}` : 'SHA-256', // todo: this needs to be more robust + hash: crv?.includes('-') ? `SHA-${crv.split('-')[1]}` : 'SHA-256' // todo: this needs to be more robust }, issuerPublicKey, decodeFrom(sign1Json.signature, Encoding.BASE64URL), - decodeFrom(recalculatedToBeSigned.base64UrlValue, Encoding.BASE64URL), + decodeFrom(recalculatedToBeSigned.base64UrlValue, Encoding.BASE64URL) ) return { @@ -128,12 +164,12 @@ export class CoseCryptoService implements ICoseCryptoCallbackJS { critical: true, error: !valid, message: `Signature of '${issuerCert ? getSubjectDN(issuerCert).DN : kid}' was ${valid ? '' : 'in'}valid`, - keyInfo: coseKeyInfo, + keyInfo: coseKeyInfo } satisfies IVerifySignatureResult } resolvePublicKeyAsync( - keyInfo: com.sphereon.crypto.IKeyInfo, + keyInfo: com.sphereon.crypto.IKeyInfo ): Promise> { if (keyInfo.key) { return Promise.resolve(CoseJoseKeyMappingService.toResolvedKeyInfo(keyInfo, keyInfo.key)) @@ -163,16 +199,16 @@ export class X509CallbackService implements IX509ServiceJS { * @param verificationTime */ async verifyCertificateChain({ - chain, - trustAnchors = this.getTrustedCerts(), - verificationTime, - opts, - }: VerifyCertificateChainArgs): Promise { + chain, + trustAnchors = this.getTrustedCerts(), + verificationTime, + opts + }: VerifyCertificateChainArgs): Promise { return await validateX509CertificateChain({ chain, trustAnchors, verificationTime, - opts, + opts }) } @@ -184,7 +220,7 @@ export class X509CallbackService implements IX509ServiceJS { chainPEM: Nullable, trustedCerts: Nullable, verificationProfile?: X509VerificationProfile | undefined, - verificationTime?: Nullable, + verificationTime?: Nullable ): Promise> { const verificationAt = verificationTime ?? DateTimeUtils.Static.DEFAULT.dateTimeLocal() let chain: Array = [] @@ -198,7 +234,7 @@ export class X509CallbackService implements IX509ServiceJS { chain: chain, // The function will handle an empty array trustAnchors: trustedCerts ?? this.getTrustedCerts(), verificationTime: new Date(verificationAt.toEpochSeconds().toULong() * 1000), - opts: { trustRootWhenNoAnchors: true }, + opts: { trustRootWhenNoAnchors: true } }) const cert: CertificateInfo | undefined = result.certificateChain ? result.certificateChain[result.certificateChain.length - 1] : undefined @@ -210,7 +246,7 @@ export class X509CallbackService implements IX509ServiceJS { critical: result.critical, message: result.message, error: result.error, - verificationTime: verificationAt, + verificationTime: verificationAt } satisfies IX509VerificationResult } @@ -253,6 +289,6 @@ const defaultCryptoEngine = () => { defaultCryptoEngine() -// We register the services with the mDL/mdoc library +// We register the services with the mDL/mdoc library. Please note that the context is not passed in, meaning we cannot sign by default. DefaultCallbacks.setCoseCryptoDefault(new CoseCryptoService()) DefaultCallbacks.setX509Default(new X509CallbackService())