From 7273739f045b33a46aae45f5003dd09f7ea6e37e Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Wed, 5 Aug 2020 17:14:12 +0200 Subject: [PATCH] feat: add exporting/importing of non rsa keys in libp2p-key format (#179) * feat: add exporting/importing of ed25519 keys in libp2p-key format * feat: add libp2p-key export/import support for rsa and secp keys * chore: dep bumps * chore: update aegir * refactor: import and export base64 strings * refactor: simplify api for now * chore: fix lint * refactor: remove extraneous param * refactor: clean up * fix: review patches --- README.md | 17 +++-- package.json | 18 ++--- src/ciphers/aes-gcm.browser.js | 89 ++++++++++++++++++++++++ src/ciphers/aes-gcm.js | 120 +++++++++++++++++++++++++++++++++ src/index.d.ts | 29 ++++---- src/keys/ed25519-class.js | 16 +++++ src/keys/exporter.js | 22 ++++++ src/keys/importer.js | 22 ++++++ src/keys/index.js | 19 +++++- src/keys/rsa-class.js | 26 +++---- src/keys/rsa-utils.js | 1 + src/keys/secp256k1-class.js | 18 +++++ test/browser.js | 6 +- test/keys/ed25519.spec.js | 20 ++++++ test/keys/rsa.spec.js | 18 +++++ test/keys/secp256k1.spec.js | 20 ++++++ 16 files changed, 415 insertions(+), 46 deletions(-) create mode 100644 src/ciphers/aes-gcm.browser.js create mode 100644 src/ciphers/aes-gcm.js create mode 100644 src/keys/exporter.js create mode 100644 src/keys/importer.js diff --git a/README.md b/README.md index 9e1c50ad..0662c789 100644 --- a/README.md +++ b/README.md @@ -262,14 +262,23 @@ Returns `Promise` Converts a protobuf serialized private key into its representative object. -### `crypto.keys.import(pem, password)` +### `crypto.keys.import(encryptedKey, password)` -- `pem: string` +- `encryptedKey: string` - `password: string` -Returns `Promise` +Returns `Promise` -Converts a PEM password protected private key into its representative object. +Converts an exported private key into its representative object. Supported formats are 'pem' (RSA only) and 'libp2p-key'. + +### `privateKey.export(password, format)` + +- `password: string` +- `format: string` the format to export to: 'pem' (rsa only), 'libp2p-key' + +Returns `string` + +Exports the password protected `PrivateKey`. RSA keys will be exported as password protected PEM by default. Ed25519 and Secp256k1 keys will be exported as password protected AES-GCM base64 encoded strings ('libp2p-key' format). ### `crypto.randomBytes(number)` diff --git a/package.json b/package.json index 3785f727..30f9d1b4 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,10 @@ "types": "src/index.d.ts", "leadMaintainer": "Jacob Heun ", "browser": { + "./src/aes/ciphers.js": "./src/aes/ciphers-browser.js", + "./src/ciphers/aes-gcm.js": "./src/ciphers/aes-gcm.browser.js", "./src/hmac/index.js": "./src/hmac/index-browser.js", "./src/keys/ecdh.js": "./src/keys/ecdh-browser.js", - "./src/aes/ciphers.js": "./src/aes/ciphers-browser.js", "./src/keys/rsa.js": "./src/keys/rsa-browser.js" }, "files": [ @@ -43,21 +44,22 @@ "is-typedarray": "^1.0.0", "iso-random-stream": "^1.1.0", "keypair": "^1.0.1", - "multibase": "^0.7.0", + "multibase": "^1.0.1", + "multicodec": "^1.0.4", "multihashing-async": "^0.8.1", "node-forge": "^0.9.1", "pem-jwk": "^2.0.0", - "protons": "^1.0.1", + "protons": "^1.2.1", "secp256k1": "^4.0.0", - "ursa-optional": "~0.10.1" + "uint8arrays": "^1.0.0", + "ursa-optional": "^0.10.1" }, "devDependencies": { - "@types/chai": "^4.2.11", + "@types/chai": "^4.2.12", "@types/chai-string": "^1.4.2", "@types/dirty-chai": "^2.0.2", - "@types/mocha": "^7.0.1", - "@types/sinon": "^9.0.0", - "aegir": "^22.0.0", + "@types/mocha": "^8.0.1", + "aegir": "^25.0.0", "benchmark": "^2.1.4", "chai": "^4.2.0", "chai-string": "^1.5.0", diff --git a/src/ciphers/aes-gcm.browser.js b/src/ciphers/aes-gcm.browser.js new file mode 100644 index 00000000..3caf67c6 --- /dev/null +++ b/src/ciphers/aes-gcm.browser.js @@ -0,0 +1,89 @@ +'use strict' + +const concat = require('uint8arrays/concat') +const fromString = require('uint8arrays/from-string') + +const webcrypto = require('../webcrypto') + +// Based off of code from https://github.com/luke-park/SecureCompatibleEncryptionExamples + +/** + * + * @param {object} [options] + * @param {string} [options.algorithm=AES-GCM] + * @param {Number} [options.nonceLength=12] + * @param {Number} [options.keyLength=16] + * @param {string} [options.digest=sha256] + * @param {Number} [options.saltLength=16] + * @param {Number} [options.iterations=32767] + * @returns {*} + */ +function create ({ + algorithm = 'AES-GCM', + nonceLength = 12, + keyLength = 16, + digest = 'SHA-256', + saltLength = 16, + iterations = 32767 +} = {}) { + const crypto = webcrypto.get() + keyLength *= 8 // Browser crypto uses bits instead of bytes + + /** + * Uses the provided password to derive a pbkdf2 key. The key + * will then be used to encrypt the data. + * + * @param {Uint8Array} data The data to decrypt + * @param {string} password A plain password + * @returns {Promise} + */ + async function encrypt (data, password) { // eslint-disable-line require-await + const salt = crypto.getRandomValues(new Uint8Array(saltLength)) + const nonce = crypto.getRandomValues(new Uint8Array(nonceLength)) + const aesGcm = { name: algorithm, iv: nonce } + + // Derive a key using PBKDF2. + const deriveParams = { name: 'PBKDF2', salt, iterations, hash: { name: digest } } + const rawKey = await crypto.subtle.importKey('raw', fromString(password), { name: 'PBKDF2' }, false, ['deriveKey', 'deriveBits']) + const cryptoKey = await crypto.subtle.deriveKey(deriveParams, rawKey, { name: algorithm, length: keyLength }, true, ['encrypt']) + + // Encrypt the string. + const ciphertext = await crypto.subtle.encrypt(aesGcm, cryptoKey, data) + return concat([salt, aesGcm.iv, new Uint8Array(ciphertext)]) + } + + /** + * Uses the provided password to derive a pbkdf2 key. The key + * will then be used to decrypt the data. The options used to create + * this decryption cipher must be the same as those used to create + * the encryption cipher. + * + * @param {Uint8Array} data The data to decrypt + * @param {string} password A plain password + * @returns {Promise} + */ + async function decrypt (data, password) { + const salt = data.slice(0, saltLength) + const nonce = data.slice(saltLength, saltLength + nonceLength) + const ciphertext = data.slice(saltLength + nonceLength) + const aesGcm = { name: algorithm, iv: nonce } + + // Derive the key using PBKDF2. + const deriveParams = { name: 'PBKDF2', salt, iterations, hash: { name: digest } } + const rawKey = await crypto.subtle.importKey('raw', fromString(password), { name: 'PBKDF2' }, false, ['deriveKey', 'deriveBits']) + const cryptoKey = await crypto.subtle.deriveKey(deriveParams, rawKey, { name: algorithm, length: keyLength }, true, ['decrypt']) + + // Decrypt the string. + const plaintext = await crypto.subtle.decrypt(aesGcm, cryptoKey, ciphertext) + return new Uint8Array(plaintext) + } + + return { + encrypt, + decrypt + } +} + +module.exports = { + create +} diff --git a/src/ciphers/aes-gcm.js b/src/ciphers/aes-gcm.js new file mode 100644 index 00000000..ac52c774 --- /dev/null +++ b/src/ciphers/aes-gcm.js @@ -0,0 +1,120 @@ +'use strict' + +const crypto = require('crypto') + +// Based off of code from https://github.com/luke-park/SecureCompatibleEncryptionExamples + +/** + * + * @param {object} [options] + * @param {Number} [options.algorithmTagLength=16] + * @param {Number} [options.nonceLength=12] + * @param {Number} [options.keyLength=16] + * @param {string} [options.digest=sha256] + * @param {Number} [options.saltLength=16] + * @param {Number} [options.iterations=32767] + * @returns {*} + */ +function create ({ + algorithmTagLength = 16, + nonceLength = 12, + keyLength = 16, + digest = 'sha256', + saltLength = 16, + iterations = 32767 +} = {}) { + const algorithm = 'aes-128-gcm' + /** + * + * @private + * @param {Buffer} data + * @param {Buffer} key + * @returns {Promise} + */ + async function encryptWithKey (data, key) { // eslint-disable-line require-await + const nonce = crypto.randomBytes(nonceLength) + + // Create the cipher instance. + const cipher = crypto.createCipheriv(algorithm, key, nonce) + + // Encrypt and prepend nonce. + const ciphertext = Buffer.concat([cipher.update(data), cipher.final()]) + + return Buffer.concat([nonce, ciphertext, cipher.getAuthTag()]) + } + + /** + * Uses the provided password to derive a pbkdf2 key. The key + * will then be used to encrypt the data. + * + * @param {Buffer} data The data to decrypt + * @param {string|Buffer} password A plain password + * @returns {Promise} + */ + async function encrypt (data, password) { // eslint-disable-line require-await + // Generate a 128-bit salt using a CSPRNG. + const salt = crypto.randomBytes(saltLength) + + // Derive a key using PBKDF2. + const key = crypto.pbkdf2Sync(Buffer.from(password), salt, iterations, keyLength, digest) + + // Encrypt and prepend salt. + return Buffer.concat([salt, await encryptWithKey(Buffer.from(data), key)]) + } + + /** + * Decrypts the given cipher text with the provided key. The `key` should + * be a cryptographically safe key and not a plaintext password. To use + * a plaintext password, use `decrypt`. The options used to create + * this decryption cipher must be the same as those used to create + * the encryption cipher. + * + * @private + * @param {Buffer} ciphertextAndNonce The data to decrypt + * @param {Buffer} key + * @returns {Promise} + */ + async function decryptWithKey (ciphertextAndNonce, key) { // eslint-disable-line require-await + // Create buffers of nonce, ciphertext and tag. + const nonce = ciphertextAndNonce.slice(0, nonceLength) + const ciphertext = ciphertextAndNonce.slice(nonceLength, ciphertextAndNonce.length - algorithmTagLength) + const tag = ciphertextAndNonce.slice(ciphertext.length + nonceLength) + + // Create the cipher instance. + const cipher = crypto.createDecipheriv(algorithm, key, nonce) + + // Decrypt and return result. + cipher.setAuthTag(tag) + return Buffer.concat([cipher.update(ciphertext), cipher.final()]) + } + + /** + * Uses the provided password to derive a pbkdf2 key. The key + * will then be used to decrypt the data. The options used to create + * this decryption cipher must be the same as those used to create + * the encryption cipher. + * + * @param {Buffer} data The data to decrypt + * @param {string|Buffer} password A plain password + */ + async function decrypt (data, password) { // eslint-disable-line require-await + // Create buffers of salt and ciphertextAndNonce. + const salt = data.slice(0, saltLength) + const ciphertextAndNonce = data.slice(saltLength) + + // Derive the key using PBKDF2. + const key = crypto.pbkdf2Sync(Buffer.from(password), salt, iterations, keyLength, digest) + + // Decrypt and return result. + return decryptWithKey(ciphertextAndNonce, key) + } + + return { + encrypt, + decrypt + } +} + +module.exports = { + create +} diff --git a/src/index.d.ts b/src/index.d.ts index d9d5d9cb..ff272849 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -94,6 +94,10 @@ export interface PrivateKey { * of the PKCS SubjectPublicKeyInfo. */ id(): Promise; + /** + * Exports the password protected key in the format specified. + */ + export(password: string, format?: "pkcs-8" | string): Promise; } export interface Keystretcher { @@ -132,9 +136,6 @@ export namespace keys { hash(): Promise; } - // Type alias for export method - export type KeyInfo = any; - class RsaPrivateKey implements PrivateKey { constructor(key: any, publicKey: Buffer); readonly public: RsaPublicKey; @@ -146,13 +147,7 @@ export namespace keys { equals(key: PrivateKey): boolean; hash(): Promise; id(): Promise; - /** - * Exports the key into a password protected PEM format - * - * @param password The password to read the encrypted PEM - * @param format Defaults to 'pkcs-8'. - */ - export(password: string, format?: "pkcs-8" | string): KeyInfo; + export(password: string, format?: string): Promise; } function unmarshalRsaPublicKey(buf: Buffer): RsaPublicKey; function unmarshalRsaPrivateKey(buf: Buffer): Promise; @@ -180,6 +175,7 @@ export namespace keys { equals(key: PrivateKey): boolean; hash(): Promise; id(): Promise; + export(password: string, format?: string): Promise; } function unmarshalEd25519PrivateKey( @@ -212,6 +208,7 @@ export namespace keys { equals(key: PrivateKey): boolean; hash(): Promise; id(): Promise; + export(password: string, format?: string): Promise; } function unmarshalSecp256k1PrivateKey( @@ -234,16 +231,14 @@ export namespace keys { bits: number ): Promise; export function generateKeyPair( - type: "Ed25519", - bits: number + type: "Ed25519" ): Promise; - export function generateKeyPair( + export function generateKeyPair( type: "RSA", bits: number ): Promise; - export function generateKeyPair( - type: "secp256k1", - bits: number + export function generateKeyPair( + type: "secp256k1" ): Promise; /** @@ -318,7 +313,7 @@ export namespace keys { * @param pem Password protected private key in PEM format. * @param password The password used to protect the key. */ - function _import(pem: string, password: string): Promise; + function _import(pem: string, password: string, format?: string): Promise; export { _import as import }; } diff --git a/src/keys/ed25519-class.js b/src/keys/ed25519-class.js index 7136cd7f..418aed2c 100644 --- a/src/keys/ed25519-class.js +++ b/src/keys/ed25519-class.js @@ -8,6 +8,7 @@ const errcode = require('err-code') const crypto = require('./ed25519') const pbm = protobuf(require('./keys.proto')) +const exporter = require('./exporter') class Ed25519PublicKey { constructor (key) { @@ -86,6 +87,21 @@ class Ed25519PrivateKey { const hash = await this.public.hash() return multibase.encode('base58btc', hash).toString().slice(1) } + + /** + * Exports the key into a password protected `format` + * + * @param {string} password - The password to encrypt the key + * @param {string} [format=libp2p-key] - The format in which to export as + * @returns {Promise} The encrypted private key + */ + async export (password, format = 'libp2p-key') { // eslint-disable-line require-await + if (format === 'libp2p-key') { + return exporter.export(this.bytes, password) + } else { + throw errcode(new Error(`export format '${format}' is not supported`), 'ERR_INVALID_EXPORT_FORMAT') + } + } } function unmarshalEd25519PrivateKey (bytes) { diff --git a/src/keys/exporter.js b/src/keys/exporter.js new file mode 100644 index 00000000..0ff2f9a5 --- /dev/null +++ b/src/keys/exporter.js @@ -0,0 +1,22 @@ +'use strict' + +const multibase = require('multibase') +const ciphers = require('../ciphers/aes-gcm') + +module.exports = { + /** + * 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. + * + * @param {Buffer} privateKey The PrivateKey protobuf buffer + * @param {string} password + * @returns {Promise} A base64 encoded string + */ + export: async function (privateKey, password) { + const cipher = ciphers.create() + const encryptedKey = await cipher.encrypt(privateKey, password) + const base64 = multibase.names.base64 + return base64.encode(encryptedKey) + } +} diff --git a/src/keys/importer.js b/src/keys/importer.js new file mode 100644 index 00000000..41e89892 --- /dev/null +++ b/src/keys/importer.js @@ -0,0 +1,22 @@ +'use strict' + +const multibase = require('multibase') +const ciphers = require('../ciphers/aes-gcm') + +module.exports = { + /** + * Attempts to decrypt a base64 encoded PrivateKey string + * with the given password. The privateKey must have been exported + * using the same password and underlying cipher (aes-gcm) + * + * @param {string} privateKey A base64 encoded encrypted key + * @param {string} password + * @returns {Promise} The private key protobuf buffer + */ + import: async function (privateKey, password) { + const base64 = multibase.names.base64 + const encryptedKey = base64.decode(privateKey) + const cipher = ciphers.create() + return await cipher.decrypt(encryptedKey, password) + } +} diff --git a/src/keys/index.js b/src/keys/index.js index ad92cd1e..251b267b 100644 --- a/src/keys/index.js +++ b/src/keys/index.js @@ -8,6 +8,8 @@ require('node-forge/lib/pbe') const forge = require('node-forge/lib/forge') const errcode = require('err-code') +const importer = require('./importer') + exports = module.exports const supportedKeys = { @@ -109,8 +111,21 @@ exports.marshalPrivateKey = (key, type) => { return key.bytes } -exports.import = async (pem, password) => { // eslint-disable-line require-await - const key = forge.pki.decryptRsaPrivateKey(pem, password) +/** + * + * @param {string} encryptedKey + * @param {string} password + */ +exports.import = async (encryptedKey, password) => { // eslint-disable-line require-await + try { + const key = await importer.import(encryptedKey, password) + return exports.unmarshalPrivateKey(key) + } catch (_) { + // Ignore and try the old pem decrypt + } + + // Only rsa supports pem right now + const key = forge.pki.decryptRsaPrivateKey(encryptedKey, password) if (key === null) { throw errcode(new Error('Cannot read the key, most likely the password is wrong or not a RSA key'), 'ERR_CANNOT_DECRYPT_PEM') } diff --git a/src/keys/rsa-class.js b/src/keys/rsa-class.js index 9cce1753..4b4884c5 100644 --- a/src/keys/rsa-class.js +++ b/src/keys/rsa-class.js @@ -5,12 +5,14 @@ const protobuf = require('protons') const multibase = require('multibase') const errcode = require('err-code') -const crypto = require('./rsa') -const pbm = protobuf(require('./keys.proto')) require('node-forge/lib/sha512') require('node-forge/lib/ed25519') const forge = require('node-forge/lib/forge') +const crypto = require('./rsa') +const pbm = protobuf(require('./keys.proto')) +const exporter = require('./exporter') + class RsaPublicKey { constructor (key) { this._key = key @@ -109,28 +111,26 @@ class RsaPrivateKey { * Exports the key into a password protected PEM format * * @param {string} password - The password to read the encrypted PEM - * @param {string} [format] - Defaults to 'pkcs-8'. + * @param {string} [format=pkcs-8] - The format in which to export as */ async export (password, format = 'pkcs-8') { // eslint-disable-line require-await - let pem = null - - const buffer = new forge.util.ByteBuffer(this.marshal()) - const asn1 = forge.asn1.fromDer(buffer) - const privateKey = forge.pki.privateKeyFromAsn1(asn1) - if (format === 'pkcs-8') { + const buffer = new forge.util.ByteBuffer(this.marshal()) + const asn1 = forge.asn1.fromDer(buffer) + const privateKey = forge.pki.privateKeyFromAsn1(asn1) + const options = { algorithm: 'aes256', count: 10000, saltSize: 128 / 8, prfAlgorithm: 'sha512' } - pem = forge.pki.encryptRsaPrivateKey(privateKey, password, options) + return forge.pki.encryptRsaPrivateKey(privateKey, password, options) + } else if (format === 'libp2p-key') { + return exporter.export(this.bytes, password) } else { - throw errcode(new Error(`Unknown export format '${format}'. Must be pkcs-8`), 'ERR_INVALID_EXPORT_FORMAT') + throw errcode(new Error(`export format '${format}' is not supported`), 'ERR_INVALID_EXPORT_FORMAT') } - - return pem } } diff --git a/src/keys/rsa-utils.js b/src/keys/rsa-utils.js index da377447..a0d9b45a 100644 --- a/src/keys/rsa-utils.js +++ b/src/keys/rsa-utils.js @@ -8,6 +8,7 @@ const { bigIntegerToUintBase64url, base64urlToBigInteger } = require('./../util' // Convert a PKCS#1 in ASN1 DER format to a JWK key exports.pkcs1ToJwk = function (bytes) { + bytes = Buffer.from(bytes) // convert Uint8Arrays const asn1 = forge.asn1.fromDer(bytes.toString('binary')) const privateKey = forge.pki.privateKeyFromAsn1(asn1) diff --git a/src/keys/secp256k1-class.js b/src/keys/secp256k1-class.js index daa54878..b6ae8009 100644 --- a/src/keys/secp256k1-class.js +++ b/src/keys/secp256k1-class.js @@ -2,6 +2,9 @@ const multibase = require('multibase') const sha = require('multihashing-async/src/sha') +const errcode = require('err-code') + +const exporter = require('./exporter') module.exports = (keysProtobuf, randomBytes, crypto) => { crypto = crypto || require('./secp256k1')(randomBytes) @@ -84,6 +87,21 @@ module.exports = (keysProtobuf, randomBytes, crypto) => { const hash = await this.public.hash() return multibase.encode('base58btc', hash).toString().slice(1) } + + /** + * Exports the key into a password protected `format` + * + * @param {string} password - The password to encrypt the key + * @param {string} [format=libp2p-key] - The format in which to export as + * @returns {Promise} The encrypted private key + */ + async export (password, format = 'libp2p-key') { // eslint-disable-line require-await + if (format === 'libp2p-key') { + return exporter.export(this.bytes, password) + } else { + throw errcode(new Error(`export format '${format}' is not supported`), 'ERR_INVALID_EXPORT_FORMAT') + } + } } function unmarshalSecp256k1PrivateKey (bytes) { diff --git a/test/browser.js b/test/browser.js index 264c41a6..e00e1ebe 100644 --- a/test/browser.js +++ b/test/browser.js @@ -2,9 +2,11 @@ 'use strict' const chai = require('chai') -const dirtyChai = require('dirty-chai') +chai.use(require('dirty-chai')) const expect = chai.expect -chai.use(dirtyChai) + +const { Buffer } = require('buffer') + const crypto = require('../') const webcrypto = require('../src/webcrypto') diff --git a/test/keys/ed25519.spec.js b/test/keys/ed25519.spec.js index 8a7d77cf..0eaa4c13 100644 --- a/test/keys/ed25519.spec.js +++ b/test/keys/ed25519.spec.js @@ -85,6 +85,26 @@ describe('ed25519', function () { expect(id).to.be.a('string') }) + it('should export a password encrypted libp2p-key', async () => { + const key = await crypto.keys.generateKeyPair('Ed25519') + const encryptedKey = await key.export('my secret') + // Import the key + const importedKey = await crypto.keys.import(encryptedKey, 'my secret') + expect(key.equals(importedKey)).to.equal(true) + }) + + it('should fail to import libp2p-key with wrong password', async () => { + const key = await crypto.keys.generateKeyPair('Ed25519') + const encryptedKey = await key.export('my secret', 'libp2p-key') + try { + await crypto.keys.import(encryptedKey, 'not my secret') + } catch (err) { + expect(err).to.exist() + return + } + expect.fail('should have thrown') + }) + describe('key equals', () => { it('equals itself', () => { expect( diff --git a/test/keys/rsa.spec.js b/test/keys/rsa.spec.js index cc4daecd..6d7ba847 100644 --- a/test/keys/rsa.spec.js +++ b/test/keys/rsa.spec.js @@ -135,6 +135,24 @@ describe('RSA', function () { expect(key.equals(clone)).to.eql(true) }) + it('should export a password encrypted libp2p-key', async () => { + const encryptedKey = await key.export('my secret', 'libp2p-key') + // Import the key + const importedKey = await crypto.keys.import(encryptedKey, 'my secret') + expect(key.equals(importedKey)).to.equal(true) + }) + + it('should fail to import libp2p-key with wrong password', async () => { + const encryptedKey = await key.export('my secret', 'libp2p-key') + try { + await crypto.keys.import(encryptedKey, 'not my secret') + } catch (err) { + expect(err).to.exist() + return + } + expect.fail('should have thrown') + }) + it('needs correct password', async () => { const pem = await key.export('another secret') try { diff --git a/test/keys/secp256k1.spec.js b/test/keys/secp256k1.spec.js index e1e3c979..943832bf 100644 --- a/test/keys/secp256k1.spec.js +++ b/test/keys/secp256k1.spec.js @@ -63,6 +63,26 @@ describe('secp256k1 keys', () => { expect(id).to.be.a('string') }) + it('should export a password encrypted libp2p-key', async () => { + const key = await crypto.keys.generateKeyPair('secp256k1') + const encryptedKey = await key.export('my secret') + // Import the key + const importedKey = await crypto.keys.import(encryptedKey, 'my secret') + expect(key.equals(importedKey)).to.equal(true) + }) + + it('should fail to import libp2p-key with wrong password', async () => { + const key = await crypto.keys.generateKeyPair('secp256k1') + const encryptedKey = await key.export('my secret', 'libp2p-key') + try { + await crypto.keys.import(encryptedKey, 'not my secret') + } catch (err) { + expect(err).to.exist() + return + } + expect.fail('should have thrown') + }) + describe('key equals', () => { it('equals itself', () => { expect(key.equals(key)).to.eql(true)