@dfinity/identity
Version:
JavaScript and TypeScript library to manage identity with the Internet Computer
164 lines (146 loc) • 5.26 kB
text/typescript
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;