"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.Key = void 0; var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _converter = require("./converter.js"); var _thumbprint = require("./thumbprint.js"); var _lodash = _interopRequireDefault(require("lodash.clonedeep")); var _jsEncodingUtils = _interopRequireDefault(require("js-encoding-utils")); var _util = require("./util.js"); /** * key.js */ /** * 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 = /*#__PURE__*/ function () { /** * @constructor * @param {String} 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) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; (0, _classCallCheck2.default)(this, Key); var localKey = (0, _lodash.default)(key); var localOpt = (0, _lodash.default)(options); this._jwk = {}; this._der = null; this._oct = {}; // only for EC keys 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 */ (0, _createClass2.default)(Key, [{ key: "_setJwk", value: function _setJwk(jwkey) { this._type = (0, _util.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: "_setAsn1", value: function _setAsn1(asn1key, format) { this._type = (0, _util.isAsn1Public)(asn1key, format) ? 'public' : 'private'; // this also check key format this._isEncrypted = (0, _util.isAsn1Encrypted)(asn1key, format); this._der = format === 'pem' ? _jsEncodingUtils.default.formatter.pemToBin(asn1key) : asn1key; if (this._isEncrypted) { this._jwk = {}; 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 {String} namedCurve - Name of curve like 'P-256'. * @private */ }, { key: "_setSec1", value: function _setSec1(sec1key, namedCurve) { this._type = (0, _util.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: "_setCurrentStatus", value: function _setCurrentStatus() { this._current.jwk = typeof this._jwk.kty === 'string' && (this._jwk.kty === 'RSA' || this._jwk.kty === 'EC'); this._current.der = typeof this._der !== 'undefined' && this._der instanceof Uint8Array && this._der.length > 0; this._current.oct = typeof this._oct.key !== 'undefined' && this._oct.key instanceof Uint8Array && this._oct.key.length > 0 && typeof this._oct.namedCurve === 'string'; } /////////////////////////////////////////////////////////// // (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} - Exported key object. */ }, { key: "export", value: function () { var _export2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee() { var format, options, jwkey, _args = arguments; return _regenerator.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: format = _args.length > 0 && _args[0] !== undefined ? _args[0] : 'jwk'; options = _args.length > 1 && _args[1] !== undefined ? _args[1] : {}; if (!(['pem', 'der', 'jwk', 'oct'].indexOf(format) < 0)) { _context.next = 4; break; } throw new Error('UnsupportedFormat'); case 4: if (!(this._isEncrypted && this._type === 'private')) { _context.next = 10; break; } if (!((format === 'der' || format === 'pem') && Object.keys(options).length === 0 && this._current.der)) { _context.next = 9; break; } return _context.abrupt("return", format === 'pem' ? _jsEncodingUtils.default.formatter.binToPem(this._der, 'encryptedPrivate') : this._der); case 9: throw new Error('DecryptionRequired'); case 10: if (!this._current.jwk) { _context.next = 14; break; } jwkey = this._jwk; _context.next = 27; break; case 14: if (!this._current.oct) { _context.next = 20; break; } _context.next = 17; return (0, _converter.toJwkFrom)('oct', this._oct.key, { namedCurve: this._oct.namedCurve }); case 17: jwkey = _context.sent; _context.next = 27; break; case 20: if (!this._current.der) { _context.next = 26; break; } _context.next = 23; return (0, _converter.toJwkFrom)('der', this._der); case 23: jwkey = _context.sent; _context.next = 27; break; case 26: throw new Error('InvalidStatus'); case 27: this._setJwk(jwkey); // store jwk if the exiting private key is not encrypted // then export as the key in intended format if (!(format === 'der' || format === 'pem')) { _context.next = 35; break; } if (typeof options.encryptParams === 'undefined') options.encryptParams = {}; _context.next = 32; return (0, _converter.fromJwkTo)(format, jwkey, { outputPublic: options.outputPublic, compact: options.compact, //passphrase: options.encryptParams.passphrase, encryptParams: options.encryptParams }); case 32: return _context.abrupt("return", _context.sent); case 35: if (!(format === 'oct')) { _context.next = 41; break; } _context.next = 38; return (0, _converter.fromJwkTo)(format, jwkey, { outputPublic: options.outputPublic, output: options.output, compact: options.compact }); case 38: return _context.abrupt("return", _context.sent); case 41: return _context.abrupt("return", jwkey); case 42: case "end": return _context.stop(); } } }, _callee, this); })); function _export() { return _export2.apply(this, arguments); } return _export; }() /** * Encrypt stored key and set the encrypted key to this instance. * @param {String} passphrase - String passphrase. * @return {Promise} - Always true otherwise thrown. * @throws {Error} - Throws if AlreadyEncrypted. */ }, { key: "encrypt", value: function () { var _encrypt = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee2(passphrase) { var options; return _regenerator.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: if (!this._isEncrypted) { _context2.next = 2; break; } throw new Error('AlreadyEncrypted'); case 2: options = { encryptParams: { passphrase: passphrase } }; _context2.t0 = this; _context2.next = 6; return this.export('der', options); case 6: _context2.t1 = _context2.sent; _context2.t0._setAsn1.call(_context2.t0, _context2.t1, 'der'); return _context2.abrupt("return", true); case 9: case "end": return _context2.stop(); } } }, _callee2, this); })); function encrypt(_x) { return _encrypt.apply(this, arguments); } return encrypt; }() /** * Decrypted stored key and set the decrypted key in JWK to this instance. * @param {String} passphrase - String passphrase. * @return {Promise} - Always true otherwise thrown. * @throws {Error} - Throws if NotEncrypted or FailedToDecrypt. */ }, { key: "decrypt", value: function () { var _decrypt = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee3(passphrase) { var jwkey; return _regenerator.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: if (this._isEncrypted) { _context3.next = 2; break; } throw new Error('NotEncrypted'); case 2: if (!(this._current.der && typeof passphrase === 'string')) { _context3.next = 8; break; } _context3.next = 5; return (0, _converter.toJwkFrom)('der', this._der, { passphrase: passphrase }); case 5: jwkey = _context3.sent; _context3.next = 9; break; case 8: throw new Error('FailedToDecrypt'); case 9: this._setJwk(jwkey); return _context3.abrupt("return", true); case 11: case "end": return _context3.stop(); } } }, _callee3, this); })); function decrypt(_x2) { return _decrypt.apply(this, arguments); } return decrypt; }() /** * Conpute JWK thumbprint specified in RFC7638 {@link https://tools.ietf.org/html/rfc7638}. * @param {String} [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} - Computed thumbprint. * @throws {Error} - Throws if DecryptionRequired. */ }, { key: "getJwkThumbprint", value: function () { var _getJwkThumbprint2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee4() { var alg, output, _args4 = arguments; return _regenerator.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: alg = _args4.length > 0 && _args4[0] !== undefined ? _args4[0] : 'SHA-256'; output = _args4.length > 1 && _args4[1] !== undefined ? _args4[1] : 'binary'; if (!this._isEncrypted) { _context4.next = 4; break; } throw new Error('DecryptionRequired'); case 4: _context4.t0 = _thumbprint.getJwkThumbprint; _context4.next = 7; return this.export('jwk'); case 7: _context4.t1 = _context4.sent; _context4.t2 = alg; _context4.t3 = output; _context4.next = 12; return (0, _context4.t0)(_context4.t1, _context4.t2, _context4.t3); case 12: return _context4.abrupt("return", _context4.sent); case 13: case "end": return _context4.stop(); } } }, _callee4, this); })); function getJwkThumbprint() { return _getJwkThumbprint2.apply(this, arguments); } return getJwkThumbprint; }() // getters /** * Get keyType in JWK format * @return {Promise} - 'RSA' or 'EC' * @throws {Error} - Throws if DecryptionRequired. */ }, { key: "keyType", get: function get() { var _this = this; if (this._isEncrypted) throw new Error('DecryptionRequired'); return new Promise( /*#__PURE__*/ function () { var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee5(resolve, reject) { var jwkey; return _regenerator.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: _context5.next = 2; return _this.export('jwk').catch(function (e) { reject(e); }); case 2: jwkey = _context5.sent; resolve(jwkey.kty); case 4: case "end": return _context5.stop(); } } }, _callee5); })); return function (_x3, _x4) { return _ref.apply(this, arguments); }; }()); } /** * Get jwkThumbprint of this key. * @return {Promise} - Returns binary thumbprint. */ }, { key: "jwkThumbprint", get: function get() { return this.getJwkThumbprint(); } /** * Check if this is encrypted. * @return {boolean} */ }, { key: "isEncrypted", get: function get() { return this._isEncrypted; } /** * Check if this is a private key. * @return {boolean} */ }, { key: "isPrivate", get: function get() { return this._type === 'private'; } /** * Returns the key in DER format. * @return {Promise} */ }, { key: "der", get: function get() { return this.export('der'); } /** * Returns the key in PEM format. * @return {Promise} */ }, { key: "pem", get: function get() { return this.export('pem'); } /** * Returns the key in JWK format * @return {Promise} */ }, { key: "jwk", get: function get() { return this.export('jwk'); } /** * Returns the 'EC' key in Octet SEC1 format. * @return {Promise} */ }, { key: "oct", get: function get() { return this.export('oct', { output: 'string' }); } }]); return Key; }(); exports.Key = Key;