UNPKG

@dfinity/identity

Version:

JavaScript and TypeScript library to manage identity with the Internet Computer

164 lines (146 loc) 5.26 kB
import { type DerEncodedPublicKey, type PublicKey, type Signature, SignIdentity, } from '@dfinity/agent'; import { uint8FromBufLike } from '@dfinity/candid'; /** * Options used in a {@link ECDSAKeyIdentity} */ export type CryptoKeyOptions = { extractable?: boolean; keyUsages?: KeyUsage[]; subtleCrypto?: SubtleCrypto; }; export class CryptoError extends Error { constructor(public readonly message: string) { super(message); Object.setPrototypeOf(this, CryptoError.prototype); } } export interface DerCryptoKey extends CryptoKey { toDer: () => DerEncodedPublicKey; } /** * Utility method to ensure that a subtleCrypto implementation is provided or is available in the global context * @param subtleCrypto SubtleCrypto implementation * @returns subleCrypto */ function _getEffectiveCrypto(subtleCrypto: CryptoKeyOptions['subtleCrypto']): SubtleCrypto { if (typeof global !== 'undefined' && global['crypto'] && global['crypto']['subtle']) { return global['crypto']['subtle']; } if (subtleCrypto) { return subtleCrypto; } else if (typeof crypto !== 'undefined' && crypto['subtle']) { return crypto.subtle; } else { throw new CryptoError( 'Global crypto was not available and none was provided. Please inlcude a SubtleCrypto implementation. See https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto', ); } } /** * An identity interface that wraps an ECDSA keypair using the P-256 named curve. Supports DER-encoding and decoding for agent calls */ export class ECDSAKeyIdentity extends SignIdentity { /** * Generates a randomly generated identity for use in calls to the Internet Computer. * @param {CryptoKeyOptions} options optional settings * @param {CryptoKeyOptions['extractable']} options.extractable - whether the key should allow itself to be used. Set to false for maximum security. * @param {CryptoKeyOptions['keyUsages']} options.keyUsages - a list of key usages that the key can be used for * @param {CryptoKeyOptions['subtleCrypto']} options.subtleCrypto interface * @returns a {@link ECDSAKeyIdentity} */ public static async generate(options?: CryptoKeyOptions): Promise<ECDSAKeyIdentity> { const { extractable = false, keyUsages = ['sign', 'verify'], subtleCrypto } = options ?? {}; const effectiveCrypto = _getEffectiveCrypto(subtleCrypto); const keyPair = await effectiveCrypto.generateKey( { name: 'ECDSA', namedCurve: 'P-256', }, extractable, keyUsages, ); const derKey: DerEncodedPublicKey = uint8FromBufLike( await effectiveCrypto.exportKey('spki', keyPair.publicKey), ); Object.assign(derKey, { __derEncodedPublicKey__: undefined, }); return new this(keyPair, derKey, effectiveCrypto); } /** * generates an identity from a public and private key. Please ensure that you are generating these keys securely and protect the user's private key * @param keyPair a CryptoKeyPair * @param subtleCrypto - a SubtleCrypto interface in case one is not available globally * @returns an {@link ECDSAKeyIdentity} */ public static async fromKeyPair( keyPair: CryptoKeyPair | { privateKey: CryptoKey; publicKey: CryptoKey }, subtleCrypto?: SubtleCrypto, ): Promise<ECDSAKeyIdentity> { const effectiveCrypto = _getEffectiveCrypto(subtleCrypto); const derKey: DerEncodedPublicKey = uint8FromBufLike( await effectiveCrypto.exportKey('spki', keyPair.publicKey), ); Object.assign(derKey, { __derEncodedPublicKey__: undefined, }); return new ECDSAKeyIdentity(keyPair, derKey, effectiveCrypto); } protected _derKey: DerEncodedPublicKey; protected _keyPair: CryptoKeyPair; protected _subtleCrypto: SubtleCrypto; // `fromKeyPair` and `generate` should be used for instantiation, not this constructor. protected constructor( keyPair: CryptoKeyPair, derKey: DerEncodedPublicKey, subtleCrypto: SubtleCrypto, ) { super(); this._keyPair = keyPair; this._derKey = derKey; this._subtleCrypto = subtleCrypto; } /** * Return the internally-used key pair. * @returns a CryptoKeyPair */ public getKeyPair(): CryptoKeyPair { return this._keyPair; } /** * Return the public key. * @returns an {@link PublicKey & DerCryptoKey} */ public getPublicKey(): PublicKey & DerCryptoKey { const derKey = this._derKey; const key: DerCryptoKey = Object.create(this._keyPair.publicKey); key.toDer = function () { return derKey; }; return key; } /** * Signs a blob of data, with this identity's private key. * @param {Uint8Array} challenge - challenge to sign with this identity's secretKey, producing a signature * @returns {Promise<Signature>} signature */ public async sign(challenge: Uint8Array): Promise<Signature> { const params: EcdsaParams = { name: 'ECDSA', hash: { name: 'SHA-256' }, }; const signature = uint8FromBufLike( await this._subtleCrypto.sign(params, this._keyPair.privateKey, challenge), ); Object.assign(signature, { __signature__: undefined, }); return signature as Signature; } } export default ECDSAKeyIdentity;