1
1
'use strict'
2
2
3
- const async = require ( 'async' )
4
3
const sanitize = require ( 'sanitize-filename' )
5
4
const forge = require ( 'node-forge' )
6
5
const deepmerge = require ( 'deepmerge' )
@@ -10,7 +9,8 @@ const CMS = require('./cms')
10
9
const DS = require ( 'interface-datastore' )
11
10
const pull = require ( 'pull-stream' )
12
11
13
- const keyExtension = '.p8'
12
+ const keyPrefix = '/pkcs8/'
13
+ const infoPrefix = '/info/'
14
14
15
15
// NIST SP 800-132
16
16
const NIST = {
@@ -74,18 +74,18 @@ function _error (callback, err) {
74
74
* @private
75
75
*/
76
76
function DsName ( name ) {
77
- return new DS . Key ( '/' + name )
77
+ return new DS . Key ( keyPrefix + name )
78
78
}
79
79
80
80
/**
81
- * Converts a datastore name into a key name.
81
+ * Converts a key name into a datastore info name.
82
82
*
83
- * @param {DS.Key } name - A datastore name
84
- * @returns {string }
83
+ * @param {string } name
84
+ * @returns {DS.Key }
85
85
* @private
86
86
*/
87
- function KsName ( name ) {
88
- return name . toString ( ) . slice ( 1 )
87
+ function DsInfoName ( name ) {
88
+ return new DS . Key ( infoPrefix + name )
89
89
}
90
90
91
91
/**
@@ -98,7 +98,12 @@ function KsName (name) {
98
98
*/
99
99
100
100
/**
101
- * Key management
101
+ * Manages the lifecycle of a key. Keys are encrypted at rest using PKCS #8.
102
+ *
103
+ * A key in the store has two entries
104
+ * - '/info/key-name', contains the KeyInfo for the key
105
+ * - '/pkcs8/key-name', contains the PKCS #8 for the key
106
+ *
102
107
*/
103
108
class Keychain {
104
109
/**
@@ -112,9 +117,6 @@ class Keychain {
112
117
throw new Error ( 'store is required' )
113
118
}
114
119
this . store = store
115
- if ( this . store . opts ) {
116
- this . store . opts . extension = keyExtension
117
- }
118
120
119
121
const opts = deepmerge ( defaultOptions , options )
120
122
@@ -149,9 +151,6 @@ class Keychain {
149
151
dek = forge . util . bytesToHex ( dek )
150
152
Object . defineProperty ( this , '_' , { value : ( ) => dek } )
151
153
152
- // JS magick
153
- this . _getKeyInfo = this . findKeyByName = this . _getKeyInfo . bind ( this )
154
-
155
154
// Provide access to protected messages
156
155
this . cms = new CMS ( this )
157
156
}
@@ -192,12 +191,22 @@ class Keychain {
192
191
}
193
192
forge . pki . rsa . generateKeyPair ( { bits : size , workers : - 1 } , ( err , keypair ) => {
194
193
if ( err ) return _error ( callback , err )
195
-
196
- const pem = forge . pki . encryptRsaPrivateKey ( keypair . privateKey , this . _ ( ) )
197
- return self . store . put ( dsname , pem , ( err ) => {
194
+ util . keyId ( keypair . privateKey , ( err , kid ) => {
198
195
if ( err ) return _error ( callback , err )
199
196
200
- self . _getKeyInfo ( name , callback )
197
+ const pem = forge . pki . encryptRsaPrivateKey ( keypair . privateKey , this . _ ( ) )
198
+ const keyInfo = {
199
+ name : name ,
200
+ id : kid
201
+ }
202
+ const batch = self . store . batch ( )
203
+ batch . put ( dsname , pem )
204
+ batch . put ( DsInfoName ( name ) , JSON . stringify ( keyInfo ) )
205
+ batch . commit ( ( err ) => {
206
+ if ( err ) return _error ( callback , err )
207
+
208
+ callback ( null , keyInfo )
209
+ } )
201
210
} )
202
211
} )
203
212
break
@@ -217,28 +226,27 @@ class Keychain {
217
226
listKeys ( callback ) {
218
227
const self = this
219
228
const query = {
220
- keysOnly : true
229
+ prefix : infoPrefix
221
230
}
222
231
pull (
223
232
self . store . query ( query ) ,
224
233
pull . collect ( ( err , res ) => {
225
234
if ( err ) return _error ( callback , err )
226
235
227
- const names = res . map ( r => KsName ( r . key ) )
228
- async . map ( names , self . _getKeyInfo , callback )
236
+ const info = res . map ( r => JSON . parse ( r . value ) )
237
+ callback ( null , info )
229
238
} )
230
239
)
231
240
}
232
241
233
242
/**
234
- * Find a key by it's name .
243
+ * Find a key by it's id .
235
244
*
236
245
* @param {string } id - The universally unique key identifier.
237
246
* @param {function(Error, KeyInfo) } callback
238
247
* @returns {undefined }
239
248
*/
240
249
findKeyById ( id , callback ) {
241
- // TODO: not very efficent.
242
250
this . listKeys ( ( err , keys ) => {
243
251
if ( err ) return _error ( callback , err )
244
252
@@ -247,6 +255,28 @@ class Keychain {
247
255
} )
248
256
}
249
257
258
+ /**
259
+ * Find a key by it's name.
260
+ *
261
+ * @param {string } name - The local key name.
262
+ * @param {function(Error, KeyInfo) } callback
263
+ * @returns {undefined }
264
+ */
265
+ findKeyByName ( name , callback ) {
266
+ if ( ! validateKeyName ( name ) ) {
267
+ return _error ( callback , `Invalid key name '${ name } '` )
268
+ }
269
+
270
+ const dsname = DsInfoName ( name )
271
+ this . store . get ( dsname , ( err , res ) => {
272
+ if ( err ) {
273
+ return _error ( callback , `Key '${ name } ' does not exist. ${ err . message } ` )
274
+ }
275
+
276
+ callback ( null , JSON . parse ( res . toString ( ) ) )
277
+ } )
278
+ }
279
+
250
280
/**
251
281
* Remove an existing key.
252
282
*
@@ -260,9 +290,12 @@ class Keychain {
260
290
return _error ( callback , `Invalid key name '${ name } '` )
261
291
}
262
292
const dsname = DsName ( name )
263
- self . _getKeyInfo ( name , ( err , keyinfo ) => {
293
+ self . findKeyByName ( name , ( err , keyinfo ) => {
264
294
if ( err ) return _error ( callback , err )
265
- self . store . delete ( dsname , ( err ) => {
295
+ const batch = self . store . batch ( )
296
+ batch . delete ( dsname )
297
+ batch . delete ( DsInfoName ( name ) )
298
+ batch . commit ( ( err ) => {
266
299
if ( err ) return _error ( callback , err )
267
300
callback ( null , keyinfo )
268
301
} )
@@ -287,6 +320,8 @@ class Keychain {
287
320
}
288
321
const oldDsname = DsName ( oldName )
289
322
const newDsname = DsName ( newName )
323
+ const oldInfoName = DsInfoName ( oldName )
324
+ const newInfoName = DsInfoName ( newName )
290
325
this . store . get ( oldDsname , ( err , res ) => {
291
326
if ( err ) {
292
327
return _error ( callback , `Key '${ oldName } ' does not exist. ${ err . message } ` )
@@ -296,12 +331,20 @@ class Keychain {
296
331
if ( err ) return _error ( callback , err )
297
332
if ( exists ) return _error ( callback , `Key '${ newName } ' already exists` )
298
333
299
- const batch = self . store . batch ( )
300
- batch . put ( newDsname , pem )
301
- batch . delete ( oldDsname )
302
- batch . commit ( ( err ) => {
334
+ self . store . get ( oldInfoName , ( err , res ) => {
303
335
if ( err ) return _error ( callback , err )
304
- self . _getKeyInfo ( newName , callback )
336
+
337
+ const keyInfo = JSON . parse ( res . toString ( ) )
338
+ keyInfo . name = newName
339
+ const batch = self . store . batch ( )
340
+ batch . put ( newDsname , pem )
341
+ batch . put ( newInfoName , JSON . stringify ( keyInfo ) )
342
+ batch . delete ( oldDsname )
343
+ batch . delete ( oldInfoName )
344
+ batch . commit ( ( err ) => {
345
+ if ( err ) return _error ( callback , err )
346
+ callback ( null , keyInfo )
347
+ } )
305
348
} )
306
349
} )
307
350
} )
@@ -372,10 +415,21 @@ class Keychain {
372
415
return _error ( callback , 'Cannot read the key, most likely the password is wrong' )
373
416
}
374
417
const newpem = forge . pki . encryptRsaPrivateKey ( privateKey , this . _ ( ) )
375
- return self . store . put ( dsname , newpem , ( err ) => {
418
+ util . keyId ( privateKey , ( err , kid ) => {
376
419
if ( err ) return _error ( callback , err )
377
420
378
- this . _getKeyInfo ( name , callback )
421
+ const keyInfo = {
422
+ name : name ,
423
+ id : kid
424
+ }
425
+ const batch = self . store . batch ( )
426
+ batch . put ( dsname , newpem )
427
+ batch . put ( DsInfoName ( name ) , JSON . stringify ( keyInfo ) )
428
+ batch . commit ( ( err ) => {
429
+ if ( err ) return _error ( callback , err )
430
+
431
+ callback ( null , keyInfo )
432
+ } )
379
433
} )
380
434
} catch ( err ) {
381
435
_error ( callback , err )
@@ -408,10 +462,21 @@ class Keychain {
408
462
return _error ( callback , 'Cannot read the peer private key' )
409
463
}
410
464
const pem = forge . pki . encryptRsaPrivateKey ( privateKey , this . _ ( ) )
411
- return self . store . put ( dsname , pem , ( err ) => {
465
+ util . keyId ( privateKey , ( err , kid ) => {
412
466
if ( err ) return _error ( callback , err )
413
467
414
- this . _getKeyInfo ( name , callback )
468
+ const keyInfo = {
469
+ name : name ,
470
+ id : kid
471
+ }
472
+ const batch = self . store . batch ( )
473
+ batch . put ( dsname , pem )
474
+ batch . put ( DsInfoName ( name ) , JSON . stringify ( keyInfo ) )
475
+ batch . commit ( ( err ) => {
476
+ if ( err ) return _error ( callback , err )
477
+
478
+ callback ( null , keyInfo )
479
+ } )
415
480
} )
416
481
} catch ( err ) {
417
482
_error ( callback , err )
@@ -426,6 +491,7 @@ class Keychain {
426
491
* @param {string } name
427
492
* @param {function(Error, string) } callback
428
493
* @returns {undefined }
494
+ * @private
429
495
*/
430
496
_getPrivateKey ( name , callback ) {
431
497
if ( ! validateKeyName ( name ) ) {
@@ -438,34 +504,6 @@ class Keychain {
438
504
callback ( null , res . toString ( ) )
439
505
} )
440
506
}
441
-
442
- _getKeyInfo ( name , callback ) {
443
- if ( ! validateKeyName ( name ) ) {
444
- return _error ( callback , `Invalid key name '${ name } '` )
445
- }
446
-
447
- const dsname = DsName ( name )
448
- this . store . get ( dsname , ( err , res ) => {
449
- if ( err ) {
450
- return _error ( callback , `Key '${ name } ' does not exist. ${ err . message } ` )
451
- }
452
- const pem = res . toString ( )
453
- try {
454
- const privateKey = forge . pki . decryptRsaPrivateKey ( pem , this . _ ( ) )
455
- util . keyId ( privateKey , ( err , kid ) => {
456
- if ( err ) return _error ( callback , err )
457
-
458
- const info = {
459
- name : name ,
460
- id : kid
461
- }
462
- return callback ( null , info )
463
- } )
464
- } catch ( e ) {
465
- _error ( callback , e )
466
- }
467
- } )
468
- }
469
507
}
470
508
471
509
module . exports = Keychain
0 commit comments