@sphereon/ssi-sdk-ext.kms-azure
Version:
Sphereon SSI-SDK plugin for Azure KeyVault Key Management System.
183 lines (157 loc) • 6.42 kB
text/typescript
import { AzureKeyVaultCryptoProvider, com } from '@sphereon/kmp-crypto-kms-azure'
import { IKey, ManagedKeyInfo, MinimalImportableKey, TKeyType } from '@veramo/core'
import { AbstractKeyManagementSystem } from '@veramo/key-manager'
import { KeyMetadata } from './index'
import { calculateJwkThumbprint } from '@sphereon/ssi-sdk-ext.key-utils'
import { JoseCurve, JWK } from '@sphereon/ssi-types'
import SignatureAlgorithm = com.sphereon.crypto.generic.SignatureAlgorithm
import KeyOperations = com.sphereon.crypto.generic.KeyOperations
import JwkUse = com.sphereon.crypto.jose.JwkUse
interface AbstractKeyManagementSystemOptions {
applicationId: string
keyVaultUrl: string
keyVaultClientIdTenantId: string
keyVaultClientId: string
keyVaultClientSecret: string
}
export class AzureKeyVaultKeyManagementSystem extends AbstractKeyManagementSystem {
private client: AzureKeyVaultCryptoProvider
private id: string
constructor(options: AbstractKeyManagementSystemOptions) {
super()
const credentialOptions = new com.sphereon.crypto.kms.azure.CredentialOpts(
com.sphereon.crypto.kms.azure.CredentialMode.SERVICE_CLIENT_SECRET,
new com.sphereon.crypto.kms.azure.SecretCredentialOpts(options.keyVaultClientId, options.keyVaultClientSecret)
)
const azureKeyVaultClientConfig = new com.sphereon.crypto.kms.azure.AzureKeyVaultClientConfig(
options.applicationId,
options.keyVaultUrl,
options.keyVaultClientIdTenantId,
credentialOptions
)
this.id = options.applicationId
this.client = new AzureKeyVaultCryptoProvider(azureKeyVaultClientConfig)
}
async createKey(args: { type: TKeyType; meta?: KeyMetadata }): Promise<ManagedKeyInfo> {
const { type, meta } = args
const signatureAlgorithm = this.mapKeyTypeToSignatureAlgorithm(type)
const options = new AzureKeyVaultCryptoProvider.GenerateKeyRequest(
meta?.keyAlias || `key-${crypto.randomUUID()}`,
meta && 'keyUsage' in meta ? this.mapKeyUsage(meta.keyUsage) : JwkUse.sig,
meta && 'keyOperations' in meta ? this.mapKeyOperations(meta.keyOperations as string[]) : [KeyOperations.SIGN],
signatureAlgorithm
)
const key = await this.client.generateKeyAsync(options)
const jwk: JWK = {
...key.jose.publicJwk.toPublicKey(),
kty: key.jose.publicJwk.toPublicKey().kty.name,
crv: this.signatureAlgorithmToCurve(signatureAlgorithm),
x: key.jose.publicJwk.toPublicKey().x!!,
y: key.jose.publicJwk.toPublicKey().y!!,
kid: key.jose.publicJwk.toPublicKey().kid!!,
}
return {
kid: key.kmsKeyRef,
kms: this.id,
type,
meta: {
alias: key.kid,
algorithms: [key.jose.publicJwk.alg?.name ?? 'PS256'],
jwkThumbprint: calculateJwkThumbprint({
jwk,
digestAlgorithm: this.signatureAlgorithmToDigestAlgorithm(signatureAlgorithm),
}),
},
publicKeyHex: Buffer.from(key.jose.toString(), 'utf8').toString('base64'),
}
}
async sign(args: { keyRef: Pick<IKey, 'kid'>; data: Uint8Array; [x: string]: any }): Promise<string> {
if (!args.keyRef) {
throw new Error('key_not_found: No key ref provided')
}
const key = await this.client.fetchKeyAsync(args.keyRef.kid)
const signature = await this.client.createRawSignatureAsync({
keyInfo: key,
// @ts-ignore
input: args.data,
})
return Buffer.from(signature).toString('hex')
}
async verify(args: { keyRef: Pick<IKey, 'kid'>; data: Uint8Array; signature: string; [x: string]: any }): Promise<Boolean> {
if (!args.keyRef) {
throw new Error('key_not_found: No key ref provided')
}
try {
const key = await this.client.fetchKeyAsync(args.keyRef.kid)
return await this.client.isValidRawSignatureAsync({
keyInfo: key,
// @ts-ignore
signature: Buffer.from(args.signature, 'hex'),
// @ts-ignore
input: args.data,
})
} catch (e) {
console.error(e)
return false
}
}
sharedSecret(args: { myKeyRef: Pick<IKey, 'kid'>; theirKey: Pick<IKey, 'publicKeyHex' | 'type'> }): Promise<string> {
throw new Error('sharedSecret is not implemented for AzureKeyVaultKMS.')
}
async importKey(args: Omit<MinimalImportableKey, 'kms'> & { privateKeyPEM?: string }): Promise<ManagedKeyInfo> {
throw new Error('importKey is not implemented for AzureKeyVaultKMS.')
}
async deleteKey({ kid }: { kid: string }): Promise<boolean> {
throw new Error('deleteKey is not implemented for AzureKeyVaultKMS.')
}
async listKeys(): Promise<ManagedKeyInfo[]> {
throw new Error('listKeys is not implemented for AzureKeyVaultKMS.')
}
private signatureAlgorithmToDigestAlgorithm = (signatureAlgorithm: SignatureAlgorithm): 'sha256' | 'sha512' => {
switch (signatureAlgorithm) {
case SignatureAlgorithm.ECDSA_SHA256:
return 'sha256'
default:
throw new Error(`Signature algorithm ${signatureAlgorithm} is not supported by AzureKeyVaultKMS`)
}
}
private signatureAlgorithmToCurve = (signatureAlgorithm: SignatureAlgorithm): JoseCurve => {
switch (signatureAlgorithm) {
case SignatureAlgorithm.ECDSA_SHA256:
return JoseCurve.P_256
default:
throw new Error(`Signature algorithm ${signatureAlgorithm} is not supported by AzureKeyVaultKMS`)
}
}
private mapKeyUsage = (usage: string): JwkUse => {
switch (usage) {
case 'sig':
return JwkUse.sig
case 'enc':
return JwkUse.enc
default:
throw new Error(`Key usage ${usage} is not supported by AzureKeyVaultKMS`)
}
}
private mapKeyTypeToSignatureAlgorithm = (type: TKeyType): SignatureAlgorithm => {
switch (type) {
case 'Secp256r1':
return SignatureAlgorithm.ECDSA_SHA256
default:
throw new Error(`Key type ${type} is not supported by AzureKeyVaultKMS`)
}
}
private mapKeyOperation = (operation: string): KeyOperations => {
switch (operation) {
case 'sign':
return KeyOperations.SIGN
case 'verify':
return KeyOperations.VERIFY
default:
throw new Error(`Key operation ${operation} is not supported by AzureKeyVaultKMS`)
}
}
private mapKeyOperations = (operations: string[]): KeyOperations[] => {
return operations.map((operation) => this.mapKeyOperation(operation))
}
}