nstdlib-nightly
Version:
Node.js standard library converted to runtime-agnostic ES modules.
343 lines (288 loc) • 9.99 kB
JavaScript
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/crypto/diffiehellman.js
import { Buffer } from "nstdlib/lib/buffer";
import {
DiffieHellman as _DiffieHellman,
DiffieHellmanGroup as _DiffieHellmanGroup,
ECDH as _ECDH,
ECDHBitsJob,
ECDHConvertKey as _ECDHConvertKey,
statelessDH,
kCryptoJobAsync,
} from "nstdlib/stub/binding/crypto";
import { codes as __codes__ } from "nstdlib/lib/internal/errors";
import {
validateInt32,
validateObject,
validateString,
} from "nstdlib/lib/internal/validators";
import {
isArrayBufferView,
isAnyArrayBuffer,
} from "nstdlib/lib/internal/util/types";
import { lazyDOMException } from "nstdlib/lib/internal/util";
import { KeyObject } from "nstdlib/lib/internal/crypto/keys";
import {
getArrayBufferOrView,
jobPromise,
toBuf,
kHandle,
kKeyObject,
} from "nstdlib/lib/internal/crypto/util";
import { crypto as __crypto__ } from "nstdlib/stub/binding/constants";
const {
ERR_CRYPTO_ECDH_INVALID_FORMAT,
ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY,
ERR_CRYPTO_INCOMPATIBLE_KEY,
ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
} = __codes__;
const {
POINT_CONVERSION_COMPRESSED,
POINT_CONVERSION_HYBRID,
POINT_CONVERSION_UNCOMPRESSED,
} = __crypto__;
const DH_GENERATOR = 2;
function DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding) {
if (!(this instanceof DiffieHellman))
return new DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding);
if (
typeof sizeOrKey !== "number" &&
typeof sizeOrKey !== "string" &&
!isArrayBufferView(sizeOrKey) &&
!isAnyArrayBuffer(sizeOrKey)
) {
throw new ERR_INVALID_ARG_TYPE(
"sizeOrKey",
["number", "string", "ArrayBuffer", "Buffer", "TypedArray", "DataView"],
sizeOrKey,
);
}
// Sizes < 0 don't make sense but they _are_ accepted (and subsequently
// rejected with ERR_OSSL_BN_BITS_TOO_SMALL) by OpenSSL. The glue code
// in node_crypto.cc accepts values that are IsInt32() for that reason
// and that's why we do that here too.
if (typeof sizeOrKey === "number") validateInt32(sizeOrKey, "sizeOrKey");
if (
keyEncoding &&
!Buffer.isEncoding(keyEncoding) &&
keyEncoding !== "buffer"
) {
genEncoding = generator;
generator = keyEncoding;
keyEncoding = false;
}
if (typeof sizeOrKey !== "number") sizeOrKey = toBuf(sizeOrKey, keyEncoding);
if (!generator) {
generator = DH_GENERATOR;
} else if (typeof generator === "number") {
validateInt32(generator, "generator");
} else if (typeof generator === "string") {
generator = toBuf(generator, genEncoding);
} else if (!isArrayBufferView(generator) && !isAnyArrayBuffer(generator)) {
throw new ERR_INVALID_ARG_TYPE(
"generator",
["number", "string", "ArrayBuffer", "Buffer", "TypedArray", "DataView"],
generator,
);
}
this[kHandle] = new _DiffieHellman(sizeOrKey, generator);
Object.defineProperty(this, "verifyError", {
__proto__: null,
enumerable: true,
value: this[kHandle].verifyError,
writable: false,
});
}
function DiffieHellmanGroup(name) {
if (!(this instanceof DiffieHellmanGroup))
return new DiffieHellmanGroup(name);
this[kHandle] = new _DiffieHellmanGroup(name);
Object.defineProperty(this, "verifyError", {
__proto__: null,
enumerable: true,
value: this[kHandle].verifyError,
writable: false,
});
}
DiffieHellmanGroup.prototype.generateKeys =
DiffieHellman.prototype.generateKeys = dhGenerateKeys;
function dhGenerateKeys(encoding) {
const keys = this[kHandle].generateKeys();
return encode(keys, encoding);
}
DiffieHellmanGroup.prototype.computeSecret =
DiffieHellman.prototype.computeSecret = dhComputeSecret;
function dhComputeSecret(key, inEnc, outEnc) {
key = getArrayBufferOrView(key, "key", inEnc);
const ret = this[kHandle].computeSecret(key);
if (typeof ret === "string") throw new ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY();
return encode(ret, outEnc);
}
DiffieHellmanGroup.prototype.getPrime = DiffieHellman.prototype.getPrime =
dhGetPrime;
function dhGetPrime(encoding) {
const prime = this[kHandle].getPrime();
return encode(prime, encoding);
}
DiffieHellmanGroup.prototype.getGenerator =
DiffieHellman.prototype.getGenerator = dhGetGenerator;
function dhGetGenerator(encoding) {
const generator = this[kHandle].getGenerator();
return encode(generator, encoding);
}
DiffieHellmanGroup.prototype.getPublicKey =
DiffieHellman.prototype.getPublicKey = dhGetPublicKey;
function dhGetPublicKey(encoding) {
const key = this[kHandle].getPublicKey();
return encode(key, encoding);
}
DiffieHellmanGroup.prototype.getPrivateKey =
DiffieHellman.prototype.getPrivateKey = dhGetPrivateKey;
function dhGetPrivateKey(encoding) {
const key = this[kHandle].getPrivateKey();
return encode(key, encoding);
}
DiffieHellman.prototype.setPublicKey = function setPublicKey(key, encoding) {
key = getArrayBufferOrView(key, "key", encoding);
this[kHandle].setPublicKey(key);
return this;
};
DiffieHellman.prototype.setPrivateKey = function setPrivateKey(key, encoding) {
key = getArrayBufferOrView(key, "key", encoding);
this[kHandle].setPrivateKey(key);
return this;
};
function ECDH(curve) {
if (!(this instanceof ECDH)) return new ECDH(curve);
validateString(curve, "curve");
this[kHandle] = new _ECDH(curve);
}
ECDH.prototype.computeSecret = DiffieHellman.prototype.computeSecret;
ECDH.prototype.setPrivateKey = DiffieHellman.prototype.setPrivateKey;
ECDH.prototype.setPublicKey = DiffieHellman.prototype.setPublicKey;
ECDH.prototype.getPrivateKey = DiffieHellman.prototype.getPrivateKey;
ECDH.prototype.generateKeys = function generateKeys(encoding, format) {
this[kHandle].generateKeys();
return this.getPublicKey(encoding, format);
};
ECDH.prototype.getPublicKey = function getPublicKey(encoding, format) {
const f = getFormat(format);
const key = this[kHandle].getPublicKey(f);
return encode(key, encoding);
};
ECDH.convertKey = function convertKey(key, curve, inEnc, outEnc, format) {
validateString(curve, "curve");
key = getArrayBufferOrView(key, "key", inEnc);
const f = getFormat(format);
const convertedKey = _ECDHConvertKey(key, curve, f);
return encode(convertedKey, outEnc);
};
function encode(buffer, encoding) {
if (encoding && encoding !== "buffer") buffer = buffer.toString(encoding);
return buffer;
}
function getFormat(format) {
if (format) {
if (format === "compressed") return POINT_CONVERSION_COMPRESSED;
if (format === "hybrid") return POINT_CONVERSION_HYBRID;
if (format !== "uncompressed")
throw new ERR_CRYPTO_ECDH_INVALID_FORMAT(format);
}
return POINT_CONVERSION_UNCOMPRESSED;
}
const dhEnabledKeyTypes = new Set(["dh", "ec", "x448", "x25519"]);
function diffieHellman(options) {
validateObject(options, "options");
const { privateKey, publicKey } = options;
if (!(privateKey instanceof KeyObject))
throw new ERR_INVALID_ARG_VALUE("options.privateKey", privateKey);
if (!(publicKey instanceof KeyObject))
throw new ERR_INVALID_ARG_VALUE("options.publicKey", publicKey);
if (privateKey.type !== "private")
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(privateKey.type, "private");
if (publicKey.type !== "public" && publicKey.type !== "private") {
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(
publicKey.type,
"private or public",
);
}
const privateType = privateKey.asymmetricKeyType;
const publicType = publicKey.asymmetricKeyType;
if (privateType !== publicType || !dhEnabledKeyTypes.has(privateType)) {
throw new ERR_CRYPTO_INCOMPATIBLE_KEY(
"key types for Diffie-Hellman",
`${privateType} and ${publicType}`,
);
}
return statelessDH(privateKey[kHandle], publicKey[kHandle]);
}
// The ecdhDeriveBits function is part of the Web Crypto API and serves both
// deriveKeys and deriveBits functions.
async function ecdhDeriveBits(algorithm, baseKey, length) {
const { public: key } = algorithm;
if (key.type !== "public") {
throw lazyDOMException(
"algorithm.public must be a public key",
"InvalidAccessError",
);
}
if (baseKey.type !== "private") {
throw lazyDOMException(
"baseKey must be a private key",
"InvalidAccessError",
);
}
if (
key.algorithm.name !== "ECDH" &&
key.algorithm.name !== "X25519" &&
key.algorithm.name !== "X448"
) {
throw lazyDOMException(
"Keys must be ECDH, X25519, or X448 keys",
"InvalidAccessError",
);
}
if (key.algorithm.name !== baseKey.algorithm.name) {
throw lazyDOMException(
"The public and private keys must be of the same type",
"InvalidAccessError",
);
}
if (
key.algorithm.name === "ECDH" &&
key.algorithm.namedCurve !== baseKey.algorithm.namedCurve
) {
throw lazyDOMException("Named curve mismatch", "InvalidAccessError");
}
const bits = await jobPromise(
() =>
new ECDHBitsJob(
kCryptoJobAsync,
key.algorithm.name === "ECDH"
? baseKey.algorithm.namedCurve
: baseKey.algorithm.name,
key[kKeyObject][kHandle],
baseKey[kKeyObject][kHandle],
),
);
// If a length is not specified, return the full derived secret
if (length === null) return bits;
// If the length is not a multiple of 8 the nearest ceiled
// multiple of 8 is sliced.
length = Math.ceil(length / 8);
const { byteLength } = bits;
// If the length is larger than the derived secret, throw.
// Otherwise, we either return the secret or a truncated
// slice.
if (byteLength < length)
throw lazyDOMException("derived bit length is too small", "OperationError");
return length === byteLength
? bits
: ArrayBuffer.prototype.slice.call(bits, 0, length);
}
export { DiffieHellman };
export { DiffieHellmanGroup };
export { ECDH };
export { diffieHellman };
export { ecdhDeriveBits };