Skip to content
This repository has been archived by the owner on Feb 12, 2024. It is now read-only.

Commit

Permalink
feat: ed25519 keys by default (#3693)
Browse files Browse the repository at this point in the history
Switch from RSA to ed25519 to match what we already do in go-ipfs.

Closes #3591

Co-authored-by: achingbrain <alex@achingbrain.net>
  • Loading branch information
lidel and achingbrain committed Aug 11, 2021
1 parent 2886134 commit 33fa734
Show file tree
Hide file tree
Showing 15 changed files with 174 additions and 48 deletions.
34 changes: 31 additions & 3 deletions packages/interface-ipfs-core/src/key/gen.js
Expand Up @@ -3,6 +3,7 @@

const { nanoid } = require('nanoid')
const { getDescribe, getIt, expect } = require('../utils/mocha')
const { keys: { supportedKeys, import: importKey } } = require('libp2p-crypto')

/**
* @typedef {import('ipfsd-ctl').Factory} Factory
Expand All @@ -18,7 +19,18 @@ module.exports = (factory, options) => {

describe('.key.gen', () => {
const keyTypes = [
{ type: 'rsa', size: 2048 }
{
opts: { type: 'rsa', size: 2048 },
expectedType: supportedKeys.rsa.RsaPrivateKey
},
{
opts: { type: 'ed25519' },
expectedType: supportedKeys.ed25519.Ed25519PrivateKey
},
{
opts: { },
expectedType: supportedKeys.ed25519.Ed25519PrivateKey
}
]

/** @type {import('ipfs-core-types').IPFS} */
Expand All @@ -31,14 +43,30 @@ module.exports = (factory, options) => {
after(() => factory.clean())

keyTypes.forEach((kt) => {
it(`should generate a new ${kt.type} key`, async function () {
it(`should generate a new ${kt.opts.type || 'default'} key`, async function () {
// @ts-ignore this is mocha
this.timeout(20 * 1000)
const name = nanoid()
const key = await ipfs.key.gen(name, kt)
const key = await ipfs.key.gen(name, kt.opts)
expect(key).to.exist()
expect(key).to.have.property('name', name)
expect(key).to.have.property('id')

try {
const password = nanoid() + '-' + nanoid()
const exported = await ipfs.key.export(name, password)
const imported = await importKey(exported, password)

expect(imported).to.be.an.instanceOf(kt.expectedType)
} catch (err) {
if (err.code === 'ERR_NOT_IMPLEMENTED') {
// key export is not exposed over the HTTP API
// @ts-ignore this is mocha
this.skip('Cannot verify key type')
}

throw err
}
})
})
})
Expand Down
18 changes: 13 additions & 5 deletions packages/ipfs-cli/src/commands/init.js
Expand Up @@ -4,6 +4,13 @@ const fs = require('fs')
const debug = require('debug')('ipfs:cli:init')
const { ipfsPathHelp } = require('../utils')

/** @type {Record<string, import('libp2p-crypto').KeyType>} */
const keyTypes = {
ed25519: 'Ed25519',
rsa: 'RSA',
secp256k1: 'secp256k1'
}

module.exports = {
command: 'init [default-config] [options]',
describe: 'Initialize a local IPFS node\n\n' +
Expand All @@ -23,15 +30,16 @@ module.exports = {
})
.option('algorithm', {
type: 'string',
choices: Object.keys(keyTypes),
alias: 'a',
default: 'RSA',
describe: 'Cryptographic algorithm to use for key generation. Supports [RSA, Ed25519, secp256k1]'
default: 'ed25519',
describe: 'Cryptographic algorithm to use for key generation'
})
.option('bits', {
type: 'number',
alias: 'b',
default: '2048',
describe: 'Number of bits to use in the generated RSA private key (defaults to 2048)',
describe: 'Number of bits to use if the generated private key is RSA (defaults to 2048)',
coerce: Number
})
.option('empty-repo', {
Expand All @@ -58,7 +66,7 @@ module.exports = {
* @param {object} argv
* @param {import('../types').Context} argv.ctx
* @param {string} argv.defaultConfig
* @param {'RSA' | 'Ed25519' | 'secp256k1'} argv.algorithm
* @param {'rsa' | 'ed25519' | 'secp256k1'} argv.algorithm
* @param {number} argv.bits
* @param {boolean} argv.emptyRepo
* @param {string} argv.privateKey
Expand Down Expand Up @@ -88,7 +96,7 @@ module.exports = {
await IPFS.create({
repo: repoPath,
init: {
algorithm: argv.algorithm,
algorithm: keyTypes[argv.algorithm],
bits: argv.bits,
privateKey: argv.privateKey,
emptyRepo: argv.emptyRepo,
Expand Down
5 changes: 3 additions & 2 deletions packages/ipfs-cli/src/commands/key/gen.js
Expand Up @@ -13,8 +13,9 @@ module.exports = {
builder: {
type: {
alias: 't',
describe: 'type of the key to create [rsa, ed25519].',
default: 'rsa'
describe: 'type of the key to create',
choices: ['rsa', 'ed25519'],
default: 'ed25519'
},
size: {
alias: 's',
Expand Down
10 changes: 5 additions & 5 deletions packages/ipfs-cli/test/key.js
Expand Up @@ -25,7 +25,7 @@ describe('key', () => {
const name = 'key-name'
const id = 'key-id'
const defaultOptions = {
type: 'rsa',
type: 'ed25519',
size: 2048,
timeout: undefined
}
Expand All @@ -43,28 +43,28 @@ describe('key', () => {
it('gen with args', async () => {
ipfs.key.gen.withArgs(name, {
...defaultOptions,
type: 'rsb',
type: 'rsa',
size: 7
}).resolves({
id,
name
})

const out = await cli(`key gen ${name} --type rsb --size 7`, { ipfs })
const out = await cli(`key gen ${name} --type rsa --size 7`, { ipfs })
expect(out).to.equal(`generated ${id} ${name}\n`)
})

it('gen with short args', async () => {
ipfs.key.gen.withArgs(name, {
...defaultOptions,
type: 'rsc',
type: 'rsa',
size: 5
}).resolves({
id,
name
})

const out = await cli(`key gen ${name} -t rsc -s 5`, { ipfs })
const out = await cli(`key gen ${name} -t rsa -s 5`, { ipfs })
expect(out).to.equal(`generated ${id} ${name}\n`)
})

Expand Down
2 changes: 1 addition & 1 deletion packages/ipfs-core/src/components/key/gen.js
Expand Up @@ -2,7 +2,7 @@

const withTimeoutOption = require('ipfs-core-utils/src/with-timeout-option')

const DEFAULT_KEY_TYPE = 'rsa'
const DEFAULT_KEY_TYPE = 'ed25519'
const DEFAULT_KEY_SIZE = 2048

/**
Expand Down
7 changes: 7 additions & 0 deletions packages/ipfs-core/src/components/libp2p.js
Expand Up @@ -13,8 +13,15 @@ const { Multiaddr } = require('multiaddr')
const pkgversion = require('../../package.json').version

/**
* @typedef {object} DekOptions
* @property {string} hash
* @property {string} salt
* @property {number} iterationCount
* @property {number} keyLength
*
* @typedef {Object} KeychainConfig
* @property {string} [pass]
* @property {DekOptions} [dek]
*
* @typedef {import('ipfs-repo').IPFSRepo} Repo
* @typedef {import('peer-id')} PeerId
Expand Down
22 changes: 20 additions & 2 deletions packages/ipfs-core/src/components/resolve.js
Expand Up @@ -2,6 +2,7 @@

const isIpfs = require('is-ipfs')
const { CID } = require('multiformats/cid')
const PeerID = require('peer-id')
const withTimeoutOption = require('ipfs-core-utils/src/with-timeout-option')
const { resolve: res } = require('../utils')

Expand All @@ -28,14 +29,18 @@ module.exports = ({ repo, codecs, bases, name }) => {
}

const [, schema, hash, ...rest] = path.split('/') // ['', 'ipfs', 'hash', ...path]
const cid = CID.parse(hash)
const base = opts.cidBase ? await bases.getBase(opts.cidBase) : undefined
const bytes = parseBytes(hash)

// nothing to resolve return the input
if (rest.length === 0) {
return `/${schema}/${cid.toString(base && base.encoder)}`
const str = base ? base.encoder.encode(bytes) : hash

return `/${schema}/${str}`
}

const cid = CID.decode(bytes)

path = rest.join('/')

const results = res(cid, path, codecs, repo, opts)
Expand All @@ -54,3 +59,16 @@ module.exports = ({ repo, codecs, bases, name }) => {

return withTimeoutOption(resolve)
}

/**
* Parse the input as a PeerID or a CID or throw an error
*
* @param {string} str
*/
function parseBytes (str) {
try {
return PeerID.parse(str).toBytes()
} catch {
return CID.parse(str).bytes
}
}
25 changes: 18 additions & 7 deletions packages/ipfs-core/src/components/storage.js
Expand Up @@ -130,23 +130,34 @@ const initRepo = async (print, repo, options) => {

log('repo opened')

/** @type {import('./libp2p').KeychainConfig} */
const keychainConfig = {
pass: options.pass
}

try {
keychainConfig.dek = await repo.config.get('Keychain.DEK')
} catch (err) {
if (err.code !== 'ERR_NOT_FOUND') {
throw err
}
}

// Create libp2p for Keychain creation
const libp2p = await createLibP2P({
options: undefined,
multiaddrs: undefined,
peerId,
repo,
config,
keychainConfig: {
pass: options.pass
}
keychainConfig
})

if (libp2p.keychain && libp2p.keychain.opts) {
await libp2p.loadKeychain()

await repo.config.set('Keychain', {
dek: libp2p.keychain.opts.dek
DEK: libp2p.keychain.opts.dek
})
}

Expand All @@ -172,13 +183,13 @@ const decodePeerId = (peerId) => {
*
* @param {Print} print
* @param {Object} options
* @param {KeyType} [options.algorithm='RSA']
* @param {KeyType} [options.algorithm='Ed25519']
* @param {number} [options.bits=2048]
* @returns {Promise<PeerId>}
*/
const initPeerId = (print, { algorithm = 'RSA', bits = 2048 }) => {
const initPeerId = (print, { algorithm = 'Ed25519', bits = 2048 }) => {
// Generate peer identity keypair + transform to desired format + add to config.
print('generating %s-bit (rsa only) %s keypair...', bits, algorithm)
print('generating %s keypair...', algorithm)
return PeerId.create({ keyType: algorithm, bits })
}

Expand Down
2 changes: 1 addition & 1 deletion packages/ipfs-core/src/types.d.ts
Expand Up @@ -84,7 +84,7 @@ export interface Options {

/**
* Occasionally a repo migration is necessary - pass true here to to this automatically at startup
* when a new version of IPFS is being run for the first time and a migration is necssary, otherwise
* when a new version of IPFS is being run for the first time and a migration is necessary, otherwise
* the node will refuse to start
*/
repoAutoMigrate?: boolean
Expand Down
6 changes: 5 additions & 1 deletion packages/ipfs-core/test/create-node.spec.js
Expand Up @@ -98,7 +98,10 @@ describe('create node', function () {
it('should throw on boot error', () => {
return expect(IPFS.create({
repo: tempRepo,
init: { bits: 256 }, // Too few bits will cause error on boot
init: {
algorithm: 'RSA',
bits: 256
}, // Too few bits will cause error on boot
config: { Addresses: { Swarm: [] } }
})).to.eventually.be.rejected()
})
Expand All @@ -109,6 +112,7 @@ describe('create node', function () {
const node = await IPFS.create({
repo: tempRepo,
init: {
algorithm: 'RSA',
bits: 1024
},
config: {
Expand Down

0 comments on commit 33fa734

Please sign in to comment.