Skip to content

Commit

Permalink
chore: mdoc signing
Browse files Browse the repository at this point in the history
  • Loading branch information
nklomp committed Nov 25, 2024
1 parent 2124048 commit 7e67975
Showing 1 changed file with 60 additions and 24 deletions.
84 changes: 60 additions & 24 deletions packages/mdl-mdoc/src/functions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<CborType>(input: ToBeSignedCbor, requireX5Chain: Nullable<boolean>): Promise<Int8Array> {
throw new Error('Method not implemented.')

constructor(private context?: IRequiredContext) {
}

setContext(context: IRequiredContext) {
this.context = context
}

async signAsync(input: ToBeSignedCbor, requireX5Chain: Nullable<boolean>): Promise<Int8Array> {
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<Jwk> = 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<CborType>(
input: CoseSign1Cbor<CborType>,
keyInfo: IKeyInfo<ICoseKeyCbor>,
requireX5Chain: Nullable<boolean>,
requireX5Chain: Nullable<boolean>
): Promise<IVerifySignatureResult<ICoseKeyCbor>> {
const getCertAndKey = async (
x5c: Nullable<Array<string>>,
x5c: Nullable<Array<string>>
): Promise<{
issuerCert?: Certificate
issuerPublicKey: CryptoKey
Expand Down Expand Up @@ -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']
)
}

Expand All @@ -116,24 +152,24 @@ 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 {
name: 'mdoc',
critical: true,
error: !valid,
message: `Signature of '${issuerCert ? getSubjectDN(issuerCert).DN : kid}' was ${valid ? '' : 'in'}valid`,
keyInfo: coseKeyInfo,
keyInfo: coseKeyInfo
} satisfies IVerifySignatureResult<ICoseKeyCbor>
}

resolvePublicKeyAsync<KT extends com.sphereon.crypto.IKey>(
keyInfo: com.sphereon.crypto.IKeyInfo<KT>,
keyInfo: com.sphereon.crypto.IKeyInfo<KT>
): Promise<com.sphereon.crypto.IResolvedKeyInfo<KT>> {
if (keyInfo.key) {
return Promise.resolve(CoseJoseKeyMappingService.toResolvedKeyInfo(keyInfo, keyInfo.key))
Expand Down Expand Up @@ -163,16 +199,16 @@ export class X509CallbackService implements IX509ServiceJS {
* @param verificationTime
*/
async verifyCertificateChain({
chain,
trustAnchors = this.getTrustedCerts(),
verificationTime,
opts,
}: VerifyCertificateChainArgs): Promise<X509ValidationResult> {
chain,
trustAnchors = this.getTrustedCerts(),
verificationTime,
opts
}: VerifyCertificateChainArgs): Promise<X509ValidationResult> {
return await validateX509CertificateChain({
chain,
trustAnchors,
verificationTime,
opts,
opts
})
}

Expand All @@ -184,7 +220,7 @@ export class X509CallbackService implements IX509ServiceJS {
chainPEM: Nullable<string[]>,
trustedCerts: Nullable<string[]>,
verificationProfile?: X509VerificationProfile | undefined,
verificationTime?: Nullable<LocalDateTimeKMP>,
verificationTime?: Nullable<LocalDateTimeKMP>
): Promise<IX509VerificationResult<KeyType>> {
const verificationAt = verificationTime ?? DateTimeUtils.Static.DEFAULT.dateTimeLocal()
let chain: Array<string | Uint8Array> = []
Expand All @@ -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
Expand All @@ -210,7 +246,7 @@ export class X509CallbackService implements IX509ServiceJS {
critical: result.critical,
message: result.message,
error: result.error,
verificationTime: verificationAt,
verificationTime: verificationAt
} satisfies IX509VerificationResult<KeyType>
}

Expand Down Expand Up @@ -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())

0 comments on commit 7e67975

Please sign in to comment.