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

Commit 10a6bba

Browse files
richardschneiderdryajov
authored andcommitted
feat: Implementation of the ipfs.key API (#1133)
1 parent e77b9a7 commit 10a6bba

33 files changed

+683
-16
lines changed

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ const node = new IPFS({
195195
// },
196196
start: true, // default
197197
// start: false,
198+
pass: undefined // default
199+
// pass: 'pass phrase for key access',
198200
EXPERIMENTAL: { // enable experimental features
199201
pubsub: true,
200202
sharding: true, // enable dir sharding
@@ -281,6 +283,17 @@ A complete API definition is in the works. Meanwhile, you can learn how to you u
281283
- [`ipfs.object.patch.setData(multihash, data, [options, callback])`](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/OBJECT.md#objectpatchsetdata)
282284
- [pin (not implemented, yet!)](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/)
283285

286+
#### `Crypto and Key Management`
287+
288+
- [key](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/KEY.md)
289+
- `ipfs.key.export(name, password, [callback])`
290+
- `ipfs.key.gen(name, options, [callback])`
291+
- `ipfs.key.import(name, pem, password, [callback])`
292+
- `ipfs.key.list([callback])`
293+
- `ipfs.key.rename(oldName, newName, [callback])`
294+
- `ipfs.key.rm(name, [callback])`
295+
- [crypto (not yet implemented)](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC)
296+
284297
#### `Network`
285298

286299
- [bootstrap](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/)
@@ -529,6 +542,10 @@ If you find any other issue, please check the [`Electron Support` issue](https:/
529542
| [`is-ipfs`](https://github.com/ipfs/is-ipfs) | [![npm](https://img.shields.io/npm/v/is-ipfs.svg?maxAge=86400&style=flat-square)](//github.com/ipfs/is-ipfs/releases) | [![Dep](https://david-dm.org/ipfs/is-ipfs.svg?style=flat-square)](https://david-dm.org/ipfs/is-ipfs) | [![devDep](https://david-dm.org/ipfs/is-ipfs/dev-status.svg?style=flat-square)](https://david-dm.org/ipfs/is-ipfs?type=dev) | [![Travis](https://travis-ci.org/ipfs/is-ipfs.svg?branch=master)](https://travis-ci.org/ipfs/is-ipfs) | | ![Appveyor CI](https://ci.appveyor.com/api/projects/status/github/ipfs/is-ipfs?svg=true) | [![Coverage Status](https://coveralls.io/repos/github/ipfs/is-ipfs/badge.svg?branch=master)](https://coveralls.io/github/ipfs/is-ipfs?branch=master) |
530543
| [`multihashing`](//github.com/multiformats/js-multihashing) | [![npm](https://img.shields.io/npm/v/multihashing.svg?maxAge=86400&style=flat-square)](//github.com/multiformats/js-multihashing/releases) | [![Dep](https://david-dm.org/multiformats/js-multihashing.svg?style=flat-square)](https://david-dm.org/multiformats/js-multihashing) | [![devDep](https://david-dm.org/multiformats/js-multihashing/dev-status.svg?style=flat-square)](https://david-dm.org/multiformats/js-multihashing?type=dev) | [![Travis](https://travis-ci.org/multiformats/js-multihashing.svg?branch=master)](https://travis-ci.org/multiformats/js-multihashing) | [![Circle CI](https://circleci.com/gh/multiformats/js-multihashing.svg?style=svg)](https://circleci.com/gh/jbenet/js-multihashing) | ![Appveyor CI](https://ci.appveyor.com/api/projects/status/github/multiformats/js-multihashing?svg=true) | [![Coverage Status](https://coveralls.io/repos/github/jbenet/js-multihashing/badge.svg?branch=master)](https://coveralls.io/github/jbenet/js-multihashing?branch=master) |
531544
| [`mafmt`](//github.com/whyrusleeping/js-mafmt) | [![npm](https://img.shields.io/npm/v/mafmt.svg?maxAge=86400&style=flat-square)](//github.com/whyrusleeping/js-mafmt/releases) | [![Dep](https://david-dm.org/whyrusleeping/js-mafmt.svg?style=flat-square)](https://david-dm.org/whyrusleeping/js-mafmt) | [![devDep](https://david-dm.org/whyrusleeping/js-mafmt/dev-status.svg?style=flat-square)](https://david-dm.org/whyrusleeping/js-mafmt?type=dev) | [![Travis](https://travis-ci.org/whyrusleeping/js-mafmt.svg?branch=master)](https://travis-ci.org/whyrusleeping/js-mafmt) | [![Circle CI](https://circleci.com/gh/whyrusleeping/js-mafmt.svg?style=svg)](https://circleci.com/gh/whyrusleeping/js-mafmt) | ![Appveyor CI](https://ci.appveyor.com/api/projects/status/github/whyrusleeping/js-mafmt?svg=true) | [![Coverage Status](https://coveralls.io/repos/github/whyrusleeping/js-mafmt/badge.svg?branch=master)](https://coveralls.io/github/whyrusleeping/js-mafmt?branch=master) |
545+
| **Crypto**
546+
| [`libp2p-crypto`](https://github.com/libp2p/js-libp2p-crypto) | [![npm](https://img.shields.io/npm/v/libp2p-crypto.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-crypto/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-crypto.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-crypto) | [![devDep](https://david-dm.org/libp2p/js-libp2p-crypto/dev-status.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-crypto?type=dev) | [![Travis](https://travis-ci.org/libp2p/js-libp2p-crypto.svg?branch=master)](https://travis-ci.org/libp2p/js-libp2p-crypto) | [![Circle CI](https://circleci.com/gh/libp2p/js-libp2p-crypto.svg?style=svg)](https://circleci.com/gh/libp2p/js-libp2p-crypto) | ![Appveyor CI](https://ci.appveyor.com/api/projects/status/github/libp2p/js-libp2p-crypto?svg=true) | [![Coverage Status](https://coveralls.io/repos/github/libp2p/js-libp2p-crypto/badge.svg?branch=master)](https://coveralls.io/github/libp2p/js-libp2p-crypto?branch=master) |
547+
| [`libp2p-keychain`](https://github.com/libp2p/js-libp2p-keychain) | [![npm](https://img.shields.io/npm/v/libp2p-keychain.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-keychain/releases) | [![Dep](https://david-dm.org/libp2p/js-libp2p-keychain.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-keychain) | [![devDep](https://david-dm.org/libp2p/js-libp2p-keychain/dev-status.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-keychain?type=dev) | [![Travis](https://travis-ci.org/libp2p/js-libp2p-keychain.svg?branch=master)](https://travis-ci.org/libp2p/js-libp2p-keychain) | [![Circle CI](https://circleci.com/gh/libp2p/js-libp2p-keychain.svg?style=svg)](https://circleci.com/gh/libp2p/js-libp2p-keychain) | ![Appveyor CI](https://ci.appveyor.com/api/projects/status/github/libp2p/js-libp2p-keychain?svg=true) | [![Coverage Status](https://coveralls.io/repos/github/libp2p/js-libp2p-keychain/badge.svg?branch=master)](https://coveralls.io/github/libp2p/js-libp2p-keychain?branch=master) |
548+
532549

533550
## Development
534551

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
"go-ipfs-dep": "^0.4.13",
7575
"hat": "0.0.3",
7676
"interface-ipfs-core": "~0.42.1",
77-
"ipfsd-ctl": "^0.27.0",
77+
"ipfsd-ctl": "~0.27.2",
7878
"left-pad": "^1.2.0",
7979
"lodash": "^4.17.4",
8080
"mocha": "^4.1.0",
@@ -122,6 +122,7 @@
122122
"libp2p-crypto": "^0.10.4",
123123
"libp2p-floodsub": "~0.13.1",
124124
"libp2p-kad-dht": "~0.6.0",
125+
"libp2p-keychain": "~0.3.0",
125126
"libp2p-mdns": "~0.9.1",
126127
"libp2p-multiplex": "~0.5.1",
127128
"libp2p-railing": "~0.7.1",

src/cli/bin.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ const cli = yargs
2121
default: false,
2222
coerce: ('silent', silent => silent ? utils.disablePrinting() : silent)
2323
})
24+
.option('pass', {
25+
desc: 'Pass phrase for the keys',
26+
type: 'string',
27+
default: ''
28+
})
2429
.commandDir('commands')
2530
.demandCommand(1)
2631
.fail((msg, err, yargs) => {
@@ -59,7 +64,7 @@ if (args[0] === 'daemon' || args[0] === 'init') {
5964
if (err) {
6065
throw err
6166
}
62-
utils.getIPFS(argv.api, (err, ipfs, cleanup) => {
67+
utils.getIPFS(argv, (err, ipfs, cleanup) => {
6368
if (err) { throw err }
6469

6570
cli

src/cli/commands/init.js

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ module.exports = {
3838
node.init({
3939
bits: argv.bits,
4040
emptyRepo: argv.emptyRepo,
41+
pass: argv.pass,
4142
log: print
4243
}, (err) => {
4344
if (err) {

src/cli/commands/key.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
'use strict'
2+
3+
module.exports = {
4+
command: 'key',
5+
6+
description: 'Manage your keys',
7+
8+
builder (yargs) {
9+
return yargs
10+
.commandDir('key')
11+
},
12+
13+
handler (argv) {}
14+
}

src/cli/commands/key/export.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict'
2+
3+
const fs = require('fs')
4+
5+
module.exports = {
6+
command: 'export <name>',
7+
8+
describe: 'Export the key as a password protected PKCS #8 PEM file',
9+
10+
builder: {
11+
passout: {
12+
alias: 'p',
13+
describe: 'Password for the PEM',
14+
type: 'string',
15+
demandOption: true
16+
},
17+
output: {
18+
alias: 'o',
19+
describe: 'Output file',
20+
type: 'string',
21+
default: 'stdout'
22+
}
23+
},
24+
25+
handler (argv) {
26+
argv.ipfs.key.export(argv.name, argv.passout, (err, pem) => {
27+
if (err) {
28+
throw err
29+
}
30+
if (argv.output === 'stdout') {
31+
process.stdout.write(pem)
32+
} else {
33+
fs.writeFileSync(argv.output, pem)
34+
}
35+
})
36+
}
37+
}

src/cli/commands/key/gen.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict'
2+
3+
const print = require('../../utils').print
4+
5+
module.exports = {
6+
command: 'gen <name>',
7+
8+
describe: 'Create a new key',
9+
10+
builder: {
11+
type: {
12+
alias: 't',
13+
describe: 'type of the key to create [rsa, ed25519].',
14+
default: 'rsa'
15+
},
16+
size: {
17+
alias: 's',
18+
describe: 'size of the key to generate.',
19+
default: '2048'
20+
}
21+
},
22+
23+
handler (argv) {
24+
const opts = {
25+
type: argv.type,
26+
size: argv.size
27+
}
28+
argv.ipfs.key.gen(argv.name, opts, (err, key) => {
29+
if (err) {
30+
throw err
31+
}
32+
print(`generated ${key.id} ${key.name}`)
33+
})
34+
}
35+
}

src/cli/commands/key/import.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use strict'
2+
3+
const fs = require('fs')
4+
const print = require('../../utils').print
5+
6+
module.exports = {
7+
command: 'import <name>',
8+
9+
describe: 'Import the key from a PKCS #8 PEM file',
10+
11+
builder: {
12+
passin: {
13+
alias: 'p',
14+
describe: 'Password for the PEM',
15+
type: 'string'
16+
},
17+
input: {
18+
alias: 'i',
19+
describe: 'Input PEM file',
20+
type: 'string',
21+
demandOption: true,
22+
coerce: ('input', input => fs.readFileSync(input, 'utf8'))
23+
}
24+
},
25+
26+
handler (argv) {
27+
argv.ipfs.key.import(argv.name, argv.input, argv.passin, (err, key) => {
28+
if (err) {
29+
throw err
30+
}
31+
print(`imported ${key.id} ${key.name}`)
32+
})
33+
}
34+
}

src/cli/commands/key/list.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict'
2+
3+
const print = require('../../utils').print
4+
5+
module.exports = {
6+
command: 'list',
7+
8+
describe: 'List all local keys',
9+
10+
builder: {},
11+
12+
handler (argv) {
13+
argv.ipfs.key.list((err, keys) => {
14+
if (err) {
15+
throw err
16+
}
17+
keys.forEach((ki) => print(`${ki.id} ${ki.name}`))
18+
})
19+
}
20+
}

src/cli/commands/key/rename.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict'
2+
3+
const print = require('../../utils').print
4+
5+
module.exports = {
6+
command: 'rename <name> <newName>',
7+
8+
describe: 'Rename a key',
9+
10+
builder: {},
11+
12+
handler (argv) {
13+
argv.ipfs.key.rename(argv.name, argv.newName, (err, res) => {
14+
if (err) {
15+
throw err
16+
}
17+
print(`renamed to ${res.id} ${res.now}`)
18+
})
19+
}
20+
}

src/cli/commands/key/rm.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict'
2+
3+
const print = require('../../utils').print
4+
5+
module.exports = {
6+
command: 'rm <name>',
7+
8+
describe: 'Remove a key',
9+
10+
builder: {},
11+
12+
handler (argv) {
13+
argv.ipfs.key.rm(argv.name, (err, key) => {
14+
if (err) {
15+
throw err
16+
}
17+
print(`${key.id} ${key.name}`)
18+
})
19+
}
20+
}

src/cli/utils.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,16 @@ function getAPICtl (apiAddr) {
3838
return APIctl(apiAddr)
3939
}
4040

41-
exports.getIPFS = (apiAddr, callback) => {
42-
if (apiAddr || isDaemonOn()) {
43-
return callback(null, getAPICtl(apiAddr), (cb) => cb())
41+
exports.getIPFS = (argv, callback) => {
42+
if (argv.api || isDaemonOn()) {
43+
return callback(null, getAPICtl(argv.api), (cb) => cb())
4444
}
4545

4646
const node = new IPFS({
4747
repo: exports.getRepoPath(),
4848
init: false,
4949
start: false,
50+
pass: argv.pass,
5051
EXPERIMENTAL: {
5152
pubsub: true
5253
}

src/core/boot.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ module.exports = (self) => {
1515
const repoOpen = !self._repo.closed
1616

1717
const customInitOptions = typeof options.init === 'object' ? options.init : {}
18-
const initOptions = Object.assign({ bits: 2048 }, customInitOptions)
18+
const initOptions = Object.assign({ bits: 2048, pass: self._options.pass }, customInitOptions)
1919

2020
// Checks if a repo exists, and if so opens it
2121
// Will return callback with a bool indicating the existence
@@ -30,6 +30,7 @@ module.exports = (self) => {
3030
(cb) => self._repo.open(cb),
3131
(cb) => self.preStart(cb),
3232
(cb) => {
33+
self.log('initialized')
3334
self.state.initialized()
3435
cb(null, true)
3536
}
@@ -56,8 +57,8 @@ module.exports = (self) => {
5657
if (err) {
5758
return self.emit('error', err)
5859
}
60+
self.log('boot:done')
5961
self.emit('ready')
60-
self.log('boot:done', err)
6162
}
6263

6364
const tasks = []

src/core/components/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ exports.bitswap = require('./bitswap')
2121
exports.pubsub = require('./pubsub')
2222
exports.dht = require('./dht')
2323
exports.dns = require('./dns')
24+
exports.key = require('./key')

src/core/components/init.js

+17-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const waterfall = require('async/waterfall')
55
const parallel = require('async/parallel')
66
const promisify = require('promisify-es6')
77
const config = require('../runtime/config-nodejs.json')
8+
const Keychain = require('libp2p-keychain')
89

910
const addDefaultAssets = require('./init-assets')
1011

@@ -36,7 +37,7 @@ module.exports = function init (self) {
3637
opts.emptyRepo = opts.emptyRepo || false
3738
opts.bits = Number(opts.bits) || 2048
3839
opts.log = opts.log || function () {}
39-
40+
let privateKey
4041
waterfall([
4142
// Verify repo does not yet exist.
4243
(cb) => self._repo.exists(cb),
@@ -57,6 +58,10 @@ module.exports = function init (self) {
5758
PeerID: keys.toB58String(),
5859
PrivKey: keys.privKey.bytes.toString('base64')
5960
}
61+
if (opts.pass) {
62+
privateKey = keys.privKey
63+
config.Keychain = Keychain.generateOptions()
64+
}
6065
opts.log('done')
6166
opts.log('peer identity: ' + config.Identity.PeerID)
6267

@@ -65,10 +70,21 @@ module.exports = function init (self) {
6570
(_, cb) => self._repo.open(cb),
6671
(cb) => {
6772
self.log('repo opened')
73+
if (opts.pass) {
74+
self.log('creating keychain')
75+
const keychainOptions = Object.assign({passPhrase: opts.pass}, config.Keychain)
76+
self._keychain = new Keychain(self._repo.keys, keychainOptions)
77+
self._keychain.importPeer('self', { privKey: privateKey }, cb)
78+
} else {
79+
cb(null, true)
80+
}
81+
},
82+
(_, cb) => {
6883
if (opts.emptyRepo) {
6984
return cb(null, true)
7085
}
7186

87+
self.log('adding assets')
7288
const tasks = [
7389
// add empty unixfs dir object (go-ipfs assumes this exists)
7490
(cb) => self.object.new('unixfs-dir', cb)

0 commit comments

Comments
 (0)