@libp2p/keychain
Advanced tools
Comparing version 4.1.6-2265e59ba to 4.1.6-2bbaf4361
@@ -53,5 +53,4 @@ /** | ||
*/ | ||
import type { ComponentLogger, KeyType, PeerId } from '@libp2p/interface'; | ||
import type { ComponentLogger, PrivateKey } from '@libp2p/interface'; | ||
import type { Datastore } from 'interface-datastore'; | ||
import type { Multibase } from 'multiformats/bases/interface.js'; | ||
export interface DEKConfig { | ||
@@ -83,3 +82,3 @@ hash: string; | ||
/** | ||
* Export an existing key as a PEM encrypted PKCS #8 string. | ||
* Find a key by name | ||
* | ||
@@ -89,9 +88,12 @@ * @example | ||
* ```TypeScript | ||
* await libp2p.services.keychain.createKey('keyTest', 'RSA', 4096) | ||
* const pemKey = await libp2p.services.keychain.exportKey('keyTest', 'password123') | ||
* import { generateKeyPair } from '@libp2p/crypto/keys' | ||
* | ||
* const key = await generateKeyPair('Ed25519') | ||
* const keyInfo = await libp2p.keychain.importKey('my-key', key) | ||
* const keyInfo2 = await libp2p.keychain.findKeyByName(keyInfo.name) | ||
* ``` | ||
*/ | ||
exportKey(name: string, password: string): Promise<Multibase<'m'>>; | ||
findKeyByName(name: string): Promise<KeyInfo>; | ||
/** | ||
* Import a new key from a PEM encoded PKCS #8 string. | ||
* Find a key by id | ||
* | ||
@@ -101,10 +103,12 @@ * @example | ||
* ```TypeScript | ||
* await libp2p.services.keychain.createKey('keyTest', 'RSA', 4096) | ||
* const pemKey = await libp2p.services.keychain.exportKey('keyTest', 'password123') | ||
* const keyInfo = await libp2p.services.keychain.importKey('keyTestImport', pemKey, 'password123') | ||
* import { generateKeyPair } from '@libp2p/crypto/keys' | ||
* | ||
* const key = await generateKeyPair('Ed25519') | ||
* const keyInfo = await libp2p.keychain.importKey('my-key', key) | ||
* const keyInfo2 = await libp2p.keychain.findKeyById(keyInfo.id) | ||
* ``` | ||
*/ | ||
importKey(name: string, pem: string, password: string): Promise<KeyInfo>; | ||
findKeyById(id: string): Promise<KeyInfo>; | ||
/** | ||
* Import a new key from a PeerId with a private key component | ||
* Import a new private key. | ||
* | ||
@@ -114,18 +118,11 @@ * @example | ||
* ```TypeScript | ||
* const keyInfo = await libp2p.services.keychain.importPeer('keyTestImport', peerIdFromString('12D3Foo...')) | ||
* ``` | ||
*/ | ||
importPeer(name: string, peerId: PeerId): Promise<KeyInfo>; | ||
/** | ||
* Export an existing key as a PeerId | ||
* import { generateKeyPair } from '@libp2p/crypto/keys' | ||
* | ||
* @example | ||
* | ||
* ```TypeScript | ||
* const peerId = await libp2p.services.keychain.exportPeerId('key-name') | ||
* const key = await generateKeyPair('Ed25519') | ||
* const keyInfo = await libp2p.keychain.importKey('my-key', key) | ||
* ``` | ||
*/ | ||
exportPeerId(name: string): Promise<PeerId>; | ||
importKey(name: string, key: PrivateKey): Promise<KeyInfo>; | ||
/** | ||
* Create a key in the keychain. | ||
* Export an existing private key. | ||
* | ||
@@ -135,16 +132,10 @@ * @example | ||
* ```TypeScript | ||
* const keyInfo = await libp2p.services.keychain.createKey('keyTest', 'RSA', 4096) | ||
* ``` | ||
*/ | ||
createKey(name: string, type: KeyType, size?: number): Promise<KeyInfo>; | ||
/** | ||
* List all the keys. | ||
* import { generateKeyPair } from '@libp2p/crypto/keys' | ||
* | ||
* @example | ||
* | ||
* ```TypeScript | ||
* const keyInfos = await libp2p.services.keychain.listKeys() | ||
* const key = await generateKeyPair('Ed25519') | ||
* const keyInfo = await libp2p.keychain.importKey('my-key', key) | ||
* const key = await libp2p.keychain.exportKey(keyInfo.id) | ||
* ``` | ||
*/ | ||
listKeys(): Promise<KeyInfo[]>; | ||
exportKey(name: string): Promise<PrivateKey>; | ||
/** | ||
@@ -162,3 +153,4 @@ * Removes a key from the keychain. | ||
/** | ||
* Rename a key in the keychain. | ||
* Rename a key in the keychain. This is done in a batch commit with rollback | ||
* so errors thrown during the operation will not cause key loss. | ||
* | ||
@@ -174,3 +166,3 @@ * @example | ||
/** | ||
* Find a key by it's id. | ||
* List all the keys. | ||
* | ||
@@ -180,19 +172,7 @@ * @example | ||
* ```TypeScript | ||
* const keyInfo = await libp2p.services.keychain.createKey('keyTest', 'RSA', 4096) | ||
* const keyInfo2 = await libp2p.services.keychain.findKeyById(keyInfo.id) | ||
* const keyInfos = await libp2p.keychain.listKeys() | ||
* ``` | ||
*/ | ||
findKeyById(id: string): Promise<KeyInfo>; | ||
listKeys(): Promise<KeyInfo[]>; | ||
/** | ||
* Find a key by it's name. | ||
* | ||
* @example | ||
* | ||
* ```TypeScript | ||
* const keyInfo = await libp2p.services.keychain.createKey('keyTest', 'RSA', 4096) | ||
* const keyInfo2 = await libp2p.services.keychain.findKeyByName('keyTest') | ||
* ``` | ||
*/ | ||
findKeyByName(name: string): Promise<KeyInfo>; | ||
/** | ||
* Rotate keychain password and re-encrypt all associated keys | ||
@@ -199,0 +179,0 @@ * |
@@ -53,8 +53,8 @@ /** | ||
*/ | ||
import { DefaultKeychain } from './keychain.js'; | ||
import { Keychain as KeychainClass } from './keychain.js'; | ||
export function keychain(init = {}) { | ||
return (components) => { | ||
return new DefaultKeychain(components, init); | ||
return new KeychainClass(components, init); | ||
}; | ||
} | ||
//# sourceMappingURL=index.js.map |
import { serviceCapabilities } from '@libp2p/interface'; | ||
import type { KeychainComponents, KeychainInit, Keychain, KeyInfo } from './index.js'; | ||
import type { KeyType, PeerId } from '@libp2p/interface'; | ||
import type { KeychainComponents, KeychainInit, Keychain as KeychainInterface, KeyInfo } from './index.js'; | ||
import type { PrivateKey } from '@libp2p/interface'; | ||
declare const defaultOptions: { | ||
@@ -12,2 +12,3 @@ dek: { | ||
}; | ||
export declare function keyId(key: PrivateKey): Promise<string>; | ||
/** | ||
@@ -21,3 +22,3 @@ * Manages the lifecycle of a key. Keys are encrypted at rest using PKCS #8. | ||
*/ | ||
export declare class DefaultKeychain implements Keychain { | ||
export declare class Keychain implements KeychainInterface { | ||
private readonly components; | ||
@@ -45,11 +46,8 @@ private readonly init; | ||
static get options(): typeof defaultOptions; | ||
findKeyByName(name: string): Promise<KeyInfo>; | ||
findKeyById(id: string): Promise<KeyInfo>; | ||
importKey(name: string, key: PrivateKey): Promise<KeyInfo>; | ||
exportKey(name: string): Promise<PrivateKey>; | ||
removeKey(name: string): Promise<KeyInfo>; | ||
/** | ||
* Create a new key. | ||
* | ||
* @param {string} name - The local key name; cannot already exist. | ||
* @param {string} type - One of the key types; 'rsa'. | ||
* @param {number} [size = 2048] - The key size in bits. Used for rsa keys only | ||
*/ | ||
createKey(name: string, type: KeyType, size?: number): Promise<KeyInfo>; | ||
/** | ||
* List all the keys. | ||
@@ -61,20 +59,2 @@ * | ||
/** | ||
* Find a key by it's id | ||
*/ | ||
findKeyById(id: string): Promise<KeyInfo>; | ||
/** | ||
* Find a key by it's name. | ||
* | ||
* @param {string} name - The local key name. | ||
* @returns {Promise<KeyInfo>} | ||
*/ | ||
findKeyByName(name: string): Promise<KeyInfo>; | ||
/** | ||
* Remove an existing key. | ||
* | ||
* @param {string} name - The local key name; must already exist. | ||
* @returns {Promise<KeyInfo>} | ||
*/ | ||
removeKey(name: string): Promise<KeyInfo>; | ||
/** | ||
* Rename a key | ||
@@ -88,27 +68,2 @@ * | ||
/** | ||
* Export an existing key as a PEM encrypted PKCS #8 string | ||
*/ | ||
exportKey(name: string, password: string): Promise<string>; | ||
/** | ||
* Export an existing key as a PeerId | ||
*/ | ||
exportPeerId(name: string): Promise<PeerId>; | ||
/** | ||
* Import a new key from a PEM encoded PKCS #8 string | ||
* | ||
* @param {string} name - The local key name; must not already exist. | ||
* @param {string} pem - The PEM encoded PKCS #8 string | ||
* @param {string} password - The password. | ||
* @returns {Promise<KeyInfo>} | ||
*/ | ||
importKey(name: string, pem: string, password: string): Promise<KeyInfo>; | ||
/** | ||
* Import a peer key | ||
*/ | ||
importPeer(name: string, peer: PeerId): Promise<KeyInfo>; | ||
/** | ||
* Gets the private key as PEM encoded PKCS #8 string | ||
*/ | ||
getPrivateKey(name: string): Promise<string>; | ||
/** | ||
* Rotate keychain password and re-encrypt all associated keys | ||
@@ -115,0 +70,0 @@ */ |
/* eslint max-nested-callbacks: ["error", 5] */ | ||
import { pbkdf2, randomBytes } from '@libp2p/crypto'; | ||
import { generateKeyPair, importKey, unmarshalPrivateKey } from '@libp2p/crypto/keys'; | ||
import { CodeError, serviceCapabilities } from '@libp2p/interface'; | ||
import { peerIdFromKeys } from '@libp2p/peer-id'; | ||
import { privateKeyToProtobuf } from '@libp2p/crypto/keys'; | ||
import { InvalidParametersError, NotFoundError, serviceCapabilities } from '@libp2p/interface'; | ||
import { Key } from 'interface-datastore/key'; | ||
import mergeOptions from 'merge-options'; | ||
import { base58btc } from 'multiformats/bases/base58'; | ||
import { sha256 } from 'multiformats/hashes/sha2'; | ||
import sanitize from 'sanitize-filename'; | ||
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'; | ||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'; | ||
import { codes } from './errors.js'; | ||
import { exportPrivateKey } from './utils/export.js'; | ||
import { importPrivateKey } from './utils/import.js'; | ||
const keyPrefix = '/pkcs8/'; | ||
@@ -63,2 +65,7 @@ const infoPrefix = '/info/'; | ||
} | ||
export async function keyId(key) { | ||
const pb = privateKeyToProtobuf(key); | ||
const hash = await sha256.digest(pb); | ||
return base58btc.encode(hash.bytes).substring(1); | ||
} | ||
/** | ||
@@ -72,3 +79,3 @@ * Manages the lifecycle of a key. Keys are encrypted at rest using PKCS #8. | ||
*/ | ||
export class DefaultKeychain { | ||
export class Keychain { | ||
components; | ||
@@ -126,17 +133,44 @@ init; | ||
} | ||
/** | ||
* Create a new key. | ||
* | ||
* @param {string} name - The local key name; cannot already exist. | ||
* @param {string} type - One of the key types; 'rsa'. | ||
* @param {number} [size = 2048] - The key size in bits. Used for rsa keys only | ||
*/ | ||
async createKey(name, type, size = 2048) { | ||
async findKeyByName(name) { | ||
if (!validateKeyName(name)) { | ||
await randomDelay(); | ||
throw new InvalidParametersError(`Invalid key name '${name}'`); | ||
} | ||
const dsname = DsInfoName(name); | ||
try { | ||
const res = await this.components.datastore.get(dsname); | ||
return JSON.parse(uint8ArrayToString(res)); | ||
} | ||
catch (err) { | ||
await randomDelay(); | ||
this.log.error(err); | ||
throw new NotFoundError(`Key '${name}' does not exist.`); | ||
} | ||
} | ||
async findKeyById(id) { | ||
try { | ||
const query = { | ||
prefix: infoPrefix | ||
}; | ||
for await (const value of this.components.datastore.query(query)) { | ||
const key = JSON.parse(uint8ArrayToString(value.value)); | ||
if (key.id === id) { | ||
return key; | ||
} | ||
} | ||
throw new InvalidParametersError(`Key with id '${id}' does not exist.`); | ||
} | ||
catch (err) { | ||
await randomDelay(); | ||
throw err; | ||
} | ||
} | ||
async importKey(name, key) { | ||
if (!validateKeyName(name) || name === 'self') { | ||
await randomDelay(); | ||
throw new CodeError('Invalid key name', codes.ERR_INVALID_KEY_NAME); | ||
throw new InvalidParametersError(`Invalid key name '${name}'`); | ||
} | ||
if (typeof type !== 'string') { | ||
if (key == null) { | ||
await randomDelay(); | ||
throw new CodeError('Invalid key type', codes.ERR_INVALID_KEY_TYPE); | ||
throw new InvalidParametersError('Key is required'); | ||
} | ||
@@ -147,32 +181,14 @@ const dsname = DsName(name); | ||
await randomDelay(); | ||
throw new CodeError('Key name already exists', codes.ERR_KEY_ALREADY_EXISTS); | ||
throw new InvalidParametersError(`Key '${name}' already exists`); | ||
} | ||
switch (type.toLowerCase()) { | ||
case 'rsa': | ||
if (!Number.isSafeInteger(size) || size < 2048) { | ||
await randomDelay(); | ||
throw new CodeError('Invalid RSA key size', codes.ERR_INVALID_KEY_SIZE); | ||
} | ||
break; | ||
default: | ||
break; | ||
} | ||
let keyInfo; | ||
let kid; | ||
let pem; | ||
try { | ||
const keypair = await generateKeyPair(type, size); | ||
const kid = await keypair.id(); | ||
kid = await keyId(key); | ||
const cached = privates.get(this); | ||
if (cached == null) { | ||
throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS); | ||
throw new InvalidParametersError('dek missing'); | ||
} | ||
const dek = cached.dek; | ||
const pem = await keypair.export(dek); | ||
keyInfo = { | ||
name, | ||
id: kid | ||
}; | ||
const batch = this.components.datastore.batch(); | ||
batch.put(dsname, uint8ArrayFromString(pem)); | ||
batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo))); | ||
await batch.commit(); | ||
pem = await exportPrivateKey(key, dek, key.type === 'RSA' ? 'pkcs-8' : 'libp2p-key'); | ||
} | ||
@@ -183,68 +199,37 @@ catch (err) { | ||
} | ||
const keyInfo = { | ||
name, | ||
id: kid | ||
}; | ||
const batch = this.components.datastore.batch(); | ||
batch.put(dsname, uint8ArrayFromString(pem)); | ||
batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo))); | ||
await batch.commit(); | ||
return keyInfo; | ||
} | ||
/** | ||
* List all the keys. | ||
* | ||
* @returns {Promise<KeyInfo[]>} | ||
*/ | ||
async listKeys() { | ||
const query = { | ||
prefix: infoPrefix | ||
}; | ||
const info = []; | ||
for await (const value of this.components.datastore.query(query)) { | ||
info.push(JSON.parse(uint8ArrayToString(value.value))); | ||
} | ||
return info; | ||
} | ||
/** | ||
* Find a key by it's id | ||
*/ | ||
async findKeyById(id) { | ||
try { | ||
const keys = await this.listKeys(); | ||
const key = keys.find((k) => k.id === id); | ||
if (key == null) { | ||
throw new CodeError(`Key with id '${id}' does not exist.`, codes.ERR_KEY_NOT_FOUND); | ||
} | ||
return key; | ||
} | ||
catch (err) { | ||
await randomDelay(); | ||
throw err; | ||
} | ||
} | ||
/** | ||
* Find a key by it's name. | ||
* | ||
* @param {string} name - The local key name. | ||
* @returns {Promise<KeyInfo>} | ||
*/ | ||
async findKeyByName(name) { | ||
async exportKey(name) { | ||
if (!validateKeyName(name)) { | ||
await randomDelay(); | ||
throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME); | ||
throw new InvalidParametersError(`Invalid key name '${name}'`); | ||
} | ||
const dsname = DsInfoName(name); | ||
const dsname = DsName(name); | ||
try { | ||
const res = await this.components.datastore.get(dsname); | ||
return JSON.parse(uint8ArrayToString(res)); | ||
const pem = uint8ArrayToString(res); | ||
const cached = privates.get(this); | ||
if (cached == null) { | ||
throw new InvalidParametersError('dek missing'); | ||
} | ||
const dek = cached.dek; | ||
return await importPrivateKey(pem, dek); | ||
} | ||
catch (err) { | ||
await randomDelay(); | ||
this.log.error(err); | ||
throw new CodeError(`Key '${name}' does not exist.`, codes.ERR_KEY_NOT_FOUND); | ||
throw err; | ||
} | ||
} | ||
/** | ||
* Remove an existing key. | ||
* | ||
* @param {string} name - The local key name; must already exist. | ||
* @returns {Promise<KeyInfo>} | ||
*/ | ||
async removeKey(name) { | ||
if (!validateKeyName(name) || name === 'self') { | ||
await randomDelay(); | ||
throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME); | ||
throw new InvalidParametersError(`Invalid key name '${name}'`); | ||
} | ||
@@ -260,2 +245,17 @@ const dsname = DsName(name); | ||
/** | ||
* List all the keys. | ||
* | ||
* @returns {Promise<KeyInfo[]>} | ||
*/ | ||
async listKeys() { | ||
const query = { | ||
prefix: infoPrefix | ||
}; | ||
const info = []; | ||
for await (const value of this.components.datastore.query(query)) { | ||
info.push(JSON.parse(uint8ArrayToString(value.value))); | ||
} | ||
return info; | ||
} | ||
/** | ||
* Rename a key | ||
@@ -270,7 +270,7 @@ * | ||
await randomDelay(); | ||
throw new CodeError(`Invalid old key name '${oldName}'`, codes.ERR_OLD_KEY_NAME_INVALID); | ||
throw new InvalidParametersError(`Invalid old key name '${oldName}'`); | ||
} | ||
if (!validateKeyName(newName) || newName === 'self') { | ||
await randomDelay(); | ||
throw new CodeError(`Invalid new key name '${newName}'`, codes.ERR_NEW_KEY_NAME_INVALID); | ||
throw new InvalidParametersError(`Invalid new key name '${newName}'`); | ||
} | ||
@@ -284,3 +284,3 @@ const oldDsname = DsName(oldName); | ||
await randomDelay(); | ||
throw new CodeError(`Key '${newName}' already exists`, codes.ERR_KEY_ALREADY_EXISTS); | ||
throw new InvalidParametersError(`Key '${newName}' already exists`); | ||
} | ||
@@ -306,157 +306,2 @@ try { | ||
/** | ||
* Export an existing key as a PEM encrypted PKCS #8 string | ||
*/ | ||
async exportKey(name, password) { | ||
if (!validateKeyName(name)) { | ||
await randomDelay(); | ||
throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME); | ||
} | ||
if (password == null) { | ||
await randomDelay(); | ||
throw new CodeError('Password is required', codes.ERR_PASSWORD_REQUIRED); | ||
} | ||
const dsname = DsName(name); | ||
try { | ||
const res = await this.components.datastore.get(dsname); | ||
const pem = uint8ArrayToString(res); | ||
const cached = privates.get(this); | ||
if (cached == null) { | ||
throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS); | ||
} | ||
const dek = cached.dek; | ||
const privateKey = await importKey(pem, dek); | ||
const keyString = await privateKey.export(password); | ||
return keyString; | ||
} | ||
catch (err) { | ||
await randomDelay(); | ||
throw err; | ||
} | ||
} | ||
/** | ||
* Export an existing key as a PeerId | ||
*/ | ||
async exportPeerId(name) { | ||
const password = 'temporary-password'; | ||
const pem = await this.exportKey(name, password); | ||
const privateKey = await importKey(pem, password); | ||
return peerIdFromKeys(privateKey.public.bytes, privateKey.bytes); | ||
} | ||
/** | ||
* Import a new key from a PEM encoded PKCS #8 string | ||
* | ||
* @param {string} name - The local key name; must not already exist. | ||
* @param {string} pem - The PEM encoded PKCS #8 string | ||
* @param {string} password - The password. | ||
* @returns {Promise<KeyInfo>} | ||
*/ | ||
async importKey(name, pem, password) { | ||
if (!validateKeyName(name) || name === 'self') { | ||
await randomDelay(); | ||
throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME); | ||
} | ||
if (pem == null) { | ||
await randomDelay(); | ||
throw new CodeError('PEM encoded key is required', codes.ERR_PEM_REQUIRED); | ||
} | ||
const dsname = DsName(name); | ||
const exists = await this.components.datastore.has(dsname); | ||
if (exists) { | ||
await randomDelay(); | ||
throw new CodeError(`Key '${name}' already exists`, codes.ERR_KEY_ALREADY_EXISTS); | ||
} | ||
let privateKey; | ||
try { | ||
privateKey = await importKey(pem, password); | ||
} | ||
catch (err) { | ||
await randomDelay(); | ||
throw new CodeError('Cannot read the key, most likely the password is wrong', codes.ERR_CANNOT_READ_KEY); | ||
} | ||
let kid; | ||
try { | ||
kid = await privateKey.id(); | ||
const cached = privates.get(this); | ||
if (cached == null) { | ||
throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS); | ||
} | ||
const dek = cached.dek; | ||
pem = await privateKey.export(dek); | ||
} | ||
catch (err) { | ||
await randomDelay(); | ||
throw err; | ||
} | ||
const keyInfo = { | ||
name, | ||
id: kid | ||
}; | ||
const batch = this.components.datastore.batch(); | ||
batch.put(dsname, uint8ArrayFromString(pem)); | ||
batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo))); | ||
await batch.commit(); | ||
return keyInfo; | ||
} | ||
/** | ||
* Import a peer key | ||
*/ | ||
async importPeer(name, peer) { | ||
try { | ||
if (!validateKeyName(name)) { | ||
throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME); | ||
} | ||
if (peer == null) { | ||
throw new CodeError('PeerId is required', codes.ERR_MISSING_PRIVATE_KEY); | ||
} | ||
if (peer.privateKey == null) { | ||
throw new CodeError('PeerId.privKey is required', codes.ERR_MISSING_PRIVATE_KEY); | ||
} | ||
const privateKey = await unmarshalPrivateKey(peer.privateKey); | ||
const dsname = DsName(name); | ||
const exists = await this.components.datastore.has(dsname); | ||
if (exists) { | ||
await randomDelay(); | ||
throw new CodeError(`Key '${name}' already exists`, codes.ERR_KEY_ALREADY_EXISTS); | ||
} | ||
const cached = privates.get(this); | ||
if (cached == null) { | ||
throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS); | ||
} | ||
const dek = cached.dek; | ||
const pem = await privateKey.export(dek); | ||
const keyInfo = { | ||
name, | ||
id: peer.toString() | ||
}; | ||
const batch = this.components.datastore.batch(); | ||
batch.put(dsname, uint8ArrayFromString(pem)); | ||
batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo))); | ||
await batch.commit(); | ||
return keyInfo; | ||
} | ||
catch (err) { | ||
await randomDelay(); | ||
throw err; | ||
} | ||
} | ||
/** | ||
* Gets the private key as PEM encoded PKCS #8 string | ||
*/ | ||
async getPrivateKey(name) { | ||
if (!validateKeyName(name)) { | ||
await randomDelay(); | ||
throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME); | ||
} | ||
try { | ||
const dsname = DsName(name); | ||
const res = await this.components.datastore.get(dsname); | ||
return uint8ArrayToString(res); | ||
} | ||
catch (err) { | ||
await randomDelay(); | ||
this.log.error(err); | ||
throw new CodeError(`Key '${name}' does not exist.`, codes.ERR_KEY_NOT_FOUND); | ||
} | ||
} | ||
/** | ||
* Rotate keychain password and re-encrypt all associated keys | ||
@@ -467,11 +312,11 @@ */ | ||
await randomDelay(); | ||
throw new CodeError(`Invalid old pass type '${typeof oldPass}'`, codes.ERR_INVALID_OLD_PASS_TYPE); | ||
throw new InvalidParametersError(`Invalid old pass type '${typeof oldPass}'`); | ||
} | ||
if (typeof newPass !== 'string') { | ||
await randomDelay(); | ||
throw new CodeError(`Invalid new pass type '${typeof newPass}'`, codes.ERR_INVALID_NEW_PASS_TYPE); | ||
throw new InvalidParametersError(`Invalid new pass type '${typeof newPass}'`); | ||
} | ||
if (newPass.length < 20) { | ||
await randomDelay(); | ||
throw new CodeError(`Invalid pass length ${newPass.length}`, codes.ERR_INVALID_PASS_LENGTH); | ||
throw new InvalidParametersError(`Invalid pass length ${newPass.length}`); | ||
} | ||
@@ -481,3 +326,3 @@ this.log('recreating keychain'); | ||
if (cached == null) { | ||
throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS); | ||
throw new InvalidParametersError('dek missing'); | ||
} | ||
@@ -494,5 +339,5 @@ const oldDek = cached.dek; | ||
const pem = uint8ArrayToString(res); | ||
const privateKey = await importKey(pem, oldDek); | ||
const privateKey = await importPrivateKey(pem, oldDek); | ||
const password = newDek.toString(); | ||
const keyAsPEM = await privateKey.export(password); | ||
const keyAsPEM = await exportPrivateKey(privateKey, password, privateKey.type === 'RSA' ? 'pkcs-8' : 'libp2p-key'); | ||
// Update stored key | ||
@@ -499,0 +344,0 @@ const batch = this.components.datastore.batch(); |
{ | ||
"name": "@libp2p/keychain", | ||
"version": "4.1.6-2265e59ba", | ||
"version": "4.1.6-2bbaf4361", | ||
"description": "Key management and cryptographically protected messages", | ||
@@ -62,8 +62,9 @@ "license": "Apache-2.0 OR MIT", | ||
"dependencies": { | ||
"@libp2p/crypto": "4.1.9-2265e59ba", | ||
"@libp2p/interface": "1.7.0-2265e59ba", | ||
"@libp2p/peer-id": "4.2.4-2265e59ba", | ||
"interface-datastore": "^8.2.11", | ||
"@libp2p/crypto": "4.1.9-2bbaf4361", | ||
"@libp2p/interface": "1.7.0-2bbaf4361", | ||
"@noble/hashes": "^1.5.0", | ||
"asn1js": "^3.0.5", | ||
"interface-datastore": "^8.3.0", | ||
"merge-options": "^3.0.4", | ||
"multiformats": "^13.1.0", | ||
"multiformats": "^13.2.2", | ||
"sanitize-filename": "^1.6.3", | ||
@@ -73,8 +74,7 @@ "uint8arrays": "^5.1.0" | ||
"devDependencies": { | ||
"@libp2p/logger": "4.0.20-2265e59ba", | ||
"@libp2p/peer-id-factory": "4.2.4-2265e59ba", | ||
"@libp2p/logger": "4.0.20-2bbaf4361", | ||
"aegir": "^44.0.1", | ||
"datastore-core": "^9.2.9" | ||
"datastore-core": "^10.0.0" | ||
}, | ||
"sideEffects": false | ||
} |
@@ -54,6 +54,5 @@ /** | ||
import { DefaultKeychain } from './keychain.js' | ||
import type { ComponentLogger, KeyType, PeerId } from '@libp2p/interface' | ||
import { Keychain as KeychainClass } from './keychain.js' | ||
import type { ComponentLogger, PrivateKey } from '@libp2p/interface' | ||
import type { Datastore } from 'interface-datastore' | ||
import type { Multibase } from 'multiformats/bases/interface.js' | ||
@@ -91,3 +90,3 @@ export interface DEKConfig { | ||
/** | ||
* Export an existing key as a PEM encrypted PKCS #8 string. | ||
* Find a key by name | ||
* | ||
@@ -97,10 +96,13 @@ * @example | ||
* ```TypeScript | ||
* await libp2p.services.keychain.createKey('keyTest', 'RSA', 4096) | ||
* const pemKey = await libp2p.services.keychain.exportKey('keyTest', 'password123') | ||
* import { generateKeyPair } from '@libp2p/crypto/keys' | ||
* | ||
* const key = await generateKeyPair('Ed25519') | ||
* const keyInfo = await libp2p.keychain.importKey('my-key', key) | ||
* const keyInfo2 = await libp2p.keychain.findKeyByName(keyInfo.name) | ||
* ``` | ||
*/ | ||
exportKey(name: string, password: string): Promise<Multibase<'m'>> | ||
findKeyByName(name: string): Promise<KeyInfo> | ||
/** | ||
* Import a new key from a PEM encoded PKCS #8 string. | ||
* Find a key by id | ||
* | ||
@@ -110,11 +112,13 @@ * @example | ||
* ```TypeScript | ||
* await libp2p.services.keychain.createKey('keyTest', 'RSA', 4096) | ||
* const pemKey = await libp2p.services.keychain.exportKey('keyTest', 'password123') | ||
* const keyInfo = await libp2p.services.keychain.importKey('keyTestImport', pemKey, 'password123') | ||
* import { generateKeyPair } from '@libp2p/crypto/keys' | ||
* | ||
* const key = await generateKeyPair('Ed25519') | ||
* const keyInfo = await libp2p.keychain.importKey('my-key', key) | ||
* const keyInfo2 = await libp2p.keychain.findKeyById(keyInfo.id) | ||
* ``` | ||
*/ | ||
importKey(name: string, pem: string, password: string): Promise<KeyInfo> | ||
findKeyById (id: string): Promise<KeyInfo> | ||
/** | ||
* Import a new key from a PeerId with a private key component | ||
* Import a new private key. | ||
* | ||
@@ -124,20 +128,12 @@ * @example | ||
* ```TypeScript | ||
* const keyInfo = await libp2p.services.keychain.importPeer('keyTestImport', peerIdFromString('12D3Foo...')) | ||
* ``` | ||
*/ | ||
importPeer(name: string, peerId: PeerId): Promise<KeyInfo> | ||
/** | ||
* Export an existing key as a PeerId | ||
* import { generateKeyPair } from '@libp2p/crypto/keys' | ||
* | ||
* @example | ||
* | ||
* ```TypeScript | ||
* const peerId = await libp2p.services.keychain.exportPeerId('key-name') | ||
* const key = await generateKeyPair('Ed25519') | ||
* const keyInfo = await libp2p.keychain.importKey('my-key', key) | ||
* ``` | ||
*/ | ||
exportPeerId(name: string): Promise<PeerId> | ||
importKey(name: string, key: PrivateKey): Promise<KeyInfo> | ||
/** | ||
* Create a key in the keychain. | ||
* Export an existing private key. | ||
* | ||
@@ -147,17 +143,10 @@ * @example | ||
* ```TypeScript | ||
* const keyInfo = await libp2p.services.keychain.createKey('keyTest', 'RSA', 4096) | ||
* ``` | ||
*/ | ||
createKey(name: string, type: KeyType, size?: number): Promise<KeyInfo> | ||
/** | ||
* List all the keys. | ||
* import { generateKeyPair } from '@libp2p/crypto/keys' | ||
* | ||
* @example | ||
* | ||
* ```TypeScript | ||
* const keyInfos = await libp2p.services.keychain.listKeys() | ||
* const key = await generateKeyPair('Ed25519') | ||
* const keyInfo = await libp2p.keychain.importKey('my-key', key) | ||
* const key = await libp2p.keychain.exportKey(keyInfo.id) | ||
* ``` | ||
*/ | ||
listKeys(): Promise<KeyInfo[]> | ||
exportKey(name: string): Promise<PrivateKey> | ||
@@ -177,3 +166,4 @@ /** | ||
/** | ||
* Rename a key in the keychain. | ||
* Rename a key in the keychain. This is done in a batch commit with rollback | ||
* so errors thrown during the operation will not cause key loss. | ||
* | ||
@@ -190,3 +180,3 @@ * @example | ||
/** | ||
* Find a key by it's id. | ||
* List all the keys. | ||
* | ||
@@ -196,21 +186,8 @@ * @example | ||
* ```TypeScript | ||
* const keyInfo = await libp2p.services.keychain.createKey('keyTest', 'RSA', 4096) | ||
* const keyInfo2 = await libp2p.services.keychain.findKeyById(keyInfo.id) | ||
* const keyInfos = await libp2p.keychain.listKeys() | ||
* ``` | ||
*/ | ||
findKeyById(id: string): Promise<KeyInfo> | ||
listKeys(): Promise<KeyInfo[]> | ||
/** | ||
* Find a key by it's name. | ||
* | ||
* @example | ||
* | ||
* ```TypeScript | ||
* const keyInfo = await libp2p.services.keychain.createKey('keyTest', 'RSA', 4096) | ||
* const keyInfo2 = await libp2p.services.keychain.findKeyByName('keyTest') | ||
* ``` | ||
*/ | ||
findKeyByName(name: string): Promise<KeyInfo> | ||
/** | ||
* Rotate keychain password and re-encrypt all associated keys | ||
@@ -229,4 +206,4 @@ * | ||
return (components: KeychainComponents) => { | ||
return new DefaultKeychain(components, init) | ||
return new KeychainClass(components, init) | ||
} | ||
} |
/* eslint max-nested-callbacks: ["error", 5] */ | ||
import { pbkdf2, randomBytes } from '@libp2p/crypto' | ||
import { generateKeyPair, importKey, unmarshalPrivateKey } from '@libp2p/crypto/keys' | ||
import { CodeError, serviceCapabilities } from '@libp2p/interface' | ||
import { peerIdFromKeys } from '@libp2p/peer-id' | ||
import { privateKeyToProtobuf } from '@libp2p/crypto/keys' | ||
import { InvalidParametersError, NotFoundError, serviceCapabilities } from '@libp2p/interface' | ||
import { Key } from 'interface-datastore/key' | ||
import mergeOptions from 'merge-options' | ||
import { base58btc } from 'multiformats/bases/base58' | ||
import { sha256 } from 'multiformats/hashes/sha2' | ||
import sanitize from 'sanitize-filename' | ||
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' | ||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string' | ||
import { codes } from './errors.js' | ||
import type { KeychainComponents, KeychainInit, Keychain, KeyInfo } from './index.js' | ||
import type { Logger, KeyType, PeerId } from '@libp2p/interface' | ||
import { exportPrivateKey } from './utils/export.js' | ||
import { importPrivateKey } from './utils/import.js' | ||
import type { KeychainComponents, KeychainInit, Keychain as KeychainInterface, KeyInfo } from './index.js' | ||
import type { Logger, PrivateKey } from '@libp2p/interface' | ||
@@ -75,2 +77,9 @@ const keyPrefix = '/pkcs8/' | ||
export async function keyId (key: PrivateKey): Promise<string> { | ||
const pb = privateKeyToProtobuf(key) | ||
const hash = await sha256.digest(pb) | ||
return base58btc.encode(hash.bytes).substring(1) | ||
} | ||
/** | ||
@@ -84,3 +93,3 @@ * Manages the lifecycle of a key. Keys are encrypted at rest using PKCS #8. | ||
*/ | ||
export class DefaultKeychain implements Keychain { | ||
export class Keychain implements KeychainInterface { | ||
private readonly components: KeychainComponents | ||
@@ -152,59 +161,35 @@ private readonly init: KeychainInit | ||
/** | ||
* Create a new key. | ||
* | ||
* @param {string} name - The local key name; cannot already exist. | ||
* @param {string} type - One of the key types; 'rsa'. | ||
* @param {number} [size = 2048] - The key size in bits. Used for rsa keys only | ||
*/ | ||
async createKey (name: string, type: KeyType, size = 2048): Promise<KeyInfo> { | ||
if (!validateKeyName(name) || name === 'self') { | ||
async findKeyByName (name: string): Promise<KeyInfo> { | ||
if (!validateKeyName(name)) { | ||
await randomDelay() | ||
throw new CodeError('Invalid key name', codes.ERR_INVALID_KEY_NAME) | ||
throw new InvalidParametersError(`Invalid key name '${name}'`) | ||
} | ||
if (typeof type !== 'string') { | ||
await randomDelay() | ||
throw new CodeError('Invalid key type', codes.ERR_INVALID_KEY_TYPE) | ||
} | ||
const dsname = DsInfoName(name) | ||
const dsname = DsName(name) | ||
const exists = await this.components.datastore.has(dsname) | ||
if (exists) { | ||
try { | ||
const res = await this.components.datastore.get(dsname) | ||
return JSON.parse(uint8ArrayToString(res)) | ||
} catch (err: any) { | ||
await randomDelay() | ||
throw new CodeError('Key name already exists', codes.ERR_KEY_ALREADY_EXISTS) | ||
this.log.error(err) | ||
throw new NotFoundError(`Key '${name}' does not exist.`) | ||
} | ||
} | ||
switch (type.toLowerCase()) { | ||
case 'rsa': | ||
if (!Number.isSafeInteger(size) || size < 2048) { | ||
await randomDelay() | ||
throw new CodeError('Invalid RSA key size', codes.ERR_INVALID_KEY_SIZE) | ||
} | ||
break | ||
default: | ||
break | ||
} | ||
let keyInfo | ||
async findKeyById (id: string): Promise<KeyInfo> { | ||
try { | ||
const keypair = await generateKeyPair(type, size) | ||
const kid = await keypair.id() | ||
const cached = privates.get(this) | ||
if (cached == null) { | ||
throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS) | ||
const query = { | ||
prefix: infoPrefix | ||
} | ||
const dek = cached.dek | ||
const pem = await keypair.export(dek) | ||
keyInfo = { | ||
name, | ||
id: kid | ||
for await (const value of this.components.datastore.query(query)) { | ||
const key = JSON.parse(uint8ArrayToString(value.value)) | ||
if (key.id === id) { | ||
return key | ||
} | ||
} | ||
const batch = this.components.datastore.batch() | ||
batch.put(dsname, uint8ArrayFromString(pem)) | ||
batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo))) | ||
await batch.commit() | ||
throw new InvalidParametersError(`Key with id '${id}' does not exist.`) | ||
} catch (err: any) { | ||
@@ -214,37 +199,32 @@ await randomDelay() | ||
} | ||
return keyInfo | ||
} | ||
/** | ||
* List all the keys. | ||
* | ||
* @returns {Promise<KeyInfo[]>} | ||
*/ | ||
async listKeys (): Promise<KeyInfo[]> { | ||
const query = { | ||
prefix: infoPrefix | ||
async importKey (name: string, key: PrivateKey): Promise<KeyInfo> { | ||
if (!validateKeyName(name) || name === 'self') { | ||
await randomDelay() | ||
throw new InvalidParametersError(`Invalid key name '${name}'`) | ||
} | ||
const info = [] | ||
for await (const value of this.components.datastore.query(query)) { | ||
info.push(JSON.parse(uint8ArrayToString(value.value))) | ||
if (key == null) { | ||
await randomDelay() | ||
throw new InvalidParametersError('Key is required') | ||
} | ||
const dsname = DsName(name) | ||
const exists = await this.components.datastore.has(dsname) | ||
if (exists) { | ||
await randomDelay() | ||
throw new InvalidParametersError(`Key '${name}' already exists`) | ||
} | ||
return info | ||
} | ||
/** | ||
* Find a key by it's id | ||
*/ | ||
async findKeyById (id: string): Promise<KeyInfo> { | ||
let kid: string | ||
let pem: string | ||
try { | ||
const keys = await this.listKeys() | ||
const key = keys.find((k) => k.id === id) | ||
kid = await keyId(key) | ||
const cached = privates.get(this) | ||
if (key == null) { | ||
throw new CodeError(`Key with id '${id}' does not exist.`, codes.ERR_KEY_NOT_FOUND) | ||
if (cached == null) { | ||
throw new InvalidParametersError('dek missing') | ||
} | ||
return key | ||
const dek = cached.dek | ||
pem = await exportPrivateKey(key, dek, key.type === 'RSA' ? 'pkcs-8' : 'libp2p-key') | ||
} catch (err: any) { | ||
@@ -254,38 +234,46 @@ await randomDelay() | ||
} | ||
const keyInfo = { | ||
name, | ||
id: kid | ||
} | ||
const batch = this.components.datastore.batch() | ||
batch.put(dsname, uint8ArrayFromString(pem)) | ||
batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo))) | ||
await batch.commit() | ||
return keyInfo | ||
} | ||
/** | ||
* Find a key by it's name. | ||
* | ||
* @param {string} name - The local key name. | ||
* @returns {Promise<KeyInfo>} | ||
*/ | ||
async findKeyByName (name: string): Promise<KeyInfo> { | ||
async exportKey (name: string): Promise<PrivateKey> { | ||
if (!validateKeyName(name)) { | ||
await randomDelay() | ||
throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME) | ||
throw new InvalidParametersError(`Invalid key name '${name}'`) | ||
} | ||
const dsname = DsInfoName(name) | ||
const dsname = DsName(name) | ||
try { | ||
const res = await this.components.datastore.get(dsname) | ||
return JSON.parse(uint8ArrayToString(res)) | ||
const pem = uint8ArrayToString(res) | ||
const cached = privates.get(this) | ||
if (cached == null) { | ||
throw new InvalidParametersError('dek missing') | ||
} | ||
const dek = cached.dek | ||
return await importPrivateKey(pem, dek) | ||
} catch (err: any) { | ||
await randomDelay() | ||
this.log.error(err) | ||
throw new CodeError(`Key '${name}' does not exist.`, codes.ERR_KEY_NOT_FOUND) | ||
throw err | ||
} | ||
} | ||
/** | ||
* Remove an existing key. | ||
* | ||
* @param {string} name - The local key name; must already exist. | ||
* @returns {Promise<KeyInfo>} | ||
*/ | ||
async removeKey (name: string): Promise<KeyInfo> { | ||
if (!validateKeyName(name) || name === 'self') { | ||
await randomDelay() | ||
throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME) | ||
throw new InvalidParametersError(`Invalid key name '${name}'`) | ||
} | ||
const dsname = DsName(name) | ||
@@ -297,2 +285,3 @@ const keyInfo = await this.findKeyByName(name) | ||
await batch.commit() | ||
return keyInfo | ||
@@ -302,2 +291,20 @@ } | ||
/** | ||
* List all the keys. | ||
* | ||
* @returns {Promise<KeyInfo[]>} | ||
*/ | ||
async listKeys (): Promise<KeyInfo[]> { | ||
const query = { | ||
prefix: infoPrefix | ||
} | ||
const info = [] | ||
for await (const value of this.components.datastore.query(query)) { | ||
info.push(JSON.parse(uint8ArrayToString(value.value))) | ||
} | ||
return info | ||
} | ||
/** | ||
* Rename a key | ||
@@ -312,7 +319,7 @@ * | ||
await randomDelay() | ||
throw new CodeError(`Invalid old key name '${oldName}'`, codes.ERR_OLD_KEY_NAME_INVALID) | ||
throw new InvalidParametersError(`Invalid old key name '${oldName}'`) | ||
} | ||
if (!validateKeyName(newName) || newName === 'self') { | ||
await randomDelay() | ||
throw new CodeError(`Invalid new key name '${newName}'`, codes.ERR_NEW_KEY_NAME_INVALID) | ||
throw new InvalidParametersError(`Invalid new key name '${newName}'`) | ||
} | ||
@@ -327,3 +334,3 @@ const oldDsname = DsName(oldName) | ||
await randomDelay() | ||
throw new CodeError(`Key '${newName}' already exists`, codes.ERR_KEY_ALREADY_EXISTS) | ||
throw new InvalidParametersError(`Key '${newName}' already exists`) | ||
} | ||
@@ -351,174 +358,2 @@ | ||
/** | ||
* Export an existing key as a PEM encrypted PKCS #8 string | ||
*/ | ||
async exportKey (name: string, password: string): Promise<string> { | ||
if (!validateKeyName(name)) { | ||
await randomDelay() | ||
throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME) | ||
} | ||
if (password == null) { | ||
await randomDelay() | ||
throw new CodeError('Password is required', codes.ERR_PASSWORD_REQUIRED) | ||
} | ||
const dsname = DsName(name) | ||
try { | ||
const res = await this.components.datastore.get(dsname) | ||
const pem = uint8ArrayToString(res) | ||
const cached = privates.get(this) | ||
if (cached == null) { | ||
throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS) | ||
} | ||
const dek = cached.dek | ||
const privateKey = await importKey(pem, dek) | ||
const keyString = await privateKey.export(password) | ||
return keyString | ||
} catch (err: any) { | ||
await randomDelay() | ||
throw err | ||
} | ||
} | ||
/** | ||
* Export an existing key as a PeerId | ||
*/ | ||
async exportPeerId (name: string): Promise<PeerId> { | ||
const password = 'temporary-password' | ||
const pem = await this.exportKey(name, password) | ||
const privateKey = await importKey(pem, password) | ||
return peerIdFromKeys(privateKey.public.bytes, privateKey.bytes) | ||
} | ||
/** | ||
* Import a new key from a PEM encoded PKCS #8 string | ||
* | ||
* @param {string} name - The local key name; must not already exist. | ||
* @param {string} pem - The PEM encoded PKCS #8 string | ||
* @param {string} password - The password. | ||
* @returns {Promise<KeyInfo>} | ||
*/ | ||
async importKey (name: string, pem: string, password: string): Promise<KeyInfo> { | ||
if (!validateKeyName(name) || name === 'self') { | ||
await randomDelay() | ||
throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME) | ||
} | ||
if (pem == null) { | ||
await randomDelay() | ||
throw new CodeError('PEM encoded key is required', codes.ERR_PEM_REQUIRED) | ||
} | ||
const dsname = DsName(name) | ||
const exists = await this.components.datastore.has(dsname) | ||
if (exists) { | ||
await randomDelay() | ||
throw new CodeError(`Key '${name}' already exists`, codes.ERR_KEY_ALREADY_EXISTS) | ||
} | ||
let privateKey | ||
try { | ||
privateKey = await importKey(pem, password) | ||
} catch (err: any) { | ||
await randomDelay() | ||
throw new CodeError('Cannot read the key, most likely the password is wrong', codes.ERR_CANNOT_READ_KEY) | ||
} | ||
let kid | ||
try { | ||
kid = await privateKey.id() | ||
const cached = privates.get(this) | ||
if (cached == null) { | ||
throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS) | ||
} | ||
const dek = cached.dek | ||
pem = await privateKey.export(dek) | ||
} catch (err: any) { | ||
await randomDelay() | ||
throw err | ||
} | ||
const keyInfo = { | ||
name, | ||
id: kid | ||
} | ||
const batch = this.components.datastore.batch() | ||
batch.put(dsname, uint8ArrayFromString(pem)) | ||
batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo))) | ||
await batch.commit() | ||
return keyInfo | ||
} | ||
/** | ||
* Import a peer key | ||
*/ | ||
async importPeer (name: string, peer: PeerId): Promise<KeyInfo> { | ||
try { | ||
if (!validateKeyName(name)) { | ||
throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME) | ||
} | ||
if (peer == null) { | ||
throw new CodeError('PeerId is required', codes.ERR_MISSING_PRIVATE_KEY) | ||
} | ||
if (peer.privateKey == null) { | ||
throw new CodeError('PeerId.privKey is required', codes.ERR_MISSING_PRIVATE_KEY) | ||
} | ||
const privateKey = await unmarshalPrivateKey(peer.privateKey) | ||
const dsname = DsName(name) | ||
const exists = await this.components.datastore.has(dsname) | ||
if (exists) { | ||
await randomDelay() | ||
throw new CodeError(`Key '${name}' already exists`, codes.ERR_KEY_ALREADY_EXISTS) | ||
} | ||
const cached = privates.get(this) | ||
if (cached == null) { | ||
throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS) | ||
} | ||
const dek = cached.dek | ||
const pem = await privateKey.export(dek) | ||
const keyInfo: KeyInfo = { | ||
name, | ||
id: peer.toString() | ||
} | ||
const batch = this.components.datastore.batch() | ||
batch.put(dsname, uint8ArrayFromString(pem)) | ||
batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo))) | ||
await batch.commit() | ||
return keyInfo | ||
} catch (err: any) { | ||
await randomDelay() | ||
throw err | ||
} | ||
} | ||
/** | ||
* Gets the private key as PEM encoded PKCS #8 string | ||
*/ | ||
async getPrivateKey (name: string): Promise<string> { | ||
if (!validateKeyName(name)) { | ||
await randomDelay() | ||
throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME) | ||
} | ||
try { | ||
const dsname = DsName(name) | ||
const res = await this.components.datastore.get(dsname) | ||
return uint8ArrayToString(res) | ||
} catch (err: any) { | ||
await randomDelay() | ||
this.log.error(err) | ||
throw new CodeError(`Key '${name}' does not exist.`, codes.ERR_KEY_NOT_FOUND) | ||
} | ||
} | ||
/** | ||
* Rotate keychain password and re-encrypt all associated keys | ||
@@ -529,11 +364,11 @@ */ | ||
await randomDelay() | ||
throw new CodeError(`Invalid old pass type '${typeof oldPass}'`, codes.ERR_INVALID_OLD_PASS_TYPE) | ||
throw new InvalidParametersError(`Invalid old pass type '${typeof oldPass}'`) | ||
} | ||
if (typeof newPass !== 'string') { | ||
await randomDelay() | ||
throw new CodeError(`Invalid new pass type '${typeof newPass}'`, codes.ERR_INVALID_NEW_PASS_TYPE) | ||
throw new InvalidParametersError(`Invalid new pass type '${typeof newPass}'`) | ||
} | ||
if (newPass.length < 20) { | ||
await randomDelay() | ||
throw new CodeError(`Invalid pass length ${newPass.length}`, codes.ERR_INVALID_PASS_LENGTH) | ||
throw new InvalidParametersError(`Invalid pass length ${newPass.length}`) | ||
} | ||
@@ -544,3 +379,3 @@ this.log('recreating keychain') | ||
if (cached == null) { | ||
throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS) | ||
throw new InvalidParametersError('dek missing') | ||
} | ||
@@ -563,5 +398,5 @@ | ||
const pem = uint8ArrayToString(res) | ||
const privateKey = await importKey(pem, oldDek) | ||
const privateKey = await importPrivateKey(pem, oldDek) | ||
const password = newDek.toString() | ||
const keyAsPEM = await privateKey.export(password) | ||
const keyAsPEM = await exportPrivateKey(privateKey, password, privateKey.type === 'RSA' ? 'pkcs-8' : 'libp2p-key') | ||
@@ -568,0 +403,0 @@ // Update stored key |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
258248
3
29
2555
9
+ Added@noble/hashes@^1.5.0
+ Addedasn1js@^3.0.5
+ Added@libp2p/crypto@4.1.9-2bbaf4361(transitive)
+ Added@libp2p/interface@1.7.0-2bbaf4361(transitive)
- Removed@libp2p/peer-id@4.2.4-2265e59ba
- Removed@libp2p/crypto@4.1.9-2265e59ba(transitive)
- Removed@libp2p/interface@1.7.0-2265e59ba(transitive)
- Removed@libp2p/peer-id@4.2.4-2265e59ba(transitive)
Updatedinterface-datastore@^8.3.0
Updatedmultiformats@^13.2.2