import { createPublicKey } from "crypto"; import { KeyManagementServiceClient } from "@google-cloud/kms"; import { crc32c } from "@node-lightning/checksum"; import { Bytes, PublicKey, Signature, Signer, } from "@cloud-cryptographic-wallet/signer"; import { parsePublicKey, parseSignature, } from "@cloud-cryptographic-wallet/asn1-parser"; type ClientOptions = ConstructorParameters< typeof KeyManagementServiceClient >[0]; export class CloudKmsSigner implements Signer { private readonly client: KeyManagementServiceClient; private readonly name: string; private cachePublicKey?: PublicKey; constructor(name: string, clientOptions?: ClientOptions) { this.client = new KeyManagementServiceClient(clientOptions ?? {}); this.name = name; } async sign(hash: Bytes): Promise<Signature> { const [signResponse] = await this.client.asymmetricSign({ name: this.name, digest: { sha256: hash.asUint8Array, }, }); if (!(signResponse.signature instanceof Uint8Array)) { throw new TypeError( "CloudKmsSigner: signResponse.signature isn't Uint8Array." ); } if ( crc32c(Buffer.from(signResponse.signature)) !== Number(signResponse.signatureCrc32c?.value) ) { throw new Error( "CloudKmsSigner: failed to validate of crc32c signResponse.signature." ); } const { r, s } = parseSignature( new Uint8Array(signResponse.signature).buffer ); const publicKey = await this.getPublicKey(); return Signature.fromHash( hash, publicKey, Bytes.fromArrayBuffer(r), Bytes.fromArrayBuffer(s) ); } async getPublicKey(): Promise<PublicKey> { if (this.cachePublicKey) { return this.cachePublicKey; } const [publicKeyResponse] = await this.client.getPublicKey({ name: this.name, }); if (publicKeyResponse.name !== this.name) { throw new Error(`CloudKmsSigner: incorrect name. actual: ${this.name}`); } if (publicKeyResponse.pem == undefined) { throw new Error("CloudKmsSigner: publicKeyResponse.pem is undefined."); } if ( crc32c(Buffer.from(publicKeyResponse.pem)) !== Number(publicKeyResponse.pemCrc32c?.value) ) { throw new Error( "CloudKmsSigner: failed to validate of crc32c publicKeyResponse.pem." ); } const key = createPublicKey({ key: publicKeyResponse.pem, format: "pem" }); const buffer = key.export({ format: "der", type: "spki" }); const bytes = Bytes.fromString(buffer.toString("hex")); const rawPublicKey = parsePublicKey(bytes.buffer); const publicKey = PublicKey.fromBytes(Bytes.fromArrayBuffer(rawPublicKey)); this.cachePublicKey = publicKey; return publicKey; } }