rittenhop-dev/versions/5.94.2/node_modules/node-jose/lib/jwk/basekey.js
2024-09-23 19:40:12 -04:00

702 lines
22 KiB
JavaScript

/*!
* jwk/basekey.js - JWK Key Base Class Implementation
*
* Copyright (c) 2015 Cisco Systems, Inc. See LICENSE file.
*/
"use strict";
var merge = require("../util/merge");
const { v4: uuidv4 } = require("uuid");
var assign = require("lodash/assign");
var clone = require("lodash/clone");
var flatten = require("lodash/flatten");
var intersection = require("lodash/intersection");
var omit = require("lodash/omit");
var pick = require("lodash/pick");
var uniq = require("lodash/uniq");
var ALGORITHMS = require("../algorithms"),
CONSTANTS = require("./constants.js"),
HELPERS = require("./helpers.js"),
UTIL = require("../util");
/**
* @class JWK.Key
* @classdesc
* Represents a JSON Web Key instance.
*
* @description
* **NOTE:** This class cannot be instantiated directly. Instead call
* {@link JWK.asKey}, {@link JWK.KeyStore#add}, or
* {@link JWK.KeyStore#generate}.
*/
var JWKBaseKeyObject = function(kty, ks, props, cfg) {
// ### validate/coerce arguments ###
if (!kty) {
throw new Error("kty cannot be null");
}
if (!ks) {
throw new Error("keystore cannot be null");
}
if (!props) {
throw new Error("props cannot be null");
} else if ("string" === typeof props) {
props = JSON.parse(props);
}
if (!cfg) {
throw new Error("cfg cannot be null");
}
var excluded = [];
var keys = {},
json = {},
prints,
kid;
props = clone(props);
// strip thumbprints if present
prints = assign({}, props[HELPERS.INTERNALS.THUMBPRINT_KEY] || {});
delete props[HELPERS.INTERNALS.THUMBPRINT_KEY];
Object.keys(prints).forEach(function(a) {
var h = prints[a];
if (!kid) {
kid = h;
if (Buffer.isBuffer(kid)) {
kid = UTIL.base64url.encode(kid);
}
}
if (!Buffer.isBuffer(h)) {
h = UTIL.base64url.decode(h);
prints[a] = h;
}
});
// force certain values
props.kty = kty;
props.kid = props.kid || kid || uuidv4();
// setup base info
var included = Object.keys(HELPERS.COMMON_PROPS).map(function(p) {
return HELPERS.COMMON_PROPS[p].name;
});
json.base = pick(props, included);
excluded = excluded.concat(Object.keys(json.base));
// setup public information
json.public = clone(props);
keys.public = cfg.publicKey(json.public);
if (keys.public) {
// exclude public values from extra
excluded = excluded.concat(Object.keys(json.public));
}
// setup private information
json.private = clone(props);
keys.private = cfg.privateKey(json.private);
if (keys.private) {
// exclude private values from extra
excluded = excluded.concat(Object.keys(json.private));
}
// setup extra information
json.extra = omit(props, excluded);
// TODO: validate 'alg' against supported algorithms
// setup calculated values
var keyLen;
if (keys.public && ("length" in keys.public)) {
keyLen = keys.public.length;
} else if (keys.private && ("length" in keys.private)) {
keyLen = keys.private.length;
} else {
keyLen = NaN;
}
// ### Public Properties ###
/**
* @member {JWK.KeyStore} JWK.Key#keystore
* @description
* The owning keystore.
*/
Object.defineProperty(this, "keystore", {
value: ks,
enumerable: true
});
/**
* @member {Number} JWK.Key#length
* @description
* The size of this Key, in bits.
*/
Object.defineProperty(this, "length", {
value: keyLen,
enumerable: true
});
/**
* @member {String} JWK.Key#kty
* @description
* The type of Key.
*/
Object.defineProperty(this, "kty", {
value: kty,
enumerable: true
});
/**
* @member {String} JWK.Key#kid
* @description
* The identifier for this Key.
*/
Object.defineProperty(this, "kid", {
value: json.base.kid,
enumerable: true
});
/**
* @member {String} JWK.Key#use
* @description
* The usage for this Key.
*/
Object.defineProperty(this, "use", {
value: json.base.use || "",
enumerable: true
});
/**
* @member {String} JWK.Key#alg
* @description
* The sole algorithm this key can be used for.
*/
Object.defineProperty(this, "alg", {
value: json.base.alg || "",
enumerable: true
});
// ### Public Methods ###
/**
* Generates the thumbprint of this Key.
*
* @param {String} [] The hash algorithm to use
* @returns {Promise} The promise for the thumbprint generation.
*/
Object.defineProperty(this, "thumbprint", {
value: function(hash) {
hash = (hash || HELPERS.INTERNALS.THUMBPRINT_HASH).toUpperCase();
if (prints[hash]) {
// return cached value
return Promise.resolve(prints[hash]);
}
var p = HELPERS.thumbprint(cfg, json, hash);
p = p.then(function(result) {
if (result) {
prints[hash] = result;
}
return result;
});
return p;
}
});
/**
* @method JWK.Key#algorithms
* @description
* The possible algorithms this Key can be used for. The returned
* list is not any particular order, but is filtered based on the
* Key's intended usage.
*
* @param {String} mode The operation mode
* @returns {String[]} The list of supported algorithms
* @see JWK.Key#supports
*/
Object.defineProperty(this, "algorithms", {
value: function(mode) {
var modes = [];
if (!this.use || this.use === "sig") {
if (!mode || CONSTANTS.MODE_SIGN === mode) {
modes.push(CONSTANTS.MODE_SIGN);
}
if (!mode || CONSTANTS.MODE_VERIFY === mode) {
modes.push(CONSTANTS.MODE_VERIFY);
}
}
if (!this.use || this.use === "enc") {
if (!mode || CONSTANTS.MODE_ENCRYPT === mode) {
modes.push(CONSTANTS.MODE_ENCRYPT);
}
if (!mode || CONSTANTS.MODE_DECRYPT === mode) {
modes.push(CONSTANTS.MODE_DECRYPT);
}
if (!mode || CONSTANTS.MODE_WRAP === mode) {
modes.push(CONSTANTS.MODE_WRAP);
}
if (!mode || CONSTANTS.MODE_UNWRAP === mode) {
modes.push(CONSTANTS.MODE_UNWRAP);
}
}
var self = this;
var algs = modes.map(function(m) {
return cfg.algorithms.call(self, keys, m);
});
algs = flatten(algs);
algs = uniq(algs);
if (this.alg) {
// TODO: fix this correctly
var valid;
if ("oct" === kty) {
valid = [this.alg, "dir"];
} else {
valid = [this.alg];
}
algs = intersection(algs, valid);
}
return algs;
}
});
/**
* @method JWK.Key#supports
* @description
* Determines if the given algorithm is supported.
*
* @param {String} alg The algorithm in question
* @param {String} [mode] The operation mode
* @returns {Boolean} `true` if {alg} is supported, and `false` otherwise.
* @see JWK.Key#algorithms
*/
Object.defineProperty(this, "supports", {
value: function(alg, mode) {
return (this.algorithms(mode).indexOf(alg) !== -1);
}
});
/**
* @method JWK.Key#has
* @description
* Determines if this Key contains the given parameter.
*
* @param {String} name The name of the parameter
* @param {Boolean} [isPrivate=false] `true` if private parameters should be
* checked.
* @returns {Boolean} `true` if the given parameter is present; `false`
* otherwise.
*/
Object.defineProperty(this, "has", {
value: function(name, isPrivate) {
var contains = false;
contains = contains || !!(json.base &&
(name in json.base));
contains = contains || !!(keys.public &&
(name in keys.public));
contains = contains || !!(json.extra &&
(name in json.extra));
contains = contains || !!(isPrivate &&
keys.private &&
(name in keys.private));
// TODO: check for export restrictions
return contains;
}
});
/**
* @method JWK.Key#get
* @description
* Retrieves the value of the given parameter. The value returned by this
* method is in its natural format, which might not exactly match its
* JSON encoding (e.g., a binary string rather than a base64url-encoded
* string).
*
* **NOTE:** This method can return `false`. Call
* {@link JWK.Key#has} to determine if the parameter is present.
*
* @param {String} name The name of the parameter
* @param {Boolean} [isPrivate=false] `true` if private parameters should
* be checked.
* @returns {any} The value of the named parameter, or undefined if
* it is not present.
*/
Object.defineProperty(this, "get", {
value: function(name, isPrivate) {
var src;
if (json.base && (name in json.base)) {
src = json.base;
} else if (keys.public && (name in keys.public)) {
src = keys.public;
} else if (json.extra && (name in json.extra)) {
src = json.extra;
} else if (isPrivate && keys.private && (name in keys.private)) {
// TODO: check for export restrictions
src = keys.private;
}
return src && src[name] || null;
}
});
/**
* @method JWK.Key#toJSON
* @description
* Returns the JSON representation of this Key. All properties of the
* returned JSON object are properly encoded (e.g., base64url encoding for
* any binary strings).
*
* @param {Boolean} [isPrivate=false] `true` if private parameters should be
* included.
* @param {String[]} [excluded] The list of parameters to exclude from
* the returned JSON.
* @returns {Object} The plain JSON object
*/
Object.defineProperty(this, "toJSON", {
value: function(isPrivate, excluded) {
// coerce arguments
if (Array.isArray(isPrivate)) {
excluded = isPrivate;
isPrivate = false;
}
var result = {};
// TODO: check for export restrictions
result = merge(result,
json.base,
json.public,
("boolean" === typeof isPrivate && isPrivate) ? json.private : {},
json.extra);
result = omit(result, excluded || []);
return result;
}
});
/**
* @method JWK.Key#toPEM
* @description
* Returns the PEM representation of this Key as a string.
*
* @param {Boolean} [isPrivate=false] `true` if private parameters should be
* included.
* @returns {string} The PEM-encoded string
*/
Object.defineProperty(this, "toPEM", {
value: function(isPrivate) {
if (isPrivate === null) {
isPrivate = false;
}
if (!cfg.convertToPEM) {
throw new Error("Unsupported key type for PEM encoding");
}
var k = (isPrivate) ? keys.private : keys.public;
if (!k) {
throw new Error("Invalid key");
}
return cfg.convertToPEM.call(this, k, isPrivate);
}
});
/**
* @method JWK.Key#toObject
* @description
* Returns the plain object representing this Key. All properties of the
* returned object are in their natural encoding (e.g., binary strings
* instead of base64url encoded).
*
* @param {Boolean} [isPrivate=false] `true` if private parameters should be
* included.
* @param {String[]} [excluded] The list of parameters to exclude from
* the returned object.
* @returns {Object} The plain Object.
*/
Object.defineProperty(this, "toObject", {
value: function(isPrivate, excluded) {
// coerce arguments
if (Array.isArray(isPrivate)) {
excluded = isPrivate;
isPrivate = false;
}
var result = {};
// TODO: check for export restrictions
result = merge(result,
json.base,
keys.public,
("boolean" === typeof isPrivate && isPrivate) ? keys.private : {},
json.extra);
result = omit(result, (excluded || []).concat("length"));
return result;
}
});
/**
* @method JWK.Key#sign
* @description
* Sign the given data using the specified algorithm.
*
* **NOTE:** This is the primitive signing operation; the output is
* _**NOT**_ a JSON Web Signature (JWS) object.
*
* The Promise, when fulfilled, returns an Object with the following
* properties:
*
* + **data**: The data that was signed (and should be equal to {data}).
* + **mac**: The signature or message authentication code (MAC).
*
* @param {String} alg The signing algorithm
* @param {String|Buffer} data The data to sign
* @param {Object} [props] Additional properties for the signing
* algorithm.
* @returns {Promise} The promise for the signing operation.
* @throws {Error} If {alg} is not appropriate for this Key; or if
* this Key does not contain the appropriate parameters.
*/
Object.defineProperty(this, "sign", {
value: function(alg, data, props) {
// validate appropriateness
if (this.algorithms("sign").indexOf(alg) === -1) {
return Promise.reject(new Error("unsupported algorithm"));
}
var k = cfg.signKey.call(this, alg, keys);
if (!k) {
return Promise.reject(new Error("improper key"));
}
// prepare properties (if any)
props = (props) ?
clone(props) :
{};
if (cfg.signProps) {
props = merge(props, cfg.signProps.call(this, alg, props));
}
return ALGORITHMS.sign(alg, k, data, props);
}
});
/**
* @method JWK.Key#verify
* @description
* Verify the given data and signature using the specified algorithm.
*
* **NOTE:** This is the primitive verification operation; the input is
* _**NOT**_ a JSON Web Signature.</p>
*
* The Promise, when fulfilled, returns an Object with the following
* properties:
*
* + **data**: The data that was verified (and should be equal to
* {data}).
* + **mac**: The signature or MAC that was verified (and should be equal
* to {mac}).
* + **valid**: `true` if {mac} is valid for {data}.
*
* @param {String} alg The verification algorithm
* @param {String|Buffer} data The data to verify
* @param {String|Buffer} mac The signature or MAC to verify
* @param {Object} [props] Additional properties for the verification
* algorithm.
* @returns {Promise} The promise for the verification operation.
* @throws {Error} If {alg} is not appropriate for this Key; or if
* the Key does not contain the appropriate properties.
*/
Object.defineProperty(this, "verify", {
value: function(alg, data, mac, props) {
// validate appropriateness
if (this.algorithms("verify").indexOf(alg) === -1) {
return Promise.reject(new Error("unsupported algorithm"));
}
var k = cfg.verifyKey.call(this, alg, keys);
if (!k) {
return Promise.reject(new Error("improper key"));
}
// prepare properties (if any)
props = (props) ?
clone(props) :
{};
if (cfg.verifyProps) {
props = merge(props, cfg.verifyProps.call(this, alg, props));
}
return ALGORITHMS.verify(alg, k, data, mac, props);
}
});
/**
* @method JWK.Key#encrypt
* @description
* Encrypts the given data using the specified algorithm.
*
* **NOTE:** This is the primitive encryption operation; the output is
* _**NOT**_ a JSON Web Encryption (JWE) object.
*
* **NOTE:** This operation is treated as distinct from {@link
* JWK.Key#wrap}, as different algorithms and properties are often
* used for wrapping a key versues encrypting arbitrary data.
*
* The Promise, when fulfilled, returns an object with the following
* properties:
*
* + **data**: The ciphertext data
* + **mac**: The associated message authentication code (MAC).
*
* @param {String} alg The encryption algorithm
* @param {Buffer|String} data The data to encrypt
* @param {Object} [props] Additional properties for the encryption
* algorithm.
* @returns {Promise} The promise for the encryption operation.
* @throws {Error} If {alg} is not appropriate for this Key; or if
* this Key does not contain the appropriate parameters.
*/
Object.defineProperty(this, "encrypt", {
value: function(alg, data, props) {
// validate appropriateness
if (this.algorithms("encrypt").indexOf(alg) === -1) {
return Promise.reject(new Error("unsupported algorithm"));
}
var k = cfg.encryptKey.call(this, alg, keys);
if (!k) {
return Promise.reject(new Error("improper key"));
}
// prepare properties (if any)
props = (props) ?
clone(props) :
{};
if (cfg.encryptProps) {
props = merge(props, cfg.encryptProps.call(this, alg, props));
}
return ALGORITHMS.encrypt(alg, k, data, props);
}
});
/**
* @method JWK.Key#decrypt
* @description
* Decrypts the given data using the specified algorithm.
*
* **NOTE:** This is the primitive decryption operation; the input is
* _**NOT**_ a JSON Web Encryption (JWE) object.
*
* **NOTE:** This operation is treated as distinct from {@link
* JWK.Key#unwrap}, as different algorithms and properties are often used
* for unwrapping a key versues decrypting arbitrary data.
*
* The Promise, when fulfilled, returns the plaintext data.
*
* @param {String} alg The decryption algorithm.
* @param {Buffer|String} data The data to decypt.
* @param {Object} [props] Additional data for the decryption operation.
* @returns {Promise} The promise for the decryption operation.
* @throws {Error} If {alg} is not appropriate for this Key; or if
* the Key does not contain the appropriate properties.
*/
Object.defineProperty(this, "decrypt", {
value: function(alg, data, props) {
// validate appropriateness
if (this.algorithms("decrypt").indexOf(alg) === -1) {
return Promise.reject(new Error("unsupported algorithm"));
}
var k = cfg.decryptKey.call(this, alg, keys);
if (!k) {
return Promise.reject(new Error("improper key"));
}
// prepare properties (if any)
props = (props) ?
clone(props) :
{};
if (cfg.decryptProps) {
props = merge(props, cfg.decryptProps.call(this, alg, props));
}
return ALGORITHMS.decrypt(alg, k, data, props);
}
});
/**
* @method JWK.Key#wrap
* @description
* Wraps the given key using the specified algorithm.
*
* **NOTE:** This is the primitive encryption operation; the output is
* _**NOT**_ a JSON Web Encryption (JWE) object.
*
* **NOTE:** This operation is treated as distinct from {@link
* JWK.Key#encrypt}, as different algorithms and properties are
* often used for wrapping a key versues encrypting arbitrary data.
*
* The Promise, when fulfilled, returns an object with the following
* properties:
*
* + **data**: The ciphertext data
* + **headers**: The additional header parameters to apply to a JWE.
*
* @param {String} alg The encryption algorithm
* @param {Buffer|String} data The data to encrypt
* @param {Object} [props] Additional properties for the encryption
* algorithm.
* @returns {Promise} The promise for the encryption operation.
* @throws {Error} If {alg} is not appropriate for this Key; or if
* this Key does not contain the appropriate parameters.
*/
Object.defineProperty(this, "wrap", {
value: function(alg, data, props) {
// validate appropriateness
if (this.algorithms("wrap").indexOf(alg) === -1) {
return Promise.reject(new Error("unsupported algorithm"));
}
var k = cfg.wrapKey.call(this, alg, keys);
if (!k) {
return Promise.reject(new Error("improper key"));
}
// prepare properties (if any)
props = (props) ?
clone(props) :
{};
if (cfg.wrapProps) {
props = merge(props, cfg.wrapProps.call(this, alg, props));
}
return ALGORITHMS.encrypt(alg, k, data, props);
}
});
/**
* @method JWK.Key#unwrap
* @description
* Unwraps the given key using the specified algorithm.
*
* **NOTE:** This is the primitive unwrap operation; the input is
* _**NOT**_ a JSON Web Encryption (JWE) object.
*
* **NOTE:** This operation is treated as distinct from {@link
* JWK.Key#decrypt}, as different algorithms and properties are often used
* for unwrapping a key versues decrypting arbitrary data.
*
* The Promise, when fulfilled, returns the unwrapped key.
*
* @param {String} alg The unwrap algorithm.
* @param {Buffer|String} data The data to unwrap.
* @param {Object} [props] Additional data for the unwrap operation.
* @returns {Promise} The promise for the unwrap operation.
* @throws {Error} If {alg} is not appropriate for this Key; or if
* the Key does not contain the appropriate properties.
*/
Object.defineProperty(this, "unwrap", {
value: function(alg, data, props) {
// validate appropriateness
if (this.algorithms("unwrap").indexOf(alg) === -1) {
return Promise.reject(new Error("unsupported algorithm"));
}
var k = cfg.unwrapKey.call(this, alg, keys);
if (!k) {
return Promise.reject(new Error("improper key"));
}
// prepare properties (if any)
props = (props) ?
clone(props) :
{};
if (cfg.unwrapProps) {
props = merge(props, cfg.unwrapProps.call(this, alg, props));
}
return ALGORITHMS.decrypt(alg, k, data, props);
}
});
};
module.exports = JWKBaseKeyObject;