UNPKG

cyberchef

Version:

The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.

1,246 lines (1,149 loc) 69.4 kB
/** * Implementation Web Crypto interfaces for GOST algorithms * 1.76 * 2014-2016, Rudolf Nickolaev. All rights reserved. * * Exported for CyberChef by mshwed [m@ttshwed.com] */ /* * 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. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ import GostRandom from './gostRandom.mjs'; import gostEngine from './gostEngine.mjs'; import crypto from 'crypto' /* * Algorithm normalization * */ // <editor-fold defaultstate="collapsed"> var root = {}; root.gostEngine = gostEngine; var rootCrypto = crypto var SyntaxError = Error, DataError = Error, NotSupportedError = Error, OperationError = Error, InvalidStateError = Error, InvalidAccessError = Error; // Normalize algorithm function normalize(algorithm, method) { if (typeof algorithm === 'string' || algorithm instanceof String) algorithm = {name: algorithm}; var name = algorithm.name; if (!name) throw new SyntaxError('Algorithm name not defined'); // Extract algorithm modes from name var modes = name.split('/'), modes = modes[0].split('-').concat(modes.slice(1)); // Normalize the name with default modes var na = {}; name = modes[0].replace(/[\.\s]/g, ''); modes = modes.slice(1); if (name.indexOf('28147') >= 0) { na = { name: 'GOST 28147', version: 1989, mode: (algorithm.mode || (// ES, MAC, KW (method === 'sign' || method === 'verify') ? 'MAC' : (method === 'wrapKey' || method === 'unwrapKey') ? 'KW' : 'ES')).toUpperCase(), length: algorithm.length || 64 }; } else if (name.indexOf('3412') >= 0) { na = { name: 'GOST R 34.12', version: 2015, mode: (algorithm.mode || (// ES, MAC, KW (method === 'sign' || method === 'verify') ? 'MAC' : (method === 'wrapKey' || method === 'unwrapKey') ? 'KW' : 'ES')).toUpperCase(), length: algorithm.length || 64 // 128 }; } else if (name.indexOf('3411') >= 0) { na = { name: 'GOST R 34.11', version: 2012, // 1994 mode: (algorithm.mode || (// HASH, KDF, HMAC, PBKDF2, PFXKDF, CPKDF (method === 'deriveKey' || method === 'deriveBits') ? 'KDF' : (method === 'sign' || method === 'verify') ? 'HMAC' : 'HASH')).toUpperCase(), length: algorithm.length || 256 // 512 }; } else if (name.indexOf('3410') >= 0) { na = { name: 'GOST R 34.10', version: 2012, // 1994, 2001 mode: (algorithm.mode || (// SIGN, DH, MASK (method === 'deriveKey' || method === 'deriveBits') ? 'DH' : 'SIGN')).toUpperCase(), length: algorithm.length || 256 // 512 }; } else if (name.indexOf('SHA') >= 0) { na = { name: 'SHA', version: (algorithm.length || 160) === 160 ? 1 : 2, // 1, 2 mode: (algorithm.mode || (// HASH, KDF, HMAC, PBKDF2, PFXKDF (method === 'deriveKey' || method === 'deriveBits') ? 'KDF' : (method === 'sign' || method === 'verify') ? 'HMAC' : 'HASH')).toUpperCase(), length: algorithm.length || 160 }; } else if (name.indexOf('RC2') >= 0) { na = { name: 'RC2', version: 1, mode: (algorithm.mode || (// ES, MAC, KW (method === 'sign' || method === 'verify') ? 'MAC' : (method === 'wrapKey' || method === 'unwrapKey') ? 'KW' : 'ES')).toUpperCase(), length: algorithm.length || 32 // 1 - 1024 }; } else if (name.indexOf('PBKDF2') >= 0) { na = normalize(algorithm.hash, 'digest'); na.mode = 'PBKDF2'; } else if (name.indexOf('PFXKDF') >= 0) { na = normalize(algorithm.hash, 'digest'); na.mode = 'PFXKDF'; } else if (name.indexOf('CPKDF') >= 0) { na = normalize(algorithm.hash, 'digest'); na.mode = 'CPKDF'; } else if (name.indexOf('HMAC') >= 0) { na = normalize(algorithm.hash, 'digest'); na.mode = 'HMAC'; } else throw new NotSupportedError('Algorithm not supported'); // Compile modes modes.forEach(function (mode) { mode = mode.toUpperCase(); if (/^[0-9]+$/.test(mode)) { if ((['8', '16', '32'].indexOf(mode) >= 0) || (na.length === '128' && mode === '64')) { // Shift bits if (na.mode === 'ES') na.shiftBits = parseInt(mode); else if (na.mode === 'MAC') na.macLength = parseInt(mode); else throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported'); } else if (['89', '94', '01', '12', '15', '1989', '1994', '2001', '2012', '2015'].indexOf(mode) >= 0) { // GOST Year var version = parseInt(mode); version = version < 1900 ? (version < 80 ? 2000 + version : 1900 + version) : version; na.version = version; } else if (['1'].indexOf(mode) >= 0 && na.name === 'SHA') { // SHA-1 na.version = 1; na.length = 160; } else if (['256', '384', '512'].indexOf(mode) >= 0 && na.name === 'SHA') { // SHA-2 na.version = 2; na.length = parseInt(mode); } else if (['40', '128'].indexOf(mode) >= 0 && na.name === 'RC2') { // RC2 na.version = 1; na.length = parseInt(mode); // key size } else if (['64', '128', '256', '512'].indexOf(mode) >= 0) // block size na.length = parseInt(mode); else if (['1000', '2000'].indexOf(mode) >= 0) // Iterations na.iterations = parseInt(mode); // Named Paramsets } else if (['E-TEST', 'E-A', 'E-B', 'E-C', 'E-D', 'E-SC', 'E-Z', 'D-TEST', 'D-A', 'D-SC'].indexOf(mode) >= 0) { na.sBox = mode; } else if (['S-TEST', 'S-A', 'S-B', 'S-C', 'S-D', 'X-A', 'X-B', 'X-C'].indexOf(mode) >= 0) { na.namedParam = mode; } else if (['S-256-TEST', 'S-256-A', 'S-256-B', 'S-256-C', 'P-256', 'T-512-TEST', 'T-512-A', 'T-512-B', 'X-256-A', 'X-256-B', 'T-256-TEST', 'T-256-A', 'T-256-B', 'S-256-B', 'T-256-C', 'S-256-C'].indexOf(mode) >= 0) { na.namedCurve = mode; } else if (['SC', 'CP', 'VN'].indexOf(mode) >= 0) { na.procreator = mode; // Encription GOST 28147 or GOST R 34.12 } else if (na.name === 'GOST 28147' || na.name === 'GOST R 34.12' || na.name === 'RC2') { if (['ES', 'MAC', 'KW', 'MASK'].indexOf(mode) >= 0) { na.mode = mode; } else if (['ECB', 'CFB', 'OFB', 'CTR', 'CBC'].indexOf(mode) >= 0) { na.mode = 'ES'; na.block = mode; } else if (['CPKW', 'NOKW', 'SCKW'].indexOf(mode) >= 0) { na.mode = 'KW'; na.keyWrapping = mode.replace('KW', ''); } else if (['ZEROPADDING', 'PKCS5PADDING', 'NOPADDING', 'RANDOMPADDING', 'BITPADDING'].indexOf(mode) >= 0) { na.padding = mode.replace('PADDING', ''); } else if (['NOKM', 'CPKM'].indexOf(mode) >= 0) { na.keyMeshing = mode.replace('KM', ''); } else throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported'); // Digesting GOST 34.11 } else if (na.name === 'GOST R 34.11' || na.name === 'SHA') { if (['HASH', 'KDF', 'HMAC', 'PBKDF2', 'PFXKDF', 'CPKDF'].indexOf(mode) >= 0) na.mode = mode; else throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported'); // Signing GOST 34.10 } else if (na.name === 'GOST R 34.10') { var hash = mode.replace(/[\.\s]/g, ''); if (hash.indexOf('GOST') >= 0 && hash.indexOf('3411') >= 0) na.hash = mode; else if (['SIGN', 'DH', 'MASK'].indexOf(mode)) na.mode = mode; else throw new NotSupportedError('Algorithm ' + na.name + ' mode ' + mode + ' not supported'); } }); // Procreator na.procreator = algorithm.procreator || na.procreator || 'CP'; // Key size switch (na.name) { case 'GOST R 34.10': na.keySize = na.length / (na.version === 1994 ? 4 : 8); break; case 'GOST R 34.11': na.keySize = 32; break; case 'GOST 28147': case 'GOST R 34.12': na.keySize = 32; break; case 'RC2': na.keySize = Math.ceil(na.length / 8); break; case 'SHA': na.keySize = na.length / 8; break; } // Encrypt additional modes if (na.mode === 'ES') { if (algorithm.block) na.block = algorithm.block; // ECB, CFB, OFB, CTR, CBC if (na.block) na.block = na.block.toUpperCase(); if (algorithm.padding) na.padding = algorithm.padding; // NO, ZERO, PKCS5, RANDOM, BIT if (na.padding) na.padding = na.padding.toUpperCase(); if (algorithm.shiftBits) na.shiftBits = algorithm.shiftBits; // 8, 16, 32, 64 if (algorithm.keyMeshing) na.keyMeshing = algorithm.keyMeshing; // NO, CP if (na.keyMeshing) na.keyMeshing = na.keyMeshing.toUpperCase(); // Default values if (method !== 'importKey' && method !== 'generateKey') { na.block = na.block || 'ECB'; na.padding = na.padding || (na.block === 'CBC' || na.block === 'ECB' ? 'ZERO' : 'NO'); if (na.block === 'CFB' || na.block === 'OFB') na.shiftBits = na.shiftBits || na.length; na.keyMeshing = na.keyMeshing || 'NO'; } } if (na.mode === 'KW') { if (algorithm.keyWrapping) na.keyWrapping = algorithm.keyWrapping; // NO, CP, SC if (na.keyWrapping) na.keyWrapping = na.keyWrapping.toUpperCase(); if (method !== 'importKey' && method !== 'generateKey') na.keyWrapping = na.keyWrapping || 'NO'; } // Paramsets ['sBox', 'namedParam', 'namedCurve', 'curve', 'param', 'modulusLength'].forEach(function (name) { algorithm[name] && (na[name] = algorithm[name]); }); // Default values if (method !== 'importKey' && method !== 'generateKey') { if (na.name === 'GOST 28147') { na.sBox = na.sBox || (na.procreator === 'SC' ? 'E-SC' : 'E-A'); // 'E-A', 'E-B', 'E-C', 'E-D', 'E-SC' } else if (na.name === 'GOST R 34.12' && na.length === 64) { na.sBox = 'E-Z'; } else if (na.name === 'GOST R 34.11' && na.version === 1994) { na.sBox = na.sBox || (na.procreator === 'SC' ? 'D-SC' : 'D-A'); // 'D-SC' } else if (na.name === 'GOST R 34.10' && na.version === 1994) { na.namedParam = na.namedParam || (na.mode === 'DH' ? 'X-A' : 'S-A'); // 'S-B', 'S-C', 'S-D', 'X-B', 'X-C' } else if (na.name === 'GOST R 34.10' && na.version === 2001) { na.namedCurve = na.namedCurve || (na.length === 256 ? na.procreator === 'SC' ? 'P-256' : (na.mode === 'DH' ? 'X-256-A' : 'S-256-A') : // 'S-256-B', 'S-256-C', 'X-256-B', 'T-256-A', 'T-256-B', 'T-256-C', 'P-256' na.mode === 'T-512-A'); // 'T-512-B', 'T-512-C' } else if (na.name === 'GOST R 34.10' && na.version === 2012) { na.namedCurve = na.namedCurve || (na.length === 256 ? na.procreator === 'SC' ? 'P-256' : (na.mode === 'DH' ? 'X-256-A' : 'S-256-A') : // 'S-256-B', 'S-256-C', 'X-256-B', 'T-256-A', 'T-256-B', 'T-256-C', 'P-256' na.mode === 'T-512-A'); // 'T-512-B', 'T-512-C' } } // Vectors switch (na.mode) { case 'DH': algorithm.ukm && (na.ukm = algorithm.ukm); algorithm['public'] && (na['public'] = algorithm['public']); break; case 'SIGN': case 'KW': algorithm.ukm && (na.ukm = algorithm.ukm); break; case 'ES': case 'MAC': algorithm.iv && (na.iv = algorithm.iv); break; case 'KDF': algorithm.label && (na.label = algorithm.label); algorithm.contex && (na.context = algorithm.contex); break; case 'PBKDF2': algorithm.salt && (na.salt = algorithm.salt); algorithm.iterations && (na.iterations = algorithm.iterations); algorithm.diversifier && (na.diversifier = algorithm.diversifier); break; case 'PFXKDF': algorithm.salt && (na.salt = algorithm.salt); algorithm.iterations && (na.iterations = algorithm.iterations); algorithm.diversifier && (na.diversifier = algorithm.diversifier); break; case 'CPKDF': algorithm.salt && (na.salt = algorithm.salt); algorithm.iterations && (na.iterations = algorithm.iterations); break; } // Verification method and modes if (method && ( ((na.mode !== 'ES' && na.mode !== 'SIGN' && na.mode !== 'MAC' && na.mode !== 'HMAC' && na.mode !== 'KW' && na.mode !== 'DH' && na.mode !== 'MASK') && (method === 'generateKey')) || ((na.mode !== 'ES') && (method === 'encrypt' || method === 'decrypt')) || ((na.mode !== 'SIGN' && na.mode !== 'MAC' && na.mode !== 'HMAC') && (method === 'sign' || method === 'verify')) || ((na.mode !== 'HASH') && (method === 'digest')) || ((na.mode !== 'KW' && na.mode !== 'MASK') && (method === 'wrapKey' || method === 'unwrapKey')) || ((na.mode !== 'DH' && na.mode !== 'PBKDF2' && na.mode !== 'PFXKDF' && na.mode !== 'CPKDF' && na.mode !== 'KDF') && (method === 'deriveKey' || method === 'deriveBits')))) throw new NotSupportedError('Algorithm mode ' + na.mode + ' not valid for method ' + method); // Normalize hash algorithm algorithm.hash && (na.hash = algorithm.hash); if (na.hash) { if ((typeof na.hash === 'string' || na.hash instanceof String) && na.procreator) na.hash = na.hash + '/' + na.procreator; na.hash = normalize(na.hash, 'digest'); } // Algorithm object identirifer algorithm.id && (na.id = algorithm.id); return na; } // Check for possibility use native crypto.subtle function checkNative(algorithm) { if (!rootCrypto || !rootCrypto.subtle || !algorithm) return false; // Prepare name var name = (typeof algorithm === 'string' || algorithm instanceof String) ? name = algorithm : algorithm.name; if (!name) return false; name = name.toUpperCase(); // Digest algorithm for key derivation if ((name.indexOf('KDF') >= 0 || name.indexOf('HMAC') >= 0) && algorithm.hash) return checkNative(algorithm.hash); // True if no supported names return name.indexOf('GOST') === -1 && name.indexOf('SHA-1') === -1 && name.indexOf('RC2') === -1 && name.indexOf('?DES') === -1; } // </editor-fold> /* * Key conversion methods * */ // <editor-fold defaultstate="collapsed"> // Check key parameter function checkKey(key, method) { if (!key.algorithm) throw new SyntaxError('Key algorithm not defined'); if (!key.algorithm.name) throw new SyntaxError('Key algorithm name not defined'); var name = key.algorithm.name, gostCipher = name === 'GOST 28147' || name === 'GOST R 34.12' || name === 'RC2', gostDigest = name === 'GOST R 34.11' || name === 'SHA', gostSign = name === 'GOST R 34.10'; if (!gostCipher && !gostSign && !gostDigest) throw new NotSupportedError('Key algorithm ' + name + ' is unsupproted'); if (!key.type) throw new SyntaxError('Key type not defined'); if (((gostCipher || gostDigest) && key.type !== 'secret') || (gostSign && !(key.type === 'public' || key.type === 'private'))) throw new DataError('Key type ' + key.type + ' is not valid for algorithm ' + name); if (!key.usages || !key.usages.indexOf) throw new SyntaxError('Key usages not defined'); for (var i = 0, n = key.usages.length; i < n; i++) { var md = key.usages[i]; if (((md === 'encrypt' || md === 'decrypt') && key.type !== 'secret') || (md === 'sign' && key.type === 'public') || (md === 'verify' && key.type === 'private')) throw new InvalidStateError('Key type ' + key.type + ' is not valid for ' + md); } if (method) if (key.usages.indexOf(method) === -1) throw new InvalidAccessError('Key usages is not contain method ' + method); if (!key.buffer) throw new SyntaxError('Key buffer is not defined'); var size = key.buffer.byteLength * 8, keySize = 8 * key.algorithm.keySize; if ((key.type === 'secret' && size !== (keySize || 256) && (key.usages.indexOf('encrypt') >= 0 || key.usages.indexOf('decrypt') >= 0)) || (key.type === 'private' && !(size === 256 || size === 512)) || (key.type === 'public' && !(size === 512 || size === 1024))) throw new SyntaxError('Key buffer has wrong size ' + size + ' bit'); } // Extract key and enrich cipher algorithm function extractKey(method, algorithm, key) { checkKey(key, method); if (algorithm) { var params; switch (algorithm.mode) { case 'ES': params = ['sBox', 'keyMeshing', 'padding', 'block']; break; case 'SIGN': params = ['namedCurve', 'namedParam', 'sBox', 'curve', 'param', 'modulusLength']; break; case 'MAC': params = ['sBox']; break; case 'KW': params = ['keyWrapping', 'ukm']; break; case 'DH': params = ['namedCurve', 'namedParam', 'sBox', 'ukm', 'curve', 'param', 'modulusLength']; break; case 'KDF': params = ['context', 'label']; break; case 'PBKDF2': params = ['sBox', 'iterations', 'salt']; break; case 'PFXKDF': params = ['sBox', 'iterations', 'salt', 'diversifier']; break; case 'CPKDF': params = ['sBox', 'salt']; break; } if (params) params.forEach(function (name) { key.algorithm[name] && (algorithm[name] = key.algorithm[name]); }); } return key.buffer; } // Make key definition function convertKey(algorithm, extractable, keyUsages, keyData, keyType) { var key = { type: keyType || (algorithm.name === 'GOST R 34.10' ? 'private' : 'secret'), extractable: extractable || 'false', algorithm: algorithm, usages: keyUsages || [], buffer: keyData }; checkKey(key); return key; } function convertKeyPair(publicAlgorithm, privateAlgorithm, extractable, keyUsages, publicBuffer, privateBuffer) { if (!keyUsages || !keyUsages.indexOf) throw new SyntaxError('Key usages not defined'); var publicUsages = keyUsages.filter(function (value) { return value !== 'sign'; }); var privateUsages = keyUsages.filter(function (value) { return value !== 'verify'; }); return { publicKey: convertKey(publicAlgorithm, extractable, publicUsages, publicBuffer, 'public'), privateKey: convertKey(privateAlgorithm, extractable, privateUsages, privateBuffer, 'private') }; } // Swap bytes in buffer function swapBytes(src) { if (src instanceof CryptoOperationData) src = new Uint8Array(src); var dst = new Uint8Array(src.length); for (var i = 0, n = src.length; i < n; i++) dst[n - i - 1] = src[i]; return dst.buffer; } // </editor-fold> /** * Promise stub object (not fulfill specification, only for internal use) * Class not defined if Promise class already defined in root context<br><br> * * The Promise object is used for deferred and asynchronous computations. A Promise is in one of the three states: * <ul> * <li>pending: initial state, not fulfilled or rejected.</li> * <li>fulfilled: successful operation</li> * <li>rejected: failed operation.</li> * </ul> * Another term describing the state is settled: the Promise is either fulfilled or rejected, but not pending.<br><br> * @class Promise * @global * @param {function} executor Function object with two arguments resolve and reject. * The first argument fulfills the promise, the second argument rejects it. * We can call these functions, once our operation is completed. */ // <editor-fold defaultstate="collapsed"> if (!Promise) { root.Promise = (function () { function mswrap(value) { if (value && value.oncomplete === null && value.onerror === null) { return new Promise(function (resolve, reject) { value.oncomplete = function () { resolve(value.result); }; value.onerror = function () { reject(new OperationError(value.toString())); }; }); } else return value; } function Promise(executor) { var state = 'pending', result, resolveQueue = [], rejectQueue = []; function call(callback) { try { callback(); } catch (e) { } } try { executor(function (value) { if (state === 'pending') { state = 'fulfilled'; result = value; resolveQueue.forEach(call); } }, function (reason) { if (state === 'pending') { state = 'rejected'; result = reason; rejectQueue.forEach(call); } }); } catch (error) { if (state === 'pending') { state = 'rejected'; result = error; rejectQueue.forEach(call); } } /** * The then() method returns a Promise. It takes two arguments, both are * callback functions for the success and failure cases of the Promise. * * @method then * @memberOf Promise * @instance * @param {function} onFulfilled A Function called when the Promise is fulfilled. This function has one argument, the fulfillment value. * @param {function} onRejected A Function called when the Promise is rejected. This function has one argument, the rejection reason. * @returns {Promise} */ this.then = function (onFulfilled, onRejected) { return new Promise(function (resolve, reject) { function asyncOnFulfilled() { var value; try { value = onFulfilled ? onFulfilled(result) : result; } catch (error) { reject(error); return; } value = mswrap(value); if (value && value?.then?.call) { value.then(resolve, reject); } else { resolve(value); } } function asyncOnRejected() { var reason; try { reason = onRejected ? onRejected(result) : result; } catch (error) { reject(error); return; } reason = mswrap(reason); if (reason && reason?.then?.call) { reason.then(resolve, reject); } else { reject(reason); } } if (state === 'fulfilled') { asyncOnFulfilled(); } else if (state === 'rejected') { asyncOnRejected(); } else { resolveQueue.push(asyncOnFulfilled); rejectQueue.push(asyncOnRejected); } }); }; /** * The catch() method returns a Promise and deals with rejected cases only. * It behaves the same as calling Promise.prototype.then(undefined, onRejected). * * @method catch * @memberOf Promise * @instance * @param {function} onRejected A Function called when the Promise is rejected. This function has one argument, the rejection reason. * @returns {Promise} */ this['catch'] = function (onRejected) { return this.then(undefined, onRejected); }; } /** * The Promise.all(iterable) method returns a promise that resolves when all * of the promises in the iterable argument have resolved.<br><br> * * The result is passed as an array of values from all the promises. * If something passed in the iterable array is not a promise, it's converted to * one by Promise.resolve. If any of the passed in promises rejects, the * all Promise immediately rejects with the value of the promise that rejected, * discarding all the other promises whether or not they have resolved. * * @method all * @memberOf Promise * @static * @param {KeyUsages} promises Array with promises. * @returns {Promise} */ Promise.all = function (promises) { return new Promise(function (resolve, reject) { var result = [], count = 0; function asyncResolve(k) { count++; return function (data) { result[k] = data; count--; if (count === 0) resolve(result); }; } function asyncReject(reason) { if (count > 0) reject(reason); count = 0; } for (var i = 0, n = promises.length; i < n; i++) { var data = promises[i]; if (data?.then?.call) data.then(asyncResolve(i), asyncReject); else result[i] = data; } if (count === 0) resolve(result); }); }; return Promise; })(); } // </editor-fold> /* * Worker executor * */ // <editor-fold defaultstate="collapsed"> var baseUrl = '', nameSuffix = ''; // Try to define from DOM model if (typeof document !== 'undefined') { (function () { var regs = /^(.*)gostCrypto(.*)\.js$/i; var list = document.querySelectorAll('script'); for (var i = 0, n = list.length; i < n; i++) { var value = list[i].getAttribute('src'); var test = regs.exec(value); if (test) { baseUrl = test[1]; nameSuffix = test[2]; } } })(); } // Local importScripts procedure for include dependens function importScripts() { for (var i = 0, n = arguments.length; i < n; i++) { var name = arguments[i].split('.'), src = baseUrl + name[0] + nameSuffix + '.' + name[1]; var el = document.querySelector('script[src="' + src + '"]'); if (!el) { el = document.createElement('script'); el.setAttribute('src', src); document.head.appendChild(el); } } } // Create Worker var worker = false, tasks = [], sequence = 0; // Worker will create only for first child process and // Gost implementation libraries not yet loaded if (!root.importScripts && !root.gostEngine) { try { worker = new Worker(baseUrl + 'gostEngine' + nameSuffix + '.js'); // Result of opertion worker.onmessage = function (event) { // Find task var id = event.data.id; for (var i = 0, n = tasks.length; i < n; i++) if (tasks[i].id === id) break; if (i < n) { var task = tasks[i]; tasks.splice(i, 1); // Reject if error or resolve with result if (event.data.error) task.reject(new OperationError(event.data.error)); else task.resolve(event.data.result); } }; // Worker error - reject all waiting tasks worker.onerror = function (event) { for (var i = 0, n = tasks.length; i < n; i++) tasks[i].reject(event.error); tasks = []; }; } catch (e) { // Worker is't supported worker = false; } } if (!root.importScripts) { // This procedure emulate load dependents as in Worker root.importScripts = importScripts; } if (!worker) { // Import main module // Reason: we are already in worker process or Worker interface is not // yet supported root.gostEngine || require('./gostEngine'); } // Executor for any method function execute(algorithm, method, args) { return new Promise(function (resolve, reject) { try { if (worker) { var id = ++sequence; tasks.push({ id: id, resolve: resolve, reject: reject }); worker.postMessage({ id: id, algorithm: algorithm, method: method, args: args }); } else { if (root.gostEngine) resolve(root.gostEngine.execute(algorithm, method, args)); else reject(new OperationError('Module gostEngine not found')); } } catch (error) { reject(error); } }); } // Self resolver function call(callback) { try { callback(); } catch (e) { } } // </editor-fold> /* * WebCrypto common class references * */ // <editor-fold defaultstate="collapsed"> /** * The Algorithm object is a dictionary object [WebIDL] which is used to * specify an algorithm and any additional parameters required to fully * specify the desired operation.<br> * <pre> * dictionary Algorithm { * DOMString name; * }; * </pre> * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#algorithm-dictionary} * @class Algorithm * @param {DOMString} name The name of the registered algorithm to use. */ /** * AlgorithmIdentifier - Algorithm or DOMString name of algorithm<br> * <pre> * typedef (Algorithm or DOMString) AlgorithmIdentifier; * </pre> * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#algorithm-dictionary} * @class AlgorithmIdentifier */ /** * The KeyAlgorithm interface represents information about the contents of a * given Key object. * <pre> * interface KeyAlgorithm { * readonly attribute DOMString name * }; * </pre> * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-algorithm-interface} * @class KeyAlgorithm * @param {DOMString} name The name of the algorithm used to generate the Key */ /** * The type of a key. The recognized key type values are "public", "private" * and "secret". Opaque keying material, including that used for symmetric * algorithms, is represented by "secret", while keys used as part of asymmetric * algorithms composed of public/private keypairs will be either "public" or "private". * <pre> * typedef DOMString KeyType; * </pre> * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface} * @class KeyType */ /** * Sequence of operation type that may be performed using a key. The recognized * key usage values are "encrypt", "decrypt", "sign", "verify", "deriveKey", * "deriveBits", "wrapKey" and "unwrapKey". * <pre> * typedef DOMString[] KeyUsages; * </pre> * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface} * @class KeyUsages */ /** * The Key object represents an opaque reference to keying material that is * managed by the user agent.<br> * This specification provides a uniform interface for many different kinds of * keying material managed by the user agent. This may include keys that have * been generated by the user agent, derived from other keys by the user agent, * imported to the user agent through user actions or using this API, * pre-provisioned within software or hardware to which the user agent has * access or made available to the user agent in other ways. The term key refers * broadly to any keying material including actual keys for cryptographic * operations and secret values obtained within key derivation or exchange operations.<br> * The Key object is not required to directly interface with the underlying key * storage mechanism, and may instead simply be a reference for the user agent * to understand how to obtain the keying material when needed, eg. when performing * a cryptographic operation. * <pre> * interface Key { * readonly attribute KeyType type; * readonly attribute boolean extractable; * readonly attribute KeyAlgorithm algorithm; * readonly attribute KeyUsages usages; * }; * </pre> * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface} * @class Key * @param {KeyType} type The type of a key. The recognized key type values are "public", "private" and "secret". * @param {boolean} extractable Whether or not the raw keying material may be exported by the application. * @param {KeyAlgorithm} algorithm The Algorithm used to generate the key. * @param {KeyUsages} usages Key usage array: type of operation that may be performed using a key. */ /** * The KeyPair interface represents an asymmetric key pair that is comprised of both public and private keys. * <pre> * interface KeyPair { * readonly attribute Key publicKey; * readonly attribute Key privateKey; * }; * </pre> * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#keypair} * @class KeyPair * @param {Key} privateKey Private key * @param {Key} publicKey Public key */ /** * Specifies a serialization format for a key. The recognized key format values are: * <ul> * <li>'raw' - An unformatted sequence of bytes. Intended for secret keys.</li> * <li>'pkcs8' - The DER encoding of the PrivateKeyInfo structure from RFC 5208.</li> * <li>'spki' - The DER encoding of the SubjectPublicKeyInfo structure from RFC 5280.</li> * <li>'jwk' - The key is represented as JSON according to the JSON Web Key format.</li> * </ul> * <pre> * typedef DOMString KeyFormat; * </pre> * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#key-interface} * @class KeyFormat */ /** * Binary data * <pre> * typedef (ArrayBuffer or ArrayBufferView) CryptoOperationData; * </pre> * @class CryptoOperationData */ var CryptoOperationData = ArrayBuffer; /** * DER-encoded ArrayBuffer or PEM-encoded DOMString constains ASN.1 object<br> * <pre> * typedef (ArrayBuffer or DOMString) FormatedData; * </pre> * @class FormatedData */ // </editor-fold> /** * The gostCrypto provide general purpose cryptographic functionality for * GOST standards including a cryptographically strong pseudo-random number * generator seeded with truly random values. * * @namespace gostCrypto */ var gostCrypto = {}; /** * The SubtleCrypto class provides low-level cryptographic primitives and algorithms. * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#subtlecrypto-interface} * * @class SubtleCrypto */ // <editor-fold> function SubtleCrypto() { } /** * The encrypt method returns a new Promise object that will encrypt data * using the specified algorithm identifier with the supplied Key. * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-encrypt}<br><br> * * Supported algorithm names: * <ul> * <li><b>GOST 28147-ECB</b> "prostaya zamena" (ECB) mode (default)</li> * <li><b>GOST 28147-CFB</b> "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode</li> * <li><b>GOST 28147-OFB</b> "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode</li> * <li><b>GOST 28147-CTR</b> "gammirovanie" (counter) mode</li> * <li><b>GOST 28147-CBC</b> Cipher-Block-Chaining (CBC) mode</li> * <li><b>GOST R 34.12-ECB</b> "prostaya zamena" (ECB) mode (default)</li> * <li><b>GOST R 34.12-CFB</b> "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode</li> * <li><b>GOST R 34.12-OFB</b> "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode</li> * <li><b>GOST R 34.12-CTR</b> "gammirovanie" (counter) mode</li> * <li><b>GOST R 34.12-CBC</b> Cipher-Block-Chaining (CBC) mode</li> * </ul> * For more information see {@link GostCipher} * * @memberOf SubtleCrypto * @method encrypt * @instance * @param {AlgorithmIdentifier} algorithm Algorithm identifier * @param {Key} key Key object * @param {CryptoOperationData} data Operation data * @returns {Promise} Promise that resolves with {@link CryptoOperationData} */ SubtleCrypto.prototype.encrypt = function (algorithm, key, data) // <editor-fold defaultstate="collapsed"> { return new Promise(call).then(function () { if (checkNative(algorithm)) return rootCrypto.subtle.encrypt(algorithm, key, data); algorithm = normalize(algorithm, 'encrypt'); return execute(algorithm, 'encrypt', [extractKey('encrypt', algorithm, key), data]); }); }; // </editor-fold> /** * The decrypt method returns a new Promise object that will decrypt data * using the specified algorithm identifier with the supplied Key. * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-decrypt}<br><br> * * Supported algorithm names: * <ul> * <li><b>GOST 28147-ECB</b> "prostaya zamena" (ECB) mode (default)</li> * <li><b>GOST 28147-CFB</b> "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode</li> * <li><b>GOST 28147-OFB</b> "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode</li> * <li><b>GOST 28147-CTR</b> "gammirovanie" (counter) mode</li> * <li><b>GOST 28147-CBC</b> Cipher-Block-Chaining (CBC) mode</li> * <li><b>GOST R 34.12-ECB</b> "prostaya zamena" (ECB) mode (default)</li> * <li><b>GOST R 34.12-CFB</b> "gammirovanie s obratnoj svyaziyu po shifrotekstu" (CFB) mode</li> * <li><b>GOST R 34.12-OFB</b> "gammirovanie s obratnoj svyaziyu po vyhodu" (OFB) mode</li> * <li><b>GOST R 34.12-CTR</b> "gammirovanie" (counter) mode</li> * <li><b>GOST R 34.12-CBC</b> Cipher-Block-Chaining (CBC) mode</li> * </ul> * For additional modes see {@link GostCipher} * * @memberOf SubtleCrypto * @method decrypt * @instance * @param {AlgorithmIdentifier} algorithm Algorithm identifier * @param {Key} key Key object * @param {CryptoOperationData} data Operation data * @returns {Promise} Promise that resolves with {@link CryptoOperationData} */ SubtleCrypto.prototype.decrypt = function (algorithm, key, data) // <editor-fold defaultstate="collapsed"> { return new Promise(call).then(function () { if (checkNative(algorithm)) return rootCrypto.subtle.decrypt(algorithm, key, data); algorithm = normalize(algorithm, 'decrypt'); return execute(algorithm, 'decrypt', [extractKey('decrypt', algorithm, key), data]); }); }; // </editor-fold> /** * The sign method returns a new Promise object that will sign data using * the specified algorithm identifier with the supplied Key. * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-sign}<br><br> * * Supported algorithm names: * <ul> * <li><b>GOST R 34.10-94</b> GOST Signature</li> * <li><b>GOST R 34.10-94/GOST R 34.11-94</b> GOST Signature with Hash</li> * <li><b>GOST R 34.10</b> ECGOST Signature</li> * <li><b>GOST R 34.10/GOST R 34.11-94</b> ECGOST Signature with Old-Style Hash</li> * <li><b>GOST R 34.10/GOST R 34.11</b> ECGOST Signature with Streebog Hash</li> * <li><b>GOST 28147-MAC</b> MAC base on GOST 28147</li> * <li><b>GOST R 34.12-MAC</b> MAC base on GOST R 43.12</li> * <li><b>GOST R 34.11-HMAC</b> HMAC base on GOST 34.11</li> * <li><b>SHA-HMAC</b> HMAC base on SHA</li> * </ul> * For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher} * * @memberOf SubtleCrypto * @method sign * @instance * @param {AlgorithmIdentifier} algorithm Algorithm identifier * @param {Key} key Key object * @param {CryptoOperationData} data Operation data * @returns {Promise} Promise that resolves with {@link CryptoOperationData} */ SubtleCrypto.prototype.sign = function (algorithm, key, data) // <editor-fold defaultstate="collapsed"> { return new Promise(call).then(function () { if (checkNative(algorithm)) return rootCrypto.subtle.sign(algorithm, key, data); algorithm = normalize(algorithm, 'sign'); var value = execute(algorithm, 'sign', [extractKey('sign', algorithm, key), data]).then(function (data) { if (algorithm.procreator === 'SC' && algorithm.mode === 'SIGN') { data = gostCrypto.asn1.GostSignature.encode(data); } return data; }); return value; }); }; // </editor-fold> /** * The verify method returns a new Promise object that will verify data * using the specified algorithm identifier with the supplied Key. * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-verify}<br><br> * * Supported algorithm names: * <ul> * <li><b>GOST R 34.10-94</b> GOST Signature</li> * <li><b>GOST R 34.10-94/GOST R 34.11-94</b> GOST Signature with Hash</li> * <li><b>GOST R 34.10</b> ECGOST Signature</li> * <li><b>GOST R 34.10/GOST R 34.11-94</b> ECGOST Signature with Old-Style Hash</li> * <li><b>GOST R 34.10/GOST R 34.11</b> ECGOST Signature with Streebog Hash</li> * <li><b>GOST 28147-MAC</b> MAC base on GOST 28147</li> * <li><b>GOST R 34.12-MAC</b> MAC base on GOST R 34.12</li> * <li><b>GOST R 34.11-HMAC</b> HMAC base on GOST 34.11</li> * <li><b>SHA-HMAC</b> HMAC base on SHA</li> * </ul> * For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher} * * @memberOf SubtleCrypto * @method verify * @instance * @param {AlgorithmIdentifier} algorithm Algorithm identifier * @param {Key} key Key object * @param {CryptoOperationData} signature Signature data * @param {CryptoOperationData} data Operation data * @returns {Promise} Promise that resolves with boolean value of verification result */ SubtleCrypto.prototype.verify = function (algorithm, key, signature, data) // <editor-fold defaultstate="collapsed"> { return new Promise(call).then(function () { if (checkNative(algorithm)) return rootCrypto.subtle.verify(algorithm, key, signature, data); algorithm = normalize(algorithm, 'verify'); if (algorithm.procreator === 'SC' && algorithm.mode === 'SIGN') { var obj = gostCrypto.asn1.GostSignature.decode(signature); signature = {r: obj.r, s: obj.s}; } return execute(algorithm, 'verify', [extractKey('verify', algorithm, key), signature, data]); }); }; // </editor-fold> /** * The digest method returns a new Promise object that will digest data * using the specified algorithm identifier. * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-digest}<br><br> * * Supported algorithm names: * <ul> * <li><b>GOST R 34.11-94</b> Old-Style GOST Hash</li> * <li><b>GOST R 34.11</b> GOST Streebog Hash</li> * <li><b>SHA</b> SHA Hash</li> * </ul> * For additional modes see {@link GostDigest} * * @memberOf SubtleCrypto * @method digest * @instance * @param {AlgorithmIdentifier} algorithm Algorithm identifier * @param {CryptoOperationData} data Operation data * @returns {Promise} Promise that resolves with {@link CryptoOperationData} */ SubtleCrypto.prototype.digest = function (algorithm, data) // <editor-fold defaultstate="collapsed"> { return new Promise(call).then(function () { if (checkNative(algorithm)) return rootCrypto.subtle.digest(algorithm, data); algorithm = normalize(algorithm, 'digest'); return execute(algorithm, 'digest', [data]); }); }; // </editor-fold> /** * The generateKey method returns a new Promise object that will key(s) using * the specified algorithm identifier. Key can be used in according with * KeyUsages sequence. The recognized key usage values are "encrypt", "decrypt", * "sign", "verify", "deriveKey", "deriveBits", "wrapKey" and "unwrapKey". * WebCrypto API reference {@link http://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-generateKey}<br><br> * * Supported algorithm names: * <ul> * <li><b>GOST R 34.10</b> ECGOST Key Pairs</li> * <li><b>GOST 28147</b> Key for encryption GOST 28147 modes</li> * <li><b>GOST 28147-KW</b> Key for wrapping GOST 28147 modes</li> * <li><b>GOST R 34.12</b> Key for encryption GOST R 34.12 modes</li> * <li><b>GOST R 34.12-KW</b> Key for wrapping GOST R 34.12 modes</li> * <li><b>GOST R 34.11-KDF</b> Key for Derivation Algorithm</li> * </ul> * For additional modes see {@link GostSign}, {@link GostDigest} and {@link GostCipher}<br> * Note: Generation key for GOST R 34.10-94 not supported. * * @memberOf SubtleCrypto * @method generateKey * @instance * @param {AlgorithmIdentifier} algorithm Key algorithm identifier * @param {boolean} extractable Whether or not the raw keying material may be exported by the application * @param {KeyUsages} keyUsages Key usage array: type of operation that may be performed using a key * @returns {Promise} Promise that resolves with {@link Key} or {@link KeyPair} in according to key algorithm */ SubtleCrypto.prototype.generateKey = function (algorithm, extractable, keyUsages) // <editor-fold defaultstate="collapsed"> { return new Promise(call).then(function () { if (checkNative(algorithm)) return rootCrypto.subtle.generateKey(algorithm, extractable, keyUsages); var privateAlgorithm = algorithm.privateKey, publicAlgorithm = algorithm.publicKey; algorithm = normalize(algorithm, 'generateKey'); if (privateAlgorithm) privateAlgorithm = normalize(privateAlgorithm, 'generateKey'); else privateAlgorithm = algorithm; if (publicAlgorithm) publicAlgorithm = normalize(publicAlgorithm, 'generateKey'); else publicAlgorithm = algorithm; return execute(algorithm, 'generateKey', []).then(function (data) { if (da