rittenhop-dev/versions/5.94.2/node_modules/node-jose/lib/algorithms/ecdh.js

466 lines
12 KiB
JavaScript
Raw Normal View History

2024-09-23 19:40:12 -04:00
/*!
* algorithms/ecdh.js - Elliptic Curve Diffie-Hellman algorithms
*
* Copyright (c) 2015 Cisco Systems, Inc. See LICENSE file.
*/
"use strict";
var merge = require("../util/merge"),
util = require("../util"),
ecUtil = require("./ec-util.js"),
hkdf = require("./hkdf.js"),
concat = require("./concat.js"),
aesKw = require("./aes-kw.js"),
helpers = require("./helpers.js"),
CONSTANTS = require("./constants.js");
var clone = require("lodash/clone");
var omit = require("lodash/omit");
var pick = require("lodash/pick");
function idealHash(curve) {
switch (curve) {
case "P-256":
return "SHA-256";
case "P-384":
return "SHA-384";
case "P-521":
return "SHA-512";
default:
throw new Error("unsupported curve: " + curve);
}
}
// ### Exported
var ecdh = module.exports = {};
// ### Derivation algorithms
// ### "raw" ECDH
function ecdhDeriveFn() {
var alg = {
name: "ECDH"
};
var validatePublic = function(pk, form) {
var pubKey = pk && ecUtil.convertToForge(pk, true);
if (!pubKey || !pubKey.isValid()) {
return Promise.reject(new Error("invalid EC public key"));
}
switch (form) {
case "jwk":
pubKey = ecUtil.convertToJWK(pk, true);
break;
case "buffer":
pubKey = ecUtil.convertToBuffer(pk, true);
break;
}
return Promise.resolve(pubKey);
}
// ### fallback implementation -- uses ecc + forge
var fallback = function(key, props) {
props = props || {};
var keyLen = props.length || 0;
// assume {key} is privateKey
// assume {props.public} is publicKey
var privKey = ecUtil.convertToForge(key, false);
var p = validatePublic(props.public, "forge");
p = p.then(function(pubKey) {
// {pubKey} is "forge"
var secret = privKey.computeSecret(pubKey);
if (keyLen) {
// truncate to requested key length
if (secret.length < keyLen) {
return Promise.reject(new Error("key length too large: " + keyLen));
}
secret = secret.slice(0, keyLen);
}
return secret;
});
return p;
};
// ### WebCryptoAPI implementation
// TODO: cache CryptoKey sooner
var webcrypto = function(key, props) {
key = key || {};
props = props || {};
var keyLen = props.length || 0,
algParams = merge(clone(alg), {
namedCurve: key.crv
});
// assume {key} is privateKey
if (!keyLen) {
// calculate key length from private key size
keyLen = key.d.length;
}
var privKey = ecUtil.convertToJWK(key, false);
privKey = helpers.subtleCrypto.importKey("jwk",
privKey,
algParams,
false,
[ "deriveBits" ]);
// assume {props.public} is publicKey
var pubKey = validatePublic(props.public, "jwk");
pubKey = pubKey.then(function(pubKey) {
// {pubKey} is "jwk"
return helpers.subtleCrypto.importKey("jwk",
pubKey,
algParams,
false,
[]);
});
var p = Promise.all([privKey, pubKey]);
p = p.then(function(keypair) {
var privKey = keypair[0],
pubKey = keypair[1];
var algParams = merge(clone(alg), {
public: pubKey
});
return helpers.subtleCrypto.deriveBits(algParams, privKey, keyLen * 8);
});
p = p.then(function(result) {
result = Buffer.from(result);
return result;
});
return p;
};
var nodejs = function(key, props) {
if ("function" !== typeof helpers.nodeCrypto.createECDH) {
throw new Error("unsupported algorithm: ECDH");
}
props = props || {};
var keyLen = props.length || 0;
var curve;
switch (key.crv) {
case "P-256":
curve = "prime256v1";
break;
case "P-384":
curve = "secp384r1";
break;
case "P-521":
curve = "secp521r1";
break;
default:
return Promise.reject(new Error("invalid curve: " + curve));
}
// assume {key} is privateKey
// assume {props.public} is publicKey
var privKey = ecUtil.convertToBuffer(key, false);
var p = validatePublic(props.public, "buffer");
p = p.then(function(pubKey) {
// {pubKey} is "buffer"
var ecdh = helpers.nodeCrypto.createECDH(curve);
// dummy call so computeSecret doesn't fail
// ecdh.generateKeys();
ecdh.setPrivateKey(privKey);
var secret = ecdh.computeSecret(pubKey);
if (keyLen) {
if (secret.length < keyLen) {
return Promise.reject(new Error("key length too large: " + keyLen));
}
secret = secret.slice(0, keyLen);
}
return secret;
});
return p;
};
return helpers.setupFallback(nodejs, webcrypto, fallback);
}
function ecdhConcatDeriveFn() {
// NOTE: no nodejs/webcrypto/fallback model, since this algorithm is
// implemented using other primitives
var fn = function(key, props) {
props = props || {};
var hash;
try {
hash = props.hash || idealHash(key.crv);
if (!hash) {
throw new Error("invalid hash: " + hash);
}
hash.toUpperCase();
} catch (ex) {
return Promise.reject(ex);
}
var params = ["public"];
// derive shared secret
// NOTE: whitelist items from {props} for ECDH
var promise = ecdh.ECDH.derive(key, pick(props, params));
// expand
promise = promise.then(function(shared) {
// NOTE: blacklist items from {props} for ECDH
return concat["CONCAT-" + hash].derive(shared, omit(props, params));
});
return promise;
};
return fn;
}
function ecdhHkdfDeriveFn() {
// NOTE: no nodejs/webcrypto/fallback model, since this algorithm is
// implemented using other primitives
var fn = function(key, props) {
props = props || {};
var hash;
try {
hash = props.hash || idealHash(key.crv);
if (!hash) {
throw new Error("invalid hash: " + hash);
}
hash.toUpperCase();
} catch (ex) {
return Promise.reject(ex);
}
var params = ["public"];
// derive shared secret
// NOTE: whitelist items from {props} for ECDH
var promise = ecdh.ECDH.derive(key, pick(props, params));
// extract-and-expand
promise = promise.then(function(shared) {
// NOTE: blacklist items from {props} for ECDH
return hkdf["HKDF-" + hash].derive(shared, omit(props, params));
});
return promise;
};
return fn;
}
// ### Wrap/Unwrap algorithms
function doEcdhesCommonDerive(privKey, pubKey, props) {
function prependLen(input) {
return Buffer.concat([
helpers.int32ToBuffer(input.length),
input
]);
}
var algId = props.algorithm || "",
keyLen = CONSTANTS.KEYLENGTH[algId],
apu = util.asBuffer(props.apu || "", "base64url"),
apv = util.asBuffer(props.apv || "", "base64url");
var otherInfo = Buffer.concat([
prependLen(Buffer.from(algId, "utf8")),
prependLen(apu),
prependLen(apv),
helpers.int32ToBuffer(keyLen)
]);
var params = {
public: pubKey,
length: keyLen / 8,
hash: "SHA-256",
otherInfo: otherInfo
};
return ecdh["ECDH-CONCAT"].derive(privKey, params);
}
function ecdhesDirEncryptFn() {
// NOTE: no nodejs/webcrypto/fallback model, since this algorithm is
// implemented using other primitives
var fn = function(key, pdata, props) {
props = props || {};
// {props.epk} is private
if (!props.epk || !props.epk.d) {
return Promise.reject(new Error("missing ephemeral private key"));
}
var epk = ecUtil.convertToObj(props.epk, false);
// {key} is public
if (!key || !key.x || !key.y) {
return Promise.reject(new Error("missing static public key"));
}
var spk = ecUtil.convertToObj(key, true);
// derive ECDH shared
var promise = doEcdhesCommonDerive(epk, spk, {
algorithm: props.enc,
apu: props.apu,
apv: props.apv
});
promise = promise.then(function(shared) {
return {
data: shared,
once: true,
direct: true
};
});
return promise;
};
return fn;
}
function ecdhesDirDecryptFn() {
// NOTE: no nodejs/webcrypto/fallback model, since this algorithm is
// implemented using other primitives
var fn = function(key, cdata, props) {
props = props || {};
// {props.epk} is public
if (!props.epk || !props.epk.x || !props.epk.y) {
return Promise.reject(new Error("missing ephemeral public key"));
}
var epk = ecUtil.convertToObj(props.epk, true);
// {key} is private
if (!key || !key.d) {
return Promise.reject(new Error("missing static private key"));
}
var spk = ecUtil.convertToObj(key, false);
// derive ECDH shared
var promise = doEcdhesCommonDerive(spk, epk, {
algorithm: props.enc,
apu: props.apu,
apv: props.apv
});
promise = promise.then(function(shared) {
return shared;
});
return promise;
};
return fn;
}
function ecdhesKwEncryptFn(wrap) {
// NOTE: no nodejs/webcrypto/fallback model, since this algorithm is
// implemented using other primitives
var fn = function(key, pdata, props) {
props = props || {};
// {props.epk} is private
if (!props.epk || !props.epk.d) {
return Promise.reject(new Error("missing ephemeral private key"));
}
var epk = ecUtil.convertToObj(props.epk, false);
// {key} is public
if (!key || !key.x || !key.y) {
return Promise.reject(new Error("missing static public key"));
}
var spk = ecUtil.convertToObj(key, true);
// derive ECDH shared
var promise = doEcdhesCommonDerive(epk, spk, {
algorithm: props.alg,
apu: props.apu,
apv: props.apv
});
promise = promise.then(function(shared) {
// wrap provided key with ECDH shared
return wrap(shared, pdata);
});
return promise;
};
return fn;
}
function ecdhesKwDecryptFn(unwrap) {
// NOTE: no nodejs/webcrypto/fallback model, since this algorithm is
// implemented using other primitives
var fn = function(key, cdata, props) {
props = props || {};
// {props.epk} is public
if (!props.epk || !props.epk.x || !props.epk.y) {
return Promise.reject(new Error("missing ephemeral public key"));
}
var epk = ecUtil.convertToObj(props.epk, true);
// {key} is private
if (!key || !key.d) {
return Promise.reject(new Error("missing static private key"));
}
var spk = ecUtil.convertToObj(key, false);
// derive ECDH shared
var promise = doEcdhesCommonDerive(spk, epk, {
algorithm: props.alg,
apu: props.apu,
apv: props.apv
});
promise = promise.then(function(shared) {
// unwrap provided key with ECDH shared
return unwrap(shared, cdata);
});
return promise;
};
return fn;
}
// ### Public API
// * [name].derive
[
"ECDH",
"ECDH-HKDF",
"ECDH-CONCAT"
].forEach(function(name) {
var kdf = /^ECDH(?:-(\w+))?$/g.exec(name || "")[1];
var op = ecdh[name] = ecdh[name] || {};
switch (kdf || "") {
case "CONCAT":
op.derive = ecdhConcatDeriveFn();
break;
case "HKDF":
op.derive = ecdhHkdfDeriveFn();
break;
case "":
op.derive = ecdhDeriveFn();
break;
default:
op.derive = null;
}
});
// * [name].encrypt
// * [name].decrypt
[
"ECDH-ES",
"ECDH-ES+A128KW",
"ECDH-ES+A192KW",
"ECDH-ES+A256KW"
].forEach(function(name) {
var kw = /^ECDH-ES(?:\+(.+))?/g.exec(name || "")[1];
var op = ecdh[name] = ecdh[name] || {};
if (!kw) {
op.encrypt = ecdhesDirEncryptFn();
op.decrypt = ecdhesDirDecryptFn();
} else {
kw = aesKw[kw];
if (kw) {
op.encrypt = ecdhesKwEncryptFn(kw.encrypt);
op.decrypt = ecdhesKwDecryptFn(kw.decrypt);
} else {
op.ecrypt = op.decrypt = null;
}
}
});
//*/