node/lib/internal/crypto/cipher.js
James M Snell ed94bc48f3
src: update crypto.getCipherInfo() to use DictionaryTemplate
Also, have it use a null prototype

PR-URL: https://github.com/nodejs/node/pull/60036
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
2025-10-05 14:05:35 +00:00

293 lines
8.3 KiB
JavaScript

'use strict';
const {
NumberIsInteger,
ObjectSetPrototypeOf,
ReflectApply,
} = primordials;
const {
CipherBase,
privateDecrypt: _privateDecrypt,
privateEncrypt: _privateEncrypt,
publicDecrypt: _publicDecrypt,
publicEncrypt: _publicEncrypt,
getCipherInfo: _getCipherInfo,
} = internalBinding('crypto');
const {
crypto: {
RSA_PKCS1_OAEP_PADDING,
RSA_PKCS1_PADDING,
},
} = internalBinding('constants');
const {
codes: {
ERR_CRYPTO_INVALID_STATE,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_UNKNOWN_ENCODING,
},
} = require('internal/errors');
const {
validateEncoding,
validateUint32,
validateObject,
validateString,
} = require('internal/validators');
const {
preparePrivateKey,
preparePublicOrPrivateKey,
prepareSecretKey,
} = require('internal/crypto/keys');
const {
getArrayBufferOrView,
getStringOption,
kHandle,
} = require('internal/crypto/util');
const {
isArrayBufferView,
} = require('internal/util/types');
const assert = require('internal/assert');
const LazyTransform = require('internal/streams/lazy_transform');
const { normalizeEncoding } = require('internal/util');
const { StringDecoder } = require('string_decoder');
function rsaFunctionFor(method, defaultPadding, keyType) {
return (options, buffer) => {
const { format, type, data, passphrase } =
keyType === 'private' ?
preparePrivateKey(options) :
preparePublicOrPrivateKey(options);
const padding = options.padding || defaultPadding;
const { oaepHash, encoding } = options;
let { oaepLabel } = options;
if (oaepHash !== undefined)
validateString(oaepHash, 'key.oaepHash');
if (oaepLabel !== undefined)
oaepLabel = getArrayBufferOrView(oaepLabel, 'key.oaepLabel', encoding);
buffer = getArrayBufferOrView(buffer, 'buffer', encoding);
return method(data, format, type, passphrase, buffer, padding, oaepHash,
oaepLabel);
};
}
const publicEncrypt = rsaFunctionFor(_publicEncrypt, RSA_PKCS1_OAEP_PADDING,
'public');
const publicDecrypt = rsaFunctionFor(_publicDecrypt, RSA_PKCS1_PADDING,
'public');
const privateEncrypt = rsaFunctionFor(_privateEncrypt, RSA_PKCS1_PADDING,
'private');
const privateDecrypt = rsaFunctionFor(_privateDecrypt, RSA_PKCS1_OAEP_PADDING,
'private');
function getDecoder(decoder, encoding) {
const normalizedEncoding = normalizeEncoding(encoding);
decoder ||= new StringDecoder(encoding);
if (decoder.encoding !== normalizedEncoding) {
if (normalizedEncoding === undefined) {
throw new ERR_UNKNOWN_ENCODING(encoding);
}
assert(false, 'Cannot change encoding');
}
return decoder;
}
function getUIntOption(options, key) {
let value;
if (options && (value = options[key]) != null) {
if (value >>> 0 !== value)
throw new ERR_INVALID_ARG_VALUE(`options.${key}`, value);
return value;
}
return -1;
}
function createCipherBase(cipher, credential, options, isEncrypt, iv) {
const authTagLength = getUIntOption(options, 'authTagLength');
this[kHandle] = new CipherBase(isEncrypt, cipher, credential, iv, authTagLength);
this._decoder = null;
ReflectApply(LazyTransform, this, [options]);
}
function createCipherWithIV(cipher, key, options, isEncrypt, iv) {
validateString(cipher, 'cipher');
const encoding = getStringOption(options, 'encoding');
key = prepareSecretKey(key, encoding);
iv = iv === null ? null : getArrayBufferOrView(iv, 'iv');
ReflectApply(createCipherBase, this, [cipher, key, options, isEncrypt, iv]);
}
// The Cipher class is part of the legacy Node.js crypto API. It exposes
// a stream-based encryption/decryption model. For backwards compatibility
// the Cipher class is defined using the legacy function syntax rather than
// ES6 classes.
function _transform(chunk, encoding, callback) {
this.push(this[kHandle].update(chunk, encoding));
callback();
};
function _flush(callback) {
try {
this.push(this[kHandle].final());
} catch (e) {
callback(e);
return;
}
callback();
};
function update(data, inputEncoding, outputEncoding) {
if (typeof data === 'string') {
validateEncoding(data, inputEncoding);
} else if (!isArrayBufferView(data)) {
throw new ERR_INVALID_ARG_TYPE(
'data', ['string', 'Buffer', 'TypedArray', 'DataView'], data);
}
const ret = this[kHandle].update(data, inputEncoding);
if (outputEncoding && outputEncoding !== 'buffer') {
this._decoder = getDecoder(this._decoder, outputEncoding);
return this._decoder.write(ret);
}
return ret;
};
function final(outputEncoding) {
const ret = this[kHandle].final();
if (outputEncoding && outputEncoding !== 'buffer') {
this._decoder = getDecoder(this._decoder, outputEncoding);
return this._decoder.end(ret);
}
return ret;
};
function setAutoPadding(ap) {
if (!this[kHandle].setAutoPadding(!!ap))
throw new ERR_CRYPTO_INVALID_STATE('setAutoPadding');
return this;
};
function getAuthTag() {
const ret = this[kHandle].getAuthTag();
if (ret === undefined)
throw new ERR_CRYPTO_INVALID_STATE('getAuthTag');
return ret;
};
function setAuthTag(tagbuf, encoding) {
tagbuf = getArrayBufferOrView(tagbuf, 'buffer', encoding);
if (!this[kHandle].setAuthTag(tagbuf))
throw new ERR_CRYPTO_INVALID_STATE('setAuthTag');
return this;
}
function setAAD(aadbuf, options) {
const encoding = getStringOption(options, 'encoding');
const plaintextLength = getUIntOption(options, 'plaintextLength');
aadbuf = getArrayBufferOrView(aadbuf, 'aadbuf', encoding);
if (!this[kHandle].setAAD(aadbuf, plaintextLength))
throw new ERR_CRYPTO_INVALID_STATE('setAAD');
return this;
};
// The Cipheriv class is part of the legacy Node.js crypto API. It exposes
// a stream-based encryption/decryption model. For backwards compatibility
// the Cipheriv class is defined using the legacy function syntax rather than
// ES6 classes.
function Cipheriv(cipher, key, iv, options) {
if (!(this instanceof Cipheriv))
return new Cipheriv(cipher, key, iv, options);
ReflectApply(createCipherWithIV, this, [cipher, key, options, true, iv]);
}
function addCipherPrototypeFunctions(constructor) {
constructor.prototype._transform = _transform;
constructor.prototype._flush = _flush;
constructor.prototype.update = update;
constructor.prototype.final = final;
constructor.prototype.setAutoPadding = setAutoPadding;
if (constructor === Cipheriv) {
constructor.prototype.getAuthTag = getAuthTag;
} else {
constructor.prototype.setAuthTag = setAuthTag;
}
constructor.prototype.setAAD = setAAD;
}
ObjectSetPrototypeOf(Cipheriv.prototype, LazyTransform.prototype);
ObjectSetPrototypeOf(Cipheriv, LazyTransform);
addCipherPrototypeFunctions(Cipheriv);
// The Decipheriv class is part of the legacy Node.js crypto API. It exposes
// a stream-based encryption/decryption model. For backwards compatibility
// the Decipheriv class is defined using the legacy function syntax rather than
// ES6 classes.
function Decipheriv(cipher, key, iv, options) {
if (!(this instanceof Decipheriv))
return new Decipheriv(cipher, key, iv, options);
ReflectApply(createCipherWithIV, this, [cipher, key, options, false, iv]);
}
ObjectSetPrototypeOf(Decipheriv.prototype, LazyTransform.prototype);
ObjectSetPrototypeOf(Decipheriv, LazyTransform);
addCipherPrototypeFunctions(Decipheriv);
const kMinNid = 1;
const kMaxNid = 2_147_483_647;
function getCipherInfo(nameOrNid, options = {}) {
validateObject(options, 'options');
const { keyLength, ivLength } = options;
if (keyLength !== undefined) {
validateUint32(keyLength, 'options.keyLength');
}
if (ivLength !== undefined) {
validateUint32(ivLength, 'options.ivLength');
}
const type = typeof nameOrNid;
if (type === 'string') {
if (nameOrNid.length === 0) return undefined;
return _getCipherInfo(nameOrNid, keyLength, ivLength);
} else if (type === 'number') {
if (!NumberIsInteger(nameOrNid) || nameOrNid < kMinNid || nameOrNid > kMaxNid) {
return undefined;
}
return _getCipherInfo(nameOrNid, keyLength, ivLength);
}
throw new ERR_INVALID_ARG_TYPE(
'nameOrNid',
['string', 'number'],
nameOrNid);
}
module.exports = {
Cipheriv,
Decipheriv,
privateDecrypt,
privateEncrypt,
publicDecrypt,
publicEncrypt,
getCipherInfo,
};