11'use strict'
22
3- const async = require ( 'async' )
43const sanitize = require ( 'sanitize-filename' )
54const forge = require ( 'node-forge' )
65const deepmerge = require ( 'deepmerge' )
@@ -10,7 +9,8 @@ const CMS = require('./cms')
109const DS = require ( 'interface-datastore' )
1110const pull = require ( 'pull-stream' )
1211
13- const keyExtension = '.p8'
12+ const keyPrefix = '/pkcs8/'
13+ const infoPrefix = '/info/'
1414
1515// NIST SP 800-132
1616const NIST = {
@@ -74,18 +74,18 @@ function _error (callback, err) {
7474 * @private
7575 */
7676function DsName ( name ) {
77- return new DS . Key ( '/' + name )
77+ return new DS . Key ( keyPrefix + name )
7878}
7979
8080/**
81- * Converts a datastore name into a key name.
81+ * Converts a key name into a datastore info name.
8282 *
83- * @param {DS.Key } name - A datastore name
84- * @returns {string }
83+ * @param {string } name
84+ * @returns {DS.Key }
8585 * @private
8686 */
87- function KsName ( name ) {
88- return name . toString ( ) . slice ( 1 )
87+ function DsInfoName ( name ) {
88+ return new DS . Key ( infoPrefix + name )
8989}
9090
9191/**
@@ -98,7 +98,12 @@ function KsName (name) {
9898 */
9999
100100/**
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+ *
102107 */
103108class Keychain {
104109 /**
@@ -112,9 +117,6 @@ class Keychain {
112117 throw new Error ( 'store is required' )
113118 }
114119 this . store = store
115- if ( this . store . opts ) {
116- this . store . opts . extension = keyExtension
117- }
118120
119121 const opts = deepmerge ( defaultOptions , options )
120122
@@ -149,9 +151,6 @@ class Keychain {
149151 dek = forge . util . bytesToHex ( dek )
150152 Object . defineProperty ( this , '_' , { value : ( ) => dek } )
151153
152- // JS magick
153- this . _getKeyInfo = this . findKeyByName = this . _getKeyInfo . bind ( this )
154-
155154 // Provide access to protected messages
156155 this . cms = new CMS ( this )
157156 }
@@ -192,12 +191,22 @@ class Keychain {
192191 }
193192 forge . pki . rsa . generateKeyPair ( { bits : size , workers : - 1 } , ( err , keypair ) => {
194193 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 ) => {
198195 if ( err ) return _error ( callback , err )
199196
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+ } )
201210 } )
202211 } )
203212 break
@@ -217,28 +226,27 @@ class Keychain {
217226 listKeys ( callback ) {
218227 const self = this
219228 const query = {
220- keysOnly : true
229+ prefix : infoPrefix
221230 }
222231 pull (
223232 self . store . query ( query ) ,
224233 pull . collect ( ( err , res ) => {
225234 if ( err ) return _error ( callback , err )
226235
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 )
229238 } )
230239 )
231240 }
232241
233242 /**
234- * Find a key by it's name .
243+ * Find a key by it's id .
235244 *
236245 * @param {string } id - The universally unique key identifier.
237246 * @param {function(Error, KeyInfo) } callback
238247 * @returns {undefined }
239248 */
240249 findKeyById ( id , callback ) {
241- // TODO: not very efficent.
242250 this . listKeys ( ( err , keys ) => {
243251 if ( err ) return _error ( callback , err )
244252
@@ -247,6 +255,28 @@ class Keychain {
247255 } )
248256 }
249257
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+
250280 /**
251281 * Remove an existing key.
252282 *
@@ -260,9 +290,12 @@ class Keychain {
260290 return _error ( callback , `Invalid key name '${ name } '` )
261291 }
262292 const dsname = DsName ( name )
263- self . _getKeyInfo ( name , ( err , keyinfo ) => {
293+ self . findKeyByName ( name , ( err , keyinfo ) => {
264294 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 ) => {
266299 if ( err ) return _error ( callback , err )
267300 callback ( null , keyinfo )
268301 } )
@@ -287,6 +320,8 @@ class Keychain {
287320 }
288321 const oldDsname = DsName ( oldName )
289322 const newDsname = DsName ( newName )
323+ const oldInfoName = DsInfoName ( oldName )
324+ const newInfoName = DsInfoName ( newName )
290325 this . store . get ( oldDsname , ( err , res ) => {
291326 if ( err ) {
292327 return _error ( callback , `Key '${ oldName } ' does not exist. ${ err . message } ` )
@@ -296,12 +331,20 @@ class Keychain {
296331 if ( err ) return _error ( callback , err )
297332 if ( exists ) return _error ( callback , `Key '${ newName } ' already exists` )
298333
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 ) => {
303335 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+ } )
305348 } )
306349 } )
307350 } )
@@ -372,10 +415,21 @@ class Keychain {
372415 return _error ( callback , 'Cannot read the key, most likely the password is wrong' )
373416 }
374417 const newpem = forge . pki . encryptRsaPrivateKey ( privateKey , this . _ ( ) )
375- return self . store . put ( dsname , newpem , ( err ) => {
418+ util . keyId ( privateKey , ( err , kid ) => {
376419 if ( err ) return _error ( callback , err )
377420
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+ } )
379433 } )
380434 } catch ( err ) {
381435 _error ( callback , err )
@@ -408,10 +462,21 @@ class Keychain {
408462 return _error ( callback , 'Cannot read the peer private key' )
409463 }
410464 const pem = forge . pki . encryptRsaPrivateKey ( privateKey , this . _ ( ) )
411- return self . store . put ( dsname , pem , ( err ) => {
465+ util . keyId ( privateKey , ( err , kid ) => {
412466 if ( err ) return _error ( callback , err )
413467
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+ } )
415480 } )
416481 } catch ( err ) {
417482 _error ( callback , err )
@@ -426,6 +491,7 @@ class Keychain {
426491 * @param {string } name
427492 * @param {function(Error, string) } callback
428493 * @returns {undefined }
494+ * @private
429495 */
430496 _getPrivateKey ( name , callback ) {
431497 if ( ! validateKeyName ( name ) ) {
@@ -438,34 +504,6 @@ class Keychain {
438504 callback ( null , res . toString ( ) )
439505 } )
440506 }
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- }
469507}
470508
471509module . exports = Keychain
0 commit comments