UNPKG

futoin-secvault

Version:

FutoIn Secure Vault reference implementation

369 lines (307 loc) 11.5 kB
'use strict'; /** * @file * * Copyright 2018 FutoIn Project (https://futoin.org) * Copyright 2018 Andrey Galkin <andrey@futoin.org> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const UUIDTool = require( 'futoin-uuid' ); const _isEqual = require( 'lodash/isEqual' ); const moment = require( 'moment' ); const BaseService = require( './lib/BaseService' ); const KeyFace = require( './KeyFace' ); const { VaultPlugin } = require( './lib/main' ); const KeyInfo = require( './lib/storage/KeyInfo' ); /** * Key Service */ class KeyService extends BaseService { static get IFACE_IMPL() { return KeyFace; } unlock( as, reqinfo ) { as.add( ( as ) => { this._storage.setStorageSecret( as, reqinfo.params().secret ); reqinfo.result( true ); }, ( as, err ) => { as.error( 'InvalidSecret' ); } ); } lock( as, reqinfo ) { this._storage.setStorageSecret( as, null ); as.add( as => reqinfo.result( true ) ); } _newKey( as, reqinfo, { inject = false, base_key = null }, gen_cb ) { const { ext_id, usage, key_type, gen_params } = reqinfo.params(); const storage = this._storage; const usage_set = new Set( usage ); const info = new KeyInfo( { uuidb64 : UUIDTool.genB64(), ext_id, u_encrypt : usage_set.has( 'encrypt' ), u_sign : usage_set.has( 'sign' ), u_derive : usage_set.has( 'derive' ), u_shared : usage_set.has( 'shared' ), u_temp : usage_set.has( 'temp' ), type : key_type, } ); if ( typeof gen_params === 'object' ) { info.params = Object.assign( {}, gen_params ); } else if ( typeof gen_params === 'number' ) { info.params = { bits : parseInt( gen_params ) }; } else if ( typeof gen_params === 'string' ) { info.params = { curve : gen_params }; } if ( base_key ) { info.params.base_key = base_key; } let vp; try { vp = VaultPlugin.getPlugin( key_type ); } catch ( _ ) { as.error( 'UnsupportedType' ); } const generate_common = ( as ) => { as.add( ( as ) => gen_cb( as, key_type, info.params ) ); as.add( ( as, key ) => { info.raw = key; if ( vp.isAsymetric() ) { vp.pubkey( as, key ); as.add( ( as, pubkey ) => info.params.pubkey = pubkey.toString() ); } } ); }; as.add( ( as ) => { if ( inject ) { generate_common( as ); } // Avoid resources spent on generation, if already exists storage.loadExt( as, ext_id, inject ); }, ( as, err ) => { if ( err !== 'UnknownKeyID' ) { return; } if ( !info.raw ) { generate_common( as ); } as.add( ( as ) => vp.validateKey( as, info.raw ), ( as, err ) => as.error( 'InvalidKey', as.state.error_info ) ); as.add( ( as ) => { storage.save( as, info ); }, ( as, err ) => { if ( err === 'Duplicate' ) { storage.loadExt( as, ext_id, inject ); } } ); } ); as.add( ( as, old_info ) => { const info_params = info.params; const old_info_params = old_info.params; if ( vp.isAsymetric() && !( 'pubkey' in info_params ) ) { info_params.pubkey = old_info_params.pubkey; } if ( ( old_info.u_encrypt !== usage_set.has( 'encrypt' ) ) || ( old_info.u_sign !== usage_set.has( 'sign' ) ) || ( old_info.u_derive !== usage_set.has( 'derive' ) ) || ( old_info.u_shared !== usage_set.has( 'shared' ) ) || ( old_info.u_temp !== usage_set.has( 'temp' ) ) || ( old_info.type !== key_type ) || !_isEqual( old_info_params, info_params ) || ( inject && !old_info.raw.equals( info.raw ) ) ) { as.error( 'OrigMismatch' ); } reqinfo.result( old_info.uuidb64 ); } ); } generateKey( as, reqinfo ) { this._newKey( as, reqinfo, {}, ( as, key_type, options ) => { const vp = VaultPlugin.getPlugin( key_type ); vp.generate( as, options ); } ); } injectKey( as, reqinfo ) { this._newKey( as, reqinfo, { inject: true }, ( as ) => { as.success( reqinfo.params().data ); } ); } injectEncryptedKey( as, reqinfo ) { this._newKey( as, reqinfo, { inject: true }, ( as ) => { const { data, enc_key, mode } = reqinfo.params(); this._loadCryptKey( as, enc_key ); as.add( ( as, enc_key_info ) => { const vp = VaultPlugin.getPlugin( enc_key_info.type ); as.add( ( as ) => { vp.decrypt( as, enc_key_info.raw, data, { mode } ); }, ( as, err ) => { this._storage.updateUsage( as, enc_key_info.uuidb64, { failures: 1, } ); as.add( ( as ) => as.error( 'InvalidKey' ) ); } ); } ); } ); } deriveKey( as, reqinfo ) { const { base_key } = reqinfo.params(); this._newKey( as, reqinfo, { base_key }, ( as, key_type, options ) => { const { base_key, kdf, hash, salt, other } = reqinfo.params(); const vp = VaultPlugin.getPlugin( key_type ); const bits = options.bits || other.bits || vp.defaultBits(); const drv_opts = Object.assign( { salt }, options, other ); let vp_kdf; try { vp_kdf = VaultPlugin.getPlugin( kdf ); } catch ( _ ) { as.error( 'UnsupportedDerivation' ); } this._storage.load( as, base_key ); as.add( ( as, base_key_info ) => { if ( !base_key_info.u_derive ) { as.error( 'NotApplicable' ); } vp_kdf.derive( as, base_key_info.raw, bits, hash, drv_opts ); } ); } ); } wipeKey( as, reqinfo ) { this._storage.remove( as, reqinfo.params().id ); as.add( ( as ) => reqinfo.result( true ) ); } _exposeCommon( as, reqinfo, out_cb ) { this._storage.load( as, reqinfo.params().id ); as.add( ( as, info ) => { if ( !info.u_shared ) { as.error( 'NotApplicable' ); } out_cb( as, info ); } ); } exposeKey( as, reqinfo ) { this._exposeCommon( as, reqinfo, ( as, info ) => { reqinfo.result( info.raw ); } ); } encryptedKey( as, reqinfo ) { this._exposeCommon( as, reqinfo, ( as, info ) => { const { mode, enc_key } = reqinfo.params(); this._loadCryptKey( as, enc_key ); as.add( ( as, enc_key_info ) => { const vp = VaultPlugin.getPlugin( enc_key_info.type ); const raw = info.raw; const iv = Buffer.from( info.uuidb64, 'base64' ); this._storage.updateUsage( as, enc_key_info.uuidb64, { times: 1, bytes: raw.length, } ); vp.encrypt( as, enc_key_info.raw, raw, { mode, iv, iv_length: 16 } ); as.add( ( as, data ) => reqinfo.result( data ) ); } ); } ); } pubEncryptedKey( as, reqinfo ) { this._exposeCommon( as, reqinfo, ( as, info ) => { const { pubkey : { type, data } } = reqinfo.params(); const vp = VaultPlugin.getPlugin( type ); if ( !vp.isAsymetric() ) { as.error( 'NotApplicable' ); } vp.encrypt( as, data, info.raw ); as.add( ( as, enckey ) => reqinfo.result( enckey ) ); } ); } publicKey( as, reqinfo ) { this._loadCryptKey( as, reqinfo.params().id ); as.add( ( as, info ) => { const { type, raw } = info; const vp = VaultPlugin.getPlugin( type ); if ( !vp.isAsymetric() ) { as.error( 'NotApplicable' ); } vp.pubkey( as, raw ); as.add( ( as, data ) => reqinfo.result( { type, data, } ) ); } ); } _keyInfoCommon( as, info ) { const usage = []; for ( let u of [ 'encrypt', 'sign', 'derive', 'shared', 'temp' ] ) { if ( info[`u_${u}`] ) { usage.push( u ); } } as.success( { id: info.uuidb64, ext_id: info.ext_id, usage, type: info.type, params: info.params, created: moment.utc( info.created ).format(), times: info.stat_times, bytes: info.stat_bytes, failures: info.stat_failures, // Deprecated used_times: info.stat_times, used_bytes: info.stat_bytes, sig_failures: info.stat_failures, } ); } keyInfo( as, reqinfo ) { this._storage.load( as, reqinfo.params().id, false ); as.add( this._keyInfoCommon ); } extKeyInfo( as, reqinfo ) { this._storage.loadExt( as, reqinfo.params().ext_id, false ); as.add( this._keyInfoCommon ); } listKeys( as, reqinfo ) { this._storage.list( as, reqinfo.params().ext_prefix ); as.add( ( as, res ) => reqinfo.result( res ) ); } addStats( as, reqinfo ) { const { id, times, bytes, failures } = reqinfo.params(); this._storage.updateUsage( as, id, { times, bytes, failures, } ); as.add( ( as ) => reqinfo.result( true ) ); } /** * Register futoin.secvault.keys interface with Executor * @alias KeyService.register * @param {AsyncSteps} as - steps interface * @param {Executor} executor - executor instance * @param {object} options - implementation defined options * @returns {KeyService} instance */ } module.exports = KeyService;