UNPKG

@libp2p/keychain

Version:

Key management and cryptographically protected messages

197 lines • 7.01 kB
import { randomBytes } from '@libp2p/crypto'; import { AES_GCM } from '@libp2p/crypto/ciphers'; import { privateKeyToProtobuf } from '@libp2p/crypto/keys'; import webcrypto from '@libp2p/crypto/webcrypto'; import { InvalidParametersError, UnsupportedKeyTypeError } from '@libp2p/interface'; import { pbkdf2Async } from '@noble/hashes/pbkdf2'; import { sha512 } from '@noble/hashes/sha512'; import * as asn1js from 'asn1js'; import { base64 } from 'multiformats/bases/base64'; import { toString as uint8ArrayToString } from 'uint8arrays/to-string'; import { ITERATIONS, KEY_SIZE, SALT_LENGTH } from './constants.js'; /** * Exports the given PrivateKey as a base64 encoded string. * The PrivateKey is encrypted via a password derived PBKDF2 key * leveraging the aes-gcm cipher algorithm. */ export async function exporter(privateKey, password) { const cipher = AES_GCM.create(); const encryptedKey = await cipher.encrypt(privateKey, password); return base64.encode(encryptedKey); } /** * Converts an exported private key into its representative object. * * Supported formats are 'pem' (RSA only) and 'libp2p-key'. */ export async function exportPrivateKey(key, password, format) { if (key.type === 'RSA') { return exportRSAPrivateKey(key, password, format); } if (key.type === 'Ed25519') { return exportEd25519PrivateKey(key, password, format); } if (key.type === 'secp256k1') { return exportSecp256k1PrivateKey(key, password, format); } if (key.type === 'ECDSA') { return exportECDSAPrivateKey(key, password, format); } throw new UnsupportedKeyTypeError(); } /** * Exports the key into a password protected `format` */ export async function exportEd25519PrivateKey(key, password, format = 'libp2p-key') { if (format === 'libp2p-key') { return exporter(privateKeyToProtobuf(key), password); } else { throw new InvalidParametersError(`export format '${format}' is not supported`); } } /** * Exports the key into a password protected `format` */ export async function exportSecp256k1PrivateKey(key, password, format = 'libp2p-key') { if (format === 'libp2p-key') { return exporter(privateKeyToProtobuf(key), password); } else { throw new InvalidParametersError('Export format is not supported'); } } /** * Exports the key into a password protected `format` */ export async function exportECDSAPrivateKey(key, password, format = 'libp2p-key') { if (format === 'libp2p-key') { return exporter(privateKeyToProtobuf(key), password); } else { throw new InvalidParametersError(`export format '${format}' is not supported`); } } /** * Exports the key as libp2p-key - a aes-gcm encrypted value with the key * derived from the password. * * To export it as a password protected PEM file, please use the `exportPEM` * function from `@libp2p/rsa`. */ export async function exportRSAPrivateKey(key, password, format = 'pkcs-8') { if (format === 'pkcs-8') { return exportToPem(key, password); } else if (format === 'libp2p-key') { return exporter(privateKeyToProtobuf(key), password); } else { throw new InvalidParametersError('Export format is not supported'); } } export async function exportToPem(privateKey, password) { const crypto = webcrypto.get(); // PrivateKeyInfo const keyWrapper = new asn1js.Sequence({ value: [ // version (0) new asn1js.Integer({ value: 0 }), // privateKeyAlgorithm new asn1js.Sequence({ value: [ // rsaEncryption OID new asn1js.ObjectIdentifier({ value: '1.2.840.113549.1.1.1' }), new asn1js.Null() ] }), // PrivateKey new asn1js.OctetString({ valueHex: privateKey.raw }) ] }); const keyBuf = keyWrapper.toBER(); const keyArr = new Uint8Array(keyBuf, 0, keyBuf.byteLength); const salt = randomBytes(SALT_LENGTH); const encryptionKey = await pbkdf2Async(sha512, password, salt, { c: ITERATIONS, dkLen: KEY_SIZE }); const iv = randomBytes(16); const cryptoKey = await crypto.subtle.importKey('raw', encryptionKey, 'AES-CBC', false, ['encrypt']); const encrypted = await crypto.subtle.encrypt({ name: 'AES-CBC', iv }, cryptoKey, keyArr); const pbkdf2Params = new asn1js.Sequence({ value: [ // salt new asn1js.OctetString({ valueHex: salt }), // iteration count new asn1js.Integer({ value: ITERATIONS }), // key length new asn1js.Integer({ value: KEY_SIZE }), // AlgorithmIdentifier new asn1js.Sequence({ value: [ // hmacWithSHA512 new asn1js.ObjectIdentifier({ value: '1.2.840.113549.2.11' }), new asn1js.Null() ] }) ] }); const encryptionAlgorithm = new asn1js.Sequence({ value: [ // pkcs5PBES2 new asn1js.ObjectIdentifier({ value: '1.2.840.113549.1.5.13' }), new asn1js.Sequence({ value: [ // keyDerivationFunc new asn1js.Sequence({ value: [ // pkcs5PBKDF2 new asn1js.ObjectIdentifier({ value: '1.2.840.113549.1.5.12' }), // PBKDF2-params pbkdf2Params ] }), // encryptionScheme new asn1js.Sequence({ value: [ // aes256-CBC new asn1js.ObjectIdentifier({ value: '2.16.840.1.101.3.4.1.42' }), // iv new asn1js.OctetString({ valueHex: iv }) ] }) ] }) ] }); const finalWrapper = new asn1js.Sequence({ value: [ encryptionAlgorithm, new asn1js.OctetString({ valueHex: encrypted }) ] }); const finalWrapperBuf = finalWrapper.toBER(); const finalWrapperArr = new Uint8Array(finalWrapperBuf, 0, finalWrapperBuf.byteLength); return [ '-----BEGIN ENCRYPTED PRIVATE KEY-----', ...uint8ArrayToString(finalWrapperArr, 'base64pad').split(/(.{64})/).filter(Boolean), '-----END ENCRYPTED PRIVATE KEY-----' ].join('\n'); } //# sourceMappingURL=export.js.map