js-crypto-key-utils
Version:
Universal Module for Cryptographic Key Utilities in JavaScript, including PEM-JWK converters
382 lines • 17.6 kB
JavaScript
"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