nstdlib-nightly
Version:
Node.js standard library converted to runtime-agnostic ES modules.
210 lines (182 loc) • 6.04 kB
JavaScript
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/crypto/hash.js
import {
Hash as _Hash,
HashJob,
Hmac as _Hmac,
kCryptoJobAsync,
oneShotDigest,
} from "nstdlib/stub/binding/crypto";
import {
getStringOption,
jobPromise,
normalizeHashName,
validateMaxBufferLength,
kHandle,
getCachedHashId,
getHashCache,
} from "nstdlib/lib/internal/crypto/util";
import { prepareSecretKey } from "nstdlib/lib/internal/crypto/keys";
import {
lazyDOMException,
normalizeEncoding,
encodingsMap,
} from "nstdlib/lib/internal/util";
import { Buffer } from "nstdlib/lib/buffer";
import { codes as __codes__ } from "nstdlib/lib/internal/errors";
import {
validateEncoding,
validateString,
validateUint32,
} from "nstdlib/lib/internal/validators";
import { isArrayBufferView } from "nstdlib/lib/internal/util/types";
import * as LazyTransform from "nstdlib/lib/internal/streams/lazy_transform";
const {
ERR_CRYPTO_HASH_FINALIZED,
ERR_CRYPTO_HASH_UPDATE_FAILED,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
} = __codes__;
const kState = Symbol("kState");
const kFinalized = Symbol("kFinalized");
function Hash(algorithm, options) {
if (!new.target) return new Hash(algorithm, options);
const isCopy = algorithm instanceof _Hash;
if (!isCopy) validateString(algorithm, "algorithm");
const xofLen =
typeof options === "object" && options !== null
? options.outputLength
: undefined;
if (xofLen !== undefined) validateUint32(xofLen, "options.outputLength");
// Lookup the cached ID from JS land because it's faster than decoding
// the string in C++ land.
const algorithmId = isCopy ? -1 : getCachedHashId(algorithm);
this[kHandle] = new _Hash(algorithm, xofLen, algorithmId, getHashCache());
this[kState] = {
[kFinalized]: false,
};
ReflectApply(LazyTransform, this, [options]);
}
Object.setPrototypeOf(Hash.prototype, LazyTransform.prototype);
Object.setPrototypeOf(Hash, LazyTransform);
Hash.prototype.copy = function copy(options) {
const state = this[kState];
if (state[kFinalized]) throw new ERR_CRYPTO_HASH_FINALIZED();
return new Hash(this[kHandle], options);
};
Hash.prototype._transform = function _transform(chunk, encoding, callback) {
this[kHandle].update(chunk, encoding);
callback();
};
Hash.prototype._flush = function _flush(callback) {
this.push(this[kHandle].digest());
callback();
};
Hash.prototype.update = function update(data, encoding) {
const state = this[kState];
if (state[kFinalized]) throw new ERR_CRYPTO_HASH_FINALIZED();
if (typeof data === "string") {
validateEncoding(data, encoding);
} else if (!isArrayBufferView(data)) {
throw new ERR_INVALID_ARG_TYPE(
"data",
["string", "Buffer", "TypedArray", "DataView"],
data,
);
}
if (!this[kHandle].update(data, encoding))
throw new ERR_CRYPTO_HASH_UPDATE_FAILED();
return this;
};
Hash.prototype.digest = function digest(outputEncoding) {
const state = this[kState];
if (state[kFinalized]) throw new ERR_CRYPTO_HASH_FINALIZED();
// Explicit conversion of truthy values for backward compatibility.
const ret = this[kHandle].digest(outputEncoding && `${outputEncoding}`);
state[kFinalized] = true;
return ret;
};
function Hmac(hmac, key, options) {
if (!(this instanceof Hmac)) return new Hmac(hmac, key, options);
validateString(hmac, "hmac");
const encoding = getStringOption(options, "encoding");
key = prepareSecretKey(key, encoding);
this[kHandle] = new _Hmac();
this[kHandle].init(hmac, key);
this[kState] = {
[kFinalized]: false,
};
ReflectApply(LazyTransform, this, [options]);
}
Object.setPrototypeOf(Hmac.prototype, LazyTransform.prototype);
Object.setPrototypeOf(Hmac, LazyTransform);
Hmac.prototype.update = Hash.prototype.update;
Hmac.prototype.digest = function digest(outputEncoding) {
const state = this[kState];
if (state[kFinalized]) {
const buf = Buffer.from("");
if (outputEncoding && outputEncoding !== "buffer")
return buf.toString(outputEncoding);
return buf;
}
// Explicit conversion of truthy values for backward compatibility.
const ret = this[kHandle].digest(outputEncoding && `${outputEncoding}`);
state[kFinalized] = true;
return ret;
};
Hmac.prototype._flush = Hash.prototype._flush;
Hmac.prototype._transform = Hash.prototype._transform;
// Implementation for WebCrypto subtle.digest()
async function asyncDigest(algorithm, data) {
validateMaxBufferLength(data, "data");
switch (algorithm.name) {
case "SHA-1":
// Fall through
case "SHA-256":
// Fall through
case "SHA-384":
// Fall through
case "SHA-512":
return jobPromise(
() =>
new HashJob(kCryptoJobAsync, normalizeHashName(algorithm.name), data),
);
}
throw lazyDOMException("Unrecognized algorithm name", "NotSupportedError");
}
function hash(algorithm, input, outputEncoding = "hex") {
validateString(algorithm, "algorithm");
if (typeof input !== "string" && !isArrayBufferView(input)) {
throw new ERR_INVALID_ARG_TYPE(
"input",
["Buffer", "TypedArray", "DataView", "string"],
input,
);
}
let normalized = outputEncoding;
// Fast case: if it's 'hex', we don't need to validate it further.
if (outputEncoding !== "hex") {
validateString(outputEncoding, "outputEncoding");
normalized = normalizeEncoding(outputEncoding);
// If the encoding is invalid, normalizeEncoding() returns undefined.
if (normalized === undefined) {
// normalizeEncoding() doesn't handle 'buffer'.
if (String.prototype.toLowerCase.call(outputEncoding) === "buffer") {
normalized = "buffer";
} else {
throw new ERR_INVALID_ARG_VALUE("outputEncoding", outputEncoding);
}
}
}
return oneShotDigest(
algorithm,
getCachedHashId(algorithm),
getHashCache(),
input,
normalized,
encodingsMap[normalized],
);
}
export { Hash };
export { Hmac };
export { asyncDigest };
export { hash };