Skip to content

Commit

Permalink
create x509 module
Browse files Browse the repository at this point in the history
  • Loading branch information
auer-martin committed Jul 8, 2024
1 parent 39236d9 commit 0da524b
Show file tree
Hide file tree
Showing 16 changed files with 274 additions and 43 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/crypto/JwsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import { injectable } from '../plugins'
import { isJsonObject, JsonEncoder, TypedArrayEncoder } from '../utils'
import { WalletError } from '../wallet/error'

import { X509Service } from './../modules/x509/X509Service'
import { JWS_COMPACT_FORMAT_MATCHER } from './JwsTypes'
import { getJwkFromJson, getJwkFromKey } from './jose/jwk'
import { JwtPayload } from './jose/jwt'
import { X509Service } from './x509'

@injectable()
export class JwsService {
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/crypto/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,3 @@ export * from './signing-provider'

export * from './webcrypto'
export * from './hashes'
export * from './x509'
22 changes: 18 additions & 4 deletions packages/core/src/crypto/webcrypto/utils/keyAlgorithmConversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ import {
x25519AlgorithmIdentifier,
} from '../algorithmIdentifiers'

export const credoKeyTypeIntoCryptoKeyAlgorithm = (keyType: KeyType): KeyGenAlgorithm => {
switch (keyType) {
case KeyType.Ed25519:
return 'Ed25119'
case KeyType.P256:
return { name: 'ECDSA', namedCurve: 'P-256' }
case KeyType.P384:
return { name: 'ECDSA', namedCurve: 'P-384' }
case KeyType.K256:
return { name: 'ECDSA', namedCurve: 'K-256' }
default:
throw new CredoWebCryptoError(`Unsupported key type: ${keyType}`)
}
}

export const cryptoKeyAlgorithmToCredoKeyType = (algorithm: KeyGenAlgorithm): KeyType => {
const algorithmName = typeof algorithm === 'string' ? algorithm.toUpperCase() : algorithm.name.toUpperCase()
switch (algorithmName) {
Expand All @@ -29,9 +44,8 @@ export const cryptoKeyAlgorithmToCredoKeyType = (algorithm: KeyGenAlgorithm): Ke
default:
throw new CredoWebCryptoError(`Unsupported curve for ECDSA: ${(algorithm as EcKeyGenParams).namedCurve}`)
}
default:
throw new CredoWebCryptoError(`Unsupported algorithm: ${algorithmName}`)
}
throw new CredoWebCryptoError(`Unsupported algorithm: ${algorithmName}`)
}

export const spkiAlgorithmIntoCredoKeyType = (algorithm: AlgorithmIdentifier): KeyType => {
Expand Down Expand Up @@ -64,7 +78,7 @@ export const credoKeyTypeIntoSpkiAlgorithm = (keyType: KeyType): AlgorithmIdenti
return ecPublicKeyWithP384AlgorithmIdentifier
case KeyType.K256:
return ecPublicKeyWithK256AlgorithmIdentifier
default:
throw new CredoWebCryptoError(`Unsupported key type: ${keyType}`)
}

throw new CredoWebCryptoError(`Unsupported key type: ${keyType}`)
}
3 changes: 0 additions & 3 deletions packages/core/src/crypto/x509/index.ts

This file was deleted.

1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export { ReturnRouteTypes } from './decorators/transport/TransportDecorator'
export * from './plugins'
export * from './transport'
export * from './modules/basic-messages'
export * from './modules/x509'
export * from './modules/common'
export * from './modules/credentials'
export * from './modules/discover-features'
Expand Down
19 changes: 13 additions & 6 deletions packages/core/src/modules/sd-jwt-vc/SdJwtVcService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@ import { uint8ArrayToBase64Url } from '@sd-jwt/utils'
import { injectable } from 'tsyringe'

import { AgentContext } from '../../agent'
import { X509Certificate, JwtPayload, Jwk, getJwkFromJson, getJwkFromKey, Hasher } from '../../crypto'
import { X509Service } from '../../crypto/x509/X509Service'
import { JwtPayload, Jwk, getJwkFromJson, getJwkFromKey, Hasher } from '../../crypto'
import { CredoError } from '../../error'
import { X509Service } from '../../modules/x509/X509Service'
import { TypedArrayEncoder, nowInSeconds } from '../../utils'
import { getDomainFromUrl } from '../../utils/domain'
import { fetchWithTimeout } from '../../utils/fetch'
import { DidResolverService, parseDid, getKeyFromVerificationMethod } from '../dids'
import { X509Certificate, X509ModuleConfig } from '../x509'

import { SdJwtVcError } from './SdJwtVcError'
import { SdJwtVcRecord, SdJwtVcRepository } from './repository'
import { getDomainFromUrl } from '../../utils/domain'

type SdJwtVcConfig = SDJwtVcInstance['userConfig']

Expand Down Expand Up @@ -183,7 +184,9 @@ export class SdJwtVcService {

private assertValidX5cJwtIssuer(iss: string, leafCertificate: X509Certificate) {
// TODO: should we throw here?
if (!iss.startsWith('https://')) throw new SdJwtVcError('The X509 certificate issuer must be a HTTPS URI.')
if (!iss.startsWith('http://') && !iss.startsWith('https://')) {
throw new SdJwtVcError('The X509 certificate issuer must be a HTTPS URI.')
}

if (!leafCertificate.sanUriNames?.includes(iss) && !leafCertificate.sanDnsNames?.includes(getDomainFromUrl(iss))) {
throw new SdJwtVcError(
Expand Down Expand Up @@ -459,8 +462,12 @@ export class SdJwtVcService {
throw new SdJwtVcError('Invalid x5c header in credential. Not an array of strings.')
}

await X509Service.validateCertificateChain(agentContext, { certificateChain: sdJwtVc.jwt.header.x5c })
// TODO: check for trusted trust anchor
const trustedCertificates = agentContext.dependencyManager.resolve(X509ModuleConfig).trustedCertificates
await X509Service.validateCertificateChain(agentContext, {
certificateChain: sdJwtVc.jwt.header.x5c,
trustedCertificates,
})

return {
method: 'x5c',
chain: sdJwtVc.jwt.header.x5c,
Expand Down
52 changes: 52 additions & 0 deletions packages/core/src/modules/x509/X509Api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { AgentContext } from '../../agent'
import { injectable } from '../../plugins'

import { X509ModuleConfig } from './X509ModuleConfig'
import { X509Service } from './X509Service'
import { X509CreateSelfSignedCertificateOptions, X509ValidateCertificateChainOptions } from './X509ServiceOptions'

/**
* @public
*/
@injectable()
export class X509Api {
public constructor(private agentContext: AgentContext, private x509Service: X509Service) {}

/**
* Adds a trusted certificate to the X509 Module Config.
*
* @param certificate
*/
public async addTrustedCertificate(certificate: string) {
const x509ModuleConfig = this.agentContext.dependencyManager.resolve(X509ModuleConfig)
x509ModuleConfig.addTrustedCertificate(certificate)
}

/**
* Overwrites the trusted certificates in the X509 Module Config.
*
* @param certificate
*/
public async setTrustedCertificates(certificates?: [string, ...string[]]) {
const x509ModuleConfig = this.agentContext.dependencyManager.resolve(X509ModuleConfig)
x509ModuleConfig.setTrustedCertificates(certificates)
}

/**
* Creates a self-signed certificate.
*
* @param options X509CreateSelfSignedCertificateOptions
*/
public async createSelfSignedCertificate(options: X509CreateSelfSignedCertificateOptions) {
return await X509Service.createSelfSignedCertificate(this.agentContext, options)
}

/**
* Validate a certificate chain.
*
* @param options X509ValidateCertificateChainOptions
*/
public async validateCertificateChain(options: X509ValidateCertificateChainOptions) {
return await X509Service.validateCertificateChain(this.agentContext, options)
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { CredoWebCrypto } from '../webcrypto'
import type { CredoWebCrypto } from '../../crypto/webcrypto'

import { AsnParser } from '@peculiar/asn1-schema'
import { id_ce_subjectAltName, SubjectPublicKeyInfo } from '@peculiar/asn1-x509'
import * as x509 from '@peculiar/x509'

import { Key } from '../Key'
import { CredoWebCryptoKey } from '../webcrypto'
import { spkiAlgorithmIntoCredoKeyType } from '../webcrypto/utils'
import { Key } from '../../crypto/Key'
import { CredoWebCryptoKey } from '../../crypto/webcrypto'
import { credoKeyTypeIntoCryptoKeyAlgorithm, spkiAlgorithmIntoCredoKeyType } from '../../crypto/webcrypto/utils'

import { X509Error } from './X509Error'

Expand All @@ -25,7 +25,7 @@ export class X509Certificate {
public privateKey?: Uint8Array
public extensions?: Array<Extension>

private rawCertificate: Uint8Array
public rawCertificate: Uint8Array

public constructor(options: X509CertificateOptions) {
this.extensions = options.extensions
Expand Down Expand Up @@ -99,9 +99,10 @@ export class X509Certificate {
},
webCrypto: CredoWebCrypto
) {
const publicKey = new CredoWebCryptoKey(key, { name: 'ECDSA', namedCurve: 'P-256' }, true, 'public', ['verify'])
const cryptoKeyAlgorithm = credoKeyTypeIntoCryptoKeyAlgorithm(key.keyType)

const privateKey = new CredoWebCryptoKey(key, { name: 'ECDSA', namedCurve: 'P-256' }, false, 'private', ['sign'])
const publicKey = new CredoWebCryptoKey(key, cryptoKeyAlgorithm, true, 'public', ['verify'])
const privateKey = new CredoWebCryptoKey(key, cryptoKeyAlgorithm, false, 'private', ['sign'])

const certificate = await x509.X509CertificateGenerator.createSelfSigned(
{
Expand All @@ -125,9 +126,11 @@ export class X509Certificate {
public async verify({ date = new Date(), publicKey }: { date: Date; publicKey?: Key }, webCrypto: CredoWebCrypto) {
const certificate = new x509.X509Certificate(this.rawCertificate)

const publicCryptoKey = publicKey
? new CredoWebCryptoKey(publicKey, { name: 'ECDSA', namedCurve: 'P-256' }, true, 'public', ['verify'])
: undefined
let publicCryptoKey: CredoWebCryptoKey | undefined
if (publicKey) {
const cryptoKeyAlgorithm = credoKeyTypeIntoCryptoKeyAlgorithm(publicKey.keyType)
publicCryptoKey = new CredoWebCryptoKey(publicKey, cryptoKeyAlgorithm, true, 'public', ['verify'])
}

// We use the library to validate the signature, but the date is manually verified
const isSignatureValid = await certificate.verify({ signatureOnly: true, publicKey: publicCryptoKey }, webCrypto)
Expand All @@ -152,4 +155,11 @@ export class X509Certificate {
const certificate = new x509.X509Certificate(this.rawCertificate)
return certificate.toString(format)
}

public equal(certificate: X509Certificate) {
const parsedThis = new x509.X509Certificate(this.rawCertificate)
const parsedOther = new x509.X509Certificate(certificate.rawCertificate)

return parsedThis.equal(parsedOther)
}
}
File renamed without changes.
39 changes: 39 additions & 0 deletions packages/core/src/modules/x509/X509Module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { X509ModuleConfigOptions } from './X509ModuleConfig'
import type { Module, DependencyManager } from '../../plugins'

import { AgentConfig } from '../../agent/AgentConfig'

import { X509Api } from './X509Api'
import { X509ModuleConfig } from './X509ModuleConfig'
import { X509Service } from './X509Service'

/**
* @public
*/
export class X509Module implements Module {
public readonly api = X509Api

public readonly config: X509ModuleConfig

public constructor(options: X509ModuleConfigOptions) {
this.config = new X509ModuleConfig(options)
}

/**
* Registers the dependencies of the sd-jwt-vc module on the dependency manager.
*/
public register(dependencyManager: DependencyManager) {
// Warn about experimental module
dependencyManager
.resolve(AgentConfig)
.logger.warn(
"The 'X509' module is experimental and could have unexpected breaking changes. When using this module, make sure to use strict versions for all @credo-ts packages."
)

// Register config
dependencyManager.registerInstance(X509ModuleConfig, this.config)

// Services
dependencyManager.registerSingleton(X509Service)
}
}
27 changes: 27 additions & 0 deletions packages/core/src/modules/x509/X509ModuleConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export interface X509ModuleConfigOptions {
trustedCertificates?: [string, ...string[]]
}

export class X509ModuleConfig {
private options: X509ModuleConfigOptions

public constructor(options: X509ModuleConfigOptions) {
this.options = options
}

public get trustedCertificates() {
return this.options.trustedCertificates
}

public setTrustedCertificates(trustedCertificates?: [string, ...string[]]) {
this.options.trustedCertificates = trustedCertificates
}

public addTrustedCertificate(trustedCertificate: string) {
if (!this.options.trustedCertificates) {
this.options.trustedCertificates = [trustedCertificate]
}

this.options.trustedCertificates.push(trustedCertificate)
}
}
Loading

0 comments on commit 0da524b

Please sign in to comment.