From 51d7ca44c1df94e15d5f267291c5923dd0111610 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Wed, 5 Aug 2020 18:19:10 +0200 Subject: [PATCH] feat(keychain): add support for ed25519 and secp keys (#725) * feat(keychain): add support for ed25519 and secp keys * chore: bump crypto * refactor: cleanup keychain usage --- doc/API.md | 6 ++-- package.json | 2 +- src/keychain/index.js | 13 ++++--- test/keychain/keychain.spec.js | 64 ++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 11 deletions(-) diff --git a/doc/API.md b/doc/API.md index eb0c4d69de..325809d97f 100644 --- a/doc/API.md +++ b/doc/API.md @@ -418,7 +418,7 @@ const latency = await libp2p.ping(otherPeerId) ## multiaddrs -Gets the multiaddrs the libp2p node announces to the network. This computes the advertising multiaddrs +Gets the multiaddrs the libp2p node announces to the network. This computes the advertising multiaddrs of the peer by joining the multiaddrs that libp2p transports are listening on with the announce multiaddrs provided in the libp2p config. Configured no announce multiaddrs will be filtered out of the advertised addresses. @@ -1454,7 +1454,7 @@ Create a key in the keychain. |------|------|-------------| | name | `string` | The local key name. It cannot already exist. | | type | `string` | One of the key types; 'rsa' | -| size | `number` | The key size in bits. | +| [size] | `number` | The key size in bits. Must be provided for rsa keys. | #### Returns @@ -1801,7 +1801,7 @@ console.log(peerStats.toJSON()) ## Events -Once you have a libp2p instance, you can listen to several events it emits, so that you can be notified of relevant network events. +Once you have a libp2p instance, you can listen to several events it emits, so that you can be notified of relevant network events. ### libp2p diff --git a/package.json b/package.json index 23ce0826da..79d80bdc04 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "it-length-prefixed": "^3.0.1", "it-pipe": "^1.1.0", "it-protocol-buffers": "^0.2.0", - "libp2p-crypto": "^0.17.8", + "libp2p-crypto": "^0.17.9", "libp2p-interfaces": "^0.3.1", "libp2p-utils": "^0.1.2", "mafmt": "^7.0.0", diff --git a/src/keychain/index.js b/src/keychain/index.js index 9af46d2628..c5009a54a6 100644 --- a/src/keychain/index.js +++ b/src/keychain/index.js @@ -7,6 +7,9 @@ const crypto = require('libp2p-crypto') const DS = require('interface-datastore') const CMS = require('./cms') const errcode = require('err-code') +const { Number } = require('ipfs-utils/src/globalthis') + +require('node-forge/lib/sha512') const keyPrefix = '/pkcs8/' const infoPrefix = '/info/' @@ -171,8 +174,8 @@ class Keychain { * * @param {string} name - The local key name; cannot already exist. * @param {string} type - One of the key types; 'rsa'. - * @param {int} size - The key size in bits. - * @returns {KeyInfo} + * @param {int} [size] - The key size in bits. Used for rsa keys only. + * @returns {KeyInfo} */ async createKey (name, type, size) { const self = this @@ -185,17 +188,13 @@ class Keychain { return throwDelayed(errcode(new Error(`Invalid key type '${type}'`), 'ERR_INVALID_KEY_TYPE')) } - if (!Number.isSafeInteger(size)) { - return throwDelayed(errcode(new Error(`Invalid key size '${size}'`), 'ERR_INVALID_KEY_SIZE')) - } - const dsname = DsName(name) const exists = await self.store.has(dsname) if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS')) switch (type.toLowerCase()) { case 'rsa': - if (size < 2048) { + if (!Number.isSafeInteger(size) || size < 2048) { return throwDelayed(errcode(new Error(`Invalid RSA key size ${size}`), 'ERR_INVALID_KEY_SIZE')) } break diff --git a/test/keychain/keychain.spec.js b/test/keychain/keychain.spec.js index 5ba0e8c3c7..73ff15bd78 100644 --- a/test/keychain/keychain.spec.js +++ b/test/keychain/keychain.spec.js @@ -149,6 +149,70 @@ describe('keychain', () => { }) }) + describe('ed25519 keys', () => { + const keyName = 'my custom key' + it('can be an ed25519 key', async () => { + const keyInfo = await ks.createKey(keyName, 'ed25519') + expect(keyInfo).to.exist() + expect(keyInfo).to.have.property('name', keyName) + expect(keyInfo).to.have.property('id') + }) + + it('does not overwrite existing key', async () => { + const err = await ks.createKey(keyName, 'ed25519').then(fail, err => err) + expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS') + }) + + it('can export/import a key', async () => { + const keyName = 'a new key' + const password = 'my sneaky password' + const keyInfo = await ks.createKey(keyName, 'ed25519') + const exportedKey = await ks.exportKey(keyName, password) + // remove it so we can import it + await ks.removeKey(keyName) + const importedKey = await ks.importKey(keyName, exportedKey, password) + expect(importedKey.id).to.eql(keyInfo.id) + }) + + it('cannot create the "self" key', async () => { + const err = await ks.createKey('self', 'ed25519').then(fail, err => err) + expect(err).to.exist() + expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME') + }) + }) + + describe('secp256k1 keys', () => { + const keyName = 'my secp256k1 key' + it('can be an secp256k1 key', async () => { + const keyInfo = await ks.createKey(keyName, 'secp256k1') + expect(keyInfo).to.exist() + expect(keyInfo).to.have.property('name', keyName) + expect(keyInfo).to.have.property('id') + }) + + it('does not overwrite existing key', async () => { + const err = await ks.createKey(keyName, 'secp256k1').then(fail, err => err) + expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS') + }) + + it('can export/import a key', async () => { + const keyName = 'a new secp256k1 key' + const password = 'my sneaky password' + const keyInfo = await ks.createKey(keyName, 'secp256k1') + const exportedKey = await ks.exportKey(keyName, password) + // remove it so we can import it + await ks.removeKey(keyName) + const importedKey = await ks.importKey(keyName, exportedKey, password) + expect(importedKey.id).to.eql(keyInfo.id) + }) + + it('cannot create the "self" key', async () => { + const err = await ks.createKey('self', 'secp256k1').then(fail, err => err) + expect(err).to.exist() + expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME') + }) + }) + describe('query', () => { it('finds all existing keys', async () => { const keys = await ks.listKeys()