Skip to content

Commit 7051b9c

Browse files
achingbrainvasco-santos
authored andcommitted
fix: throw errors with correct stack trace (#35)
The stack trace of thrown error objects is created when the object is instantiated - if we defer to a function to create the error we end up with misleading stack traces. This PR instantiates errors where errors occur and also uses the `err-code` module to add a `.code` property so we don't have to depend on string error messages for the type of error that was thrown.
1 parent ef47374 commit 7051b9c

File tree

4 files changed

+48
-30
lines changed

4 files changed

+48
-30
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"homepage": "https://github.com/libp2p/js-libp2p-keychain#readme",
4444
"dependencies": {
4545
"async": "^2.6.2",
46+
"err-code": "^1.1.2",
4647
"interface-datastore": "~0.6.0",
4748
"libp2p-crypto": "~0.16.1",
4849
"merge-options": "^1.0.1",

src/cms.js

+8-6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require('node-forge/lib/pkcs7')
88
require('node-forge/lib/pbe')
99
const forge = require('node-forge/lib/forge')
1010
const util = require('./util')
11+
const errcode = require('err-code')
1112

1213
/**
1314
* Cryptographic Message Syntax (aka PKCS #7)
@@ -26,7 +27,7 @@ class CMS {
2627
*/
2728
constructor (keychain) {
2829
if (!keychain) {
29-
throw new Error('keychain is required')
30+
throw errcode(new Error('keychain is required'), 'ERR_KEYCHAIN_REQUIRED')
3031
}
3132

3233
this.keychain = keychain
@@ -47,7 +48,7 @@ class CMS {
4748
const done = (err, result) => setImmediate(() => callback(err, result))
4849

4950
if (!Buffer.isBuffer(plain)) {
50-
return done(new Error('Plain data must be a Buffer'))
51+
return done(errcode(new Error('Plain data must be a Buffer'), 'ERR_INVALID_PARAMS'))
5152
}
5253

5354
series([
@@ -93,7 +94,7 @@ class CMS {
9394
const done = (err, result) => setImmediate(() => callback(err, result))
9495

9596
if (!Buffer.isBuffer(cmsData)) {
96-
return done(new Error('CMS data is required'))
97+
return done(errcode(new Error('CMS data is required'), 'ERR_INVALID_PARAMS'))
9798
}
9899

99100
const self = this
@@ -103,7 +104,7 @@ class CMS {
103104
const obj = forge.asn1.fromDer(buf)
104105
cms = forge.pkcs7.messageFromAsn1(obj)
105106
} catch (err) {
106-
return done(new Error('Invalid CMS: ' + err.message))
107+
return done(errcode(new Error('Invalid CMS: ' + err.message), 'ERR_INVALID_CMS'))
107108
}
108109

109110
// Find a recipient whose key we hold. We only deal with recipient certs
@@ -124,8 +125,9 @@ class CMS {
124125
if (err) return done(err)
125126
if (!r) {
126127
const missingKeys = recipients.map(r => r.keyId)
127-
err = new Error('Decryption needs one of the key(s): ' + missingKeys.join(', '))
128-
err.missingKeys = missingKeys
128+
err = errcode(new Error('Decryption needs one of the key(s): ' + missingKeys.join(', ')), 'ERR_MISSING_KEYS', {
129+
missingKeys
130+
})
129131
return done(err)
130132
}
131133

src/keychain.js

+25-24
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const DS = require('interface-datastore')
88
const collect = require('pull-stream/sinks/collect')
99
const pull = require('pull-stream/pull')
1010
const CMS = require('./cms')
11+
const errcode = require('err-code')
1112

1213
const keyPrefix = '/pkcs8/'
1314
const infoPrefix = '/info/'
@@ -50,7 +51,7 @@ function _error (callback, err) {
5051
const min = 200
5152
const max = 1000
5253
const delay = Math.random() * (max - min) + min
53-
if (typeof err === 'string') err = new Error(err)
54+
5455
setTimeout(callback, delay, err, null)
5556
}
5657

@@ -181,26 +182,26 @@ class Keychain {
181182
const self = this
182183

183184
if (!validateKeyName(name) || name === 'self') {
184-
return _error(callback, `Invalid key name '${name}'`)
185+
return _error(callback, errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
185186
}
186187

187188
if (typeof type !== 'string') {
188-
return _error(callback, `Invalid key type '${type}'`)
189+
return _error(callback, errcode(new Error(`Invalid key type '${type}'`), 'ERR_INVALID_KEY_TYPE'))
189190
}
190191

191192
if (!Number.isSafeInteger(size)) {
192-
return _error(callback, `Invalid key size '${size}'`)
193+
return _error(callback, errcode(new Error(`Invalid key size '${size}'`), 'ERR_INVALID_KEY_SIZE'))
193194
}
194195

195196
const dsname = DsName(name)
196197
self.store.has(dsname, (err, exists) => {
197198
if (err) return _error(callback, err)
198-
if (exists) return _error(callback, `Key '${name}' already exists`)
199+
if (exists) return _error(callback, errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
199200

200201
switch (type.toLowerCase()) {
201202
case 'rsa':
202203
if (size < 2048) {
203-
return _error(callback, `Invalid RSA key size ${size}`)
204+
return _error(callback, errcode(new Error(`Invalid RSA key size ${size}`), 'ERR_INVALID_KEY_SIZE'))
204205
}
205206
break
206207
default:
@@ -278,13 +279,13 @@ class Keychain {
278279
*/
279280
findKeyByName (name, callback) {
280281
if (!validateKeyName(name)) {
281-
return _error(callback, `Invalid key name '${name}'`)
282+
return _error(callback, errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
282283
}
283284

284285
const dsname = DsInfoName(name)
285286
this.store.get(dsname, (err, res) => {
286287
if (err) {
287-
return _error(callback, `Key '${name}' does not exist. ${err.message}`)
288+
return _error(callback, errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND'))
288289
}
289290

290291
callback(null, JSON.parse(res.toString()))
@@ -301,7 +302,7 @@ class Keychain {
301302
removeKey (name, callback) {
302303
const self = this
303304
if (!validateKeyName(name) || name === 'self') {
304-
return _error(callback, `Invalid key name '${name}'`)
305+
return _error(callback, errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
305306
}
306307
const dsname = DsName(name)
307308
self.findKeyByName(name, (err, keyinfo) => {
@@ -327,23 +328,23 @@ class Keychain {
327328
renameKey (oldName, newName, callback) {
328329
const self = this
329330
if (!validateKeyName(oldName) || oldName === 'self') {
330-
return _error(callback, `Invalid old key name '${oldName}'`)
331+
return _error(callback, errcode(new Error(`Invalid old key name '${oldName}'`), 'ERR_OLD_KEY_NAME_INVALID'))
331332
}
332333
if (!validateKeyName(newName) || newName === 'self') {
333-
return _error(callback, `Invalid new key name '${newName}'`)
334+
return _error(callback, errcode(new Error(`Invalid new key name '${newName}'`), 'ERR_NEW_KEY_NAME_INVALID'))
334335
}
335336
const oldDsname = DsName(oldName)
336337
const newDsname = DsName(newName)
337338
const oldInfoName = DsInfoName(oldName)
338339
const newInfoName = DsInfoName(newName)
339340
this.store.get(oldDsname, (err, res) => {
340341
if (err) {
341-
return _error(callback, `Key '${oldName}' does not exist. ${err.message}`)
342+
return _error(callback, errcode(new Error(`Key '${oldName}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND'))
342343
}
343344
const pem = res.toString()
344345
self.store.has(newDsname, (err, exists) => {
345346
if (err) return _error(callback, err)
346-
if (exists) return _error(callback, `Key '${newName}' already exists`)
347+
if (exists) return _error(callback, errcode(new Error(`Key '${newName}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
347348

348349
self.store.get(oldInfoName, (err, res) => {
349350
if (err) return _error(callback, err)
@@ -374,16 +375,16 @@ class Keychain {
374375
*/
375376
exportKey (name, password, callback) {
376377
if (!validateKeyName(name)) {
377-
return _error(callback, `Invalid key name '${name}'`)
378+
return _error(callback, errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
378379
}
379380
if (!password) {
380-
return _error(callback, 'Password is required')
381+
return _error(callback, errcode(new Error('Password is required'), 'ERR_PASSWORD_REQUIRED'))
381382
}
382383

383384
const dsname = DsName(name)
384385
this.store.get(dsname, (err, res) => {
385386
if (err) {
386-
return _error(callback, `Key '${name}' does not exist. ${err.message}`)
387+
return _error(callback, errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND'))
387388
}
388389
const pem = res.toString()
389390
crypto.keys.import(pem, this._(), (err, privateKey) => {
@@ -405,17 +406,17 @@ class Keychain {
405406
importKey (name, pem, password, callback) {
406407
const self = this
407408
if (!validateKeyName(name) || name === 'self') {
408-
return _error(callback, `Invalid key name '${name}'`)
409+
return _error(callback, errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
409410
}
410411
if (!pem) {
411412
return _error(callback, 'PEM encoded key is required')
412413
}
413414
const dsname = DsName(name)
414415
self.store.has(dsname, (err, exists) => {
415416
if (err) return _error(callback, err)
416-
if (exists) return _error(callback, `Key '${name}' already exists`)
417+
if (exists) return _error(callback, errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
417418
crypto.keys.import(pem, password, (err, privateKey) => {
418-
if (err) return _error(callback, 'Cannot read the key, most likely the password is wrong')
419+
if (err) return _error(callback, errcode(new Error('Cannot read the key, most likely the password is wrong'), 'ERR_CANNOT_READ_KEY'))
419420
privateKey.id((err, kid) => {
420421
if (err) return _error(callback, err)
421422
privateKey.export(this._(), (err, pem) => {
@@ -441,17 +442,17 @@ class Keychain {
441442
importPeer (name, peer, callback) {
442443
const self = this
443444
if (!validateKeyName(name)) {
444-
return _error(callback, `Invalid key name '${name}'`)
445+
return _error(callback, errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
445446
}
446447
if (!peer || !peer.privKey) {
447-
return _error(callback, 'Peer.privKey is required')
448+
return _error(callback, errcode(new Error('Peer.privKey is required'), 'ERR_MISSING_PRIVATE_KEY'))
448449
}
449450

450451
const privateKey = peer.privKey
451452
const dsname = DsName(name)
452453
self.store.has(dsname, (err, exists) => {
453454
if (err) return _error(callback, err)
454-
if (exists) return _error(callback, `Key '${name}' already exists`)
455+
if (exists) return _error(callback, errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
455456

456457
privateKey.id((err, kid) => {
457458
if (err) return _error(callback, err)
@@ -484,11 +485,11 @@ class Keychain {
484485
*/
485486
_getPrivateKey (name, callback) {
486487
if (!validateKeyName(name)) {
487-
return _error(callback, `Invalid key name '${name}'`)
488+
return _error(callback, errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
488489
}
489490
this.store.get(DsName(name), (err, res) => {
490491
if (err) {
491-
return _error(callback, `Key '${name}' does not exist. ${err.message}`)
492+
return _error(callback, errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND'))
492493
}
493494
callback(null, res.toString())
494495
})

test/keychain.spec.js

+14
Original file line numberDiff line numberDiff line change
@@ -59,22 +59,27 @@ module.exports = (datastore1, datastore2) => {
5959
ks.removeKey('../../nasty', (err) => {
6060
expect(err).to.exist()
6161
expect(err).to.have.property('message', 'Invalid key name \'../../nasty\'')
62+
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
6263
})
6364
ks.removeKey('', (err) => {
6465
expect(err).to.exist()
6566
expect(err).to.have.property('message', 'Invalid key name \'\'')
67+
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
6668
})
6769
ks.removeKey(' ', (err) => {
6870
expect(err).to.exist()
6971
expect(err).to.have.property('message', 'Invalid key name \' \'')
72+
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
7073
})
7174
ks.removeKey(null, (err) => {
7275
expect(err).to.exist()
7376
expect(err).to.have.property('message', 'Invalid key name \'null\'')
77+
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
7478
})
7579
ks.removeKey(undefined, (err) => {
7680
expect(err).to.exist()
7781
expect(err).to.have.property('message', 'Invalid key name \'undefined\'')
82+
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
7883
})
7984
})
8085
})
@@ -106,6 +111,7 @@ module.exports = (datastore1, datastore2) => {
106111
it('does not overwrite existing key', (done) => {
107112
ks.createKey(rsaKeyName, 'rsa', 2048, (err) => {
108113
expect(err).to.exist()
114+
expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS')
109115
done()
110116
})
111117
})
@@ -146,6 +152,7 @@ module.exports = (datastore1, datastore2) => {
146152
ks.createKey('bad-nist-rsa', 'rsa', 1024, (err) => {
147153
expect(err).to.exist()
148154
expect(err).to.have.property('message', 'Invalid RSA key size 1024')
155+
expect(err).to.have.property('code', 'ERR_INVALID_KEY_SIZE')
149156
done()
150157
})
151158
})
@@ -246,6 +253,7 @@ module.exports = (datastore1, datastore2) => {
246253
expect(err).to.exist()
247254
expect(err).to.have.property('missingKeys')
248255
expect(err.missingKeys).to.eql([rsaKeyInfo.id])
256+
expect(err).to.have.property('code', 'ERR_MISSING_KEYS')
249257
done()
250258
})
251259
})
@@ -344,27 +352,31 @@ module.exports = (datastore1, datastore2) => {
344352
it('requires an existing key name', (done) => {
345353
ks.renameKey('not-there', renamedRsaKeyName, (err) => {
346354
expect(err).to.exist()
355+
expect(err).to.have.property('code', 'ERR_KEY_NOT_FOUND')
347356
done()
348357
})
349358
})
350359

351360
it('requires a valid new key name', (done) => {
352361
ks.renameKey(rsaKeyName, '..\not-valid', (err) => {
353362
expect(err).to.exist()
363+
expect(err).to.have.property('code', 'ERR_NEW_KEY_NAME_INVALID')
354364
done()
355365
})
356366
})
357367

358368
it('does not overwrite existing key', (done) => {
359369
ks.renameKey(rsaKeyName, rsaKeyName, (err) => {
360370
expect(err).to.exist()
371+
expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS')
361372
done()
362373
})
363374
})
364375

365376
it('cannot create the "self" key', (done) => {
366377
ks.renameKey(rsaKeyName, 'self', (err) => {
367378
expect(err).to.exist()
379+
expect(err).to.have.property('code', 'ERR_NEW_KEY_NAME_INVALID')
368380
done()
369381
})
370382
})
@@ -406,13 +418,15 @@ module.exports = (datastore1, datastore2) => {
406418
it('cannot remove the "self" key', (done) => {
407419
ks.removeKey('self', (err) => {
408420
expect(err).to.exist()
421+
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
409422
done()
410423
})
411424
})
412425

413426
it('cannot remove an unknown key', (done) => {
414427
ks.removeKey('not-there', (err) => {
415428
expect(err).to.exist()
429+
expect(err).to.have.property('code', 'ERR_KEY_NOT_FOUND')
416430
done()
417431
})
418432
})

0 commit comments

Comments
 (0)