UNPKG

js-crypto-key-utils

Version:

Universal Module for Cryptographic Key Utilities in JavaScript, including PEM-JWK converters

382 lines 17.6 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Key = void 0; /** * key.js */ var converter_1 = require("./converter"); var thumbprint_1 = require("./thumbprint"); var js_encoding_utils_1 = __importDefault(require("js-encoding-utils")); var util_1 = require("./util"); var cloneDeep = require('lodash.clonedeep'); /** * Key class to abstract public and private key objects in string or binary. * This class provides functions to interchangeably convert key formats, * and key objects will be used for the root package, js-crypto-utils, as inputs to exposed APIs. */ var Key = /** @class */ (function () { /** * @constructor * @param {KeyFormat} format - Key format: 'jwk', 'der', 'pem' or 'oct' (only for ECC key). * @param {JsonWebKey|PEM|DER|OctetEC} key - Key object in the specified format. * @param {Object} [options={}] - Required if format='oct', and then it is {namedCurve: String}. * @throws {Error} - Throws if the input format and key are incompatible to the constructor. */ function Key(format, key, options) { if (options === void 0) { options = {}; } var localKey = cloneDeep(key); var localOpt = cloneDeep(options); this._type = null; this._jwk = null; this._der = null; this._oct = {}; // only for EC keys this._isEncrypted = false; this._current = { jwk: false, der: false, oct: false }; if (format === 'jwk') { this._setJwk(localKey); } else if (format === 'der' || format === 'pem') { if (format === 'der' && !(localKey instanceof Uint8Array)) throw new Error('DerKeyMustBeUint8Array'); if (format === 'pem' && (typeof localKey !== 'string')) throw new Error('PemKeyMustBeString'); this._setAsn1(localKey, format); } else if (format === 'oct') { if (typeof localOpt.namedCurve !== 'string') throw new Error('namedCurveMustBeSpecified'); if (!(localKey instanceof Uint8Array)) throw new Error('OctetKeyMustBeUint8Array'); this._setSec1(localKey, localOpt.namedCurve); } else throw new Error('UnsupportedType'); } /////////////////////////////////////////////////////////// // private method handling instance variables // all instance variables must be set via these methods /** * Set a key in JWK to the Key object. * @param {JsonWebKey} jwkey - The Json Web Key. * @private */ Key.prototype._setJwk = function (jwkey) { this._type = (0, util_1.getJwkType)(jwkey); // this also check key format this._jwk = jwkey; if (this._isEncrypted) this._der = null; this._isEncrypted = false; this._setCurrentStatus(); }; /** * Set a key in DER or PEM to the Key object. * @param {DER|PEM} asn1key - The DER key byte array or PEM key string. * @param {String} format - 'der' or 'pem' specifying the format. * @private */ Key.prototype._setAsn1 = function (asn1key, format) { this._type = ((0, util_1.isAsn1Public)(asn1key, format)) ? 'public' : 'private'; // this also check key format this._isEncrypted = (0, util_1.isAsn1Encrypted)(asn1key, format); this._der = ((format === 'pem') ? js_encoding_utils_1.default.formatter.pemToBin(asn1key) : asn1key); if (this._isEncrypted) { this._jwk = null; this._oct = {}; } this._setCurrentStatus(); }; /** * Set a key in SEC1 = Octet format to the Key Object. * @param {OctetEC} sec1key - The Octet SEC1 key byte array. * @param {CurveTypes} namedCurve - Name of curve like 'P-256'. * @private */ Key.prototype._setSec1 = function (sec1key, namedCurve) { this._type = (0, util_1.getSec1KeyType)(sec1key, namedCurve); // this also check key format this._oct = { namedCurve: namedCurve, key: sec1key }; if (this._isEncrypted) this._der = null; this._isEncrypted = false; this._setCurrentStatus(); }; /** * Set the current internal status. In particular, manage what the object is based on. * @private */ Key.prototype._setCurrentStatus = function () { this._current.jwk = (this._jwk !== null && (this._jwk.kty === 'RSA' || this._jwk.kty === 'EC')); this._current.der = (this._der !== null && this._der.length > 0); this._current.oct = (typeof this._oct.key !== 'undefined' && typeof this._oct.namedCurve !== 'undefined' && this._oct.key.length > 0); }; /////////////////////////////////////////////////////////// // (pseudo) public methods allowed to be accessed from outside /** * Convert the stored key and export the key in desired format. * Imported key must be basically decrypted except the case where the key is exported as-is. * @param {String} format - Intended format of exported key. 'jwk', 'pem', 'der' or 'oct' * @param {KeyExportOptions} [options={}] - Optional arguments. * @return {Promise<JsonWebKey|PEM|DER|OctetEC>} - Exported key object. */ Key.prototype.export = function (format, options) { if (format === void 0) { format = 'jwk'; } if (options === void 0) { options = {}; } return __awaiter(this, void 0, void 0, function () { var jwkey; return __generator(this, function (_a) { switch (_a.label) { case 0: // return 'as is' without passphrase when nothing is given as 'options' // only for the case to export der key from der key (considering encrypted key). expect to be called from getter if (this._isEncrypted && this._type === 'private') { if ((format === 'der' || format === 'pem') && Object.keys(options).length === 0 && this._current.der) { return [2 /*return*/, (format === 'pem') ? js_encoding_utils_1.default.formatter.binToPem((this._der), 'encryptedPrivate') : this._der]; } else throw new Error('DecryptionRequired'); } if (!this._current.jwk) return [3 /*break*/, 1]; jwkey = this._jwk; return [3 /*break*/, 6]; case 1: if (!this._current.oct) return [3 /*break*/, 3]; return [4 /*yield*/, (0, converter_1.toJwkFrom)('oct', this._oct.key, { namedCurve: this._oct.namedCurve })]; case 2: jwkey = _a.sent(); return [3 /*break*/, 6]; case 3: if (!this._current.der) return [3 /*break*/, 5]; return [4 /*yield*/, (0, converter_1.toJwkFrom)('der', this._der)]; case 4: jwkey = _a.sent(); return [3 /*break*/, 6]; case 5: throw new Error('InvalidStatus'); case 6: this._setJwk(jwkey); // store jwk if the exiting private key is not encrypted if (!(format === 'der' || format === 'pem')) return [3 /*break*/, 8]; return [4 /*yield*/, (0, converter_1.fromJwkTo)(format, jwkey, { outputPublic: options.outputPublic, compact: options.compact, //passphrase: options.encryptParams.passphrase, encryptParams: options.encryptParams })]; case 7: return [2 /*return*/, _a.sent()]; case 8: if (!(format === 'oct')) return [3 /*break*/, 10]; return [4 /*yield*/, (0, converter_1.fromJwkTo)(format, jwkey, { outputPublic: options.outputPublic, output: options.output, compact: options.compact })]; case 9: return [2 /*return*/, _a.sent()]; case 10: return [2 /*return*/, jwkey]; } }); }); }; /** * Encrypt stored key and set the encrypted key to this instance. * @param {String} passphrase - String passphrase. * @return {Promise<boolean>} - Always true otherwise thrown. * @throws {Error} - Throws if AlreadyEncrypted. */ Key.prototype.encrypt = function (passphrase) { return __awaiter(this, void 0, void 0, function () { var options, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: if (this._isEncrypted) throw new Error('AlreadyEncrypted'); options = { encryptParams: { passphrase: passphrase } }; _a = this._setAsn1; return [4 /*yield*/, this.export('der', options)]; case 1: _a.apply(this, [(_b.sent()), 'der']); return [2 /*return*/, true]; } }); }); }; /** * Decrypted stored key and set the decrypted key in JWK to this instance. * @param {String} passphrase - String passphrase. * @return {Promise<boolean>} - Always true otherwise thrown. * @throws {Error} - Throws if NotEncrypted or FailedToDecrypt. */ Key.prototype.decrypt = function (passphrase) { return __awaiter(this, void 0, void 0, function () { var jwkey; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!this._isEncrypted) throw new Error('NotEncrypted'); if (!this._current.der) return [3 /*break*/, 2]; return [4 /*yield*/, (0, converter_1.toJwkFrom)('der', this._der, { passphrase: passphrase })]; case 1: jwkey = _a.sent(); // type is not specified here to import jwk return [3 /*break*/, 3]; case 2: throw new Error('FailedToDecrypt'); case 3: this._setJwk(jwkey); return [2 /*return*/, true]; } }); }); }; /** * Conpute JWK thumbprint specified in RFC7638 {@link https://tools.ietf.org/html/rfc7638}. * @param {HashTypes} [alg='SHA-256'] - Name of hash algorithm for thumbprint computation like 'SHA-256'. * @param {JwkThumbpirntFormat} [output='binary'] - Output format of JWK thumbprint. 'binary', 'hex' or 'base64'. * @return {Promise<Uint8Array|String>} - Computed thumbprint. * @throws {Error} - Throws if DecryptionRequired. */ Key.prototype.getJwkThumbprint = function (alg, output) { if (alg === void 0) { alg = 'SHA-256'; } if (output === void 0) { output = 'binary'; } return __awaiter(this, void 0, void 0, function () { var _a; return __generator(this, function (_b) { switch (_b.label) { case 0: if (this._isEncrypted) throw new Error('DecryptionRequired'); _a = thumbprint_1.getJwkThumbprint; return [4 /*yield*/, this.export('jwk')]; case 1: return [4 /*yield*/, _a.apply(void 0, [(_b.sent()), alg, output])]; case 2: return [2 /*return*/, _b.sent()]; } }); }); }; Object.defineProperty(Key.prototype, "keyType", { // getters /** * Get keyType in JWK format * @return {Promise<String>} - 'RSA' or 'EC' * @throws {Error} - Throws if DecryptionRequired. */ get: function () { var _this = this; if (this._isEncrypted) throw new Error('DecryptionRequired'); return new Promise(function (resolve, reject) { _this.export('jwk') .then(function (r) { resolve((r.kty)); }) .catch(function (e) { reject(e); }); }); }, enumerable: false, configurable: true }); Object.defineProperty(Key.prototype, "jwkThumbprint", { /** * Get jwkThumbprint of this key. * @return {Promise<Uint8Array>} - Returns binary thumbprint. */ get: function () { return this.getJwkThumbprint(); }, enumerable: false, configurable: true }); Object.defineProperty(Key.prototype, "isEncrypted", { /** * Check if this is encrypted. * @return {boolean} */ get: function () { return this._isEncrypted; }, enumerable: false, configurable: true }); Object.defineProperty(Key.prototype, "isPrivate", { /** * Check if this is a private key. * @return {boolean} */ get: function () { return this._type === 'private'; }, enumerable: false, configurable: true }); Object.defineProperty(Key.prototype, "der", { /** * Returns the key in DER format. * @return {Promise<DER>} */ get: function () { return this.export('der'); }, enumerable: false, configurable: true }); Object.defineProperty(Key.prototype, "pem", { /** * Returns the key in PEM format. * @return {Promise<PEM>} */ get: function () { return this.export('pem'); }, enumerable: false, configurable: true }); Object.defineProperty(Key.prototype, "jwk", { /** * Returns the key in JWK format * @return {Promise<JsonWebKey>} */ get: function () { return this.export('jwk'); }, enumerable: false, configurable: true }); Object.defineProperty(Key.prototype, "oct", { /** * Returns the 'EC' key in Octet SEC1 format. * @return {Promise<OctetEC>} */ get: function () { return this.export('oct', { output: 'string' }); }, enumerable: false, configurable: true }); return Key; }()); exports.Key = Key; //# sourceMappingURL=key.js.map