crypto: refactoring internals, add WebCrypto

Fixes: https://github.com/nodejs/node/issues/678
Refs: https://github.com/nodejs/node/issues/26854

Signed-off-by: James M Snell <jasnell@gmail.com>

PR-URL: https://github.com/nodejs/node/pull/35093
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
This commit is contained in:
James M Snell 2020-08-25 10:05:51 -07:00
parent ba77dc8597
commit dae283d96f
No known key found for this signature in database
GPG Key ID: 7341B15C070877AC
149 changed files with 28395 additions and 8580 deletions

2
.github/CODEOWNERS vendored
View File

@ -59,7 +59,7 @@
/lib/crypto.js @nodejs/crypto
/lib/tls.js @nodejs/crypto @nodejs/net
/src/node_crypto* @nodejs/crypto
/src/node_crypto_common* @nodejs/crypto @nodejs/quic
/src/crypto/* @nodejs/crypto @nodejs/quic
# http

44
benchmark/crypto/hkdf.js Normal file
View File

@ -0,0 +1,44 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const {
hkdf,
hkdfSync
} = require('crypto');
const bench = common.createBenchmark(main, {
sync: [0, 1],
size: [10, 64, 1024],
key: ['a', 'secret', 'this-is-a-much-longer-secret'],
salt: ['', 'salt'],
info: ['', 'info'],
hash: ['sha256', 'sha512'],
n: [1e3],
});
function measureSync(n, size, salt, info, hash, key) {
bench.start();
for (let i = 0; i < n; ++i)
hkdfSync(hash, key, salt, info, size);
bench.end(n);
}
function measureAsync(n, size, salt, info, hash, key) {
let remaining = n;
function done(err) {
assert.ifError(err);
if (--remaining === 0)
bench.end(n);
}
bench.start();
for (let i = 0; i < n; ++i)
hkdf(hash, key, salt, info, size, done);
}
function main({ n, sync, size, salt, info, hash, key }) {
if (sync)
measureSync(n, size, salt, info, hash, key);
else
measureAsync(n, size, salt, info, hash, key);
}

View File

@ -0,0 +1,71 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const {
generateKeyPair,
generateKeyPairSync
} = require('crypto');
const bench = common.createBenchmark(main, {
method: ['rsaSync', 'rsaAsync', 'dsaSync', 'dsaAsync'],
n: [1e2],
});
const methods = {
rsaSync(n) {
bench.start();
for (let i = 0; i < n; ++i) {
generateKeyPairSync('rsa', {
modulusLength: 1024,
publicExponent: 0x10001
});
}
bench.end(n);
},
rsaAsync(n) {
let remaining = n;
function done(err) {
assert.ifError(err);
if (--remaining === 0)
bench.end(n);
}
bench.start();
for (let i = 0; i < n; ++i)
generateKeyPair('rsa', {
modulusLength: 512,
publicExponent: 0x10001
}, done);
},
dsaSync(n) {
bench.start();
for (let i = 0; i < n; ++i) {
generateKeyPairSync('dsa', {
modulusLength: 512,
divisorLength: 256,
});
}
bench.end(n);
},
dsaAsync(n) {
let remaining = n;
function done(err) {
assert.ifError(err);
if (--remaining === 0)
bench.end(n);
}
bench.start();
for (let i = 0; i < n; ++i)
generateKeyPair('dsa', {
modulusLength: 512,
divisorLength: 256,
}, done);
},
};
function main({ n, method }) {
methods[method](n);
}

View File

@ -0,0 +1,58 @@
'use strict';
const common = require('../common.js');
const {
createHash,
webcrypto: {
subtle,
getRandomValues
}
} = require('crypto');
const bench = common.createBenchmark(main, {
sync: ['createHash', 'subtle'],
data: [10, 20, 50, 100],
method: ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512'],
n: [1e3],
});
const kMethods = {
'SHA-1': 'sha1',
'SHA-256': 'sha256',
'SHA-384': 'sha384',
'SHA-512': 'sha512'
};
// This benchmark only looks at clock time and ignores factors
// such as event loop delay, event loop utilization, and memory.
// As such, it is expected that the synchronous legacy method
// will always be faster in clock time.
function measureLegacy(n, data, method) {
method = kMethods[method];
bench.start();
for (let i = 0; i < n; ++i) {
createHash(method).update(data).digest();
}
bench.end(n);
}
function measureSubtle(n, data, method) {
const ec = new TextEncoder();
data = ec.encode(data);
const jobs = new Array(n);
bench.start();
for (let i = 0; i < n; i++)
jobs[i] = subtle.digest(method, data);
Promise.all(jobs).then(() => bench.end(n)).catch((err) => {
process.nextTick(() => { throw err; });
});
}
function main({ n, sync, data, method }) {
data = getRandomValues(Buffer.alloc(data));
switch (sync) {
case 'createHash': return measureLegacy(n, data, method);
case 'subtle': return measureSubtle(n, data, method);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2690,7 +2690,7 @@ The [`crypto.Certificate()` constructor][] is deprecated. Use
[`crypto.randomBytes()`]: crypto.md#crypto_crypto_randombytes_size_callback
[`crypto.scrypt()`]: crypto.md#crypto_crypto_scrypt_password_salt_keylen_options_callback
[`decipher.final()`]: crypto.md#crypto_decipher_final_outputencoding
[`decipher.setAuthTag()`]: crypto.md#crypto_decipher_setauthtag_buffer
[`decipher.setAuthTag()`]: crypto.md#crypto_decipher_setauthtag_buffer_encoding
[`domain`]: domain.md
[`ecdh.setPublicKey()`]: crypto.md#crypto_ecdh_setpublickey_publickey_encoding
[`emitter.listenerCount(eventName)`]: events.md#events_emitter_listenercount_eventname

View File

@ -792,22 +792,134 @@ The given crypto keys are incompatible with the attempted operation.
The selected public or private key encoding is incompatible with other options.
<a id="ERR_CRYPTO_INITIALIZATION_FAILED"></a>
### `ERR_CRYPTO_INITIALIZATION_FAILED`
<!-- YAML
added: REPLACEME
-->
Initialization of the crypto subsystem failed.
<a id="ERR_CRYPTO_INVALID_AUTH_TAG"></a>
### `ERR_CRYPTO_INVALID_AUTH_TAG`
<!-- YAML
added: REPLACEME
-->
An invalid authentication tag was provided.
<a id="ERR_CRYPTO_INVALID_COUNTER"></a>
### `ERR_CRYPTO_INVALID_COUNTER`
<!-- YAML
added: REPLACEME
-->
An invalid counter was provided for a counter-mode cipher.
<a id="ERR_CRYPTO_INVALID_CURVE"></a>
### `ERR_CRYPTO_INVALID_CURVE`
<!-- YAML
added: REPLACEME
-->
An invalid elliptic-curve was provided.
<a id="ERR_CRYPTO_INVALID_DIGEST"></a>
### `ERR_CRYPTO_INVALID_DIGEST`
An invalid [crypto digest algorithm][] was specified.
<a id="ERR_CRYPTO_INVALID_IV"></a>
### `ERR_CRYPTO_INVALID_IV`
<!-- YAML
added: REPLACEME
-->
An invalid initialization vector was provided.
<a id="ERR_CRYPTO_INVALID_JWK"></a>
### `ERR_CRYPTO_INVALID_JWK`
<!-- YAML
added: REPLACEME
-->
An invalid JSON Web Key was provided.
<a id="ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE"></a>
### `ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE`
The given crypto key object's type is invalid for the attempted operation.
<a id="ERR_CRYPTO_INVALID_KEYLEN"></a>
### `ERR_CRYPTO_INVALID_KEYLEN`
<!-- YAML
added: REPLACEME
-->
An invalid key length was provided.
<a id="ERR_CRYPTO_INVALID_KEYPAIR"></a>
### `ERR_CRYPTO_INVALID_KEYPAIR`
<!-- YAML
added: REPLACEME
-->
An invalid key pair was provided.
<a id="ERR_CRYPTO_INVALID_KEYTYPE"></a>
### `ERR_CRYPTO_INVALID_KEYTYPE`
<!-- YAML
added: REPLACEME
-->
An invalid key type was provided.
<a id="ERR_CRYPTO_INVALID_MESSAGELEN"></a>
### `ERR_CRYPTO_INVALID_MESSAGELEN`
<!-- YAML
added: REPLACEME
-->
An invalid message length was provided.
<a id="ERR_CRYPTO_INVALID_SCRYPT_PARAMS"></a>
### `ERR_CRYPTO_INVALID_SCRYPT_PARAMS`
<!-- YAML
added: REPLACEME
-->
Invalid scrypt algorithm parameters were provided.
<a id="ERR_CRYPTO_INVALID_STATE"></a>
### `ERR_CRYPTO_INVALID_STATE`
A crypto method was used on an object that was in an invalid state. For
instance, calling [`cipher.getAuthTag()`][] before calling `cipher.final()`.
<a id="ERR_CRYPTO_INVALID_TAG_LENGTH"></a>
### `ERR_CRYPTO_INVALID_TAG_LENGTH`
<!-- YAML
added: REPLACEME
-->
An invalid authentication tag length was provided.
<a id="ERR_CRYPTO_JOB_INIT_FAILED"></a>
### `ERR_CRYPTO_JOB_INIT_FAILED`
<!-- YAML
added: REPLACEME
-->
Initialization of an asynchronous crypto operation failed.
<a id="ERR_CRYPTO_OPERATION_FAILED"></a>
### `ERR_CRYPTO_OPERATION_FAILED`
<!-- YAML
added: REPLACEME
-->
A crypto operation failed for an otherwise unspecified reason.
<a id="ERR_CRYPTO_PBKDF2_ERROR"></a>
### `ERR_CRYPTO_PBKDF2_ERROR`
@ -853,6 +965,14 @@ An unknown Diffie-Hellman group name was given. See
The [`fs.Dir`][] was previously closed.
<a id"ERR_CRYPTO_UNSUPPORTED_OPERATION"></a>
### `ERR_CRYPTO_UNSUPPORTED_OPERATION`
<!-- YAML
added: REPLACEME
-->
An attempt to invoke an unsupported crypto operation was made.
<a id="ERR_DIR_CONCURRENT_OPERATION"></a>
### `ERR_DIR_CONCURRENT_OPERATION`
<!-- YAML

View File

@ -62,6 +62,7 @@
* [V8](v8.md)
* [VM](vm.md)
* [WASI](wasi.md)
* [Web Crypto API](webcrypto.md)
* [Worker threads](worker_threads.md)
* [Zlib](zlib.md)

1672
doc/api/webcrypto.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -63,9 +63,15 @@ const {
scrypt,
scryptSync
} = require('internal/crypto/scrypt');
const {
hkdf,
hkdfSync
} = require('internal/crypto/hkdf');
const {
generateKeyPair,
generateKeyPairSync
generateKeyPairSync,
generateKey,
generateKeySync,
} = require('internal/crypto/keygen');
const {
createSecretKey,
@ -106,6 +112,7 @@ const {
getHashes,
setDefaultEncoding,
setEngine,
lazyRequire,
} = require('internal/crypto/util');
const Certificate = require('internal/crypto/certificate');
@ -174,10 +181,14 @@ module.exports = {
getCurves,
getDiffieHellman: createDiffieHellmanGroup,
getHashes,
hkdf,
hkdfSync,
pbkdf2,
pbkdf2Sync,
generateKeyPair,
generateKeyPairSync,
generateKey,
generateKeySync,
privateDecrypt,
privateEncrypt,
publicDecrypt,
@ -266,6 +277,12 @@ ObjectDefineProperties(module.exports, {
value: constants
},
webcrypto: {
configurable: false,
enumerable: true,
get() { return lazyRequire('internal/crypto/webcrypto'); }
},
// Aliases for randomBytes are deprecated.
// The ecosystem needs those to exist for backwards compatibility.
prng: {

342
lib/internal/crypto/aes.js Normal file
View File

@ -0,0 +1,342 @@
'use strict';
const {
ArrayFrom,
ArrayPrototypeIncludes,
ArrayPrototypePush,
MathFloor,
Promise,
SafeSet,
} = primordials;
const {
AESCipherJob,
KeyObjectHandle,
kCryptoJobAsync,
kKeyVariantAES_CTR_128,
kKeyVariantAES_CBC_128,
kKeyVariantAES_GCM_128,
kKeyVariantAES_KW_128,
kKeyVariantAES_CTR_192,
kKeyVariantAES_CBC_192,
kKeyVariantAES_GCM_192,
kKeyVariantAES_KW_192,
kKeyVariantAES_CTR_256,
kKeyVariantAES_CBC_256,
kKeyVariantAES_GCM_256,
kKeyVariantAES_KW_256,
kWebCryptoCipherDecrypt,
kWebCryptoCipherEncrypt,
} = internalBinding('crypto');
const {
getArrayBufferOrView,
hasAnyNotIn,
jobPromise,
lazyDOMException,
validateByteLength,
validateKeyOps,
validateMaxBufferLength,
kAesKeyLengths,
kHandle,
kKeyObject,
} = require('internal/crypto/util');
const {
codes: {
ERR_INVALID_ARG_TYPE,
}
} = require('internal/errors');
const {
InternalCryptoKey,
SecretKeyObject,
createSecretKey,
isKeyObject,
} = require('internal/crypto/keys');
const {
generateKey,
} = require('internal/crypto/keygen');
const {
validateInteger,
validateOneOf,
} = require('internal/validators');
const kMaxCounterLength = 128;
const kTagLengths = [32, 64, 96, 104, 112, 120, 128];
function getAlgorithmName(name, length) {
switch (name) {
case 'AES-CBC': return `A${length}CBC`;
case 'AES-CTR': return `A${length}CTR`;
case 'AES-GCM': return `A${length}GCM`;
case 'AES-KW': return `A${length}KW`;
}
}
function validateKeyLength(length) {
if (length !== 128 && length !== 192 && length !== 256)
throw lazyDOMException('Invalid key length', 'DataError');
}
function getVariant(name, length) {
switch (name) {
case 'AES-CBC':
switch (length) {
case 128: return kKeyVariantAES_CBC_128;
case 192: return kKeyVariantAES_CBC_192;
case 256: return kKeyVariantAES_CBC_256;
}
break;
case 'AES-CTR':
switch (length) {
case 128: return kKeyVariantAES_CTR_128;
case 192: return kKeyVariantAES_CTR_192;
case 256: return kKeyVariantAES_CTR_256;
}
break;
case 'AES-GCM':
switch (length) {
case 128: return kKeyVariantAES_GCM_128;
case 192: return kKeyVariantAES_GCM_192;
case 256: return kKeyVariantAES_GCM_256;
}
break;
case 'AES-KW':
switch (length) {
case 128: return kKeyVariantAES_KW_128;
case 192: return kKeyVariantAES_KW_192;
case 256: return kKeyVariantAES_KW_256;
}
break;
}
}
function asyncAesCtrCipher(mode, key, data, { counter, length }) {
counter = getArrayBufferOrView(counter, 'algorithm.counter');
validateByteLength(counter, 'algorithm.counter', 16);
// The length must specify an integer between 1 and 128. While
// there is no default, this should typically be 64.
if (typeof length !== 'number' ||
length <= 0 ||
length > kMaxCounterLength) {
throw lazyDOMException(
'AES-CTR algorithm.length must be between 1 and 128',
'OperationError');
}
return jobPromise(new AESCipherJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
data,
getVariant('AES-CTR', key.algorithm.length),
counter,
length));
}
function asyncAesCbcCipher(mode, key, data, { iv }) {
iv = getArrayBufferOrView(iv, 'algorithm.iv');
validateByteLength(iv, 'algorithm.iv', 16);
return jobPromise(new AESCipherJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
data,
getVariant('AES-CBC', key.algorithm.length),
iv));
}
function asyncAesKwCipher(mode, key, data) {
return jobPromise(new AESCipherJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
data,
getVariant('AES-KW', key.algorithm.length)));
}
function asyncAesGcmCipher(
mode,
key,
data,
{ iv, additionalData, tagLength = 128 }) {
if (!ArrayPrototypeIncludes(kTagLengths, tagLength)) {
throw lazyDOMException(
`${tagLength} is not a valid AES-GCM tag length`,
'OperationError');
}
iv = getArrayBufferOrView(iv, 'algorithm.iv');
validateMaxBufferLength(iv, 'algorithm.iv');
if (additionalData !== undefined) {
additionalData =
getArrayBufferOrView(additionalData, 'algorithm.additionalData');
validateMaxBufferLength(additionalData, 'algorithm.additionalData');
}
const tagByteLength = MathFloor(tagLength / 8);
let tag;
switch (mode) {
case kWebCryptoCipherDecrypt:
tag = data.slice(-tagByteLength);
data = data.slice(0, -tagByteLength);
break;
case kWebCryptoCipherEncrypt:
tag = tagByteLength;
break;
}
return jobPromise(new AESCipherJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
data,
getVariant('AES-GCM', key.algorithm.length),
iv,
tag,
additionalData));
}
function aesCipher(mode, key, data, algorithm) {
switch (algorithm.name) {
case 'AES-CTR': return asyncAesCtrCipher(mode, key, data, algorithm);
case 'AES-CBC': return asyncAesCbcCipher(mode, key, data, algorithm);
case 'AES-GCM': return asyncAesGcmCipher(mode, key, data, algorithm);
case 'AES-KW': return asyncAesKwCipher(mode, key, data);
}
}
async function aesGenerateKey(algorithm, extractable, keyUsages) {
const { name, length } = algorithm;
validateInteger(length, 'algorithm.length');
validateOneOf(length, 'algorithm.length', kAesKeyLengths);
const usageSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usageSet, 'encrypt', 'decrypt', 'wrapKey', 'unwrapKey')) {
throw lazyDOMException(
'Unsupported key usage for an AES key',
'SyntaxError');
}
return new Promise((resolve, reject) => {
generateKey('aes', { length }, (err, key) => {
if (err) {
return reject(lazyDOMException(
'The operation failed for an operation-specific reason ' +
`[${err.message}]`,
'OperationError'));
}
resolve(new InternalCryptoKey(
key,
{ name, length },
ArrayFrom(usageSet),
extractable));
});
});
}
async function aesImportKey(
algorithm,
format,
keyData,
extractable,
keyUsages) {
const { name } = algorithm;
const checkUsages = ['wrapKey', 'unwrapKey'];
if (name !== 'AES-KW')
ArrayPrototypePush(checkUsages, 'encrypt', 'decrypt');
const usagesSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usagesSet, ...checkUsages)) {
throw lazyDOMException(
'Unsupported key usage for an AES key',
'SyntaxError');
}
let keyObject;
let length;
switch (format) {
case 'node.keyObject': {
if (!isKeyObject(keyData))
throw new ERR_INVALID_ARG_TYPE('keyData', 'KeyObject', keyData);
if (keyData.type !== 'secret') {
throw lazyDOMException(
`Unable to import AES key with format ${format}`,
'NotSupportedError');
}
keyObject = keyData;
break;
}
case 'raw': {
validateKeyLength(keyData.byteLength * 8);
keyObject = createSecretKey(keyData);
break;
}
case 'jwk': {
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid JWK keyData', 'DataError');
if (keyData.kty !== 'oct')
throw lazyDOMException('Invalid key type', 'DataError');
if (usagesSet.size > 0 &&
keyData.use !== undefined &&
keyData.use !== 'enc') {
throw lazyDOMException('Invalid use type', 'DataError');
}
validateKeyOps(keyData.key_ops, usagesSet);
if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException('JWK is not extractable', 'DataError');
}
const handle = new KeyObjectHandle();
handle.initJwk(keyData);
({ length } = handle.keyDetail({ }));
validateKeyLength(length);
if (keyData.alg !== undefined) {
if (typeof keyData.alg !== 'string')
throw lazyDOMException('Invalid alg', 'DataError');
if (keyData.alg !== getAlgorithmName(algorithm.name, length))
throw lazyDOMException('Algorithm mismatch', 'DataError');
}
keyObject = new SecretKeyObject(handle);
break;
}
default:
throw lazyDOMException(
`Unable to import AES key with format ${format}`,
'NotSupportedError');
}
if (length === undefined) {
({ length } = keyObject[kHandle].keyDetail({ }));
validateKeyLength(length);
}
return new InternalCryptoKey(
keyObject,
{ name, length },
keyUsages,
extractable);
}
module.exports = {
aesCipher,
aesGenerateKey,
aesImportKey,
getAlgorithmName,
};

View File

@ -3,33 +3,38 @@
const {
certExportChallenge,
certExportPublicKey,
certVerifySpkac
certVerifySpkac,
} = internalBinding('crypto');
const {
validateBuffer
} = require('internal/validators');
const {
getArrayBufferView
getArrayBufferOrView,
} = require('internal/crypto/util');
function verifySpkac(spkac) {
validateBuffer(spkac, 'spkac');
return certVerifySpkac(spkac);
// The functions contained in this file cover the SPKAC format
// (also refered to as Netscape SPKI). A general description of
// the format can be found at https://en.wikipedia.org/wiki/SPKAC
function verifySpkac(spkac, encoding) {
return certVerifySpkac(
getArrayBufferOrView(spkac, 'spkac', encoding));
}
function exportPublicKey(spkac, encoding) {
return certExportPublicKey(
getArrayBufferView(spkac, 'spkac', encoding)
);
getArrayBufferOrView(spkac, 'spkac', encoding));
}
function exportChallenge(spkac, encoding) {
return certExportChallenge(
getArrayBufferView(spkac, 'spkac', encoding)
);
getArrayBufferOrView(spkac, 'spkac', encoding));
}
// The legacy implementation of this exposed the Certificate
// object and required that users create an instance before
// calling the member methods. This API pattern has been
// deprecated, however, as the method implementations do not
// rely on any object state.
// For backwards compatibility reasons, this cannot be converted into a
// ES6 Class.
function Certificate() {

View File

@ -4,40 +4,53 @@ const {
ObjectSetPrototypeOf,
} = primordials;
const {
RSA_PKCS1_OAEP_PADDING,
RSA_PKCS1_PADDING
} = internalBinding('constants').crypto;
const {
ERR_CRYPTO_INVALID_STATE,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE
} = require('internal/errors').codes;
const { validateEncoding, validateString } = require('internal/validators');
const {
preparePrivateKey,
preparePublicOrPrivateKey,
prepareSecretKey
} = require('internal/crypto/keys');
const {
getDefaultEncoding,
kHandle,
getArrayBufferView
} = require('internal/crypto/util');
const { isArrayBufferView } = require('internal/util/types');
const {
CipherBase,
privateDecrypt: _privateDecrypt,
privateEncrypt: _privateEncrypt,
publicDecrypt: _publicDecrypt,
publicEncrypt: _publicEncrypt
publicEncrypt: _publicEncrypt,
} = 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,
}
} = require('internal/errors');
const {
validateEncoding,
validateString,
} = require('internal/validators');
const {
preparePrivateKey,
preparePublicOrPrivateKey,
prepareSecretKey,
} = require('internal/crypto/keys');
const {
getDefaultEncoding,
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');
@ -52,14 +65,13 @@ function rsaFunctionFor(method, defaultPadding, keyType) {
preparePrivateKey(options) :
preparePublicOrPrivateKey(options);
const padding = options.padding || defaultPadding;
const { oaepHash, oaepLabel } = options;
if (oaepHash !== undefined && typeof oaepHash !== 'string')
throw new ERR_INVALID_ARG_TYPE('options.oaepHash', 'string', oaepHash);
if (oaepLabel !== undefined && !isArrayBufferView(oaepLabel)) {
throw new ERR_INVALID_ARG_TYPE('options.oaepLabel',
['Buffer', 'TypedArray', 'DataView'],
oaepLabel);
}
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);
};
@ -95,7 +107,6 @@ function getUIntOption(options, key) {
function createCipherBase(cipher, credential, options, decipher, iv) {
const authTagLength = getUIntOption(options, 'authTagLength');
this[kHandle] = new CipherBase(decipher);
if (iv === undefined) {
this[kHandle].init(cipher, credential, authTagLength);
@ -109,18 +120,24 @@ function createCipherBase(cipher, credential, options, decipher, iv) {
function createCipher(cipher, password, options, decipher) {
validateString(cipher, 'cipher');
password = getArrayBufferView(password, 'password');
password = getArrayBufferOrView(password, 'password');
createCipherBase.call(this, cipher, password, options, decipher);
}
function createCipherWithIV(cipher, key, options, decipher, iv) {
validateString(cipher, 'cipher');
key = prepareSecretKey(key);
iv = iv === null ? null : getArrayBufferView(iv, 'iv');
const encoding = getStringOption(options, 'encoding');
key = prepareSecretKey(key, encoding);
iv = iv === null ? null : getArrayBufferOrView(iv, 'iv');
createCipherBase.call(this, cipher, key, options, decipher, 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 Cipher(cipher, password, options) {
if (!(this instanceof Cipher))
return new Cipher(cipher, password, options);
@ -196,30 +213,27 @@ Cipher.prototype.getAuthTag = function getAuthTag() {
};
function setAuthTag(tagbuf) {
if (!isArrayBufferView(tagbuf)) {
throw new ERR_INVALID_ARG_TYPE('buffer',
['Buffer', 'TypedArray', 'DataView'],
tagbuf);
}
function setAuthTag(tagbuf, encoding) {
tagbuf = getArrayBufferOrView(tagbuf, 'buffer', encoding);
if (!this[kHandle].setAuthTag(tagbuf))
throw new ERR_CRYPTO_INVALID_STATE('setAuthTag');
return this;
}
Cipher.prototype.setAAD = function setAAD(aadbuf, options) {
if (!isArrayBufferView(aadbuf)) {
throw new ERR_INVALID_ARG_TYPE('buffer',
['Buffer', 'TypedArray', 'DataView'],
aadbuf);
}
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);
@ -245,6 +259,11 @@ ObjectSetPrototypeOf(Cipheriv.prototype, LazyTransform.prototype);
ObjectSetPrototypeOf(Cipheriv, LazyTransform);
addCipherPrototypeFunctions(Cipheriv);
// The Decipher class is part of the legacy Node.js crypto API. It exposes
// a stream-based encryption/decryption model. For backwards compatibility
// the Decipher class is defined using the legacy function syntax rather than
// ES6 classes.
function Decipher(cipher, password, options) {
if (!(this instanceof Decipher))
return new Decipher(cipher, password, options);
@ -256,6 +275,10 @@ ObjectSetPrototypeOf(Decipher.prototype, LazyTransform.prototype);
ObjectSetPrototypeOf(Decipher, LazyTransform);
addCipherPrototypeFunctions(Decipher);
// 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))

View File

@ -1,42 +1,84 @@
'use strict';
const {
FunctionPrototypeCall,
MathFloor,
ObjectDefineProperty,
Set
Promise,
SafeSet,
} = primordials;
const { Buffer } = require('buffer');
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,
} = require('internal/errors').codes;
const {
validateString,
validateInt32,
} = require('internal/validators');
const { isArrayBufferView } = require('internal/util/types');
const { KeyObject } = require('internal/crypto/keys');
const {
getDefaultEncoding,
kHandle,
toBuf
} = require('internal/crypto/util');
const {
DHBitsJob,
DHKeyExportJob,
DiffieHellman: _DiffieHellman,
DiffieHellmanGroup: _DiffieHellmanGroup,
ECDH: _ECDH,
ECDHBitsJob,
ECDHConvertKey: _ECDHConvertKey,
statelessDH
statelessDH,
kCryptoJobAsync,
} = internalBinding('crypto');
const {
POINT_CONVERSION_COMPRESSED,
POINT_CONVERSION_HYBRID,
POINT_CONVERSION_UNCOMPRESSED
} = internalBinding('constants').crypto;
codes: {
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,
ERR_INVALID_CALLBACK,
}
} = require('internal/errors');
const {
validateInt32,
validateObject,
validateString,
validateUint32,
} = require('internal/validators');
const {
isArrayBufferView,
isAnyArrayBuffer,
} = require('internal/util/types');
const {
KeyObject,
InternalCryptoKey,
createPrivateKey,
createPublicKey,
isCryptoKey,
isKeyObject,
} = require('internal/crypto/keys');
const {
generateKeyPair,
} = require('internal/crypto/keygen');
const {
getArrayBufferOrView,
getDefaultEncoding,
getUsagesUnion,
hasAnyNotIn,
jobPromise,
lazyDOMException,
toBuf,
kHandle,
kKeyObject,
kNamedCurveAliases,
} = require('internal/crypto/util');
const {
crypto: {
POINT_CONVERSION_COMPRESSED,
POINT_CONVERSION_HYBRID,
POINT_CONVERSION_UNCOMPRESSED,
}
} = internalBinding('constants');
const DH_GENERATOR = 2;
@ -46,10 +88,11 @@ function DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding) {
if (typeof sizeOrKey !== 'number' &&
typeof sizeOrKey !== 'string' &&
!isArrayBufferView(sizeOrKey)) {
!isArrayBufferView(sizeOrKey) &&
!isAnyArrayBuffer(sizeOrKey)) {
throw new ERR_INVALID_ARG_TYPE(
'sizeOrKey',
['number', 'string', 'Buffer', 'TypedArray', 'DataView'],
['number', 'string', 'ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],
sizeOrKey
);
}
@ -122,7 +165,8 @@ function dhComputeSecret(key, inEnc, outEnc) {
const encoding = getDefaultEncoding();
inEnc = inEnc || encoding;
outEnc = outEnc || encoding;
const ret = this[kHandle].computeSecret(toBuf(key, inEnc));
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);
@ -175,14 +219,16 @@ function dhGetPrivateKey(encoding) {
DiffieHellman.prototype.setPublicKey = function setPublicKey(key, encoding) {
encoding = encoding || getDefaultEncoding();
this[kHandle].setPublicKey(toBuf(key, encoding));
key = getArrayBufferOrView(key, 'key', encoding);
this[kHandle].setPublicKey(key);
return this;
};
DiffieHellman.prototype.setPrivateKey = function setPrivateKey(key, encoding) {
encoding = encoding || getDefaultEncoding();
this[kHandle].setPrivateKey(toBuf(key, encoding));
key = getArrayBufferOrView(key, 'key', encoding);
this[kHandle].setPrivateKey(key);
return this;
};
@ -214,21 +260,12 @@ ECDH.prototype.getPublicKey = function getPublicKey(encoding, format) {
};
ECDH.convertKey = function convertKey(key, curve, inEnc, outEnc, format) {
if (typeof key !== 'string' && !isArrayBufferView(key)) {
throw new ERR_INVALID_ARG_TYPE(
'key',
['string', 'Buffer', 'TypedArray', 'DataView'],
key
);
}
validateString(curve, 'curve');
const encoding = getDefaultEncoding();
inEnc = inEnc || encoding;
const encoding = inEnc || getDefaultEncoding();
key = getArrayBufferOrView(key, 'key', encoding);
outEnc = outEnc || encoding;
const f = getFormat(format);
const convertedKey = _ECDHConvertKey(toBuf(key, inEnc), curve, f);
const convertedKey = _ECDHConvertKey(key, curve, f);
return encode(convertedKey, outEnc);
};
@ -250,7 +287,7 @@ function getFormat(format) {
return POINT_CONVERSION_UNCOMPRESSED;
}
const dhEnabledKeyTypes = new Set(['dh', 'ec', 'x448', 'x25519']);
const dhEnabledKeyTypes = new SafeSet(['dh', 'ec', 'x448', 'x25519']);
function diffieHellman(options) {
if (typeof options !== 'object')
@ -281,9 +318,294 @@ function diffieHellman(options) {
return statelessDH(privateKey[kHandle], publicKey[kHandle]);
}
// The deriveBitsECDH function is part of the Web Crypto API and serves both
// deriveKeys and deriveBits functions.
function deriveBitsECDH(name, publicKey, privateKey, callback) {
validateString(name, 'name');
validateObject(publicKey, 'publicKey');
validateObject(privateKey, 'privateKey');
if (typeof callback !== 'function')
throw new ERR_INVALID_CALLBACK(callback);
const job = new ECDHBitsJob(kCryptoJobAsync, name, publicKey, privateKey);
job.ondone = (error, bits) => {
if (error) return FunctionPrototypeCall(callback, job, error);
FunctionPrototypeCall(callback, job, null, bits);
};
job.run();
}
// The deriveBitsDH function is part of the Web Crypto API and serves both
// deriveKeys and deriveBits functions.
function deriveBitsDH(publicKey, privateKey, callback) {
validateObject(publicKey, 'publicKey');
validateObject(privateKey, 'privateKey');
if (typeof callback !== 'function')
throw new ERR_INVALID_CALLBACK(callback);
const job = new DHBitsJob(kCryptoJobAsync, publicKey, privateKey);
job.ondone = (error, bits) => {
if (error) return FunctionPrototypeCall(callback, job, error);
FunctionPrototypeCall(callback, job, null, bits);
};
job.run();
}
function verifyAcceptableDhKeyUse(name, type, usages) {
let checkSet;
switch (type) {
case 'private':
checkSet = ['deriveBits', 'deriveKey'];
break;
case 'public':
checkSet = [];
break;
}
if (hasAnyNotIn(usages, ...checkSet)) {
throw lazyDOMException(
`Unsupported key usage for an ${name} key`,
'SyntaxError');
}
}
async function dhGenerateKey(
algorithm,
extractable,
keyUsages) {
const usageSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usageSet, 'deriveKey', 'deriveBits')) {
throw lazyDOMException(
'Unsupported key usage for a DH key',
'SyntaxError');
}
const {
name,
primeLength,
generator,
group
} = algorithm;
let { prime } = algorithm;
if (prime !== undefined)
prime = getArrayBufferOrView(prime);
return new Promise((resolve, reject) => {
generateKeyPair('dh', {
prime,
primeLength,
generator,
group,
}, (err, pubKey, privKey) => {
if (err) {
return reject(lazyDOMException(
'The operation failed for an operation-specific reason',
'OperationError'));
}
const algorithm = { name, prime, primeLength, generator, group };
const publicKey = new InternalCryptoKey(pubKey, algorithm, [], true);
const privateKey =
new InternalCryptoKey(
privKey,
algorithm,
getUsagesUnion(usageSet, 'deriveBits', 'deriveKey'),
extractable);
resolve({ publicKey, privateKey });
});
});
}
async function asyncDeriveBitsECDH(algorithm, baseKey, length) {
const { 'public': key } = algorithm;
// Null means that we're not asking for a specific number of bits, just
// give us everything that is generated.
if (length !== null)
validateUint32(length, 'length');
if (!isCryptoKey(key))
throw new ERR_INVALID_ARG_TYPE('algorithm.public', 'CryptoKey', key);
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') {
throw lazyDOMException('Keys must be ECDH 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.namedCurve !== baseKey.algorithm.namedCurve)
throw lazyDOMException('Named curve mismatch', 'InvalidAccessError');
const bits = await new Promise((resolve, reject) => {
deriveBitsECDH(
kNamedCurveAliases[baseKey.algorithm.namedCurve],
key[kKeyObject][kHandle],
baseKey[kKeyObject][kHandle], (err, bits) => {
if (err) return reject(err);
resolve(bits);
});
});
// 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, it will be truncated
// down to the nearest multiple of 8.
length = MathFloor(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 : bits.slice(0, length);
}
async function asyncDeriveBitsDH(algorithm, baseKey, length) {
const { 'public': key } = algorithm;
// Null has a specific meaning for DH
if (length !== null)
validateUint32(length, 'length');
if (!isCryptoKey(key))
throw new ERR_INVALID_ARG_TYPE('algorithm.public', 'CryptoKey', key);
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 !== 'NODE-DH')
throw lazyDOMException('Keys must be DH keys', 'InvalidAccessError');
if (key.algorithm.name !== baseKey.algorithm.name) {
throw lazyDOMException(
'The public and private keys must be of the same type',
'InvalidAccessError');
}
const bits = await new Promise((resolve, reject) => {
deriveBitsDH(
key[kKeyObject][kHandle],
baseKey[kKeyObject][kHandle], (err, bits) => {
if (err) return reject(err);
resolve(bits);
});
});
// 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, it will be truncated
// down to the nearest multiple of 8.
length = MathFloor(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 : bits.slice(0, length);
}
function dhExportKey(key, format) {
return jobPromise(new DHKeyExportJob(
kCryptoJobAsync,
format,
key[kKeyObject][kHandle]));
}
async function dhImportKey(
format,
keyData,
algorithm,
extractable,
keyUsages) {
const usagesSet = new SafeSet(keyUsages);
let keyObject;
switch (format) {
case 'node.keyObject': {
if (!isKeyObject(keyData))
throw new ERR_INVALID_ARG_TYPE('keyData', 'KeyObject', keyData);
if (keyData.type === 'secret')
throw lazyDOMException('Invalid key type', 'InvalidAccessException');
verifyAcceptableDhKeyUse(algorithm.name, keyData.type, usagesSet);
keyObject = keyData;
break;
}
case 'spki': {
verifyAcceptableDhKeyUse(algorithm.name, 'public', usagesSet);
keyObject = createPublicKey({
key: keyData,
format: 'der',
type: 'spki'
});
break;
}
case 'pkcs8': {
verifyAcceptableDhKeyUse(algorithm.name, 'private', usagesSet);
keyObject = createPrivateKey({
key: keyData,
format: 'der',
type: 'pkcs8'
});
break;
}
default:
throw lazyDOMException(
`Unable to import DH key with format ${format}`,
'NotSupportedError');
}
const {
prime,
primeLength,
generator,
group,
} = keyObject[kHandle].keyDetail({});
return new InternalCryptoKey(keyObject, {
name: algorithm.name,
prime,
primeLength,
generator,
group,
}, keyUsages, extractable);
}
module.exports = {
DiffieHellman,
DiffieHellmanGroup,
ECDH,
diffieHellman
diffieHellman,
deriveBitsECDH,
deriveBitsDH,
dhGenerateKey,
asyncDeriveBitsECDH,
asyncDeriveBitsDH,
dhExportKey,
dhImportKey,
};

265
lib/internal/crypto/dsa.js Normal file
View File

@ -0,0 +1,265 @@
'use strict';
const {
Promise,
SafeSet,
} = primordials;
const {
DSAKeyExportJob,
KeyObjectHandle,
SignJob,
kCryptoJobAsync,
kKeyTypePrivate,
kSignJobModeSign,
kSignJobModeVerify,
} = internalBinding('crypto');
const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_MISSING_OPTION,
}
} = require('internal/errors');
const {
validateUint32,
} = require('internal/validators');
const {
InternalCryptoKey,
PrivateKeyObject,
PublicKeyObject,
createPrivateKey,
createPublicKey,
isKeyObject,
} = require('internal/crypto/keys');
const {
generateKeyPair,
} = require('internal/crypto/keygen');
const {
getUsagesUnion,
hasAnyNotIn,
jobPromise,
lazyDOMException,
normalizeHashName,
validateKeyOps,
kKeyObject,
kHandle,
} = require('internal/crypto/util');
function verifyAcceptableDsaKeyUse(name, type, usages) {
let checkSet;
switch (type) {
case 'private':
checkSet = ['sign'];
break;
case 'public':
checkSet = ['verify'];
break;
}
if (hasAnyNotIn(usages, ...checkSet)) {
throw lazyDOMException(
`Unsupported key usage for an ${name} key`,
'SyntaxError');
}
}
async function dsaGenerateKey(
algorithm,
extractable,
keyUsages) {
const {
name,
modulusLength,
divisorLength,
hash
} = algorithm;
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
validateUint32(modulusLength, 'algorithm.modulusLength');
const usageSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usageSet, 'sign', 'verify')) {
throw lazyDOMException(
'Unsupported key usage for a DSA key',
'SyntaxError');
}
return new Promise((resolve, reject) => {
generateKeyPair('dsa', {
modulusLength,
divisorLength,
}, (err, pubKey, privKey) => {
if (err) {
return reject(lazyDOMException(
'The operation failed for an operation-specific reason',
'OperationError'));
}
const algorithm = {
name,
modulusLength,
divisorLength,
hash: { name: hash.name }
};
const publicKey =
new InternalCryptoKey(
pubKey,
algorithm,
getUsagesUnion(usageSet, 'verify'),
true);
const privateKey =
new InternalCryptoKey(
privKey,
algorithm,
getUsagesUnion(usageSet, 'sign'),
extractable);
resolve({ publicKey, privateKey });
});
});
}
function dsaExportKey(key, format) {
return jobPromise(new DSAKeyExportJob(
kCryptoJobAsync,
format,
key[kKeyObject][kHandle]));
}
async function dsaImportKey(
format,
keyData,
algorithm,
extractable,
keyUsages) {
const { hash } = algorithm;
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
const usagesSet = new SafeSet(keyUsages);
let keyObject;
switch (format) {
case 'node.keyObject': {
if (!isKeyObject(keyData))
throw new ERR_INVALID_ARG_TYPE('keyData', 'KeyObject', keyData);
if (keyData.type === 'secret')
throw lazyDOMException('Invalid key type', 'InvalidAccessException');
verifyAcceptableDsaKeyUse(algorithm.name, keyData.type, usagesSet);
keyObject = keyData;
break;
}
case 'spki': {
verifyAcceptableDsaKeyUse(algorithm.name, 'public', usagesSet);
keyObject = createPublicKey({
key: keyData,
format: 'der',
type: 'spki'
});
break;
}
case 'pkcs8': {
verifyAcceptableDsaKeyUse(algorithm.name, 'private', usagesSet);
keyObject = createPrivateKey({
key: keyData,
format: 'der',
type: 'pkcs8'
});
break;
}
case 'jwk': {
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid JWK keyData', 'DataError');
verifyAcceptableDsaKeyUse(
algorithm.name,
keyData.x !== undefined ? 'private' : 'public',
usagesSet);
if (keyData.kty !== 'DSA')
throw lazyDOMException('Invalid key type', 'DataError');
if (usagesSet.size > 0 &&
keyData.use !== undefined &&
keyData.use !== 'sig') {
throw lazyDOMException('Invalid use type', 'DataError');
}
validateKeyOps(keyData.key_ops, usagesSet);
if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException('JWK is not extractable', 'DataError');
}
if (keyData.alg !== undefined) {
if (typeof keyData.alg !== 'string')
throw lazyDOMException('Invalid alg', 'DataError');
const hash =
normalizeHashName(keyData.alg, normalizeHashName.kContextWebCrypto);
if (hash !== algorithm.hash.name)
throw lazyDOMException('Hash mismatch', 'DataError');
}
const handle = new KeyObjectHandle();
const type = handle.initJwk(keyData);
if (type === undefined)
throw lazyDOMException('Invalid JWK keyData', 'DataError');
keyObject = type === kKeyTypePrivate ?
new PrivateKeyObject(handle) :
new PublicKeyObject(handle);
break;
}
default:
throw lazyDOMException(
`Unable to import DSA key with format ${format}`,
'NotSupportedError');
}
const {
modulusLength,
divisorLength,
} = keyObject[kHandle].keyDetail({});
return new InternalCryptoKey(keyObject, {
name: algorithm.name,
modulusLength,
divisorLength,
hash: algorithm.hash
}, keyUsages, extractable);
}
function dsaSignVerify(key, data, algorithm, signature) {
const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
const type = mode === kSignJobModeSign ? 'private' : 'public';
if (key.type !== type)
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
return jobPromise(new SignJob(
kCryptoJobAsync,
signature === undefined ? kSignJobModeSign : kSignJobModeVerify,
key[kKeyObject][kHandle],
data,
normalizeHashName(key.algorithm.hash.name),
undefined, // Salt-length is not used in DSA
undefined, // Padding is not used in DSA
signature));
}
module.exports = {
dsaExportKey,
dsaGenerateKey,
dsaImportKey,
dsaSignVerify,
};

304
lib/internal/crypto/ec.js Normal file
View File

@ -0,0 +1,304 @@
'use strict';
const {
ObjectKeys,
Promise,
SafeSet,
} = primordials;
const {
ECKeyExportJob,
KeyObjectHandle,
SignJob,
kCryptoJobAsync,
kKeyTypePrivate,
kSignJobModeSign,
kSignJobModeVerify,
} = internalBinding('crypto');
const {
validateOneOf,
validateString,
} = require('internal/validators');
const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_MISSING_OPTION,
}
} = require('internal/errors');
const {
getArrayBufferOrView,
getUsagesUnion,
hasAnyNotIn,
jobPromise,
lazyDOMException,
normalizeHashName,
validateKeyOps,
kHandle,
kKeyObject,
kNamedCurveAliases,
} = require('internal/crypto/util');
const {
generateKeyPair,
} = require('internal/crypto/keygen');
const {
InternalCryptoKey,
PrivateKeyObject,
PublicKeyObject,
createPrivateKey,
createPublicKey,
isKeyObject,
} = require('internal/crypto/keys');
function verifyAcceptableEcKeyUse(name, type, usages) {
let checkSet;
switch (name) {
case 'ECDH':
checkSet = ['deriveKey', 'deriveBits'];
break;
case 'ECDSA':
switch (type) {
case 'private':
checkSet = ['sign'];
break;
case 'public':
checkSet = ['verify'];
break;
}
}
if (hasAnyNotIn(usages, ...checkSet)) {
throw lazyDOMException(
`Unsupported key usage for a ${name} key`,
'SyntaxError');
}
}
function createECPublicKeyRaw(namedCurve, keyData) {
const handle = new KeyObjectHandle();
keyData = getArrayBufferOrView(keyData, 'keyData');
if (handle.initECRaw(kNamedCurveAliases[namedCurve], keyData))
return new PublicKeyObject(handle);
}
async function ecGenerateKey(algorithm, extractable, keyUsages) {
const { name, namedCurve } = algorithm;
validateString(namedCurve, 'algorithm.namedCurve');
validateOneOf(
namedCurve,
'algorithm.namedCurve',
ObjectKeys(kNamedCurveAliases));
const usageSet = new SafeSet(keyUsages);
switch (name) {
case 'ECDSA':
if (hasAnyNotIn(usageSet, 'sign', 'verify')) {
throw lazyDOMException(
'Unsupported key usage for an ECDSA key',
'SyntaxError');
}
break;
case 'ECDH':
if (hasAnyNotIn(usageSet, 'deriveKey', 'deriveBits')) {
throw lazyDOMException(
'Unsupported key usage for an ECDH key',
'SyntaxError');
}
}
return new Promise((resolve, reject) => {
generateKeyPair('ec', { namedCurve }, (err, pubKey, privKey) => {
if (err) {
return reject(lazyDOMException(
'The operation failed for an operation-specific reason',
'OperationError'));
}
const algorithm = { name, namedCurve };
let publicUsages;
let privateUsages;
switch (name) {
case 'ECDSA':
publicUsages = getUsagesUnion(usageSet, 'verify');
privateUsages = getUsagesUnion(usageSet, 'sign');
break;
case 'ECDH':
publicUsages = [];
privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits');
break;
}
const publicKey =
new InternalCryptoKey(
pubKey,
algorithm,
publicUsages,
true);
const privateKey =
new InternalCryptoKey(
privKey,
algorithm,
privateUsages,
extractable);
resolve({ publicKey, privateKey });
});
});
}
function ecExportKey(key, format) {
return jobPromise(new ECKeyExportJob(
kCryptoJobAsync,
format,
key[kKeyObject][kHandle]));
}
async function ecImportKey(
format,
keyData,
algorithm,
extractable,
keyUsages) {
const { name, namedCurve } = algorithm;
validateString(namedCurve, 'algorithm.namedCurve');
validateOneOf(
namedCurve,
'algorithm.namedCurve',
ObjectKeys(kNamedCurveAliases));
let keyObject;
const usagesSet = new SafeSet(keyUsages);
switch (format) {
case 'node.keyObject': {
if (!isKeyObject(keyData))
throw new ERR_INVALID_ARG_TYPE('keyData', 'KeyObject', keyData);
if (keyData.type === 'secret')
throw lazyDOMException('Invalid key type', 'InvalidAccessException');
verifyAcceptableEcKeyUse(name, keyData.type, usagesSet);
keyObject = keyData;
break;
}
case 'spki': {
verifyAcceptableEcKeyUse(name, 'public', usagesSet);
keyObject = createPublicKey({
key: keyData,
format: 'der',
type: 'spki'
});
break;
}
case 'pkcs8': {
verifyAcceptableEcKeyUse(name, 'private', usagesSet);
keyObject = createPrivateKey({
key: keyData,
format: 'der',
type: 'pkcs8'
});
break;
}
case 'jwk': {
let curve;
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid JWK keyData', 'DataError');
if (keyData.kty !== 'EC')
throw lazyDOMException('Invalid key type', 'DataError');
if (keyData.d !== undefined) {
verifyAcceptableEcKeyUse(name, 'private', usagesSet);
} else {
verifyAcceptableEcKeyUse(name, 'public', usagesSet);
}
if (usagesSet.size > 0 && keyData.use !== undefined) {
if (algorithm.name === 'ECDSA' && keyData.use !== 'sig')
throw lazyDOMException('Invalid use type', 'DataError');
if (algorithm.name === 'ECDH' && keyData.use !== 'enc')
throw lazyDOMException('Invalid use type', 'DataError');
}
validateKeyOps(keyData.key_ops, usagesSet);
if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException('JWK is not extractable', 'DataError');
}
if (keyData.alg !== undefined) {
if (typeof keyData.alg !== 'string')
throw lazyDOMException('Invalid alg', 'DataError');
switch (keyData.alg) {
case 'ES256': curve = 'P-256'; break;
case 'ES384': curve = 'P-384'; break;
case 'ES512': curve = 'P-521'; break;
}
if (curve !== namedCurve)
throw lazyDOMException('Named curve mismatch', 'DataError');
}
const handle = new KeyObjectHandle();
const type = handle.initJwk(keyData, namedCurve);
if (type === undefined)
throw lazyDOMException('Invalid JWK keyData', 'DataError');
keyObject = type === kKeyTypePrivate ?
new PrivateKeyObject(handle) :
new PublicKeyObject(handle);
break;
}
case 'raw': {
verifyAcceptableEcKeyUse(name, 'public', usagesSet);
keyObject = createECPublicKeyRaw(namedCurve, keyData);
if (keyObject === undefined)
throw lazyDOMException('Unable to import EC key', 'OperationError');
break;
}
}
const {
namedCurve: checkNamedCurve
} = keyObject[kHandle].keyDetail({});
if (kNamedCurveAliases[namedCurve] !== checkNamedCurve)
throw lazyDOMException('Named curve mismatch', 'DataError');
return new InternalCryptoKey(
keyObject,
{ name, namedCurve },
keyUsages,
extractable);
}
function ecdsaSignVerify(key, data, { hash }, signature) {
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
const type = mode === kSignJobModeSign ? 'private' : 'public';
if (key.type !== type)
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
return jobPromise(new SignJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
data,
normalizeHashName(hash.name),
undefined, // Salt length, not used with ECDSA
undefined, // PSS Padding, not used with ECDSA
signature));
}
module.exports = {
ecExportKey,
ecImportKey,
ecGenerateKey,
ecdsaSignVerify,
};

View File

@ -7,30 +7,50 @@ const {
const {
Hash: _Hash,
Hmac: _Hmac
HashJob,
Hmac: _Hmac,
kCryptoJobAsync,
} = internalBinding('crypto');
const {
getArrayBufferOrView,
getDefaultEncoding,
getStringOption,
jobPromise,
normalizeAlgorithm,
normalizeHashName,
validateMaxBufferLength,
kHandle,
toBuf
} = require('internal/crypto/util');
const {
prepareSecretKey
prepareSecretKey,
} = require('internal/crypto/keys');
const { Buffer } = require('buffer');
const {
Buffer,
} = require('buffer');
const {
ERR_CRYPTO_HASH_FINALIZED,
ERR_CRYPTO_HASH_UPDATE_FAILED,
ERR_INVALID_ARG_TYPE
} = require('internal/errors').codes;
const { validateEncoding, validateString, validateUint32 } =
require('internal/validators');
const { isArrayBufferView } = require('internal/util/types');
codes: {
ERR_CRYPTO_HASH_FINALIZED,
ERR_CRYPTO_HASH_UPDATE_FAILED,
ERR_INVALID_ARG_TYPE,
}
} = require('internal/errors');
const {
validateEncoding,
validateString,
validateUint32,
} = require('internal/validators');
const {
isArrayBufferView,
} = require('internal/util/types');
const LazyTransform = require('internal/streams/lazy_transform');
const kState = Symbol('kState');
const kFinalized = Symbol('kFinalized');
@ -103,14 +123,14 @@ Hash.prototype.digest = function digest(outputEncoding) {
return ret;
};
function Hmac(hmac, key, options) {
if (!(this instanceof Hmac))
return new Hmac(hmac, key, options);
validateString(hmac, 'hmac');
key = prepareSecretKey(key);
const encoding = getStringOption(options, 'encoding');
key = prepareSecretKey(key, encoding);
this[kHandle] = new _Hmac();
this[kHandle].init(hmac, toBuf(key));
this[kHandle].init(hmac, key);
this[kState] = {
[kFinalized]: false
};
@ -140,7 +160,25 @@ Hmac.prototype.digest = function digest(outputEncoding) {
Hmac.prototype._flush = Hash.prototype._flush;
Hmac.prototype._transform = Hash.prototype._transform;
// Implementation for WebCrypto subtle.digest()
async function asyncDigest(algorithm, data) {
algorithm = normalizeAlgorithm(algorithm);
data = getArrayBufferOrView(data, 'data');
validateMaxBufferLength(data, 'data');
if (algorithm.length !== undefined)
validateUint32(algorithm.length, 'algorithm.length');
return jobPromise(new HashJob(
kCryptoJobAsync,
normalizeHashName(algorithm.name),
data,
algorithm.length));
}
module.exports = {
Hash,
Hmac
Hmac,
asyncDigest,
};

View File

@ -0,0 +1,90 @@
'use strict';
const {
ObjectKeys,
StringPrototypeToLowerCase,
} = primordials;
const kHashContextNode = 1;
const kHashContextWebCrypto = 2;
const kHashContextJwkRsa = 3;
const kHashContextJwkRsaPss = 4;
const kHashContextJwkRsaOaep = 5;
const kHashContextJwkHmac = 6;
const kHashContextJwkDsa = 7;
// WebCrypto and JWK use a bunch of different names for the
// standard set of SHA-* digest algorithms... which is ... fun.
// Here we provide a utility for mapping between them in order
// make it easier in the code.
const kHashNames = {
sha1: {
[kHashContextNode]: 'sha1',
[kHashContextWebCrypto]: 'SHA-1',
[kHashContextJwkRsa]: 'RS1',
[kHashContextJwkRsaPss]: 'PS1',
[kHashContextJwkRsaOaep]: 'RSA-OAEP',
[kHashContextJwkHmac]: 'HS1',
[kHashContextJwkDsa]: 'NODE-DSA-SHA-1',
},
sha256: {
[kHashContextNode]: 'sha256',
[kHashContextWebCrypto]: 'SHA-256',
[kHashContextJwkRsa]: 'RS256',
[kHashContextJwkRsaPss]: 'PS256',
[kHashContextJwkRsaOaep]: 'RSA-OAEP-256',
[kHashContextJwkHmac]: 'HS256',
[kHashContextJwkDsa]: 'NODE-DSA-SHA-256',
},
sha384: {
[kHashContextNode]: 'sha384',
[kHashContextWebCrypto]: 'SHA-384',
[kHashContextJwkRsa]: 'RS384',
[kHashContextJwkRsaPss]: 'PS384',
[kHashContextJwkRsaOaep]: 'RSA-OAEP-384',
[kHashContextJwkHmac]: 'HS384',
[kHashContextJwkDsa]: 'NODE-DSA-SHA-384',
},
sha512: {
[kHashContextNode]: 'sha512',
[kHashContextWebCrypto]: 'SHA-512',
[kHashContextJwkRsa]: 'RS512',
[kHashContextJwkRsaPss]: 'PS512',
[kHashContextJwkRsaOaep]: 'RSA-OAEP-512',
[kHashContextJwkHmac]: 'HS512',
[kHashContextJwkDsa]: 'NODE-DSA-SHA-512',
}
};
{
// Index the aliases
const keys = ObjectKeys(kHashNames);
for (let n = 0; n < keys.length; n++) {
const contexts = ObjectKeys(kHashNames[keys[n]]);
for (let i = 0; i < contexts.length; i++) {
const alias =
StringPrototypeToLowerCase(kHashNames[keys[n]][contexts[i]]);
if (kHashNames[alias] === undefined)
kHashNames[alias] = kHashNames[keys[n]];
}
}
}
function normalizeHashName(name, context = kHashContextNode) {
if (typeof name !== 'string')
return name;
name = StringPrototypeToLowerCase(name);
const alias = kHashNames[name] && kHashNames[name][context];
return alias || name;
}
normalizeHashName.kContextNode = kHashContextNode;
normalizeHashName.kContextWebCrypto = kHashContextWebCrypto;
normalizeHashName.kContextJwkRsa = kHashContextJwkRsa;
normalizeHashName.kContextJwkRsaPss = kHashContextJwkRsaPss;
normalizeHashName.kContextJwkRsaOaep = kHashContextJwkRsaOaep;
normalizeHashName.kContextJwkHmac = kHashContextJwkHmac;
normalizeHashName.kContextJwkDsa = kHashContextJwkDsa;
module.exports = normalizeHashName;

183
lib/internal/crypto/hkdf.js Normal file
View File

@ -0,0 +1,183 @@
'use strict';
const {
FunctionPrototypeCall,
Promise,
Uint8Array,
} = primordials;
const {
HKDFJob,
kCryptoJobAsync,
kCryptoJobSync,
} = internalBinding('crypto');
const {
validateInteger,
validateString,
validateUint32,
} = require('internal/validators');
const { kMaxLength } = require('buffer');
const {
getArrayBufferOrView,
lazyDOMException,
normalizeHashName,
toBuf,
validateByteSource,
kKeyObject,
} = require('internal/crypto/util');
const {
createSecretKey,
isKeyObject,
} = require('internal/crypto/keys');
const {
isAnyArrayBuffer,
isArrayBufferView,
} = require('internal/util/types');
const {
codes: {
ERR_INVALID_CALLBACK,
ERR_INVALID_ARG_TYPE,
ERR_OUT_OF_RANGE,
ERR_MISSING_OPTION,
},
hideStackFrames,
} = require('internal/errors');
const validateParameters = hideStackFrames((hash, key, salt, info, length) => {
key = prepareKey(key);
salt = toBuf(salt);
info = toBuf(info);
validateString(hash, 'digest');
validateByteSource(salt, 'salt');
validateByteSource(info, 'info');
validateInteger(length, 'length', 0, kMaxLength);
if (info.byteLength > 1024) {
throw ERR_OUT_OF_RANGE(
'info',
'must not contain more than 1024 bytes',
info.byteLength);
}
return {
hash,
key,
salt,
info,
length,
};
});
function prepareKey(key) {
if (isKeyObject(key))
return key;
// TODO(@jasnell): createSecretKey should allow using an ArrayBuffer
if (isAnyArrayBuffer(key))
return createSecretKey(new Uint8Array(key));
key = toBuf(key);
if (!isArrayBufferView(key)) {
throw new ERR_INVALID_ARG_TYPE(
'key',
[
'string',
'SecretKeyObject',
'ArrayBuffer',
'TypedArray',
'DataView',
'Buffer'
],
key);
}
return createSecretKey(key);
}
function hkdf(hash, key, salt, info, length, callback) {
({
hash,
key,
salt,
info,
length,
} = validateParameters(hash, key, salt, info, length));
if (typeof callback !== 'function')
throw new ERR_INVALID_CALLBACK(callback);
const job = new HKDFJob(kCryptoJobAsync, hash, key, salt, info, length);
job.ondone = (error, bits) => {
if (error) return FunctionPrototypeCall(callback, job, error);
FunctionPrototypeCall(callback, job, null, bits);
};
job.run();
}
function hkdfSync(hash, key, salt, info, length) {
({
hash,
key,
salt,
info,
length,
} = validateParameters(hash, key, salt, info, length));
const job = new HKDFJob(kCryptoJobSync, hash, key, salt, info, length);
const [err, bits] = job.run();
if (err !== undefined)
throw err;
return bits;
}
async function hkdfDeriveBits(algorithm, baseKey, length) {
validateUint32(length, 'length');
const { hash } = algorithm;
const salt = getArrayBufferOrView(algorithm.salt, 'algorithm.salt');
const info = getArrayBufferOrView(algorithm.info, 'algorithm.info');
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
let byteLength = 512 / 8;
if (length !== undefined) {
if (length === 0)
throw lazyDOMException('length cannot be zero', 'OperationError');
if (length % 8) {
throw lazyDOMException(
'length must be a multiple of 8',
'OperationError');
}
byteLength = length / 8;
}
return new Promise((resolve, reject) => {
hkdf(
normalizeHashName(hash.name),
baseKey[kKeyObject],
salt,
info,
byteLength,
(err, bits) => {
if (err) return reject(err);
resolve(bits);
});
});
}
module.exports = {
hkdf,
hkdfSync,
hkdfDeriveBits,
};

View File

@ -1,43 +1,59 @@
'use strict';
const {
FunctionPrototypeCall,
ObjectDefineProperty,
} = primordials;
const { AsyncWrap, Providers } = internalBinding('async_wrap');
const {
generateKeyPairRSA,
generateKeyPairRSAPSS,
generateKeyPairDSA,
generateKeyPairEC,
generateKeyPairNid,
generateKeyPairDH,
DhKeyPairGenJob,
DsaKeyPairGenJob,
EcKeyPairGenJob,
NidKeyPairGenJob,
RsaKeyPairGenJob,
SecretKeyGenJob,
kCryptoJobAsync,
kCryptoJobSync,
kKeyVariantRSA_PSS,
kKeyVariantRSA_SSA_PKCS1_V1_5,
EVP_PKEY_ED25519,
EVP_PKEY_ED448,
EVP_PKEY_X25519,
EVP_PKEY_X448,
OPENSSL_EC_NAMED_CURVE,
OPENSSL_EC_EXPLICIT_CURVE
OPENSSL_EC_EXPLICIT_CURVE,
} = internalBinding('crypto');
const {
PublicKeyObject,
PrivateKeyObject,
SecretKeyObject,
parsePublicKeyEncoding,
parsePrivateKeyEncoding,
PublicKeyObject,
PrivateKeyObject
} = require('internal/crypto/keys');
const {
kAesKeyLengths,
} = require('internal/crypto/util');
const { customPromisifyArgs } = require('internal/util');
const {
isUint32,
validateString,
validateInteger,
validateObject,
validateOneOf,
} = require('internal/validators');
const {
ERR_INCOMPATIBLE_OPTION_PAIR,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_CALLBACK,
ERR_MISSING_OPTION
} = require('internal/errors').codes;
codes: {
ERR_INCOMPATIBLE_OPTION_PAIR,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_CALLBACK,
ERR_MISSING_OPTION,
}
} = require('internal/errors');
const { isArrayBufferView } = require('internal/util/types');
@ -52,22 +68,21 @@ function generateKeyPair(type, options, callback) {
callback = options;
options = undefined;
}
const impl = check(type, options);
if (typeof callback !== 'function')
throw new ERR_INVALID_CALLBACK(callback);
const wrap = new AsyncWrap(Providers.KEYPAIRGENREQUEST);
wrap.ondone = (ex, pubkey, privkey) => {
if (ex) return callback.call(wrap, ex);
const job = check(kCryptoJobAsync, type, options);
job.ondone = (error, result) => {
if (error) return FunctionPrototypeCall(callback, job, error);
// If no encoding was chosen, return key objects instead.
let [pubkey, privkey] = result;
pubkey = wrapKey(pubkey, PublicKeyObject);
privkey = wrapKey(privkey, PrivateKeyObject);
callback.call(wrap, null, pubkey, privkey);
FunctionPrototypeCall(callback, job, null, pubkey, privkey);
};
handleError(impl(wrap));
job.run();
}
ObjectDefineProperty(generateKeyPair, customPromisifyArgs, {
@ -76,15 +91,14 @@ ObjectDefineProperty(generateKeyPair, customPromisifyArgs, {
});
function generateKeyPairSync(type, options) {
const impl = check(type, options);
return handleError(impl());
return handleError(check(kCryptoJobSync, type, options).run());
}
function handleError(ret) {
if (ret === undefined)
return; // async
const [err, publicKey, privateKey] = ret;
const [err, [publicKey, privateKey]] = ret;
if (err !== undefined)
throw err;
@ -95,7 +109,7 @@ function handleError(ret) {
};
}
function parseKeyEncoding(keyType, options) {
function parseKeyEncoding(keyType, options = {}) {
const { publicKeyEncoding, privateKeyEncoding } = options;
let publicFormat, publicType;
@ -128,194 +142,238 @@ function parseKeyEncoding(keyType, options) {
privateKeyEncoding);
}
return {
cipher, passphrase, publicType, publicFormat, privateType, privateFormat
};
return [
publicFormat,
publicType,
privateFormat,
privateType,
cipher,
passphrase
];
}
function check(type, options, callback) {
function check(mode, type, options) {
validateString(type, 'type');
// These will be set after parsing the type and type-specific options to make
// the order a bit more intuitive.
let cipher, passphrase, publicType, publicFormat, privateType, privateFormat;
const encoding = parseKeyEncoding(type, options);
if (options !== undefined)
validateObject(options, 'options');
let impl;
switch (type) {
case 'rsa':
case 'rsa-pss':
{
validateObject(options, 'options');
const { modulusLength } = options;
if (!isUint32(modulusLength))
throw new ERR_INVALID_ARG_VALUE('options.modulusLength',
modulusLength);
{
validateObject(options, 'options');
const { modulusLength } = options;
if (!isUint32(modulusLength))
throw new ERR_INVALID_ARG_VALUE('options.modulusLength', modulusLength);
let { publicExponent } = options;
if (publicExponent == null) {
publicExponent = 0x10001;
} else if (!isUint32(publicExponent)) {
throw new ERR_INVALID_ARG_VALUE('options.publicExponent',
publicExponent);
}
if (type === 'rsa') {
impl = (wrap) => generateKeyPairRSA(modulusLength, publicExponent,
publicFormat, publicType,
privateFormat, privateType,
cipher, passphrase, wrap);
break;
}
const { hash, mgf1Hash, saltLength } = options;
if (hash !== undefined && typeof hash !== 'string')
throw new ERR_INVALID_ARG_VALUE('options.hash', hash);
if (mgf1Hash !== undefined && typeof mgf1Hash !== 'string')
throw new ERR_INVALID_ARG_VALUE('options.mgf1Hash', mgf1Hash);
if (saltLength !== undefined && !isUint32(saltLength))
throw new ERR_INVALID_ARG_VALUE('options.saltLength', saltLength);
impl = (wrap) => generateKeyPairRSAPSS(modulusLength, publicExponent,
hash, mgf1Hash, saltLength,
publicFormat, publicType,
privateFormat, privateType,
cipher, passphrase, wrap);
let { publicExponent } = options;
if (publicExponent == null) {
publicExponent = 0x10001;
} else if (!isUint32(publicExponent)) {
throw new ERR_INVALID_ARG_VALUE(
'options.publicExponent',
publicExponent);
}
break;
if (type === 'rsa') {
return new RsaKeyPairGenJob(
mode,
kKeyVariantRSA_SSA_PKCS1_V1_5, // Used also for RSA-OAEP
modulusLength,
publicExponent,
...encoding);
}
const { hash, mgf1Hash, saltLength } = options;
if (hash !== undefined && typeof hash !== 'string')
throw new ERR_INVALID_ARG_VALUE('options.hash', hash);
if (mgf1Hash !== undefined && typeof mgf1Hash !== 'string')
throw new ERR_INVALID_ARG_VALUE('options.mgf1Hash', mgf1Hash);
if (saltLength !== undefined && !isUint32(saltLength))
throw new ERR_INVALID_ARG_VALUE('options.saltLength', saltLength);
return new RsaKeyPairGenJob(
mode,
kKeyVariantRSA_PSS,
modulusLength,
publicExponent,
hash,
mgf1Hash,
saltLength,
...encoding);
}
case 'dsa':
{
validateObject(options, 'options');
const { modulusLength } = options;
if (!isUint32(modulusLength))
throw new ERR_INVALID_ARG_VALUE('options.modulusLength',
modulusLength);
{
validateObject(options, 'options');
const { modulusLength } = options;
if (!isUint32(modulusLength))
throw new ERR_INVALID_ARG_VALUE('options.modulusLength', modulusLength);
let { divisorLength } = options;
if (divisorLength == null) {
divisorLength = -1;
} else if (!isUint32(divisorLength)) {
throw new ERR_INVALID_ARG_VALUE('options.divisorLength',
divisorLength);
}
impl = (wrap) => generateKeyPairDSA(modulusLength, divisorLength,
publicFormat, publicType,
privateFormat, privateType,
cipher, passphrase, wrap);
let { divisorLength } = options;
if (divisorLength == null) {
divisorLength = -1;
} else if (!isUint32(divisorLength)) {
throw new ERR_INVALID_ARG_VALUE('options.divisorLength', divisorLength);
}
break;
return new DsaKeyPairGenJob(
mode,
modulusLength,
divisorLength,
...encoding);
}
case 'ec':
{
validateObject(options, 'options');
const { namedCurve } = options;
if (typeof namedCurve !== 'string')
throw new ERR_INVALID_ARG_VALUE('options.namedCurve', namedCurve);
let { paramEncoding } = options;
if (paramEncoding == null || paramEncoding === 'named')
paramEncoding = OPENSSL_EC_NAMED_CURVE;
else if (paramEncoding === 'explicit')
paramEncoding = OPENSSL_EC_EXPLICIT_CURVE;
else
throw new ERR_INVALID_ARG_VALUE('options.paramEncoding',
paramEncoding);
{
validateObject(options, 'options');
const { namedCurve } = options;
if (typeof namedCurve !== 'string')
throw new ERR_INVALID_ARG_VALUE('options.namedCurve', namedCurve);
let { paramEncoding } = options;
if (paramEncoding == null || paramEncoding === 'named')
paramEncoding = OPENSSL_EC_NAMED_CURVE;
else if (paramEncoding === 'explicit')
paramEncoding = OPENSSL_EC_EXPLICIT_CURVE;
else
throw new ERR_INVALID_ARG_VALUE('options.paramEncoding', paramEncoding);
impl = (wrap) => generateKeyPairEC(namedCurve, paramEncoding,
publicFormat, publicType,
privateFormat, privateType,
cipher, passphrase, wrap);
}
break;
return new EcKeyPairGenJob(
mode,
namedCurve,
paramEncoding,
...encoding);
}
case 'ed25519':
case 'ed448':
case 'x25519':
case 'x448':
{
let id;
switch (type) {
case 'ed25519':
id = EVP_PKEY_ED25519;
break;
case 'ed448':
id = EVP_PKEY_ED448;
break;
case 'x25519':
id = EVP_PKEY_X25519;
break;
case 'x448':
id = EVP_PKEY_X448;
break;
}
impl = (wrap) => generateKeyPairNid(id,
publicFormat, publicType,
privateFormat, privateType,
cipher, passphrase, wrap);
{
let id;
switch (type) {
case 'ed25519':
id = EVP_PKEY_ED25519;
break;
case 'ed448':
id = EVP_PKEY_ED448;
break;
case 'x25519':
id = EVP_PKEY_X25519;
break;
case 'x448':
id = EVP_PKEY_X448;
break;
}
break;
return new NidKeyPairGenJob(mode, id, ...encoding);
}
case 'dh':
{
validateObject(options, 'options');
const { group, primeLength, prime, generator } = options;
let args;
if (group != null) {
if (prime != null)
throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'prime');
if (primeLength != null)
throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'primeLength');
if (generator != null)
throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'generator');
if (typeof group !== 'string')
throw new ERR_INVALID_ARG_VALUE('options.group', group);
args = [group];
} else {
if (prime != null) {
if (primeLength != null)
throw new ERR_INCOMPATIBLE_OPTION_PAIR('prime', 'primeLength');
if (!isArrayBufferView(prime))
throw new ERR_INVALID_ARG_VALUE('options.prime', prime);
} else if (primeLength != null) {
if (!isUint32(primeLength))
throw new ERR_INVALID_ARG_VALUE('options.primeLength',
primeLength);
} else {
throw new ERR_MISSING_OPTION(
'At least one of the group, prime, or primeLength options');
}
{
validateObject(options, 'options');
const { group, primeLength, prime, generator } = options;
if (group != null) {
if (prime != null)
throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'prime');
if (primeLength != null)
throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'primeLength');
if (generator != null)
throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'generator');
if (typeof group !== 'string')
throw new ERR_INVALID_ARG_VALUE('options.group', group);
if (generator != null) {
if (!isUint32(generator))
throw new ERR_INVALID_ARG_VALUE('options.generator', generator);
}
args = [prime != null ? prime : primeLength,
generator == null ? 2 : generator];
}
impl = (wrap) => generateKeyPairDH(...args,
publicFormat, publicType,
privateFormat, privateType,
cipher, passphrase, wrap);
return new DhKeyPairGenJob(mode, group, ...encoding);
}
break;
if (prime != null) {
if (primeLength != null)
throw new ERR_INCOMPATIBLE_OPTION_PAIR('prime', 'primeLength');
if (!isArrayBufferView(prime))
throw new ERR_INVALID_ARG_VALUE('options.prime', prime);
} else if (primeLength != null) {
if (!isUint32(primeLength))
throw new ERR_INVALID_ARG_VALUE('options.primeLength', primeLength);
} else {
throw new ERR_MISSING_OPTION(
'At least one of the group, prime, or primeLength options');
}
if (generator != null) {
if (!isUint32(generator))
throw new ERR_INVALID_ARG_VALUE('options.generator', generator);
}
return new DhKeyPairGenJob(
mode,
prime != null ? prime : primeLength,
generator == null ? 2 : generator,
...encoding);
}
default:
throw new ERR_INVALID_ARG_VALUE('type', type,
'must be a supported key type');
// Fall through
}
if (options) {
({
cipher,
passphrase,
publicType,
publicFormat,
privateType,
privateFormat
} = parseKeyEncoding(type, options));
}
return impl;
throw new ERR_INVALID_ARG_VALUE('type', type, 'must be a supported key type');
}
module.exports = { generateKeyPair, generateKeyPairSync };
// Symmetric Key Generation
function generateKeyJob(mode, keyType, options) {
validateString(keyType, 'type');
validateObject(options, 'options');
const { length } = options;
switch (keyType) {
case 'hmac':
validateInteger(length, 'options.length', 1, 2 ** 31 - 1);
break;
case 'aes':
validateOneOf(length, 'options.length', kAesKeyLengths);
break;
default:
throw new ERR_INVALID_ARG_VALUE(
'type',
keyType,
'must be a supported key type');
}
return new SecretKeyGenJob(mode, length);
}
function handleGenerateKeyError(ret) {
if (ret === undefined)
return; // async
const [err, key] = ret;
if (err !== undefined)
throw err;
return wrapKey(key, SecretKeyObject);
}
function generateKey(type, options, callback) {
if (typeof options === 'function') {
callback = options;
options = undefined;
}
if (typeof callback !== 'function')
throw new ERR_INVALID_CALLBACK(callback);
const job = generateKeyJob(kCryptoJobAsync, type, options);
job.ondone = (error, key) => {
if (error) return FunctionPrototypeCall(callback, job, error);
FunctionPrototypeCall(callback, job, null, wrapKey(key, SecretKeyObject));
};
handleGenerateKeyError(job.run());
}
function generateKeySync(type, options) {
return handleGenerateKeyError(
generateKeyJob(kCryptoJobSync, type, options).run());
}
module.exports = {
generateKeyPair,
generateKeyPairSync,
generateKey,
generateKeySync,
};

View File

@ -1,7 +1,9 @@
'use strict';
const {
ArrayFrom,
ObjectDefineProperty,
ObjectSetPrototypeOf,
Symbol,
} = primordials;
@ -16,20 +18,47 @@ const {
kKeyEncodingPKCS1,
kKeyEncodingPKCS8,
kKeyEncodingSPKI,
kKeyEncodingSEC1
kKeyEncodingSEC1,
} = internalBinding('crypto');
const {
ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_OUT_OF_RANGE
} = require('internal/errors').codes;
const { kHandle } = require('internal/crypto/util');
codes: {
ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_OUT_OF_RANGE,
ERR_OPERATION_FAILED,
}
} = require('internal/errors');
const { isArrayBufferView } = require('internal/util/types');
const {
kHandle,
kKeyObject,
getArrayBufferOrView,
} = require('internal/crypto/util');
const {
isAnyArrayBuffer,
isArrayBufferView,
} = require('internal/util/types');
const {
JSTransferable,
kClone,
kDeserialize,
} = require('internal/worker/js_transferable');
const {
customInspectSymbol: kInspect,
} = require('internal/util');
const { inspect } = require('internal/util/inspect');
const kAlgorithm = Symbol('kAlgorithm');
const kExtractable = Symbol('kExtractable');
const kKeyType = Symbol('kKeyType');
const kKeyUsages = Symbol('kKeyUsages');
// Key input contexts.
const kConsumePublic = 0;
@ -76,6 +105,12 @@ const [
get type() {
return this[kKeyType];
}
static from(key) {
if (!isCryptoKey(key))
throw new ERR_INVALID_ARG_TYPE('key', 'CryptoKey', key);
return key[kKeyObject];
}
}
class SecretKeyObject extends KeyObject {
@ -191,7 +226,9 @@ function parseKeyFormatAndType(enc, keyType, isPublic, objName) {
}
function isStringOrBuffer(val) {
return typeof val === 'string' || isArrayBufferView(val);
return typeof val === 'string' ||
isArrayBufferView(val) ||
isAnyArrayBuffer(val);
}
function parseKeyEncoding(enc, keyType, isPublic, objName) {
@ -205,9 +242,9 @@ function parseKeyEncoding(enc, keyType, isPublic, objName) {
type
} = parseKeyFormatAndType(enc, keyType, isPublic, objName);
let cipher, passphrase;
let cipher, passphrase, encoding;
if (isPublic !== true) {
({ cipher, passphrase } = enc);
({ cipher, passphrase, encoding } = enc);
if (!isInput) {
if (cipher != null) {
@ -232,6 +269,9 @@ function parseKeyEncoding(enc, keyType, isPublic, objName) {
}
}
if (passphrase !== undefined)
passphrase = getArrayBufferOrView(passphrase, 'key.passphrase', encoding);
return { format, type, cipher, passphrase };
}
@ -253,7 +293,7 @@ function getKeyObjectHandle(key, ctx) {
if (ctx === kCreatePrivate) {
throw new ERR_INVALID_ARG_TYPE(
'key',
['string', 'Buffer', 'TypedArray', 'DataView'],
['string', 'ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],
key
);
}
@ -270,38 +310,52 @@ function getKeyObjectHandle(key, ctx) {
return key[kHandle];
}
function getKeyTypes(allowKeyObject, bufferOnly = false) {
return [
'ArrayBuffer',
'Buffer',
'TypedArray',
'DataView',
...(!bufferOnly ? ['string'] : []),
...(!bufferOnly && allowKeyObject ? ['KeyObject', 'CryptoKey'] : [])];
}
function prepareAsymmetricKey(key, ctx) {
if (isKeyObject(key)) {
// Best case: A key object, as simple as that.
return { data: getKeyObjectHandle(key, ctx) };
} else if (typeof key === 'string' || isArrayBufferView(key)) {
} else if (isCryptoKey(key)) {
return { data: getKeyObjectHandle(key[kKeyObject], ctx) };
} else if (isStringOrBuffer(key)) {
// Expect PEM by default, mostly for backward compatibility.
return { format: kKeyFormatPEM, data: key };
return { format: kKeyFormatPEM, data: getArrayBufferOrView(key, 'key') };
} else if (typeof key === 'object') {
const data = key.key;
const { key: data, encoding } = key;
// The 'key' property can be a KeyObject as well to allow specifying
// additional options such as padding along with the key.
if (isKeyObject(data))
return { data: getKeyObjectHandle(data, ctx) };
else if (isCryptoKey(data))
return { data: getKeyObjectHandle(data[kKeyObject], ctx) };
// Either PEM or DER using PKCS#1 or SPKI.
if (!isStringOrBuffer(data)) {
throw new ERR_INVALID_ARG_TYPE(
'key.key',
['string', 'Buffer', 'TypedArray', 'DataView',
...(ctx !== kCreatePrivate ? ['KeyObject'] : [])],
getKeyTypes(ctx !== kCreatePrivate),
data);
}
const isPublic =
(ctx === kConsumePrivate || ctx === kCreatePrivate) ? false : undefined;
return { data, ...parseKeyEncoding(key, undefined, isPublic) };
return {
data: getArrayBufferOrView(data, 'key', encoding),
...parseKeyEncoding(key, undefined, isPublic)
};
}
throw new ERR_INVALID_ARG_TYPE(
'key',
['string', 'Buffer', 'TypedArray', 'DataView',
...(ctx !== kCreatePrivate ? ['KeyObject'] : [])],
key
);
getKeyTypes(ctx !== kCreatePrivate),
key);
}
function preparePrivateKey(key) {
@ -312,24 +366,31 @@ function preparePublicOrPrivateKey(key) {
return prepareAsymmetricKey(key, kConsumePublic);
}
function prepareSecretKey(key, bufferOnly = false) {
if (!isArrayBufferView(key) && (bufferOnly || typeof key !== 'string')) {
if (isKeyObject(key) && !bufferOnly) {
function prepareSecretKey(key, encoding, bufferOnly = false) {
if (!bufferOnly) {
if (isKeyObject(key)) {
if (key.type !== 'secret')
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret');
return key[kHandle];
} else if (isCryptoKey(key)) {
if (key.type !== 'secret')
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret');
return key[kKeyObject][kHandle];
}
}
if (typeof key !== 'string' &&
!isArrayBufferView(key) &&
!isAnyArrayBuffer(key)) {
throw new ERR_INVALID_ARG_TYPE(
'key',
['Buffer', 'TypedArray', 'DataView',
...(bufferOnly ? [] : ['string', 'KeyObject'])],
getKeyTypes(!bufferOnly, bufferOnly),
key);
}
return key;
return getArrayBufferOrView(key, 'key', encoding);
}
function createSecretKey(key) {
key = prepareSecretKey(key, true);
function createSecretKey(key, encoding) {
key = prepareSecretKey(key, encoding, true);
if (key.byteLength === 0)
throw new ERR_OUT_OF_RANGE('key.byteLength', '> 0', key.byteLength);
const handle = new KeyObjectHandle();
@ -356,21 +417,123 @@ function isKeyObject(key) {
return key instanceof KeyObject;
}
// Our implementation of CryptoKey is a simple wrapper around a KeyObject
// that adapts it to the standard interface. This implementation also
// extends the JSTransferable class, allowing the CryptoKey to be cloned
// to Workers.
// TODO(@jasnell): Embedder environments like electron may have issues
// here similar to other things like URL. A chromium provided CryptoKey
// will not be recognized as a Node.js CryptoKey, and vice versa. It
// would be fantastic if we could find a way of making those interop.
class CryptoKey extends JSTransferable {
constructor() {
throw new ERR_OPERATION_FAILED('Illegal constructor');
}
[kInspect](depth, options) {
if (depth < 0)
return this;
const opts = {
...options,
depth: options.depth == null ? null : options.depth - 1
};
return `CryptoKey ${inspect({
type: this.type,
extractable: this.extractable,
algorithm: this.algorithm,
usages: this.usages
}, opts)}`;
}
get type() {
return this[kKeyObject].type;
}
get extractable() {
return this[kExtractable];
}
get algorithm() {
return this[kAlgorithm];
}
get usages() {
return ArrayFrom(this[kKeyUsages]);
}
[kClone]() {
const keyObject = this[kKeyObject];
const algorithm = this.algorithm;
const extractable = this.extractable;
const usages = this.usages;
return {
data: {
keyObject,
algorithm,
usages,
extractable,
},
deserializeInfo: 'internal/crypto/keys:InternalCryptoKey'
};
}
[kDeserialize]({ keyObject, algorithm, usages, extractable }) {
this[kKeyObject] = keyObject;
this[kAlgorithm] = algorithm;
this[kKeyUsages] = usages;
this[kExtractable] = extractable;
}
}
// All internal code must use new InternalCryptoKey to create
// CryptoKey instances. The CryptoKey class is exposed to end
// user code but is not permitted to be constructed directly.
class InternalCryptoKey extends JSTransferable {
constructor(
keyObject,
algorithm,
keyUsages,
extractable) {
super();
// Using symbol properties here currently instead of private
// properties because (for now) the performance penalty of
// private fields is still too high.
this[kKeyObject] = keyObject;
this[kAlgorithm] = algorithm;
this[kExtractable] = extractable;
this[kKeyUsages] = keyUsages;
}
}
InternalCryptoKey.prototype.constructor = CryptoKey;
ObjectSetPrototypeOf(InternalCryptoKey.prototype, CryptoKey.prototype);
function isCryptoKey(obj) {
return obj != null && obj[kKeyObject] !== undefined;
}
module.exports = {
// Public API.
createSecretKey,
createPublicKey,
createPrivateKey,
KeyObject,
CryptoKey,
InternalCryptoKey,
// These are designed for internal use only and should not be exposed.
parsePublicKeyEncoding,
parsePrivateKeyEncoding,
parseKeyEncoding,
preparePrivateKey,
preparePublicOrPrivateKey,
prepareSecretKey,
SecretKeyObject,
PublicKeyObject,
PrivateKeyObject,
isKeyObject
isKeyObject,
isCryptoKey,
};

211
lib/internal/crypto/mac.js Normal file
View File

@ -0,0 +1,211 @@
'use strict';
const {
ArrayFrom,
Promise,
SafeSet,
} = primordials;
const {
HmacJob,
KeyObjectHandle,
kCryptoJobAsync,
kSignJobModeSign,
kSignJobModeVerify,
} = internalBinding('crypto');
const {
getHashLength,
hasAnyNotIn,
jobPromise,
lazyDOMException,
normalizeHashName,
validateBitLength,
validateKeyOps,
kHandle,
kKeyObject,
} = require('internal/crypto/util');
const {
codes: {
ERR_MISSING_OPTION,
ERR_INVALID_ARG_TYPE,
}
} = require('internal/errors');
const {
generateKey,
} = require('internal/crypto/keygen');
const {
InternalCryptoKey,
SecretKeyObject,
createSecretKey,
isKeyObject,
} = require('internal/crypto/keys');
async function hmacGenerateKey(algorithm, extractable, keyUsages) {
const { hash, name } = algorithm;
let { length } = algorithm;
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
if (length === undefined)
length = getHashLength(hash.name);
validateBitLength(length, 'algorithm.length', true);
const usageSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usageSet, 'sign', 'verify')) {
throw lazyDOMException(
'Unsupported key usage for an HMAC key',
'SyntaxError');
}
return new Promise((resolve, reject) => {
generateKey('hmac', { length }, (err, key) => {
if (err) {
return reject(lazyDOMException(
'The operation failed for an operation-specific reason',
'OperationError'));
}
resolve(new InternalCryptoKey(
key,
{ name, length, hash: { name: hash.name } },
ArrayFrom(usageSet),
extractable));
});
});
}
async function hmacImportKey(
format,
keyData,
algorithm,
extractable,
keyUsages) {
const { hash } = algorithm;
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
const usagesSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usagesSet, 'sign', 'verify')) {
throw lazyDOMException(
'Unsupported key usage for an HMAC key',
'SyntaxError');
}
let keyObject;
switch (format) {
case 'node.keyObject': {
if (!isKeyObject(keyData))
throw new ERR_INVALID_ARG_TYPE('keyData', 'KeyObject', keyData);
if (keyData.type !== 'secret') {
throw lazyDOMException(
`Unable to import HMAC key with format ${format}`);
}
keyObject = keyData;
break;
}
case 'raw': {
const checkLength = keyData.byteLength * 8;
if (checkLength === 0 || algorithm.length === 0)
throw lazyDOMException('Zero-length key is not supported', 'DataError');
// The Web Crypto spec allows for key lengths that are not multiples of
// 8. We don't. Our check here is stricter than that defined by the spec
// in that we require that algorithm.length match keyData.length * 8 if
// algorithm.length is specified.
if (algorithm.length !== undefined &&
algorithm.length !== checkLength) {
throw lazyDOMException('Invalid key length', 'DataError');
}
keyObject = createSecretKey(keyData);
break;
}
case 'jwk': {
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid JWK keyData', 'DataError');
if (keyData.kty !== 'oct')
throw lazyDOMException('Invalid key type', 'DataError');
if (usagesSet.size > 0 &&
keyData.use !== undefined &&
keyData.use !== 'sig') {
throw lazyDOMException('Invalid use type', 'DataError');
}
validateKeyOps(keyData.key_ops, usagesSet);
if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException('JWK is not extractable', 'DataError');
}
if (keyData.alg !== undefined) {
if (typeof keyData.alg !== 'string')
throw lazyDOMException('Invalid alg', 'DataError');
switch (keyData.alg) {
case 'HS1':
if (algorithm.hash.name !== 'SHA-1')
throw lazyDOMException('Digest algorithm mismatch', 'DataError');
break;
case 'HS256':
if (algorithm.hash.name !== 'SHA-256')
throw lazyDOMException('Digest algorithm mismatch', 'DataError');
break;
case 'HS384':
if (algorithm.hash.name !== 'SHA-384')
throw lazyDOMException('Digest algorithm mismatch', 'DataError');
break;
case 'HS512':
if (algorithm.hash.name !== 'SHA-512')
throw lazyDOMException('Digest algorithm mismatch', 'DataError');
break;
default:
throw lazyDOMException('Unsupported digest algorithm', 'DataError');
}
}
const handle = new KeyObjectHandle();
handle.initJwk(keyData);
keyObject = new SecretKeyObject(handle);
break;
}
default:
throw lazyDOMException(`Unable to import HMAC key with format ${format}`);
}
const { length } = keyObject[kHandle].keyDetail({});
return new InternalCryptoKey(
keyObject, {
name: 'HMAC',
hash: algorithm.hash,
length,
},
keyUsages,
extractable);
}
function hmacSignVerify(key, data, algorithm, signature) {
const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
return jobPromise(new HmacJob(
kCryptoJobAsync,
mode,
normalizeHashName(key.algorithm.hash.name),
key[kKeyObject][kHandle],
data,
signature));
}
module.exports = {
hmacImportKey,
hmacGenerateKey,
hmacSignVerify,
};

View File

@ -1,18 +1,35 @@
'use strict';
const { AsyncWrap, Providers } = internalBinding('async_wrap');
const { Buffer } = require('buffer');
const { pbkdf2: _pbkdf2 } = internalBinding('crypto');
const { validateUint32 } = require('internal/validators');
const {
ERR_CRYPTO_INVALID_DIGEST,
ERR_CRYPTO_PBKDF2_ERROR,
FunctionPrototypeCall,
Promise,
} = primordials;
const { Buffer } = require('buffer');
const {
PBKDF2Job,
kCryptoJobAsync,
kCryptoJobSync,
} = internalBinding('crypto');
const {
validateInteger,
validateUint32,
} = require('internal/validators');
const {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_CALLBACK,
ERR_MISSING_OPTION,
} = require('internal/errors').codes;
const {
getArrayBufferOrView,
getDefaultEncoding,
getArrayBufferView,
lazyDOMException,
normalizeHashName,
kKeyObject,
} = require('internal/crypto/util');
function pbkdf2(password, salt, iterations, keylen, digest, callback) {
@ -27,51 +44,95 @@ function pbkdf2(password, salt, iterations, keylen, digest, callback) {
if (typeof callback !== 'function')
throw new ERR_INVALID_CALLBACK(callback);
const encoding = getDefaultEncoding();
const keybuf = Buffer.alloc(keylen);
const job = new PBKDF2Job(
kCryptoJobAsync,
password,
salt,
iterations,
keylen,
digest);
const wrap = new AsyncWrap(Providers.PBKDF2REQUEST);
wrap.ondone = (ok) => { // Retains keybuf while request is in flight.
if (!ok) return callback.call(wrap, new ERR_CRYPTO_PBKDF2_ERROR());
if (encoding === 'buffer') return callback.call(wrap, null, keybuf);
callback.call(wrap, null, keybuf.toString(encoding));
const encoding = getDefaultEncoding();
job.ondone = (err, result) => {
if (err !== undefined)
return FunctionPrototypeCall(callback, job, err);
const buf = Buffer.from(result);
if (encoding === 'buffer')
return FunctionPrototypeCall(callback, job, null, buf);
FunctionPrototypeCall(callback, job, null, buf.toString(encoding));
};
handleError(_pbkdf2(keybuf, password, salt, iterations, digest, wrap),
digest);
job.run();
}
function pbkdf2Sync(password, salt, iterations, keylen, digest) {
({ password, salt, iterations, keylen, digest } =
check(password, salt, iterations, keylen, digest));
const keybuf = Buffer.alloc(keylen);
handleError(_pbkdf2(keybuf, password, salt, iterations, digest), digest);
const job = new PBKDF2Job(
kCryptoJobSync,
password,
salt,
iterations,
keylen,
digest);
const [err, result] = job.run();
if (err !== undefined)
throw err;
const buf = Buffer.from(result);
const encoding = getDefaultEncoding();
if (encoding === 'buffer') return keybuf;
return keybuf.toString(encoding);
return encoding === 'buffer' ? buf : buf.toString(encoding);
}
function check(password, salt, iterations, keylen, digest) {
if (typeof digest !== 'string')
throw new ERR_INVALID_ARG_TYPE('digest', 'string', digest);
password = getArrayBufferView(password, 'password');
salt = getArrayBufferView(salt, 'salt');
password = getArrayBufferOrView(password, 'password');
salt = getArrayBufferOrView(salt, 'salt');
validateUint32(iterations, 'iterations', true);
validateUint32(keylen, 'keylen');
return { password, salt, iterations, keylen, digest };
}
function handleError(rc, digest) {
if (rc === -1)
throw new ERR_CRYPTO_INVALID_DIGEST(digest);
async function pbkdf2DeriveBits(algorithm, baseKey, length) {
validateUint32(length, 'length');
const { iterations } = algorithm;
let { hash } = algorithm;
const salt = getArrayBufferOrView(algorithm.salt, 'algorithm.salt');
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
validateInteger(iterations, 'algorithm.iterations', 1);
if (rc === false)
throw new ERR_CRYPTO_PBKDF2_ERROR();
hash = normalizeHashName(hash.name);
const raw = baseKey[kKeyObject].export();
let byteLength = 64; // the default
if (length !== undefined) {
if (length === 0)
throw lazyDOMException('length cannot be zero', 'OperationError');
if (length % 8) {
throw lazyDOMException(
'length must be a multiple of 8',
'OperationError');
}
byteLength = length / 8;
}
return new Promise((resolve, reject) => {
pbkdf2(raw, salt, iterations, byteLength, hash, (err, result) => {
if (err) return reject(err);
resolve(result);
});
});
}
module.exports = {
pbkdf2,
pbkdf2Sync
pbkdf2Sync,
pbkdf2DeriveBits,
};

View File

@ -1,21 +1,43 @@
'use strict';
const {
FunctionPrototypeBind,
FunctionPrototypeCall,
MathMin,
NumberIsNaN,
NumberIsSafeInteger
NumberIsSafeInteger,
} = primordials;
const { AsyncWrap, Providers } = internalBinding('async_wrap');
const { kMaxLength } = require('buffer');
const { randomBytes: _randomBytes } = internalBinding('crypto');
const {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_CALLBACK,
ERR_OUT_OF_RANGE
} = require('internal/errors').codes;
RandomBytesJob,
kCryptoJobAsync,
kCryptoJobSync,
} = internalBinding('crypto');
const {
lazyDOMException,
} = require('internal/crypto/util');
const { kMaxLength } = require('buffer');
const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_CALLBACK,
ERR_OUT_OF_RANGE,
}
} = require('internal/errors');
const { validateNumber } = require('internal/validators');
const { isArrayBufferView } = require('internal/util/types');
const {
isArrayBufferView,
isAnyArrayBuffer,
isBigInt64Array,
isFloat32Array,
isFloat64Array,
} = require('internal/util/types');
const { FastBuffer } = require('internal/buffer');
const kMaxUint32 = 2 ** 32 - 1;
@ -56,20 +78,24 @@ function randomBytes(size, callback) {
const buf = new FastBuffer(size);
if (!callback) return handleError(_randomBytes(buf, 0, size), buf);
if (callback === undefined) {
randomFillSync(buf.buffer, 0, size);
return buf;
}
const wrap = new AsyncWrap(Providers.RANDOMBYTESREQUEST);
wrap.ondone = (ex) => { // Retains buf while request is in flight.
if (ex) return callback.call(wrap, ex);
callback.call(wrap, null, buf);
};
_randomBytes(buf, 0, size, wrap);
// Keep the callback as a regular function so this is propagated.
randomFill(buf.buffer, 0, size, function(error) {
if (error) FunctionPrototypeCall(callback, this, error);
FunctionPrototypeCall(callback, this, null, buf);
});
}
function randomFillSync(buf, offset = 0, size) {
if (!isArrayBufferView(buf)) {
throw new ERR_INVALID_ARG_TYPE('buf', 'ArrayBufferView', buf);
if (!isAnyArrayBuffer(buf) && !isArrayBufferView(buf)) {
throw new ERR_INVALID_ARG_TYPE(
'buf',
['ArrayBuffer', 'ArrayBufferView'],
buf);
}
const elementSize = buf.BYTES_PER_ELEMENT || 1;
@ -82,12 +108,28 @@ function randomFillSync(buf, offset = 0, size) {
size = assertSize(size, elementSize, offset, buf.byteLength);
}
return handleError(_randomBytes(buf, offset, size), buf);
if (size === 0)
return buf;
const job = new RandomBytesJob(
kCryptoJobSync,
buf.buffer || buf,
offset,
size);
const [ err ] = job.run();
if (err)
throw err;
return buf;
}
function randomFill(buf, offset, size, callback) {
if (!isArrayBufferView(buf)) {
throw new ERR_INVALID_ARG_TYPE('buf', 'ArrayBufferView', buf);
if (!isAnyArrayBuffer(buf) && !isArrayBufferView(buf)) {
throw new ERR_INVALID_ARG_TYPE(
'buf',
['ArrayBuffer', 'ArrayBufferView'],
buf);
}
const elementSize = buf.BYTES_PER_ELEMENT || 1;
@ -111,13 +153,19 @@ function randomFill(buf, offset, size, callback) {
size = assertSize(size, elementSize, offset, buf.byteLength);
}
const wrap = new AsyncWrap(Providers.RANDOMBYTESREQUEST);
wrap.ondone = (ex) => { // Retains buf while request is in flight.
if (ex) return callback.call(wrap, ex);
callback.call(wrap, null, buf);
};
if (size === 0) {
callback(null, buf);
return;
}
_randomBytes(buf, offset, size, wrap);
// TODO(@jasnell): This is not yet handling byte offsets right
const job = new RandomBytesJob(
kCryptoJobAsync,
buf,
offset,
size);
job.ondone = FunctionPrototypeBind(onJobDone, job, buf, callback);
job.run();
}
// Largest integer we can read from a buffer.
@ -198,14 +246,42 @@ function randomInt(min, max, callback) {
}
}
function handleError(ex, buf) {
if (ex) throw ex;
return buf;
function onJobDone(buf, callback, error) {
if (error) return FunctionPrototypeCall(callback, this, error);
FunctionPrototypeCall(callback, this, null, buf);
}
// Really just the Web Crypto API alternative
// to require('crypto').randomFillSync() with an
// additional limitation that the input buffer is
// not allowed to exceed 65536 bytes, and can only
// be an integer-type TypedArray.
function getRandomValues(data) {
if (!isArrayBufferView(data) ||
isBigInt64Array(data) ||
isFloat32Array(data) ||
isFloat64Array(data)) {
// Ordinarily this would be an ERR_INVALID_ARG_TYPE. However,
// the Web Crypto API and web platform tests expect this to
// be a DOMException with type TypeMismatchError.
throw lazyDOMException(
'The data argument must be an integer-type TypedArray',
'TypeMismatchError');
}
if (data.byteLength > 65536) {
throw lazyDOMException(
'The requested length exceeds 65,536 bytes',
'QuotaExceededError');
}
randomFillSync(data, 0);
return data;
}
module.exports = {
randomBytes,
randomFill,
randomFillSync,
randomInt
randomInt,
getRandomValues,
};

371
lib/internal/crypto/rsa.js Normal file
View File

@ -0,0 +1,371 @@
'use strict';
const {
Promise,
SafeSet,
Uint8Array,
} = primordials;
const {
KeyObjectHandle,
RSACipherJob,
RSAKeyExportJob,
SignJob,
kCryptoJobAsync,
kSignJobModeSign,
kSignJobModeVerify,
kKeyVariantRSA_SSA_PKCS1_V1_5,
kKeyVariantRSA_PSS,
kKeyVariantRSA_OAEP,
kKeyTypePrivate,
kWebCryptoCipherEncrypt,
RSA_PKCS1_PSS_PADDING,
} = internalBinding('crypto');
const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_MISSING_OPTION,
}
} = require('internal/errors');
const {
validateUint32,
} = require('internal/validators');
const {
bigIntArrayToUnsignedInt,
getArrayBufferOrView,
getUsagesUnion,
hasAnyNotIn,
jobPromise,
lazyDOMException,
normalizeHashName,
validateKeyOps,
validateMaxBufferLength,
kHandle,
kKeyObject,
} = require('internal/crypto/util');
const {
isUint8Array,
} = require('internal/util/types');
const {
InternalCryptoKey,
PrivateKeyObject,
PublicKeyObject,
createPublicKey,
createPrivateKey,
isKeyObject,
} = require('internal/crypto/keys');
const {
generateKeyPair,
} = require('internal/crypto/keygen');
const kRsaVariants = {
'RSASSA-PKCS1-V1_5': kKeyVariantRSA_SSA_PKCS1_V1_5,
'RSA-PSS': kKeyVariantRSA_PSS,
'RSA-OAEP': kKeyVariantRSA_OAEP,
};
function verifyAcceptableRsaKeyUse(name, type, usages) {
let checkSet;
switch (name) {
case 'RSA-OAEP':
switch (type) {
case 'private':
checkSet = ['decrypt', 'unwrapKey'];
break;
case 'public':
checkSet = ['encrypt', 'wrapKey'];
break;
}
break;
default:
switch (type) {
case 'private':
checkSet = ['sign'];
break;
case 'public':
checkSet = ['verify'];
break;
}
}
if (hasAnyNotIn(usages, ...checkSet)) {
throw lazyDOMException(
`Unsupported key usage for an ${name} key`,
'SyntaxError');
}
}
function rsaOaepCipher(mode, key, data, { label }) {
const type = mode === kWebCryptoCipherEncrypt ? 'public' : 'private';
if (key.type !== type) {
throw lazyDOMException(
'The requested operation is not valid for the provided key',
'InvalidAccessError');
}
if (label !== undefined) {
label = getArrayBufferOrView(label, 'algorithm.label');
validateMaxBufferLength(label, 'algorithm.label');
}
return jobPromise(new RSACipherJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
data,
kKeyVariantRSA_OAEP,
normalizeHashName(key.algorithm.hash.name),
label));
}
async function rsaKeyGenerate(
algorithm,
extractable,
keyUsages) {
const {
name,
modulusLength,
publicExponent,
hash
} = algorithm;
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
validateUint32(modulusLength, 'algorithm.modulusLength');
if (!isUint8Array(publicExponent)) {
throw new ERR_INVALID_ARG_TYPE(
'algorithm.publicExponent',
'Uint8Array',
publicExponent);
}
const usageSet = new SafeSet(keyUsages);
const publicExponentConverted = bigIntArrayToUnsignedInt(publicExponent);
if (publicExponentConverted === undefined) {
throw lazyDOMException(
'The publicExponent must be equivalent to an unsigned 32-bit value',
'OperationError');
}
switch (name) {
case 'RSA-OAEP':
if (hasAnyNotIn(usageSet, 'encrypt', 'decrypt', 'wrapKey', 'unwrapKey')) {
throw lazyDOMException(
'Unsupported key usage for a RSA key',
'SyntaxError');
}
break;
default:
if (hasAnyNotIn(usageSet, 'sign', 'verify')) {
throw lazyDOMException(
'Unsupported key usage for a RSA key',
'SyntaxError');
}
}
return new Promise((resolve, reject) => {
generateKeyPair('rsa', {
modulusLength,
publicExponentConverted,
}, (err, pubKey, privKey) => {
if (err) {
return reject(lazyDOMException(
'The operation failed for an operation-specific reason',
'OperationError'));
}
const algorithm = {
name,
modulusLength,
publicExponent,
hash: { name: hash.name }
};
let publicUsages;
let privateUsages;
switch (name) {
case 'RSA-OAEP': {
publicUsages = getUsagesUnion(usageSet, 'encrypt', 'wrapKey');
privateUsages = getUsagesUnion(usageSet, 'decrypt', 'unwrapKey');
break;
}
default: {
publicUsages = getUsagesUnion(usageSet, 'verify');
privateUsages = getUsagesUnion(usageSet, 'sign');
break;
}
}
const publicKey =
new InternalCryptoKey(
pubKey,
algorithm,
publicUsages,
true);
const privateKey =
new InternalCryptoKey(
privKey,
algorithm,
privateUsages,
extractable);
resolve({ publicKey, privateKey });
});
});
}
function rsaExportKey(key, format) {
return jobPromise(new RSAKeyExportJob(
kCryptoJobAsync,
format,
key[kKeyObject][kHandle],
kRsaVariants[key.algorithm.name]));
}
async function rsaImportKey(
format,
keyData,
algorithm,
extractable,
keyUsages) {
const { hash } = algorithm;
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
const usagesSet = new SafeSet(keyUsages);
let keyObject;
switch (format) {
case 'node.keyObject': {
if (!isKeyObject(keyData))
throw new ERR_INVALID_ARG_TYPE('keyData', 'KeyObject', keyData);
if (keyData.type === 'secret')
throw lazyDOMException('Invalid key type', 'InvalidAccessException');
verifyAcceptableRsaKeyUse(algorithm.name, keyData.type, usagesSet);
keyObject = keyData;
break;
}
case 'spki': {
verifyAcceptableRsaKeyUse(algorithm.name, 'public', usagesSet);
keyObject = createPublicKey({
key: keyData,
format: 'der',
type: 'spki'
});
break;
}
case 'pkcs8': {
verifyAcceptableRsaKeyUse(algorithm.name, 'private', usagesSet);
keyObject = createPrivateKey({
key: keyData,
format: 'der',
type: 'pkcs8'
});
break;
}
case 'jwk': {
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid JWK keyData', 'DataError');
verifyAcceptableRsaKeyUse(
algorithm.name,
keyData.d !== undefined ? 'private' : 'public',
usagesSet);
if (keyData.kty !== 'RSA')
throw lazyDOMException('Invalid key type', 'DataError');
if (usagesSet.size > 0 && keyData.use !== undefined) {
const checkUse = algorithm.name === 'RSA-OAEP' ? 'enc' : 'sig';
if (keyData.use !== checkUse)
throw lazyDOMException('Invalid use type', 'DataError');
}
validateKeyOps(keyData.key_ops, usagesSet);
if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException('JWK is not extractable', 'DataError');
}
if (keyData.alg !== undefined) {
if (typeof keyData.alg !== 'string')
throw lazyDOMException('Invalid alg', 'DataError');
const hash =
normalizeHashName(keyData.alg, normalizeHashName.kContextWebCrypto);
if (hash !== algorithm.hash.name)
throw lazyDOMException('Hash mismatch', 'DataError');
}
const handle = new KeyObjectHandle();
const type = handle.initJwk(keyData);
if (type === undefined)
throw lazyDOMException('Invalid JWK keyData', 'DataError');
keyObject = type === kKeyTypePrivate ?
new PrivateKeyObject(handle) :
new PublicKeyObject(handle);
break;
}
default:
throw lazyDOMException(
`Unable to import RSA key with format ${format}`,
'NotSupportedError');
}
const {
modulusLength,
publicExponent,
} = keyObject[kHandle].keyDetail({});
return new InternalCryptoKey(keyObject, {
name: algorithm.name,
modulusLength,
publicExponent: new Uint8Array(publicExponent),
hash: algorithm.hash
}, keyUsages, extractable);
}
function rsaSignVerify(key, data, { saltLength }, signature) {
let padding;
if (key.algorithm.name === 'RSA-PSS') {
padding = RSA_PKCS1_PSS_PADDING;
// TODO(@jasnell): Validate maximum size of saltLength
// based on the key size:
// Math.ceil((keySizeInBits - 1)/8) - digestSizeInBytes - 2
validateUint32(saltLength, 'algorithm.saltLength');
}
const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
const type = mode === kSignJobModeSign ? 'private' : 'public';
if (key.type !== type)
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
return jobPromise(new SignJob(
kCryptoJobAsync,
signature === undefined ? kSignJobModeSign : kSignJobModeVerify,
key[kKeyObject][kHandle],
data,
normalizeHashName(key.algorithm.hash.name),
saltLength,
padding,
signature));
}
module.exports = {
rsaCipher: rsaOaepCipher,
rsaExportKey,
rsaImportKey,
rsaKeyGenerate,
rsaSignVerify,
};

View File

@ -1,17 +1,36 @@
'use strict';
const { AsyncWrap, Providers } = internalBinding('async_wrap');
const {
FunctionPrototypeCall,
Promise,
} = primordials;
const { Buffer } = require('buffer');
const { scrypt: _scrypt } = internalBinding('crypto');
const { validateInteger, validateUint32 } = require('internal/validators');
const {
ERR_CRYPTO_SCRYPT_INVALID_PARAMETER,
ERR_CRYPTO_SCRYPT_NOT_SUPPORTED,
ERR_INVALID_CALLBACK
} = require('internal/errors').codes;
ScryptJob,
kCryptoJobAsync,
kCryptoJobSync,
} = internalBinding('crypto');
const {
validateInteger,
validateUint32,
} = require('internal/validators');
const {
codes: {
ERR_CRYPTO_SCRYPT_INVALID_PARAMETER,
ERR_CRYPTO_SCRYPT_NOT_SUPPORTED,
ERR_INVALID_CALLBACK,
}
} = require('internal/errors');
const {
getArrayBufferOrView,
getDefaultEncoding,
getArrayBufferView,
lazyDOMException,
kKeyObject,
} = require('internal/crypto/util');
const defaults = {
@ -34,46 +53,44 @@ function scrypt(password, salt, keylen, options, callback = defaults) {
if (typeof callback !== 'function')
throw new ERR_INVALID_CALLBACK(callback);
const encoding = getDefaultEncoding();
const keybuf = Buffer.alloc(keylen);
const job = new ScryptJob(
kCryptoJobAsync, password, salt, N, r, p, maxmem, keylen);
const wrap = new AsyncWrap(Providers.SCRYPTREQUEST);
wrap.ondone = (ex) => { // Retains keybuf while request is in flight.
if (ex) return callback.call(wrap, ex);
if (encoding === 'buffer') return callback.call(wrap, null, keybuf);
callback.call(wrap, null, keybuf.toString(encoding));
const encoding = getDefaultEncoding();
job.ondone = (error, result) => {
if (error !== undefined)
return FunctionPrototypeCall(callback, job, error);
const buf = Buffer.from(result);
if (encoding === 'buffer')
return FunctionPrototypeCall(callback, job, null, buf);
FunctionPrototypeCall(callback, job, null, buf.toString(encoding));
};
handleError(_scrypt(keybuf, password, salt, N, r, p, maxmem, wrap));
job.run();
}
function scryptSync(password, salt, keylen, options = defaults) {
options = check(password, salt, keylen, options);
const { N, r, p, maxmem } = options;
({ password, salt, keylen } = options);
const keybuf = Buffer.alloc(keylen);
handleError(_scrypt(keybuf, password, salt, N, r, p, maxmem));
const job = new ScryptJob(
kCryptoJobSync, password, salt, N, r, p, maxmem, keylen);
const [err, result] = job.run();
if (err !== undefined)
throw err;
const buf = Buffer.from(result);
const encoding = getDefaultEncoding();
if (encoding === 'buffer') return keybuf;
return keybuf.toString(encoding);
}
function handleError(ex) {
if (ex === undefined)
return;
if (ex === null)
throw new ERR_CRYPTO_SCRYPT_INVALID_PARAMETER(); // Bad N, r, p, or maxmem.
throw ex; // Scrypt operation failed, exception object contains details.
return encoding === 'buffer' ? buf : buf.toString(encoding);
}
function check(password, salt, keylen, options) {
if (_scrypt === undefined)
if (ScryptJob === undefined)
throw new ERR_CRYPTO_SCRYPT_NOT_SUPPORTED();
password = getArrayBufferView(password, 'password');
salt = getArrayBufferView(salt, 'salt');
password = getArrayBufferOrView(password, 'password');
salt = getArrayBufferOrView(salt, 'salt');
validateUint32(keylen, 'keylen');
let { N, r, p, maxmem } = defaults;
@ -119,4 +136,45 @@ function check(password, salt, keylen, options) {
return { password, salt, keylen, N, r, p, maxmem };
}
module.exports = { scrypt, scryptSync };
async function scryptDeriveBits(algorithm, baseKey, length) {
validateUint32(length, 'length');
const {
N = 16384,
r = 8,
p = 1,
maxmem = 32 * 1024 * 1024,
encoding,
} = algorithm;
validateUint32(N, 'algorithm.N');
validateUint32(r, 'algorithm.r');
validateUint32(p, 'algorithm.p');
validateUint32(maxmem, 'algorithm.maxmem');
const salt = getArrayBufferOrView(algorithm.salt, 'algorithm.salt', encoding);
const raw = baseKey[kKeyObject].export();
let byteLength = 64; // the default
if (length !== undefined) {
if (length === 0)
throw lazyDOMException('length cannot be zero', 'OperationError');
if (length % 8) {
throw lazyDOMException(
'length must be a multiple of 8',
'OperationError');
}
byteLength = length / 8;
}
return new Promise((resolve, reject) => {
scrypt(raw, salt, byteLength, { N, r, p, maxmem }, (err, result) => {
if (err) return reject(err);
resolve(result);
});
});
}
module.exports = {
scrypt,
scryptSync,
scryptDeriveBits,
};

View File

@ -5,30 +5,43 @@ const {
} = primordials;
const {
ERR_CRYPTO_SIGN_KEY_REQUIRED,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
} = require('internal/errors').codes;
const { validateEncoding, validateString } = require('internal/validators');
codes: {
ERR_CRYPTO_SIGN_KEY_REQUIRED,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
}
} = require('internal/errors');
const {
validateEncoding,
validateString,
} = require('internal/validators');
const {
Sign: _Sign,
Verify: _Verify,
signOneShot: _signOneShot,
verifyOneShot: _verifyOneShot,
kSigEncDER,
kSigEncP1363,
signOneShot: _signOneShot,
verifyOneShot: _verifyOneShot
} = internalBinding('crypto');
const {
getArrayBufferOrView,
getDefaultEncoding,
kHandle,
getArrayBufferView,
} = require('internal/crypto/util');
const {
preparePublicOrPrivateKey,
preparePrivateKey,
preparePublicOrPrivateKey
} = require('internal/crypto/keys');
const { Writable } = require('stream');
const { isArrayBufferView } = require('internal/util/types');
const {
isArrayBufferView,
} = require('internal/util/types');
function Sign(algorithm, options) {
if (!(this instanceof Sign))
@ -121,13 +134,7 @@ function signOneShot(algorithm, data, key) {
if (algorithm != null)
validateString(algorithm, 'algorithm');
if (!isArrayBufferView(data)) {
throw new ERR_INVALID_ARG_TYPE(
'data',
['Buffer', 'TypedArray', 'DataView'],
data
);
}
data = getArrayBufferOrView(data, 'data');
if (!key)
throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();
@ -183,7 +190,7 @@ Verify.prototype.verify = function verify(options, signature, sigEncoding) {
// Options specific to (EC)DSA
const dsaSigEnc = getDSASignatureEncoding(options);
signature = getArrayBufferView(signature, 'signature', sigEncoding);
signature = getArrayBufferOrView(signature, 'signature', sigEncoding);
return this[kHandle].verify(data, format, type, passphrase, signature,
rsaPadding, pssSaltLength, dsaSigEnc);
@ -193,6 +200,8 @@ function verifyOneShot(algorithm, data, key, signature) {
if (algorithm != null)
validateString(algorithm, 'algorithm');
data = getArrayBufferOrView(data, 'data');
if (!isArrayBufferView(data)) {
throw new ERR_INVALID_ARG_TYPE(
'data',
@ -231,5 +240,5 @@ module.exports = {
Sign,
signOneShot,
Verify,
verifyOneShot
verifyOneShot,
};

View File

@ -1,6 +1,12 @@
'use strict';
const {
ArrayPrototypeIncludes,
ArrayPrototypePush,
FunctionPrototypeBind,
Promise,
StringPrototypeToLowerCase,
StringPrototypeToUpperCase,
Symbol,
} = primordials;
@ -12,27 +18,58 @@ const {
} = internalBinding('crypto');
const {
ENGINE_METHOD_ALL
} = internalBinding('constants').crypto;
crypto: {
ENGINE_METHOD_ALL
}
} = internalBinding('constants');
const normalizeHashName = require('internal/crypto/hashnames');
const {
hideStackFrames,
codes: {
ERR_CRYPTO_ENGINE_UNKNOWN,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_OUT_OF_RANGE,
}
} = require('internal/errors');
const { validateString } = require('internal/validators');
const {
validateArray,
validateString
} = require('internal/validators');
const { Buffer } = require('buffer');
const {
cachedResult,
filterDuplicateStrings
filterDuplicateStrings,
} = require('internal/util');
const {
isArrayBufferView
isArrayBufferView,
isAnyArrayBuffer,
} = require('internal/util/types');
const kHandle = Symbol('kHandle');
const kKeyObject = Symbol('kKeyObject');
const lazyRequireCache = {};
function lazyRequire(name) {
let ret = lazyRequireCache[name];
if (ret === undefined)
ret = lazyRequireCache[name] = require(name);
return ret;
}
let DOMException;
const lazyDOMException = hideStackFrames((message, name) => {
if (DOMException === undefined)
DOMException = internalBinding('messaging').DOMException;
return new DOMException(message, name);
});
var defaultEncoding = 'buffer';
@ -74,7 +111,9 @@ function setEngine(id, flags) {
throw new ERR_CRYPTO_ENGINE_UNKNOWN(id);
}
const getArrayBufferView = hideStackFrames((buffer, name, encoding) => {
const getArrayBufferOrView = hideStackFrames((buffer, name, encoding) => {
if (isAnyArrayBuffer(buffer))
return buffer;
if (typeof buffer === 'string') {
if (encoding === 'buffer')
encoding = 'utf8';
@ -83,21 +122,284 @@ const getArrayBufferView = hideStackFrames((buffer, name, encoding) => {
if (!isArrayBufferView(buffer)) {
throw new ERR_INVALID_ARG_TYPE(
name,
['string', 'Buffer', 'TypedArray', 'DataView'],
[
'string',
'ArrayBuffer',
'Buffer',
'TypedArray',
'DataView'
],
buffer
);
}
return buffer;
});
// The maximum buffer size that we'll support in the WebCrypto impl
const kMaxBufferLength = (2 ** 31) - 1;
// The EC named curves that we currently support via the Web Crypto API.
const kNamedCurveAliases = {
'P-256': 'prime256v1',
'P-384': 'secp384r1',
'P-521': 'secp521r1',
};
const kAesKeyLengths = [128, 192, 256];
// These are the only algorithms we currently support
// via the Web Crypto API
const kAlgorithms = [
'rsassa-pkcs1-v1_5',
'rsa-pss',
'rsa-oaep',
'ecdsa',
'ecdh',
'aes-ctr',
'aes-cbc',
'aes-gcm',
'aes-kw',
'hmac',
'sha-1',
'sha-256',
'sha-384',
'sha-512',
'hkdf',
'pbkdf2',
// Following here are Node.js specific extensions. All
// should be prefixed with 'node-'
'node-dsa',
'node-dh',
'node-scrypt'
];
// These are the only export and import formats we currently
// support via the Web Crypto API
const kExportFormats = [
'raw',
'pkcs8',
'spki',
'jwk',
'node.keyObject'];
// These are the only hash algorithms we currently support via
// the Web Crypto API.
const kHashTypes = [
'SHA-1',
'SHA-256',
'SHA-384',
'SHA-512'
];
function validateMaxBufferLength(data, name) {
if (data.byteLength > kMaxBufferLength) {
throw lazyDOMException(
`${name} must be less than ${kMaxBufferLength + 1} bits`,
'OperationError');
}
}
function normalizeAlgorithm(algorithm, label = 'algorithm') {
if (algorithm != null) {
if (typeof algorithm === 'string')
algorithm = { name: algorithm };
if (typeof algorithm === 'object') {
const { name } = algorithm;
let hash;
if (typeof name !== 'string' ||
!ArrayPrototypeIncludes(
kAlgorithms,
StringPrototypeToLowerCase(name))) {
throw lazyDOMException('Unrecognized name.', 'NotSupportedError');
}
if (algorithm.hash !== undefined) {
hash = normalizeAlgorithm(algorithm.hash, 'algorithm.hash');
if (!ArrayPrototypeIncludes(kHashTypes, hash.name))
throw lazyDOMException('Unrecognized name.', 'NotSupportedError');
}
return { ...algorithm, name: StringPrototypeToUpperCase(name), hash };
}
}
throw lazyDOMException('Unrecognized name.', 'NotSupportedError');
}
function hasAnyNotIn(set, ...check) {
for (const s of set)
if (!ArrayPrototypeIncludes(check, s))
return true;
return false;
}
function validateBitLength(length, name, required = false) {
if (length !== undefined || required) {
if (typeof length !== 'number')
throw new ERR_INVALID_ARG_TYPE(name, 'number', length);
if (length < 0)
throw new ERR_OUT_OF_RANGE(name, '> 0');
if (length % 8) {
throw new ERR_INVALID_ARG_VALUE(
name,
length,
'must be a multiple of 8');
}
}
}
function validateByteLength(buf, name, target) {
if (buf.byteLength !== target) {
throw lazyDOMException(
`${name} must contain exactly ${target} bytes`,
'OperationError');
}
}
const validateByteSource = hideStackFrames((val, name) => {
val = toBuf(val);
if (isAnyArrayBuffer(val) || isArrayBufferView(val))
return;
throw new ERR_INVALID_ARG_TYPE(
name,
[
'string',
'ArrayBuffer',
'TypedArray',
'DataView',
'Buffer'
],
val);
});
function onDone(resolve, reject, err, result) {
if (err) return reject(err);
resolve(result);
}
function jobPromise(job) {
return new Promise((resolve, reject) => {
job.ondone = FunctionPrototypeBind(onDone, job, resolve, reject);
job.run();
});
}
// In WebCrypto, the publicExponent option in RSA is represented as a
// WebIDL "BigInteger"... that is, a Uint8Array that allows an arbitrary
// number of leading zero bits. Our conventional APIs for reading
// an unsigned int from a Buffer are not adequate. The implementation
// here is adapted from the chromium implementation here:
// https://github.com/chromium/chromium/blob/master/third_party/blink/public/platform/web_crypto_algorithm_params.h, but ported to JavaScript
// Returns undefined if the conversion was unsuccessful.
function bigIntArrayToUnsignedInt(input) {
let result = 0;
for (let n = 0; n < input.length; ++n) {
const n_reversed = input.length - n - 1;
if (n_reversed >= 4 && input[n])
return; // Too large
result |= input[n] << 8 * n_reversed;
}
return result;
}
function getStringOption(options, key) {
let value;
if (options && (value = options[key]) != null)
validateString(value, `options.${key}`);
return value;
}
function getUsagesUnion(usageSet, ...usages) {
const newset = [];
for (let n = 0; n < usages.length; n++) {
if (usageSet.has(usages[n]))
ArrayPrototypePush(newset, usages[n]);
}
return newset;
}
function getHashLength(name) {
switch (name) {
case 'SHA-1': return 160;
case 'SHA-256': return 256;
case 'SHA-384': return 384;
case 'SHA-512': return 512;
}
}
const kKeyOps = {
sign: 1,
verify: 2,
encrypt: 3,
decrypt: 4,
wrapKey: 5,
unwrapKey: 6,
deriveKey: 7,
deriveBits: 8,
};
function validateKeyOps(keyOps, usagesSet) {
if (keyOps === undefined) return;
validateArray(keyOps, 'keyData.key_ops');
let flags = 0;
for (let n = 0; n < keyOps.length; n++) {
const op = keyOps[n];
const op_flag = kKeyOps[op];
// Skipping unknown key ops
if (op_flag === undefined)
continue;
// Have we seen it already? if so, error
if (flags & (1 << op_flag))
throw lazyDOMException('Duplicate key operation', 'DataError');
flags |= (1 << op_flag);
// TODO(@jasnell): RFC7517 section 4.3 strong recommends validating
// key usage combinations. Specifically, it says that unrelated key
// ops SHOULD NOT be used together. We're not yet validating that here.
}
if (usagesSet !== undefined) {
for (const use of usagesSet) {
if (!ArrayPrototypeIncludes(keyOps, use)) {
throw lazyDOMException(
'Key operations and usage mismatch',
'DataError');
}
}
}
}
module.exports = {
getArrayBufferView,
getArrayBufferOrView,
getCiphers,
getCurves,
getDefaultEncoding,
getHashes,
kHandle,
kKeyObject,
setDefaultEncoding,
setEngine,
toBuf
toBuf,
kHashTypes,
kNamedCurveAliases,
kAesKeyLengths,
kExportFormats,
normalizeAlgorithm,
normalizeHashName,
hasAnyNotIn,
lazyDOMException,
validateBitLength,
validateByteLength,
validateByteSource,
validateKeyOps,
jobPromise,
lazyRequire,
validateMaxBufferLength,
bigIntArrayToUnsignedInt,
getStringOption,
getUsagesUnion,
getHashLength,
};

View File

@ -0,0 +1,760 @@
'use strict';
const {
ArrayPrototypeIncludes,
JSONParse,
JSONStringify,
ObjectDefineProperties,
SafeSet,
SymbolToStringTag,
} = primordials;
const {
kWebCryptoKeyFormatRaw,
kWebCryptoKeyFormatPKCS8,
kWebCryptoKeyFormatSPKI,
kWebCryptoCipherEncrypt,
kWebCryptoCipherDecrypt,
} = internalBinding('crypto');
const {
validateArray,
validateBoolean,
validateObject,
validateOneOf,
validateString,
} = require('internal/validators');
const { TextDecoder } = require('internal/encoding');
const {
codes: {
ERR_INVALID_ARG_TYPE,
}
} = require('internal/errors');
const {
CryptoKey,
InternalCryptoKey,
createSecretKey,
isCryptoKey,
isKeyObject,
} = require('internal/crypto/keys');
const {
asyncDigest,
} = require('internal/crypto/hash');
const {
getArrayBufferOrView,
hasAnyNotIn,
lazyDOMException,
lazyRequire,
normalizeAlgorithm,
normalizeHashName,
validateMaxBufferLength,
kExportFormats,
kHandle,
kKeyObject,
} = require('internal/crypto/util');
const {
getRandomValues,
} = require('internal/crypto/random');
async function generateKey(
algorithm,
extractable,
keyUsages) {
algorithm = normalizeAlgorithm(algorithm);
validateBoolean(extractable, 'extractable');
validateArray(keyUsages, 'keyUsages');
switch (algorithm.name) {
case 'RSASSA-PKCS1-V1_5':
// Fall through
case 'RSA-PSS':
// Fall through
case 'RSA-OAEP':
return lazyRequire('internal/crypto/rsa')
.rsaKeyGenerate(algorithm, extractable, keyUsages);
case 'ECDSA':
// Fall through
case 'ECDH':
return lazyRequire('internal/crypto/ec')
.ecGenerateKey(algorithm, extractable, keyUsages);
case 'HMAC':
return lazyRequire('internal/crypto/mac')
.hmacGenerateKey(algorithm, extractable, keyUsages);
case 'AES-CTR':
// Fall through
case 'AES-CBC':
// Fall through
case 'AES-GCM':
// Fall through
case 'AES-KW':
return lazyRequire('internal/crypto/aes')
.aesGenerateKey(algorithm, extractable, keyUsages);
// Following are Node.js specific extensions. Names must be prefixed
// with the `NODE-`
case 'NODE-DSA':
return lazyRequire('internal/crypto/dsa')
.dsaGenerateKey(algorithm, extractable, keyUsages);
case 'NODE-DH':
return lazyRequire('internal/crypto/diffiehellman')
.dhGenerateKey(algorithm, extractable, keyUsages);
default:
throw lazyDOMException('Unrecognized name.');
}
}
async function deriveBits(algorithm, baseKey, length) {
algorithm = normalizeAlgorithm(algorithm);
if (!isCryptoKey(baseKey))
throw new ERR_INVALID_ARG_TYPE('baseKey', 'CryptoKey', baseKey);
if (!ArrayPrototypeIncludes(baseKey.usages, 'deriveBits')) {
throw lazyDOMException(
'baseKey does not have deriveBits usage',
'InvalidAccessError');
}
if (baseKey.algorithm.name !== algorithm.name)
throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError');
switch (algorithm.name) {
case 'ECDH':
return lazyRequire('internal/crypto/diffiehellman')
.asyncDeriveBitsECDH(algorithm, baseKey, length);
case 'HKDF':
return lazyRequire('internal/crypto/hkdf')
.hkdfDeriveBits(algorithm, baseKey, length);
case 'PBKDF2':
return lazyRequire('internal/crypto/pbkdf2')
.pbkdf2DeriveBits(algorithm, baseKey, length);
case 'NODE-SCRYPT':
return lazyRequire('internal/crypto/scrypt')
.asyncScryptDeriveBits(algorithm, baseKey, length);
case 'NODE-DH':
return lazyRequire('internal/crypto/diffiehellman')
.asyncDeriveBitsDH(algorithm, baseKey, length);
}
throw lazyDOMException('Unrecognized name.');
}
async function deriveKey(
algorithm,
baseKey,
derivedKeyAlgorithm,
extractable,
keyUsages) {
algorithm = normalizeAlgorithm(algorithm);
derivedKeyAlgorithm = normalizeAlgorithm(derivedKeyAlgorithm);
if (!isCryptoKey(baseKey))
throw new ERR_INVALID_ARG_TYPE('baseKey', 'CryptoKey', baseKey);
if (!ArrayPrototypeIncludes(baseKey.usages, 'deriveKey')) {
throw lazyDOMException(
'baseKey does not have deriveKey usage',
'InvalidAccessError');
}
if (baseKey.algorithm.name !== algorithm.name)
throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError');
validateObject(derivedKeyAlgorithm, 'derivedKeyAlgorithm');
validateBoolean(extractable, 'extractable');
validateArray(keyUsages, 'keyUsages');
const { length } = derivedKeyAlgorithm;
let bits;
switch (algorithm.name) {
case 'ECDH':
bits = await lazyRequire('internal/crypto/diffiehellman')
.asyncDeriveBitsECDH(algorithm, baseKey, length);
break;
case 'HKDF':
bits = await lazyRequire('internal/crypto/hkdf')
.hkdfDeriveBits(algorithm, baseKey, length);
break;
case 'PBKDF2':
bits = await lazyRequire('internal/crypto/pbkdf2')
.pbkdf2DeriveBits(algorithm, baseKey, length);
break;
case 'NODE-SCRYPT':
bits = await lazyRequire('internal/crypto/scrypt')
.scryptDeriveBits(algorithm, baseKey, length);
break;
case 'NODE-DH':
bits = await lazyRequire('internal/crypto/diffiehellman')
.asyncDeriveBitsDH(algorithm, baseKey, length);
break;
default:
throw lazyDOMException('Unrecognized name.');
}
return importKey('raw', bits, derivedKeyAlgorithm, extractable, keyUsages);
}
async function exportKeySpki(key) {
switch (key.algorithm.name) {
case 'RSASSA-PKCS1-V1_5':
// Fall through
case 'RSA-PSS':
// Fall through
case 'RSA-OAEP':
if (key.type === 'public') {
return lazyRequire('internal/crypto/rsa')
.rsaExportKey(key, kWebCryptoKeyFormatSPKI);
}
break;
case 'ECDSA':
// Fall through
case 'ECDH':
if (key.type === 'public') {
return lazyRequire('internal/crypto/ec')
.ecExportKey(key, kWebCryptoKeyFormatSPKI);
}
break;
case 'NODE-DSA':
if (key.type === 'public') {
return lazyRequire('internal/crypto/dsa')
.dsaExportKey(key, kWebCryptoKeyFormatSPKI);
}
break;
case 'NODE-DH':
if (key.type === 'public') {
return lazyRequire('internal/crypto/diffiehellman')
.dhExportKey(key, kWebCryptoKeyFormatSPKI);
}
break;
}
throw lazyDOMException(
`Unable to export a raw ${key.algorithm.name} ${key.type} key`,
'InvalidAccessError');
}
async function exportKeyPkcs8(key) {
switch (key.algorithm.name) {
case 'RSASSA-PKCS1-V1_5':
// Fall through
case 'RSA-PSS':
// Fall through
case 'RSA-OAEP':
if (key.type === 'private') {
return lazyRequire('internal/crypto/rsa')
.rsaExportKey(key, kWebCryptoKeyFormatPKCS8);
}
break;
case 'ECDSA':
// Fall through
case 'ECDH':
if (key.type === 'private') {
return lazyRequire('internal/crypto/ec')
.ecExportKey(key, kWebCryptoKeyFormatPKCS8);
}
break;
case 'NODE-DSA':
if (key.type === 'private') {
return lazyRequire('internal/crypto/dsa')
.dsaExportKey(key, kWebCryptoKeyFormatPKCS8);
}
break;
case 'NODE-DH':
if (key.type === 'private') {
return lazyRequire('internal/crypto/diffiehellman')
.dhExportKey(key, kWebCryptoKeyFormatPKCS8);
}
break;
}
throw lazyDOMException(
`Unable to export a pkcs8 ${key.algorithm.name} ${key.type} key`,
'InvalidAccessError');
}
async function exportKeyRaw(key) {
switch (key.algorithm.name) {
case 'ECDSA':
// Fall through
case 'ECDH':
if (key.type === 'public') {
return lazyRequire('internal/crypto/ec')
.ecExportKey(key, kWebCryptoKeyFormatRaw);
}
break;
case 'AES-CTR':
// Fall through
case 'AES-CBC':
// Fall through
case 'AES-GCM':
// Fall through
case 'AES-KW':
// Fall through
case 'HMAC':
return key[kKeyObject].export().buffer;
}
throw lazyDOMException(
`Unable to export a raw ${key.algorithm.name} ${key.type} key`,
'InvalidAccessError');
}
async function exportKeyJWK(key) {
const jwk = key[kKeyObject][kHandle].exportJwk({
key_ops: key.usages,
ext: key.extractable,
});
switch (key.algorithm.name) {
case 'RSASSA-PKCS1-V1_5':
jwk.alg = normalizeHashName(
key.algorithm.hash.name,
normalizeHashName.kContextJwkRsa);
return jwk;
case 'RSA-PSS':
jwk.alg = normalizeHashName(
key.algorithm.hash.name,
normalizeHashName.kContextJwkRsaPss);
return jwk;
case 'RSA-OAEP':
jwk.alg = normalizeHashName(
key.algorithm.hash.name,
normalizeHashName.kContextJwkRsaOaep);
return jwk;
case 'ECDSA':
// Fall through
case 'ECDH':
jwk.crv = key.algorithm.namedCurve;
return jwk;
case 'AES-CTR':
// Fall through
case 'AES-CBC':
// Fall through
case 'AES-GCM':
// Fall through
case 'AES-KW':
jwk.alg = lazyRequire('internal/crypto/aes')
.getAlgorithmName(key.algorithm.name, key.algorithm.length);
return jwk;
case 'HMAC':
jwk.alg = normalizeHashName(
key.algorithm.hash.name,
normalizeHashName.kContextJwkHmac);
return jwk;
case 'NODE-DSA':
jwk.alg = normalizeHashName(
key.algorithm.hash.name,
normalizeHashName.kContextJwkDsa);
return jwk;
default:
// Fall through
}
throw lazyDOMException('Not yet supported', 'NotSupportedError');
}
async function exportKey(format, key) {
validateString(format, 'format');
validateOneOf(format, 'format', kExportFormats);
if (!isCryptoKey(key))
throw new ERR_INVALID_ARG_TYPE('key', 'CryptoKey', key);
if (!key.extractable)
throw lazyDOMException('key is not extractable', 'InvalidAccessException');
switch (format) {
case 'node.keyObject': return key[kKeyObject];
case 'spki': return exportKeySpki(key);
case 'pkcs8': return exportKeyPkcs8(key);
case 'jwk': return exportKeyJWK(key);
case 'raw': return exportKeyRaw(key);
}
throw lazyDOMException(
'Export format is unsupported', 'NotSupportedError');
}
async function importGenericSecretKey(
{ name, length },
format,
keyData,
extractable,
keyUsages) {
const usagesSet = new SafeSet(keyUsages);
if (extractable)
throw lazyDOMException(`${name} keys are not extractable`, 'SyntaxError');
if (hasAnyNotIn(usagesSet, 'deriveKey', 'deriveBits')) {
throw lazyDOMException(
`Unsupported key usage for a ${name} key`,
'SyntaxError');
}
switch (format) {
case 'node.keyObject': {
if (!isKeyObject(keyData))
throw new ERR_INVALID_ARG_TYPE('keyData', 'KeyObject', keyData);
if (keyData.type === 'secret')
return new InternalCryptoKey(keyData, { name }, keyUsages, extractable);
break;
}
case 'raw':
if (hasAnyNotIn(usagesSet, 'deriveKey', 'deriveBits')) {
throw lazyDOMException(
`Unsupported key usage for a ${name} key`,
'SyntaxError');
}
const checkLength = keyData.byteLength * 8;
if (checkLength === 0 || length === 0)
throw lazyDOMException('Zero-length key is not supported', 'DataError');
// The Web Crypto spec allows for key lengths that are not multiples of
// 8. We don't. Our check here is stricter than that defined by the spec
// in that we require that algorithm.length match keyData.length * 8 if
// algorithm.length is specified.
if (length !== undefined && length !== checkLength) {
throw lazyDOMException('Invalid key length', 'DataError');
}
const keyObject = createSecretKey(keyData);
return new InternalCryptoKey(keyObject, { name }, keyUsages, false);
}
throw lazyDOMException(
`Unable to import ${name} key with format ${format}`,
'NotSupportedError');
}
async function importKey(
format,
keyData,
algorithm,
extractable,
keyUsages) {
validateString(format, 'format');
validateOneOf(format, 'format', kExportFormats);
if (format !== 'node.keyObject' && format !== 'jwk')
keyData = getArrayBufferOrView(keyData, 'keyData');
algorithm = normalizeAlgorithm(algorithm);
validateBoolean(extractable, 'extractable');
validateArray(keyUsages, 'keyUsages');
switch (algorithm.name) {
case 'RSASSA-PKCS1-V1_5':
// Fall through
case 'RSA-PSS':
// Fall through
case 'RSA-OAEP':
return lazyRequire('internal/crypto/rsa')
.rsaImportKey(format, keyData, algorithm, extractable, keyUsages);
case 'ECDSA':
// Fall through
case 'ECDH':
return lazyRequire('internal/crypto/ec')
.ecImportKey(format, keyData, algorithm, extractable, keyUsages);
case 'HMAC':
return lazyRequire('internal/crypto/mac')
.hmacImportKey(format, keyData, algorithm, extractable, keyUsages);
case 'AES-CTR':
// Fall through
case 'AES-CBC':
// Fall through
case 'AES-GCM':
// Fall through
case 'AES-KW':
return lazyRequire('internal/crypto/aes')
.aesImportKey(algorithm, format, keyData, extractable, keyUsages);
case 'HKDF':
// Fall through
case 'NODE-SCRYPT':
// Fall through
case 'PBKDF2':
return importGenericSecretKey(
algorithm,
format,
keyData,
extractable,
keyUsages);
case 'NODE-DSA':
return lazyRequire('internal/crypto/dsa')
.dsaImportKey(format, keyData, algorithm, extractable, keyUsages);
case 'NODE-DH':
return lazyRequire('internal/crypto/diffiehellman')
.dhImportKey(format, keyData, algorithm, extractable, keyUsages);
}
throw lazyDOMException('Unrecognized name.', 'NotSupportedError');
}
// subtle.wrapKey() is essentially a subtle.exportKey() followed
// by a subtle.encrypt().
async function wrapKey(format, key, wrappingKey, algorithm) {
algorithm = normalizeAlgorithm(algorithm);
let keyData = await exportKey(format, key);
if (format === 'jwk') {
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid exported JWK key', 'DataError');
const ec = new TextEncoder();
const raw = JSONStringify(keyData);
keyData = ec.encode(raw + ' '.repeat(8 - (raw.length % 8)));
}
return cipherOrWrap(
kWebCryptoCipherEncrypt,
algorithm,
wrappingKey,
keyData,
'wrapKey');
}
// subtle.unwrapKey() is essentially a subtle.decrypt() followed
// by a subtle.importKey().
async function unwrapKey(
format,
wrappedKey,
unwrappingKey,
unwrapAlgo,
unwrappedKeyAlgo,
extractable,
keyUsages) {
wrappedKey = getArrayBufferOrView(wrappedKey, 'wrappedKey');
let keyData = await cipherOrWrap(
kWebCryptoCipherDecrypt,
normalizeAlgorithm(unwrapAlgo),
unwrappingKey,
wrappedKey,
'unwrapKey');
if (format === 'jwk') {
// The fatal: true option is only supported in builds that have ICU.
const options = process.versions.icu !== undefined ?
{ fatal: true } : undefined;
const dec = new TextDecoder('utf-8', options);
try {
keyData = JSONParse(dec.decode(keyData));
} catch {
throw lazyDOMException('Invalid imported JWK key', 'DataError');
}
}
return importKey(format, keyData, unwrappedKeyAlgo, extractable, keyUsages);
}
function signVerify(algorithm, key, data, signature) {
algorithm = normalizeAlgorithm(algorithm);
if (!isCryptoKey(key))
throw new ERR_INVALID_ARG_TYPE('key', 'CryptoKey', key);
data = getArrayBufferOrView(data, 'data');
let usage = 'sign';
if (signature !== undefined) {
signature = getArrayBufferOrView(signature, 'signature');
usage = 'verify';
}
if (!ArrayPrototypeIncludes(key.usages, usage) ||
algorithm.name !== key.algorithm.name) {
throw lazyDOMException(
`Unable to use this key to ${usage}`,
'InvalidAccessError');
}
switch (algorithm.name) {
case 'RSA-PSS':
// Fall through
case 'RSASSA-PKCS1-V1_5':
return lazyRequire('internal/crypto/rsa')
.rsaSignVerify(key, data, algorithm, signature);
case 'ECDSA':
return lazyRequire('internal/crypto/ec')
.ecdsaSignVerify(key, data, algorithm, signature);
case 'HMAC':
return lazyRequire('internal/crypto/mac')
.hmacSignVerify(key, data, algorithm, signature);
// The following are Node.js specific extensions. They must begin with
// the `NODE-` prefix
case 'NODE-DSA':
return lazyRequire('internal/crypto/dsa')
.dsaSignVerify(key, data, algorithm, signature);
}
throw lazyDOMException('Unrecognized named.', 'NotSupportedError');
}
async function sign(algorithm, key, data) {
return signVerify(algorithm, key, data);
}
async function verify(algorithm, key, signature, data) {
return signVerify(algorithm, key, data, signature);
}
async function cipherOrWrap(mode, algorithm, key, data, op) {
algorithm = normalizeAlgorithm(algorithm);
// We use a Node.js style error here instead of a DOMException because
// the WebCrypto spec is not specific what kind of error is to be thrown
// in this case. Both Firefox and Chrome throw simple TypeErrors here.
if (!isCryptoKey(key))
throw new ERR_INVALID_ARG_TYPE('key', 'CryptoKey', key);
// The key algorithm and cipher algorithm must match, and the
// key must have the proper usage.
if (key.algorithm.name !== algorithm.name ||
!ArrayPrototypeIncludes(key.usages, op)) {
throw lazyDOMException(
'The requested operation is not valid for the provided key',
'InvalidAccessError');
}
// For the Web Crypto API, the input data can be any ArrayBuffer,
// TypedArray, or DataView.
data = getArrayBufferOrView(data, 'data');
// While WebCrypto allows for larger input buffer sizes, we limit
// those to sizes that can fit within uint32_t because of limitations
// in the OpenSSL API.
validateMaxBufferLength(data, 'data');
switch (algorithm.name) {
case 'RSA-OAEP':
return lazyRequire('internal/crypto/rsa')
.rsaCipher(mode, key, data, algorithm);
case 'AES-CTR':
// Fall through
case 'AES-CBC':
// Fall through
case 'AES-GCM':
return lazyRequire('internal/crypto/aes')
.aesCipher(mode, key, data, algorithm);
case 'AES-KW':
if (op === 'wrapKey' || op === 'unwrapKey') {
return lazyRequire('internal/crypto/aes')
.aesCipher(mode, key, data, algorithm);
}
}
throw lazyDOMException('Unrecognized name.', 'NotSupportedError');
}
async function encrypt(algorithm, key, data) {
return cipherOrWrap(kWebCryptoCipherEncrypt, algorithm, key, data, 'encrypt');
}
async function decrypt(algorithm, key, data) {
return cipherOrWrap(kWebCryptoCipherDecrypt, algorithm, key, data, 'decrypt');
}
// The SubtleCrypto and Crypto classes are defined as part of the
// Web Crypto API standard: https://www.w3.org/TR/WebCryptoAPI/
class SubtleCrypto {}
const subtle = new SubtleCrypto();
class Crypto {}
const crypto = new Crypto();
ObjectDefineProperties(
Crypto.prototype, {
[SymbolToStringTag]: {
enumerable: false,
configurable: true,
writable: false,
value: 'Crypto',
},
subtle: {
enumerable: true,
configurable: false,
value: subtle,
},
getRandomValues: {
enumerable: true,
configurable: true,
writable: true,
value: getRandomValues,
},
CryptoKey: {
enumerable: true,
configurable: true,
writable: true,
value: CryptoKey,
}
});
ObjectDefineProperties(
SubtleCrypto.prototype, {
[SymbolToStringTag]: {
enumerable: false,
configurable: true,
writable: false,
value: 'SubtleCrypto',
},
encrypt: {
enumerable: true,
configurable: true,
writable: true,
value: encrypt,
},
decrypt: {
enumerable: true,
configurable: true,
writable: true,
value: decrypt,
},
sign: {
enumerable: true,
configurable: true,
writable: true,
value: sign,
},
verify: {
enumerable: true,
configurable: true,
writable: true,
value: verify,
},
digest: {
enumerable: true,
configurable: true,
writable: true,
value: asyncDigest,
},
generateKey: {
enumerable: true,
configurable: true,
writable: true,
value: generateKey,
},
deriveKey: {
enumerable: true,
configurable: true,
writable: true,
value: deriveKey,
},
deriveBits: {
enumerable: true,
configurable: true,
writable: true,
value: deriveBits,
},
importKey: {
enumerable: true,
configurable: true,
writable: true,
value: importKey,
},
exportKey: {
enumerable: true,
configurable: true,
writable: true,
value: exportKey,
},
wrapKey: {
enumerable: true,
configurable: true,
writable: true,
value: wrapKey,
},
unwrapKey: {
enumerable: true,
configurable: true,
writable: true,
value: unwrapKey,
}
});
module.exports = crypto;

View File

@ -123,6 +123,7 @@ primordials.SafePromise = makeSafe(
'BigInt64Array',
'BigUint64Array',
'Boolean',
'DataView',
'Date',
'Error',
'EvalError',

View File

@ -120,17 +120,25 @@
'lib/internal/cluster/worker.js',
'lib/internal/console/constructor.js',
'lib/internal/console/global.js',
'lib/internal/crypto/aes.js',
'lib/internal/crypto/certificate.js',
'lib/internal/crypto/cipher.js',
'lib/internal/crypto/diffiehellman.js',
'lib/internal/crypto/dsa.js',
'lib/internal/crypto/ec.js',
'lib/internal/crypto/hash.js',
'lib/internal/crypto/hashnames.js',
'lib/internal/crypto/hkdf.js',
'lib/internal/crypto/keygen.js',
'lib/internal/crypto/keys.js',
'lib/internal/crypto/mac.js',
'lib/internal/crypto/pbkdf2.js',
'lib/internal/crypto/random.js',
'lib/internal/crypto/rsa.js',
'lib/internal/crypto/scrypt.js',
'lib/internal/crypto/sig.js',
'lib/internal/crypto/util.js',
'lib/internal/crypto/webcrypto.js',
'lib/internal/constants.js',
'lib/internal/dgram.js',
'lib/internal/dns/promises.js',
@ -902,14 +910,54 @@
} ],
[ 'node_use_openssl=="true"', {
'sources': [
'src/crypto/crypto_common.cc',
'src/crypto/crypto_aes.cc',
'src/crypto/crypto_bio.cc',
'src/crypto/crypto_common.cc',
'src/crypto/crypto_dsa.cc',
'src/crypto/crypto_hkdf.cc',
'src/crypto/crypto_pbkdf2.cc',
'src/crypto/crypto_sig.cc',
'src/crypto/crypto_timing.cc',
'src/crypto/crypto_cipher.cc',
'src/crypto/crypto_context.cc',
'src/crypto/crypto_ecdh.cc',
'src/crypto/crypto_hmac.cc',
'src/crypto/crypto_random.cc',
'src/crypto/crypto_rsa.cc',
'src/crypto/crypto_spkac.cc',
'src/crypto/crypto_util.cc',
'src/crypto/crypto_clienthello.cc',
'src/crypto/crypto_common.h',
'src/crypto/crypto_dh.cc',
'src/crypto/crypto_hash.cc',
'src/crypto/crypto_keys.cc',
'src/crypto/crypto_keygen.cc',
'src/crypto/crypto_scrypt.cc',
'src/crypto/crypto_ssl.cc',
'src/crypto/crypto_aes.cc',
'src/crypto/crypto_bio.h',
'src/crypto/crypto_clienthello.h',
'src/crypto/crypto_clienthello-inl.h',
'src/crypto/crypto_dh.h',
'src/crypto/crypto_groups.h',
'src/crypto/crypto_hmac.h',
'src/crypto/crypto_rsa.h',
'src/crypto/crypto_spkac.h',
'src/crypto/crypto_util.h',
'src/crypto/crypto_cipher.h',
'src/crypto/crypto_common.h',
'src/crypto/crypto_dsa.h',
'src/crypto/crypto_hash.h',
'src/crypto/crypto_keys.h',
'src/crypto/crypto_keygen.h',
'src/crypto/crypto_scrypt.h',
'src/crypto/crypto_ssl.h',
'src/crypto/crypto_clienthello.h',
'src/crypto/crypto_context.h',
'src/crypto/crypto_ecdh.h',
'src/crypto/crypto_hkdf.h',
'src/crypto/crypto_pbkdf2.h',
'src/crypto/crypto_sig.h',
'src/crypto/crypto_random.h',
'src/crypto/crypto_timing.h',
'src/node_crypto.cc',
'src/node_crypto.h',
'src/tls_wrap.cc',

View File

@ -85,9 +85,16 @@ namespace node {
#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V) \
V(PBKDF2REQUEST) \
V(KEYPAIRGENREQUEST) \
V(KEYGENREQUEST) \
V(KEYEXPORTREQUEST) \
V(CIPHERREQUEST) \
V(DERIVEBITSREQUEST) \
V(HASHREQUEST) \
V(RANDOMBYTESREQUEST) \
V(SCRYPTREQUEST) \
V(TLSWRAP)
V(SIGNREQUEST) \
V(TLSWRAP) \
V(VERIFYREQUEST)
#else
#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V)
#endif // HAVE_OPENSSL

View File

@ -119,12 +119,13 @@ size_t base64_decode(char* const dst, const size_t dstlen,
inline size_t base64_encode(const char* src,
size_t slen,
char* dst,
size_t dlen) {
size_t dlen,
Base64Mode mode) {
// We know how much we'll write, just make sure that there's space.
CHECK(dlen >= base64_encoded_size(slen) &&
CHECK(dlen >= base64_encoded_size(slen, mode) &&
"not enough space provided for base64 encode");
dlen = base64_encoded_size(slen);
dlen = base64_encoded_size(slen, mode);
unsigned a;
unsigned b;
@ -133,9 +134,7 @@ inline size_t base64_encode(const char* src,
unsigned k;
unsigned n;
static const char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
const char* table = base64_select_table(mode);
i = 0;
k = 0;
@ -160,8 +159,10 @@ inline size_t base64_encode(const char* src,
a = src[i + 0] & 0xff;
dst[k + 0] = table[a >> 2];
dst[k + 1] = table[(a & 3) << 4];
dst[k + 2] = '=';
dst[k + 3] = '=';
if (mode == Base64Mode::NORMAL) {
dst[k + 2] = '=';
dst[k + 3] = '=';
}
break;
case 2:
a = src[i + 0] & 0xff;
@ -169,7 +170,8 @@ inline size_t base64_encode(const char* src,
dst[k + 0] = table[a >> 2];
dst[k + 1] = table[((a & 3) << 4) | (b >> 4)];
dst[k + 2] = table[(b & 0x0f) << 2];
dst[k + 3] = '=';
if (mode == Base64Mode::NORMAL)
dst[k + 3] = '=';
break;
}

View File

@ -5,13 +5,40 @@
#include "util.h"
#include <cmath>
#include <cstddef>
#include <cstdint>
namespace node {
//// Base 64 ////
static inline constexpr size_t base64_encoded_size(size_t size) {
return ((size + 2) / 3 * 4);
enum class Base64Mode {
NORMAL,
URL
};
static constexpr char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static constexpr char base64_table_url[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789-_";
static inline const char* base64_select_table(Base64Mode mode) {
switch (mode) {
case Base64Mode::NORMAL: return base64_table;
case Base64Mode::URL: return base64_table_url;
default: UNREACHABLE();
}
}
static inline constexpr size_t base64_encoded_size(
size_t size,
Base64Mode mode = Base64Mode::NORMAL) {
return mode == Base64Mode::NORMAL
? ((size + 2) / 3 * 4)
: std::ceil(static_cast<double>(size * 4) / 3);
}
// Doesn't check for padding at the end. Can be 1-2 bytes over.
@ -32,7 +59,8 @@ size_t base64_decode(char* const dst, const size_t dstlen,
inline size_t base64_encode(const char* src,
size_t slen,
char* dst,
size_t dlen);
size_t dlen,
Base64Mode mode = Base64Mode::NORMAL);
} // namespace node

View File

@ -101,7 +101,7 @@ class BaseObject : public MemoryRetainer {
// This is a bit of a hack. See the override in async_wrap.cc for details.
virtual bool IsDoneInitializing() const;
// Can be used to avoid this object keepling itself alive as a GC root
// Can be used to avoid this object keeping itself alive as a GC root
// indefinitely, for example when this object is owned and deleted by another
// BaseObject once that is torn down. This can only be called when there is
// a BaseObjectPtr to this object.

349
src/crypto/README.md Normal file
View File

@ -0,0 +1,349 @@
# Node.js src/crypto Documentation
Welcome. You've found your way to the Node.js native crypto subsystem.
Do not be afraid.
While crypto may be a dark, mysterious, and forboding subject; and while
this directory may be filled with many `*.h` and `*.cc` files, finding
your way around is not too difficult. And I can promise you that a Gru
will not jump out of the shadows and eat you (well, "promise" may be a
bit too strong, a Gru may jump out of the shadows and eat you if you
live in a place where such things are possible).
## Finding your way around
All of the code in this directory is structured into units organized by
function or crypto protocol.
The following provide generalized utility declarations that are used throughout
the various other crypto files and other parts of Node.js:
* `crypto_util.h` / `crypto_util.cc` (Core crypto definitions)
* `crypto_common.h` / `crypto_common.h` (Shared TLS utility functions)
* `crypto_bio.c` / `crypto_bio.c` (Custom OpenSSL i/o implementation)
* `crypto_groups.h` (modp group definitions)
Of these, `crypto_util.h` and `crypto_util.cc` are the most important, as
they provide the core declarations and utility functions used most extensively
throughout the rest of the code.
The rest of the files are structured by their function, as detailed in the
following table:
| File (*.h/*.cc) | Description |
| -------------------- | ----------- |
| `crypto_aes` | AES Cipher support. |
| `crypto_cipher` | General Encryption/Decryption utilities. |
| `crypto_clienthello` | TLS/SSL client hello parser implementation. Used during SSL/TLS handshake. |
| `crypto_context` | Implementation of the `SecureContext` object. |
| `crypto_dh` | Diffie-Hellman Key Agreement implementation. |
| `crypto_dsa` | DSA (Digital Signature) Key Generation functions. |
| `crypto_ecdh` | Elliptic-Curve Diffie-Hellman Key Agreement implementation. |
| `crypto_hash` | Basic hash (e.g. SHA-256) functions. |
| `crypto_hkdf` | HKDF (Key derivation) implementation. |
| `crypto_hmac` | HMAC implementations. |
| `crypto_keys` | Utilities for using and generating secret, private, and public keys. |
| `crypto_pbkdf2` | PBKDF2 key / bit generation implementation. |
| `crypto_rsa` | RSA Key Generation functions. |
| `crypto_scrypt` | Scrypt key / bit generation implementation. |
| `crypto_sig` | General digital signature and verification utilities. |
| `crypto_spkac` | Netscape SPKAC certificate utilities. |
| `crypto_ssl` | Implementation of the `SSLWrap` object. |
| `crypto_timing` | Implementation of the TimingSafeEqual. |
When new crypto protocols are added, they will be added into their own
`crypto_` `*.h` and `*.cc` files.
## Helpful concepts
Node.js currently uses OpenSSL to provide it's crypto substructure.
(Some custom Node.js distributions -- such as Electron -- use BoringSSL
instead.)
This section aims to explain some of the utilities that have been
provided to make working with the OpenSSL APIs a bit easier.
### Pointer Types
Most of the key OpenSSL types need to be explicitly freed when they are
no longer needed. Failure to do so introduces memory leaks. To make this
easier (and less error prone), the `crypto_util.h` defines a number of
smart-pointer aliases that should be used:
```cpp
using X509Pointer = DeleteFnPtr<X509, X509_free>;
using BIOPointer = DeleteFnPtr<BIO, BIO_free_all>;
using SSLCtxPointer = DeleteFnPtr<SSL_CTX, SSL_CTX_free>;
using SSLSessionPointer = DeleteFnPtr<SSL_SESSION, SSL_SESSION_free>;
using SSLPointer = DeleteFnPtr<SSL, SSL_free>;
using PKCS8Pointer = DeleteFnPtr<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free>;
using EVPKeyPointer = DeleteFnPtr<EVP_PKEY, EVP_PKEY_free>;
using EVPKeyCtxPointer = DeleteFnPtr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>;
using EVPMDPointer = DeleteFnPtr<EVP_MD_CTX, EVP_MD_CTX_free>;
using RSAPointer = DeleteFnPtr<RSA, RSA_free>;
using ECPointer = DeleteFnPtr<EC_KEY, EC_KEY_free>;
using BignumPointer = DeleteFnPtr<BIGNUM, BN_free>;
using NetscapeSPKIPointer = DeleteFnPtr<NETSCAPE_SPKI, NETSCAPE_SPKI_free>;
using ECGroupPointer = DeleteFnPtr<EC_GROUP, EC_GROUP_free>;
using ECPointPointer = DeleteFnPtr<EC_POINT, EC_POINT_free>;
using ECKeyPointer = DeleteFnPtr<EC_KEY, EC_KEY_free>;
using DHPointer = DeleteFnPtr<DH, DH_free>;
using ECDSASigPointer = DeleteFnPtr<ECDSA_SIG, ECDSA_SIG_free>;
using HMACCtxPointer = DeleteFnPtr<HMAC_CTX, HMAC_CTX_free>;
using CipherCtxPointer = DeleteFnPtr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
```
Examples of these being used are pervasive through the `src/crypto` code.
### `ByteSource`
The `ByteSource` class is a helper utility representing a *read-only* byte
array. Instances can either wrap external ("foreign") data sources, such as
an `ArrayBuffer` (`v8::BackingStore`) or allocated data. If allocated data
is used, then the allocation is freed automatically when the `ByteSource` is
destroyed.
### `ArrayBufferOrViewContents`
The `ArrayBufferOfViewContents` class is a helper utility that abstracts
`ArrayBuffer`, `TypedArray`, or `DataView` inputs and provides access to
their underlying data pointers. It is used extensively through `src/crypto`
to make it easier to deal with inputs that allow any `ArrayBuffer`-backed
object.
### `AllocatedBuffer`
The `AllocatedBuffer` utility is defined in `allocated_buffer.h` and is not
specific to `src/crypto`. It is used extensively within `src/crypto` to hold
allocated data that is intended to be output in response to various
crypto functions (generated hash values, or ciphertext, for instance).
*Currently, we are working to transition away from using `AllocatedBuffer`
to directly using the `v8::BackingStore` API. This will take some time.
New uses of `AllocatedBuffer` should be avoided if possible.*
### Key Objects
Most crypto operations involve the use of keys -- cryptographic inputs
that protect data. There are three general types of keys:
* Secret Keys (Symmetric)
* Public Keys (Asymmetric)
* Private Keys (Asymmetric)
Secret keys consist of a variable number of bytes. They are "symmetrical"
in that the same key used to encrypt data, or generate a signature, must
be used to decrypt or validate that signature. If two people are exchanging
messages encrypted using a secret key, both of them must have access to the
same secret key data.
Public and Private keys always come in pairs. When one is used to encrypt
data or generate a signature, the other is used to decrypt or validate the
signature. The Public key is intended to be shared and can be shared openly.
The Private key must be kept secret and known only to the owner of the key.
The `src/crypto` subsystem uses several objects to represent keys. These
objects are structured in a way to allow key data to be shared across
multiple threads (the Node.js main thread, Worker Threads, and the libuv
threadpool).
Refer to `crypto_keys.h` and `crypto_keys.cc` for all code relating to the
core key objects.
#### `ManagedEVPPKey`
The `ManagedEVPPKey` class is a smart pointer for OpenSSL `EVP_PKEY`
structures. These manage the lifecycle of Public and Private key pairs.
#### `KeyObjectData`
`KeyObjectData` is an internal thread-safe structure used to wrap either
a `ManagedEVPPKey` (for Public or Private keys) or a `ByteSource` containing
a Secret key.
#### `KeyObjectHandle`
The `KeyObjectHandle` provides the interface between the native C++ code
handling keys and the public JavaScript `KeyObject` API.
#### `KeyObject`
A `KeyObject` is the public Node.js-specific API for keys. A single
`KeyObject` wraps exactly one `KeyObjectHandle`.
#### `CryptoKey`
A `CryptoKey` is the Web Crypto API's alternative to `KeyObject`. In the
Node.js implementation, `CryptoKey` is a thin wrapper around the
`KeyObject` and it is largely possible to use them interchangeably.
### `CryptoJob`
All operations that are not either Stream-based or single-use functions
are built around the `CryptoJob` class.
A `CryptoJob` encapsulates a single crypto operation that can be
invoked synchronously or asynchronously.
The `CryptoJob` class itself is a C++ template that takes a single
`CryptoJobTraits` struct as a parameter. The `CryptoJobTraits`
provides the implementation detail of the job.
There are (currently) four basic `CryptoJob` specializations:
* `CipherJob` (defined in `src/crypto_cipher.h`) -- Used for
encrypt and decrypt operations.
* `KeyGenJob` (defined in `src/crypto_keygen.h`) -- Used for
secret and key pair generation operations.
* `KeyExportJob` (defined in `src/crypto_keys.h`) -- Used for
key export operations.
* `DeriveBitsJob` (defined in `src/crypto_util.h`) -- Used for
key and byte derivation operations.
Every `CryptoJobTraits` provides two fundamental operations:
* Configuration -- Processes input arguments when a
`CryptoJob` instance is created.
* Implementation -- Provides the specific implementation of
the operation.
The Configuration is typically provided by an `AdditionalConfig()`
method, the signature of which is slightly different for each
of the above `CryptoJob` specializations. Despite the signature
differences, the purpose of the `AdditionalConfig()` function
remains the same: to process input arguments and set the properties
on the `CryptoJob`'s parameters object.
The parameters object is specific to each `CryptoJob` type, and
is stored with the `CryptoJob`. It holds all of the inputs that
are used by the Implementation. The inputs held by the parameters
must be threadsafe.
The `AdditionalConfig()` function is always called when the
`CryptoJob` instance is being created.
The Implementation function is unique to each of the `CryptoJob`
specializations and will either be called synchronously within
the current thread or from within the libuv threadpool.
Every `CryptoJob` instance exposes a `run()` function to the
JavaScript layer. When called, `run()` with either dispatch the
job to the libuv threadpool or invoke the Implementation
function synchronously. If invoked synchronously, run() will
return a JavaScript array. The first value in the array is
either an `Error` or `undefined`. If the operation was successful,
the second value in the array will contain the result of the
operation. Typically, the result is an `ArrayBuffer`, but
certain `CryptoJob` types can alter the output.
If the `CryptoJob` is processed asynchronously, then the job
must have an `ondone` property whose value is a function that
is invoked when the operation is complete. This function will
be called with two arguments. The first is either an `Error`
or `undefined`, and the second is the result of the operation
if successful.
For `CipherJob` types, the output is always an `ArrayBuffer`.
For `KeyExportJob` types, the output is either an `ArrayBuffer` or
a JavaScript object (for JWK output format);
For `KeyGenJob` types, the output is either a single KeyObject,
or an array containing a Public/Private key pair represented
either as a `KeyObjectHandle` object or a `Buffer`.
For `DeriveBitsJob` type output is typically an `ArrayBuffer` but
can be other values (`RandomBytesJob` for instance, fills an
input buffer and always returns `undefined`).
### Errors
#### `ThrowCryptoError` and the `THROW_ERR_CRYPTO_*` macros
The `ThrowCryptoError()` is a legacy utility that will throw a
JavaScript exception containing details collected from OpenSSL
about a failed operation. `ThrowCryptoError()` should only be
used when necessary to report low-level OpenSSL failures.
In `node_errors.h`, there are a number of `ERR_CRYPTO_*`
macro definitions that define semantically specific errors.
These can be called from within the C++ code as functions,
like `THROW_ERR_CRYPTO_INVALID_IV(env)`. These methods
should be used to throw JavaScript errors when necessary.
## Crypto API Patterns
### Operation Mode
All crypto functions in Node.js operate in one of three
modes:
* Synchronous single-call
* Asynchronous single-call
* Stream-oriented
It is often possible to perform various operations across
multiple modes. For instance, cipher and decipher operations
can be performed in any of the three modes.
Synchronous single-call operations are always blocking.
They perform their actions immediately.
```js
// Example synchronous single-call operation
const a = new Uint8Array(10);
const b = new Uint8Array(10);
crypto.timingSafeEqual(a, b);
```
Asynchronous single-call operations generally perform a
number of synchronous input validation steps, but then
defer the actual crypto-operation work to the libuv threadpool.
```js
// Example asynchronous single-call operation
const buf = new Uint8Array(10);
crypto.randomFill(buf, (err, buf) => {
console.log(buf);
});
```
For the legacy Node.js crypto API, asynchronous single-call
operations use the traditional Node.js callback pattern, as
illustrated in the previous `randomFill()` example. In the
Web Crypto API (accessible via `require('crypto').webcrypto`),
all asynchronous single-call operations are Promise-based.
```js
// Example Web Crypto API asynchronous single-call operation
const { subtle } = require('crypto').webcrypto;
subtle.generateKeys({ name: 'HMAC', length: 256 }, true, ['sign'])
.then((key) => {
console.log(key);
})
.catch((error) => {
console.error('an error occurred');
});
```
In nearly every case, asynchronous single-call operations make use
of the libuv threadpool to perform crypto operations off the main
event loop thread.
Stream-oriented operations use an object to maintain state
over multiple individual synchronous steps. The steps themselves
can be performed over time.
```js
// Example stream-oriented operation
const hash = crypto.createHash('sha256');
let updates = 10;
setTimeout(() => {
hash.update('hello world');
setTimeout(() => {
console.log(hash.digest();)
}, 1000);
}, 1000);
```

597
src/crypto/crypto_aes.cc Normal file
View File

@ -0,0 +1,597 @@
#include "crypto/crypto_aes.h"
#include "crypto/crypto_cipher.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer-inl.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
#include <openssl/bn.h>
#include <openssl/aes.h>
#include <vector>
namespace node {
using v8::FunctionCallbackInfo;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::Nothing;
using v8::Object;
using v8::Uint32;
using v8::Value;
namespace crypto {
namespace {
// Implements general AES encryption and decryption for CBC
// The key_data must be a secret key.
// On success, this function sets out to a new AllocatedBuffer
// instance containing the results and returns WebCryptoCipherStatus::ERR_OK.
WebCryptoCipherStatus AES_Cipher(
Environment* env,
KeyObjectData* key_data,
WebCryptoCipherMode cipher_mode,
const AESCipherConfig& params,
const ByteSource& in,
ByteSource* out) {
CHECK_NOT_NULL(key_data);
CHECK_EQ(key_data->GetKeyType(), kKeyTypeSecret);
const int mode = EVP_CIPHER_mode(params.cipher);
CipherCtxPointer ctx(EVP_CIPHER_CTX_new());
EVP_CIPHER_CTX_init(ctx.get());
if (mode == EVP_CIPH_WRAP_MODE)
EVP_CIPHER_CTX_set_flags(ctx.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt;
if (!EVP_CipherInit_ex(
ctx.get(),
params.cipher,
nullptr,
nullptr,
nullptr,
encrypt)) {
// Cipher init failed
return WebCryptoCipherStatus::ERR_FAILED;
}
if (mode == EVP_CIPH_GCM_MODE && !EVP_CIPHER_CTX_ctrl(
ctx.get(),
EVP_CTRL_AEAD_SET_IVLEN,
params.iv.size(),
nullptr)) {
return WebCryptoCipherStatus::ERR_FAILED;
}
if (!EVP_CIPHER_CTX_set_key_length(
ctx.get(),
key_data->GetSymmetricKeySize()) ||
!EVP_CipherInit_ex(
ctx.get(),
nullptr,
nullptr,
reinterpret_cast<const unsigned char*>(key_data->GetSymmetricKey()),
params.iv.data<unsigned char>(),
encrypt)) {
return WebCryptoCipherStatus::ERR_FAILED;
}
size_t tag_len = 0;
if (mode == EVP_CIPH_GCM_MODE) {
switch (cipher_mode) {
case kWebCryptoCipherDecrypt:
// If in decrypt mode, the auth tag must be set in the params.tag.
CHECK(params.tag);
if (!EVP_CIPHER_CTX_ctrl(
ctx.get(),
EVP_CTRL_AEAD_SET_TAG,
params.tag.size(),
const_cast<char*>(params.tag.get()))) {
return WebCryptoCipherStatus::ERR_FAILED;
}
break;
case kWebCryptoCipherEncrypt:
// In decrypt mode, we grab the tag length here. We'll use it to
// ensure that that allocated buffer has enough room for both the
// final block and the auth tag. Unlike our other AES-GCM implementation
// in CipherBase, in WebCrypto, the auth tag is concatentated to the end
// of the generated ciphertext and returned in the same ArrayBuffer.
tag_len = params.length;
break;
default:
UNREACHABLE();
}
}
size_t total = 0;
int buf_len = in.size() + EVP_CIPHER_CTX_block_size(ctx.get()) + tag_len;
int out_len;
if (mode == EVP_CIPH_GCM_MODE &&
params.additional_data.size() &&
!EVP_CipherUpdate(
ctx.get(),
nullptr,
&out_len,
params.additional_data.data<unsigned char>(),
params.additional_data.size())) {
return WebCryptoCipherStatus::ERR_FAILED;
}
char* data = MallocOpenSSL<char>(buf_len);
ByteSource buf = ByteSource::Allocated(data, buf_len);
unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
if (!EVP_CipherUpdate(
ctx.get(),
ptr,
&out_len,
in.data<unsigned char>(),
in.size())) {
return WebCryptoCipherStatus::ERR_FAILED;
}
total += out_len;
CHECK_LE(out_len, buf_len);
ptr += out_len;
out_len = EVP_CIPHER_CTX_block_size(ctx.get());
if (!EVP_CipherFinal_ex(ctx.get(), ptr, &out_len)) {
return WebCryptoCipherStatus::ERR_FAILED;
}
total += out_len;
// If using AES_GCM, grab the generated auth tag and append
// it to the end of the ciphertext.
if (cipher_mode == kWebCryptoCipherEncrypt && mode == EVP_CIPH_GCM_MODE) {
data += out_len;
if (!EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_GET_TAG, tag_len, ptr))
return WebCryptoCipherStatus::ERR_FAILED;
total += tag_len;
}
// It's possible that we haven't used the full allocated space. Size down.
buf.Resize(total);
*out = std::move(buf);
return WebCryptoCipherStatus::ERR_OK;
}
// The AES_CTR implementation here takes it's inspiration from the chromium
// implementation here:
// https://github.com/chromium/chromium/blob/7af6cfd/components/webcrypto/algorithms/aes_ctr.cc
template <typename T>
T CeilDiv(T a, T b) {
return a == 0 ? 0 : 1 + (a - 1) / b;
}
BignumPointer GetCounter(const AESCipherConfig& params) {
unsigned int remainder = (params.length % CHAR_BIT);
const unsigned char* data = params.iv.data<unsigned char>();
if (remainder == 0) {
unsigned int byte_length = params.length / CHAR_BIT;
return BignumPointer(BN_bin2bn(
data + params.iv.size() - byte_length,
byte_length,
nullptr));
}
unsigned int byte_length =
CeilDiv(params.length, static_cast<size_t>(CHAR_BIT));
std::vector<unsigned char> counter(
data + params.iv.size() - byte_length,
data + params.iv.size());
counter[0] &= ~(0xFF << remainder);
return BignumPointer(BN_bin2bn(counter.data(), counter.size(), nullptr));
}
std::vector<unsigned char> BlockWithZeroedCounter(
const AESCipherConfig& params) {
unsigned int length_bytes = params.length / CHAR_BIT;
unsigned int remainder = params.length % CHAR_BIT;
const unsigned char* data = params.iv.data<unsigned char>();
std::vector<unsigned char> new_counter_block(data, data + params.iv.size());
size_t index = new_counter_block.size() - length_bytes;
memset(&new_counter_block.front() + index, 0, length_bytes);
if (remainder)
new_counter_block[index - 1] &= 0xFF << remainder;
return new_counter_block;
}
WebCryptoCipherStatus AES_CTR_Cipher2(
KeyObjectData* key_data,
WebCryptoCipherMode cipher_mode,
const AESCipherConfig& params,
const ByteSource& in,
unsigned const char* counter,
unsigned char* out) {
CipherCtxPointer ctx(EVP_CIPHER_CTX_new());
const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt;
if (!EVP_CipherInit_ex(
ctx.get(),
params.cipher,
nullptr,
reinterpret_cast<const unsigned char*>(key_data->GetSymmetricKey()),
counter,
encrypt)) {
// Cipher init failed
return WebCryptoCipherStatus::ERR_FAILED;
}
int out_len = 0;
int final_len = 0;
if (!EVP_CipherUpdate(
ctx.get(),
out,
&out_len,
in.data<unsigned char>(),
in.size())) {
return WebCryptoCipherStatus::ERR_FAILED;
}
if (!EVP_CipherFinal_ex(ctx.get(), out + out_len, &final_len))
return WebCryptoCipherStatus::ERR_FAILED;
out_len += final_len;
if (static_cast<unsigned>(out_len) != in.size())
return WebCryptoCipherStatus::ERR_FAILED;
return WebCryptoCipherStatus::ERR_OK;
}
WebCryptoCipherStatus AES_CTR_Cipher(
Environment* env,
KeyObjectData* key_data,
WebCryptoCipherMode cipher_mode,
const AESCipherConfig& params,
const ByteSource& in,
ByteSource* out) {
BignumPointer num_counters(BN_new());
if (!BN_lshift(num_counters.get(), BN_value_one(), params.length))
return WebCryptoCipherStatus::ERR_FAILED;
BignumPointer current_counter = GetCounter(params);
BignumPointer num_output(BN_new());
if (!BN_set_word(num_output.get(), CeilDiv(in.size(), kAesBlockSize)))
return WebCryptoCipherStatus::ERR_FAILED;
// Just like in chromium's implementation, if the counter will
// be incremented more than there are counter values, we fail.
if (BN_cmp(num_output.get(), num_counters.get()) > 0)
return WebCryptoCipherStatus::ERR_FAILED;
BignumPointer remaining_until_reset(BN_new());
if (!BN_sub(remaining_until_reset.get(),
num_counters.get(),
current_counter.get())) {
return WebCryptoCipherStatus::ERR_FAILED;
}
// Output size is identical to the input size
char* data = MallocOpenSSL<char>(in.size());
ByteSource buf = ByteSource::Allocated(data, in.size());
unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
// Also just like in chromium's implementation, if we can process
// the input without wrapping the counter, we'll do it as a single
// call here. If we can't, we'll fallback to the a two-step approach
if (BN_cmp(remaining_until_reset.get(), num_output.get()) >= 0) {
auto status = AES_CTR_Cipher2(
key_data,
cipher_mode,
params,
in,
params.iv.data<unsigned char>(),
ptr);
if (status == WebCryptoCipherStatus::ERR_OK)
*out = std::move(buf);
return status;
}
BN_ULONG blocks_part1 = BN_get_word(remaining_until_reset.get());
BN_ULONG input_size_part1 = blocks_part1 * kAesBlockSize;
// Encrypt the first part...
auto status = AES_CTR_Cipher2(
key_data,
cipher_mode,
params,
ByteSource::Foreign(in.get(), input_size_part1),
params.iv.data<unsigned char>(),
ptr);
if (status != WebCryptoCipherStatus::ERR_OK)
return status;
// Wrap the counter around to zero
std::vector<unsigned char> new_counter_block = BlockWithZeroedCounter(params);
// Encrypt the second part...
status = AES_CTR_Cipher2(
key_data,
cipher_mode,
params,
ByteSource::Foreign(
in.get() + input_size_part1,
in.size() - input_size_part1),
new_counter_block.data(),
ptr + input_size_part1);
if (status == WebCryptoCipherStatus::ERR_OK)
*out = std::move(buf);
return status;
}
bool ValidateIV(
Environment* env,
CryptoJobMode mode,
Local<Value> value,
AESCipherConfig* params) {
ArrayBufferOrViewContents<char> iv(value);
if (UNLIKELY(!iv.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "iv is too big");
return false;
}
params->iv = (mode == kCryptoJobAsync)
? iv.ToCopy()
: iv.ToByteSource();
return true;
}
bool ValidateCounter(
Environment* env,
Local<Value> value,
AESCipherConfig* params) {
CHECK(value->IsUint32()); // Length
params->length = value.As<Uint32>()->Value();
if (params->iv.size() != 16 ||
params->length == 0 ||
params->length > 128) {
THROW_ERR_CRYPTO_INVALID_COUNTER(env);
return false;
}
return true;
}
bool ValidateAuthTag(
Environment* env,
CryptoJobMode mode,
WebCryptoCipherMode cipher_mode,
Local<Value> value,
AESCipherConfig* params) {
switch (cipher_mode) {
case kWebCryptoCipherDecrypt: {
if (!IsAnyByteSource(value)) {
THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
return false;
}
ArrayBufferOrViewContents<char> tag_contents(value);
if (UNLIKELY(!tag_contents.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "tagLength is too big");
return false;
}
params->tag = mode == kCryptoJobAsync
? tag_contents.ToCopy()
: tag_contents.ToByteSource();
break;
}
case kWebCryptoCipherEncrypt: {
if (!value->IsUint32()) {
THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
return false;
}
params->length = value.As<Uint32>()->Value();
if (params->length > 128) {
THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
return false;
}
break;
}
default:
UNREACHABLE();
}
return true;
}
bool ValidateAdditionalData(
Environment* env,
CryptoJobMode mode,
Local<Value> value,
AESCipherConfig* params) {
// Additional Data
if (IsAnyByteSource(value)) {
ArrayBufferOrViewContents<char> additional(value);
if (UNLIKELY(!additional.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "additionalData is too big");
return false;
}
params->additional_data = mode == kCryptoJobAsync
? additional.ToCopy()
: additional.ToByteSource();
}
return true;
}
void UseDefaultIV(AESCipherConfig* params) {
params->iv = ByteSource::Foreign(kDefaultWrapIV, strlen(kDefaultWrapIV));
}
} // namespace
AESCipherConfig::AESCipherConfig(AESCipherConfig&& other) noexcept
: mode(other.mode),
variant(other.variant),
cipher(other.cipher),
length(other.length),
iv(std::move(other.iv)),
additional_data(std::move(other.additional_data)),
tag(std::move(other.tag)) {}
AESCipherConfig& AESCipherConfig::operator=(AESCipherConfig&& other) noexcept {
if (&other == this) return *this;
this->~AESCipherConfig();
return *new (this) AESCipherConfig(std::move(other));
}
void AESCipherConfig::MemoryInfo(MemoryTracker* tracker) const {
// If mode is sync, then the data in each of these properties
// is not owned by the AESCipherConfig, so we ignore it.
if (mode == kCryptoJobAsync) {
tracker->TrackFieldWithSize("iv", iv.size());
tracker->TrackFieldWithSize("additional_data", additional_data.size());
tracker->TrackFieldWithSize("tag", tag.size());
}
}
Maybe<bool> AESCipherTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
WebCryptoCipherMode cipher_mode,
AESCipherConfig* params) {
Environment* env = Environment::GetCurrent(args);
params->mode = mode;
CHECK(args[offset]->IsUint32()); // Key Variant
params->variant =
static_cast<AESKeyVariant>(args[offset].As<Uint32>()->Value());
int cipher_nid;
switch (params->variant) {
case kKeyVariantAES_CTR_128:
if (!ValidateIV(env, mode, args[offset + 1], params) ||
!ValidateCounter(env, args[offset + 2], params)) {
return Nothing<bool>();
}
cipher_nid = NID_aes_128_ctr;
break;
case kKeyVariantAES_CTR_192:
if (!ValidateIV(env, mode, args[offset + 1], params) ||
!ValidateCounter(env, args[offset + 2], params)) {
return Nothing<bool>();
}
cipher_nid = NID_aes_192_ctr;
break;
case kKeyVariantAES_CTR_256:
if (!ValidateIV(env, mode, args[offset + 1], params) ||
!ValidateCounter(env, args[offset + 2], params)) {
return Nothing<bool>();
}
cipher_nid = NID_aes_256_ctr;
break;
case kKeyVariantAES_CBC_128:
if (!ValidateIV(env, mode, args[offset + 1], params))
return Nothing<bool>();
cipher_nid = NID_aes_128_cbc;
break;
case kKeyVariantAES_CBC_192:
if (!ValidateIV(env, mode, args[offset + 1], params))
return Nothing<bool>();
cipher_nid = NID_aes_192_cbc;
break;
case kKeyVariantAES_CBC_256:
if (!ValidateIV(env, mode, args[offset + 1], params))
return Nothing<bool>();
cipher_nid = NID_aes_256_cbc;
break;
case kKeyVariantAES_KW_128:
UseDefaultIV(params);
cipher_nid = NID_id_aes128_wrap;
break;
case kKeyVariantAES_KW_192:
UseDefaultIV(params);
cipher_nid = NID_id_aes192_wrap;
break;
case kKeyVariantAES_KW_256:
UseDefaultIV(params);
cipher_nid = NID_id_aes256_wrap;
break;
case kKeyVariantAES_GCM_128:
if (!ValidateIV(env, mode, args[offset + 1], params) ||
!ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
!ValidateAdditionalData(env, mode, args[offset + 3], params)) {
return Nothing<bool>();
}
cipher_nid = NID_aes_128_gcm;
break;
case kKeyVariantAES_GCM_192:
if (!ValidateIV(env, mode, args[offset + 1], params) ||
!ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
!ValidateAdditionalData(env, mode, args[offset + 3], params)) {
return Nothing<bool>();
}
cipher_nid = NID_aes_192_gcm;
break;
case kKeyVariantAES_GCM_256:
if (!ValidateIV(env, mode, args[offset + 1], params) ||
!ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
!ValidateAdditionalData(env, mode, args[offset + 3], params)) {
return Nothing<bool>();
}
cipher_nid = NID_aes_256_gcm;
break;
default:
UNREACHABLE();
}
params->cipher = EVP_get_cipherbynid(cipher_nid);
CHECK_NOT_NULL(params->cipher);
if (params->iv.size() <
static_cast<size_t>(EVP_CIPHER_iv_length(params->cipher))) {
THROW_ERR_CRYPTO_INVALID_IV(env);
return Nothing<bool>();
}
return Just(true);
}
WebCryptoCipherStatus AESCipherTraits::DoCipher(
Environment* env,
std::shared_ptr<KeyObjectData> key_data,
WebCryptoCipherMode cipher_mode,
const AESCipherConfig& params,
const ByteSource& in,
ByteSource* out) {
#define V(name, fn) \
case kKeyVariantAES_ ## name: \
return fn(env, key_data.get(), cipher_mode, params, in, out);
switch (params.variant) {
VARIANTS(V)
default:
UNREACHABLE();
}
#undef V
}
void AES::Initialize(Environment* env, Local<Object> target) {
AESCryptoJob::Initialize(env, target);
#define V(name, _) NODE_DEFINE_CONSTANT(target, kKeyVariantAES_ ## name);
VARIANTS(V)
#undef V
}
} // namespace crypto
} // namespace node

89
src/crypto/crypto_aes.h Normal file
View File

@ -0,0 +1,89 @@
#ifndef SRC_CRYPTO_CRYPTO_AES_H_
#define SRC_CRYPTO_CRYPTO_AES_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_cipher.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer.h"
#include "env.h"
#include "v8.h"
namespace node {
namespace crypto {
constexpr size_t kAesBlockSize = 16;
constexpr unsigned kNoAuthTagLength = static_cast<unsigned>(-1);
constexpr const char* kDefaultWrapIV = "\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6";
#define VARIANTS(V) \
V(CTR_128, AES_CTR_Cipher) \
V(CTR_192, AES_CTR_Cipher) \
V(CTR_256, AES_CTR_Cipher) \
V(CBC_128, AES_Cipher) \
V(CBC_192, AES_Cipher) \
V(CBC_256, AES_Cipher) \
V(GCM_128, AES_Cipher) \
V(GCM_192, AES_Cipher) \
V(GCM_256, AES_Cipher) \
V(KW_128, AES_Cipher) \
V(KW_192, AES_Cipher) \
V(KW_256, AES_Cipher)
enum AESKeyVariant {
#define V(name, _) kKeyVariantAES_ ## name,
VARIANTS(V)
#undef V
};
struct AESCipherConfig final : public MemoryRetainer {
CryptoJobMode mode;
AESKeyVariant variant;
const EVP_CIPHER* cipher;
size_t length;
ByteSource iv; // Used for both iv or counter
ByteSource additional_data;
ByteSource tag; // Used only for authenticated modes (GCM)
AESCipherConfig() = default;
AESCipherConfig(AESCipherConfig&& other) noexcept;
AESCipherConfig& operator=(AESCipherConfig&& other) noexcept;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(AESCipherConfig);
SET_SELF_SIZE(AESCipherConfig);
};
struct AESCipherTraits final {
static constexpr const char* JobName = "AESCipherJob";
using AdditionalParameters = AESCipherConfig;
static v8::Maybe<bool> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
WebCryptoCipherMode cipher_mode,
AESCipherConfig* config);
static WebCryptoCipherStatus DoCipher(
Environment* env,
std::shared_ptr<KeyObjectData> key_data,
WebCryptoCipherMode cipher_mode,
const AESCipherConfig& params,
const ByteSource& in,
ByteSource* out);
};
using AESCryptoJob = CipherJob<AESCipherTraits>;
namespace AES {
void Initialize(Environment* env, v8::Local<v8::Object> target);
} // namespace AES
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_AES_H_

View File

@ -19,12 +19,14 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "crypto/crypto_bio.h"
#include "base_object-inl.h"
#include "memory_tracker-inl.h"
#include "allocated_buffer-inl.h" // Inlined functions needed by node_crypto.h.
#include "crypto/crypto_bio.h"
#include "openssl/bio.h"
#include "allocated_buffer-inl.h"
#include "util-inl.h"
#include <openssl/bio.h>
#include <climits>
#include <cstring>

View File

@ -34,7 +34,6 @@ namespace node {
class Environment;
namespace crypto {
// This class represents buffers for OpenSSL I/O, implemented as a singly-linked
// list of chunks. It can be used either for writing data from Node to OpenSSL,
// or for reading data back, but not both.

848
src/crypto/crypto_cipher.cc Normal file
View File

@ -0,0 +1,848 @@
#include "crypto/crypto_cipher.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer-inl.h"
#include "base_object-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node_buffer.h"
#include "node_internals.h"
#include "node_process.h"
#include "v8.h"
namespace node {
using v8::Array;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Int32;
using v8::Local;
using v8::Object;
using v8::Uint32;
using v8::Value;
namespace crypto {
#ifdef OPENSSL_NO_OCB
# define IS_OCB_MODE(mode) false
#else
# define IS_OCB_MODE(mode) ((mode) == EVP_CIPH_OCB_MODE)
#endif
namespace {
bool IsSupportedAuthenticatedMode(const EVP_CIPHER* cipher) {
const int mode = EVP_CIPHER_mode(cipher);
// Check `chacha20-poly1305` separately, it is also an AEAD cipher,
// but its mode is 0 which doesn't indicate
return EVP_CIPHER_nid(cipher) == NID_chacha20_poly1305 ||
mode == EVP_CIPH_CCM_MODE ||
mode == EVP_CIPH_GCM_MODE ||
IS_OCB_MODE(mode);
}
bool IsSupportedAuthenticatedMode(const EVP_CIPHER_CTX* ctx) {
const EVP_CIPHER* cipher = EVP_CIPHER_CTX_cipher(ctx);
return IsSupportedAuthenticatedMode(cipher);
}
bool IsValidGCMTagLength(unsigned int tag_len) {
return tag_len == 4 || tag_len == 8 || (tag_len >= 12 && tag_len <= 16);
}
} // namespace
void CipherBase::GetSSLCiphers(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
SSLCtxPointer ctx(SSL_CTX_new(TLS_method()));
CHECK(ctx);
SSLPointer ssl(SSL_new(ctx.get()));
CHECK(ssl);
STACK_OF(SSL_CIPHER)* ciphers = SSL_get_ciphers(ssl.get());
// TLSv1.3 ciphers aren't listed by EVP. There are only 5, we could just
// document them, but since there are only 5, easier to just add them manually
// and not have to explain their absence in the API docs. They are lower-cased
// because the docs say they will be.
static const char* TLS13_CIPHERS[] = {
"tls_aes_256_gcm_sha384",
"tls_chacha20_poly1305_sha256",
"tls_aes_128_gcm_sha256",
"tls_aes_128_ccm_8_sha256",
"tls_aes_128_ccm_sha256"
};
const int n = sk_SSL_CIPHER_num(ciphers);
std::vector<Local<Value>> arr(n + arraysize(TLS13_CIPHERS));
for (int i = 0; i < n; ++i) {
const SSL_CIPHER* cipher = sk_SSL_CIPHER_value(ciphers, i);
arr[i] = OneByteString(env->isolate(), SSL_CIPHER_get_name(cipher));
}
for (unsigned i = 0; i < arraysize(TLS13_CIPHERS); ++i) {
const char* name = TLS13_CIPHERS[i];
arr[n + i] = OneByteString(env->isolate(), name);
}
args.GetReturnValue().Set(Array::New(env->isolate(), arr.data(), arr.size()));
}
void CipherBase::GetCiphers(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CipherPushContext ctx(env);
EVP_CIPHER_do_all_sorted(array_push_back<EVP_CIPHER>, &ctx);
args.GetReturnValue().Set(ctx.ToJSArray());
}
CipherBase::CipherBase(Environment* env,
Local<Object> wrap,
CipherKind kind)
: BaseObject(env, wrap),
ctx_(nullptr),
kind_(kind),
auth_tag_state_(kAuthTagUnknown),
auth_tag_len_(kNoAuthTagLength),
pending_auth_failed_(false) {
MakeWeak();
}
void CipherBase::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("context", ctx_ ? kSizeOf_EVP_CIPHER_CTX : 0);
}
void CipherBase::Initialize(Environment* env, Local<Object> target) {
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
t->InstanceTemplate()->SetInternalFieldCount(
CipherBase::kInternalFieldCount);
t->Inherit(BaseObject::GetConstructorTemplate(env));
env->SetProtoMethod(t, "init", Init);
env->SetProtoMethod(t, "initiv", InitIv);
env->SetProtoMethod(t, "update", Update);
env->SetProtoMethod(t, "final", Final);
env->SetProtoMethod(t, "setAutoPadding", SetAutoPadding);
env->SetProtoMethodNoSideEffect(t, "getAuthTag", GetAuthTag);
env->SetProtoMethod(t, "setAuthTag", SetAuthTag);
env->SetProtoMethod(t, "setAAD", SetAAD);
target->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "CipherBase"),
t->GetFunction(env->context()).ToLocalChecked()).Check();
env->SetMethodNoSideEffect(target, "getSSLCiphers", GetSSLCiphers);
env->SetMethodNoSideEffect(target, "getCiphers", GetCiphers);
env->SetMethod(target, "publicEncrypt",
PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
EVP_PKEY_encrypt_init,
EVP_PKEY_encrypt>);
env->SetMethod(target, "privateDecrypt",
PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
EVP_PKEY_decrypt_init,
EVP_PKEY_decrypt>);
env->SetMethod(target, "privateEncrypt",
PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
EVP_PKEY_sign_init,
EVP_PKEY_sign>);
env->SetMethod(target, "publicDecrypt",
PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
EVP_PKEY_verify_recover_init,
EVP_PKEY_verify_recover>);
NODE_DEFINE_CONSTANT(target, kWebCryptoCipherEncrypt);
NODE_DEFINE_CONSTANT(target, kWebCryptoCipherDecrypt);
}
void CipherBase::New(const FunctionCallbackInfo<Value>& args) {
CHECK(args.IsConstructCall());
Environment* env = Environment::GetCurrent(args);
new CipherBase(env, args.This(), args[0]->IsTrue() ? kCipher : kDecipher);
}
void CipherBase::CommonInit(const char* cipher_type,
const EVP_CIPHER* cipher,
const unsigned char* key,
int key_len,
const unsigned char* iv,
int iv_len,
unsigned int auth_tag_len) {
CHECK(!ctx_);
ctx_.reset(EVP_CIPHER_CTX_new());
const int mode = EVP_CIPHER_mode(cipher);
if (mode == EVP_CIPH_WRAP_MODE)
EVP_CIPHER_CTX_set_flags(ctx_.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
const bool encrypt = (kind_ == kCipher);
if (1 != EVP_CipherInit_ex(ctx_.get(), cipher, nullptr,
nullptr, nullptr, encrypt)) {
return ThrowCryptoError(env(), ERR_get_error(),
"Failed to initialize cipher");
}
if (IsSupportedAuthenticatedMode(cipher)) {
CHECK_GE(iv_len, 0);
if (!InitAuthenticated(cipher_type, iv_len, auth_tag_len))
return;
}
if (!EVP_CIPHER_CTX_set_key_length(ctx_.get(), key_len)) {
ctx_.reset();
return THROW_ERR_CRYPTO_INVALID_KEYLEN(env());
}
if (1 != EVP_CipherInit_ex(ctx_.get(), nullptr, nullptr, key, iv, encrypt)) {
return ThrowCryptoError(env(), ERR_get_error(),
"Failed to initialize cipher");
}
}
void CipherBase::Init(const char* cipher_type,
const ArrayBufferOrViewContents<unsigned char>& key_buf,
unsigned int auth_tag_len) {
HandleScope scope(env()->isolate());
MarkPopErrorOnReturn mark_pop_error_on_return;
#ifdef NODE_FIPS_MODE
if (FIPS_mode()) {
return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env(),
"crypto.createCipher() is not supported in FIPS mode.");
}
#endif // NODE_FIPS_MODE
const EVP_CIPHER* const cipher = EVP_get_cipherbyname(cipher_type);
if (cipher == nullptr)
return THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env());
unsigned char key[EVP_MAX_KEY_LENGTH];
unsigned char iv[EVP_MAX_IV_LENGTH];
int key_len = EVP_BytesToKey(cipher,
EVP_md5(),
nullptr,
key_buf.data(),
key_buf.size(),
1,
key,
iv);
CHECK_NE(key_len, 0);
const int mode = EVP_CIPHER_mode(cipher);
if (kind_ == kCipher && (mode == EVP_CIPH_CTR_MODE ||
mode == EVP_CIPH_GCM_MODE ||
mode == EVP_CIPH_CCM_MODE)) {
// Ignore the return value (i.e. possible exception) because we are
// not calling back into JS anyway.
ProcessEmitWarning(env(),
"Use Cipheriv for counter mode of %s",
cipher_type);
}
CommonInit(cipher_type, cipher, key, key_len, iv,
EVP_CIPHER_iv_length(cipher), auth_tag_len);
}
void CipherBase::Init(const FunctionCallbackInfo<Value>& args) {
CipherBase* cipher;
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());
Environment* env = Environment::GetCurrent(args);
CHECK_GE(args.Length(), 3);
const Utf8Value cipher_type(args.GetIsolate(), args[0]);
ArrayBufferOrViewContents<unsigned char> key_buf(args[1]);
if (!key_buf.CheckSizeInt32())
return THROW_ERR_OUT_OF_RANGE(env, "password is too large");
// Don't assign to cipher->auth_tag_len_ directly; the value might not
// represent a valid length at this point.
unsigned int auth_tag_len;
if (args[2]->IsUint32()) {
auth_tag_len = args[2].As<Uint32>()->Value();
} else {
CHECK(args[2]->IsInt32() && args[2].As<Int32>()->Value() == -1);
auth_tag_len = kNoAuthTagLength;
}
cipher->Init(*cipher_type, key_buf, auth_tag_len);
}
void CipherBase::InitIv(const char* cipher_type,
const ByteSource& key_buf,
const ArrayBufferOrViewContents<unsigned char>& iv_buf,
unsigned int auth_tag_len) {
HandleScope scope(env()->isolate());
MarkPopErrorOnReturn mark_pop_error_on_return;
const EVP_CIPHER* const cipher = EVP_get_cipherbyname(cipher_type);
if (cipher == nullptr)
return THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env());
const int expected_iv_len = EVP_CIPHER_iv_length(cipher);
const bool is_authenticated_mode = IsSupportedAuthenticatedMode(cipher);
const bool has_iv = iv_buf.size() > 0;
// Throw if no IV was passed and the cipher requires an IV
if (!has_iv && expected_iv_len != 0)
return THROW_ERR_CRYPTO_INVALID_IV(env());
// Throw if an IV was passed which does not match the cipher's fixed IV length
// static_cast<int> for the iv_buf.size() is safe because we've verified
// prior that the value is not larger than MAX_INT.
if (!is_authenticated_mode &&
has_iv &&
static_cast<int>(iv_buf.size()) != expected_iv_len) {
return THROW_ERR_CRYPTO_INVALID_IV(env());
}
if (EVP_CIPHER_nid(cipher) == NID_chacha20_poly1305) {
CHECK(has_iv);
// Check for invalid IV lengths, since OpenSSL does not under some
// conditions:
// https://www.openssl.org/news/secadv/20190306.txt.
if (iv_buf.size() > 12)
return THROW_ERR_CRYPTO_INVALID_IV(env());
}
CommonInit(
cipher_type,
cipher,
key_buf.data<unsigned char>(),
key_buf.size(),
iv_buf.data(),
iv_buf.size(),
auth_tag_len);
}
void CipherBase::InitIv(const FunctionCallbackInfo<Value>& args) {
CipherBase* cipher;
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());
Environment* env = cipher->env();
CHECK_GE(args.Length(), 4);
const Utf8Value cipher_type(env->isolate(), args[0]);
// The argument can either be a KeyObjectHandle or a byte source
// (e.g. ArrayBuffer, TypedArray, etc). Whichever it is, grab the
// raw bytes and proceed...
const ByteSource key_buf = ByteSource::FromSecretKeyBytes(env, args[1]);
if (UNLIKELY(key_buf.size() > INT_MAX))
return THROW_ERR_OUT_OF_RANGE(env, "key is too big");
ArrayBufferOrViewContents<unsigned char> iv_buf;
if (!args[2]->IsNull())
iv_buf = ArrayBufferOrViewContents<unsigned char>(args[2]);
if (UNLIKELY(!iv_buf.CheckSizeInt32()))
return THROW_ERR_OUT_OF_RANGE(env, "iv is too big");
// Don't assign to cipher->auth_tag_len_ directly; the value might not
// represent a valid length at this point.
unsigned int auth_tag_len;
if (args[3]->IsUint32()) {
auth_tag_len = args[3].As<Uint32>()->Value();
} else {
CHECK(args[3]->IsInt32() && args[3].As<Int32>()->Value() == -1);
auth_tag_len = kNoAuthTagLength;
}
cipher->InitIv(*cipher_type, key_buf, iv_buf, auth_tag_len);
}
bool CipherBase::InitAuthenticated(
const char* cipher_type,
int iv_len,
unsigned int auth_tag_len) {
CHECK(IsAuthenticatedMode());
MarkPopErrorOnReturn mark_pop_error_on_return;
if (!EVP_CIPHER_CTX_ctrl(ctx_.get(),
EVP_CTRL_AEAD_SET_IVLEN,
iv_len,
nullptr)) {
THROW_ERR_CRYPTO_INVALID_IV(env());
return false;
}
const int mode = EVP_CIPHER_CTX_mode(ctx_.get());
if (mode == EVP_CIPH_GCM_MODE) {
if (auth_tag_len != kNoAuthTagLength) {
if (!IsValidGCMTagLength(auth_tag_len)) {
char msg[50];
snprintf(msg, sizeof(msg),
"Invalid authentication tag length: %u", auth_tag_len);
THROW_ERR_CRYPTO_INVALID_AUTH_TAG(env(), msg);
return false;
}
// Remember the given authentication tag length for later.
auth_tag_len_ = auth_tag_len;
}
} else {
if (auth_tag_len == kNoAuthTagLength) {
char msg[128];
snprintf(msg, sizeof(msg), "authTagLength required for %s", cipher_type);
THROW_ERR_CRYPTO_INVALID_AUTH_TAG(env(), msg);
return false;
}
#ifdef NODE_FIPS_MODE
// TODO(tniessen) Support CCM decryption in FIPS mode
if (mode == EVP_CIPH_CCM_MODE && kind_ == kDecipher && FIPS_mode()) {
THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env(),
"CCM encryption not supported in FIPS mode");
return false;
}
#endif
// Tell OpenSSL about the desired length.
if (!EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_SET_TAG, auth_tag_len,
nullptr)) {
THROW_ERR_CRYPTO_INVALID_AUTH_TAG(env());
return false;
}
// Remember the given authentication tag length for later.
auth_tag_len_ = auth_tag_len;
if (mode == EVP_CIPH_CCM_MODE) {
// Restrict the message length to min(INT_MAX, 2^(8*(15-iv_len))-1) bytes.
CHECK(iv_len >= 7 && iv_len <= 13);
max_message_size_ = INT_MAX;
if (iv_len == 12) max_message_size_ = 16777215;
if (iv_len == 13) max_message_size_ = 65535;
}
}
return true;
}
bool CipherBase::CheckCCMMessageLength(int message_len) {
CHECK(ctx_);
CHECK(EVP_CIPHER_CTX_mode(ctx_.get()) == EVP_CIPH_CCM_MODE);
if (message_len > max_message_size_) {
THROW_ERR_CRYPTO_INVALID_MESSAGELEN(env());
return false;
}
return true;
}
bool CipherBase::IsAuthenticatedMode() const {
// Check if this cipher operates in an AEAD mode that we support.
CHECK(ctx_);
return IsSupportedAuthenticatedMode(ctx_.get());
}
void CipherBase::GetAuthTag(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CipherBase* cipher;
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());
// Only callable after Final and if encrypting.
if (cipher->ctx_ ||
cipher->kind_ != kCipher ||
cipher->auth_tag_len_ == kNoAuthTagLength) {
return;
}
args.GetReturnValue().Set(
Buffer::Copy(env, cipher->auth_tag_, cipher->auth_tag_len_)
.ToLocalChecked());
}
void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
CipherBase* cipher;
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());
Environment* env = Environment::GetCurrent(args);
if (!cipher->ctx_ ||
!cipher->IsAuthenticatedMode() ||
cipher->kind_ != kDecipher ||
cipher->auth_tag_state_ != kAuthTagUnknown) {
return args.GetReturnValue().Set(false);
}
ArrayBufferOrViewContents<char> auth_tag(args[0]);
if (UNLIKELY(!auth_tag.CheckSizeInt32()))
return THROW_ERR_OUT_OF_RANGE(env, "buffer is too big");
unsigned int tag_len = auth_tag.size();
const int mode = EVP_CIPHER_CTX_mode(cipher->ctx_.get());
bool is_valid;
if (mode == EVP_CIPH_GCM_MODE) {
// Restrict GCM tag lengths according to NIST 800-38d, page 9.
is_valid = (cipher->auth_tag_len_ == kNoAuthTagLength ||
cipher->auth_tag_len_ == tag_len) &&
IsValidGCMTagLength(tag_len);
} else {
// At this point, the tag length is already known and must match the
// length of the given authentication tag.
CHECK(IsSupportedAuthenticatedMode(cipher->ctx_.get()));
CHECK_NE(cipher->auth_tag_len_, kNoAuthTagLength);
is_valid = cipher->auth_tag_len_ == tag_len;
}
if (!is_valid) {
char msg[50];
snprintf(msg, sizeof(msg),
"Invalid authentication tag length: %u", tag_len);
return THROW_ERR_CRYPTO_INVALID_AUTH_TAG(env, msg);
}
cipher->auth_tag_len_ = tag_len;
cipher->auth_tag_state_ = kAuthTagKnown;
CHECK_LE(cipher->auth_tag_len_, sizeof(cipher->auth_tag_));
memset(cipher->auth_tag_, 0, sizeof(cipher->auth_tag_));
auth_tag.CopyTo(cipher->auth_tag_, cipher->auth_tag_len_);
args.GetReturnValue().Set(true);
}
bool CipherBase::MaybePassAuthTagToOpenSSL() {
if (auth_tag_state_ == kAuthTagKnown) {
if (!EVP_CIPHER_CTX_ctrl(ctx_.get(),
EVP_CTRL_AEAD_SET_TAG,
auth_tag_len_,
reinterpret_cast<unsigned char*>(auth_tag_))) {
return false;
}
auth_tag_state_ = kAuthTagPassedToOpenSSL;
}
return true;
}
bool CipherBase::SetAAD(
const ArrayBufferOrViewContents<unsigned char>& data,
int plaintext_len) {
if (!ctx_ || !IsAuthenticatedMode())
return false;
MarkPopErrorOnReturn mark_pop_error_on_return;
int outlen;
const int mode = EVP_CIPHER_CTX_mode(ctx_.get());
// When in CCM mode, we need to set the authentication tag and the plaintext
// length in advance.
if (mode == EVP_CIPH_CCM_MODE) {
if (plaintext_len < 0) {
THROW_ERR_MISSING_ARGS(env(),
"options.plaintextLength required for CCM mode with AAD");
return false;
}
if (!CheckCCMMessageLength(plaintext_len))
return false;
if (kind_ == kDecipher) {
if (!MaybePassAuthTagToOpenSSL())
return false;
}
// Specify the plaintext length.
if (!EVP_CipherUpdate(ctx_.get(), nullptr, &outlen, nullptr, plaintext_len))
return false;
}
return 1 == EVP_CipherUpdate(ctx_.get(),
nullptr,
&outlen,
data.data(),
data.size());
}
void CipherBase::SetAAD(const FunctionCallbackInfo<Value>& args) {
CipherBase* cipher;
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());
Environment* env = Environment::GetCurrent(args);
CHECK_EQ(args.Length(), 2);
CHECK(args[1]->IsInt32());
int plaintext_len = args[1].As<Int32>()->Value();
ArrayBufferOrViewContents<unsigned char> buf(args[0]);
if (UNLIKELY(!buf.CheckSizeInt32()))
return THROW_ERR_OUT_OF_RANGE(env, "buffer is too big");
args.GetReturnValue().Set(cipher->SetAAD(buf, plaintext_len));
}
CipherBase::UpdateResult CipherBase::Update(
const char* data,
size_t len,
AllocatedBuffer* out) {
if (!ctx_ || len > INT_MAX)
return kErrorState;
MarkPopErrorOnReturn mark_pop_error_on_return;
const int mode = EVP_CIPHER_CTX_mode(ctx_.get());
if (mode == EVP_CIPH_CCM_MODE && !CheckCCMMessageLength(len))
return kErrorMessageSize;
// Pass the authentication tag to OpenSSL if possible. This will only happen
// once, usually on the first update.
if (kind_ == kDecipher && IsAuthenticatedMode())
CHECK(MaybePassAuthTagToOpenSSL());
int buf_len = len + EVP_CIPHER_CTX_block_size(ctx_.get());
// For key wrapping algorithms, get output size by calling
// EVP_CipherUpdate() with null output.
if (kind_ == kCipher && mode == EVP_CIPH_WRAP_MODE &&
EVP_CipherUpdate(ctx_.get(),
nullptr,
&buf_len,
reinterpret_cast<const unsigned char*>(data),
len) != 1) {
return kErrorState;
}
*out = AllocatedBuffer::AllocateManaged(env(), buf_len);
int r = EVP_CipherUpdate(ctx_.get(),
reinterpret_cast<unsigned char*>(out->data()),
&buf_len,
reinterpret_cast<const unsigned char*>(data),
len);
CHECK_LE(static_cast<size_t>(buf_len), out->size());
out->Resize(buf_len);
// When in CCM mode, EVP_CipherUpdate will fail if the authentication tag is
// invalid. In that case, remember the error and throw in final().
if (!r && kind_ == kDecipher && mode == EVP_CIPH_CCM_MODE) {
pending_auth_failed_ = true;
return kSuccess;
}
return r == 1 ? kSuccess : kErrorState;
}
void CipherBase::Update(const FunctionCallbackInfo<Value>& args) {
Decode<CipherBase>(args, [](CipherBase* cipher,
const FunctionCallbackInfo<Value>& args,
const char* data, size_t size) {
AllocatedBuffer out;
Environment* env = Environment::GetCurrent(args);
if (UNLIKELY(size > INT_MAX))
return THROW_ERR_OUT_OF_RANGE(env, "data is too long");
UpdateResult r = cipher->Update(data, size, &out);
if (r != kSuccess) {
if (r == kErrorState) {
ThrowCryptoError(env, ERR_get_error(),
"Trying to add data in unsupported state");
}
return;
}
CHECK(out.data() != nullptr || out.size() == 0);
args.GetReturnValue().Set(out.ToBuffer().ToLocalChecked());
});
}
bool CipherBase::SetAutoPadding(bool auto_padding) {
if (!ctx_)
return false;
MarkPopErrorOnReturn mark_pop_error_on_return;
return EVP_CIPHER_CTX_set_padding(ctx_.get(), auto_padding);
}
void CipherBase::SetAutoPadding(const FunctionCallbackInfo<Value>& args) {
CipherBase* cipher;
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());
bool b = cipher->SetAutoPadding(args.Length() < 1 || args[0]->IsTrue());
args.GetReturnValue().Set(b); // Possibly report invalid state failure
}
bool CipherBase::Final(AllocatedBuffer* out) {
if (!ctx_)
return false;
const int mode = EVP_CIPHER_CTX_mode(ctx_.get());
*out = AllocatedBuffer::AllocateManaged(
env(),
static_cast<size_t>(EVP_CIPHER_CTX_block_size(ctx_.get())));
if (kind_ == kDecipher && IsSupportedAuthenticatedMode(ctx_.get())) {
MaybePassAuthTagToOpenSSL();
}
// In CCM mode, final() only checks whether authentication failed in update().
// EVP_CipherFinal_ex must not be called and will fail.
bool ok;
if (kind_ == kDecipher && mode == EVP_CIPH_CCM_MODE) {
ok = !pending_auth_failed_;
*out = AllocatedBuffer::AllocateManaged(env(), 0); // Empty buffer.
} else {
int out_len = out->size();
ok = EVP_CipherFinal_ex(ctx_.get(),
reinterpret_cast<unsigned char*>(out->data()),
&out_len) == 1;
if (out_len >= 0)
out->Resize(out_len);
else
*out = AllocatedBuffer(); // *out will not be used.
if (ok && kind_ == kCipher && IsAuthenticatedMode()) {
// In GCM mode, the authentication tag length can be specified in advance,
// but defaults to 16 bytes when encrypting. In CCM and OCB mode, it must
// always be given by the user.
if (auth_tag_len_ == kNoAuthTagLength) {
CHECK(mode == EVP_CIPH_GCM_MODE);
auth_tag_len_ = sizeof(auth_tag_);
}
CHECK_EQ(1, EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_GET_TAG,
auth_tag_len_,
reinterpret_cast<unsigned char*>(auth_tag_)));
}
}
ctx_.reset();
return ok;
}
void CipherBase::Final(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CipherBase* cipher;
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());
if (cipher->ctx_ == nullptr)
return THROW_ERR_CRYPTO_INVALID_STATE(env);
AllocatedBuffer out;
// Check IsAuthenticatedMode() first, Final() destroys the EVP_CIPHER_CTX.
const bool is_auth_mode = cipher->IsAuthenticatedMode();
bool r = cipher->Final(&out);
if (!r) {
const char* msg = is_auth_mode
? "Unsupported state or unable to authenticate data"
: "Unsupported state";
return ThrowCryptoError(env, ERR_get_error(), msg);
}
args.GetReturnValue().Set(out.ToBuffer().ToLocalChecked());
}
template <PublicKeyCipher::Operation operation,
PublicKeyCipher::EVP_PKEY_cipher_init_t EVP_PKEY_cipher_init,
PublicKeyCipher::EVP_PKEY_cipher_t EVP_PKEY_cipher>
bool PublicKeyCipher::Cipher(
Environment* env,
const ManagedEVPPKey& pkey,
int padding,
const EVP_MD* digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
AllocatedBuffer* out) {
EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr));
if (!ctx)
return false;
if (EVP_PKEY_cipher_init(ctx.get()) <= 0)
return false;
if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), padding) <= 0)
return false;
if (digest != nullptr) {
if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx.get(), digest) <= 0)
return false;
}
if (oaep_label.size() != 0) {
// OpenSSL takes ownership of the label, so we need to create a copy.
void* label = OPENSSL_memdup(oaep_label.data(), oaep_label.size());
CHECK_NOT_NULL(label);
if (0 >= EVP_PKEY_CTX_set0_rsa_oaep_label(ctx.get(),
reinterpret_cast<unsigned char*>(label),
oaep_label.size())) {
OPENSSL_free(label);
return false;
}
}
size_t out_len = 0;
if (EVP_PKEY_cipher(
ctx.get(),
nullptr,
&out_len,
data.data(),
data.size()) <= 0) {
return false;
}
*out = AllocatedBuffer::AllocateManaged(env, out_len);
if (EVP_PKEY_cipher(
ctx.get(),
reinterpret_cast<unsigned char*>(out->data()),
&out_len,
data.data(),
data.size()) <= 0) {
return false;
}
out->Resize(out_len);
return true;
}
template <PublicKeyCipher::Operation operation,
PublicKeyCipher::EVP_PKEY_cipher_init_t EVP_PKEY_cipher_init,
PublicKeyCipher::EVP_PKEY_cipher_t EVP_PKEY_cipher>
void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {
MarkPopErrorOnReturn mark_pop_error_on_return;
Environment* env = Environment::GetCurrent(args);
unsigned int offset = 0;
ManagedEVPPKey pkey =
ManagedEVPPKey::GetPublicOrPrivateKeyFromJs(args, &offset);
if (!pkey)
return;
ArrayBufferOrViewContents<unsigned char> buf(args[offset]);
if (UNLIKELY(!buf.CheckSizeInt32()))
return THROW_ERR_OUT_OF_RANGE(env, "buffer is too long");
uint32_t padding;
if (!args[offset + 1]->Uint32Value(env->context()).To(&padding)) return;
const EVP_MD* digest = nullptr;
if (args[offset + 2]->IsString()) {
const Utf8Value oaep_str(env->isolate(), args[offset + 2]);
digest = EVP_get_digestbyname(*oaep_str);
if (digest == nullptr)
return THROW_ERR_OSSL_EVP_INVALID_DIGEST(env);
}
ArrayBufferOrViewContents<unsigned char> oaep_label;
if (!args[offset + 3]->IsUndefined()) {
oaep_label = ArrayBufferOrViewContents<unsigned char>(args[offset + 3]);
if (UNLIKELY(!oaep_label.CheckSizeInt32()))
return THROW_ERR_OUT_OF_RANGE(env, "oaep_label is too big");
}
AllocatedBuffer out;
if (!Cipher<operation, EVP_PKEY_cipher_init, EVP_PKEY_cipher>(
env, pkey, padding, digest, oaep_label, buf, &out)) {
return ThrowCryptoError(env, ERR_get_error());
}
Local<Value> result;
if (out.ToBuffer().ToLocal(&result))
args.GetReturnValue().Set(result);
}
} // namespace crypto
} // namespace node

278
src/crypto/crypto_cipher.h Normal file
View File

@ -0,0 +1,278 @@
#ifndef SRC_CRYPTO_CRYPTO_CIPHER_H_
#define SRC_CRYPTO_CRYPTO_CIPHER_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer-inl.h"
#include "base_object.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
#include <string>
namespace node {
namespace crypto {
class CipherBase : public BaseObject {
public:
static void GetSSLCiphers(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetCiphers(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Initialize(Environment* env, v8::Local<v8::Object> target);
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(CipherBase)
SET_SELF_SIZE(CipherBase)
protected:
enum CipherKind {
kCipher,
kDecipher
};
enum UpdateResult {
kSuccess,
kErrorMessageSize,
kErrorState
};
enum AuthTagState {
kAuthTagUnknown,
kAuthTagKnown,
kAuthTagPassedToOpenSSL
};
static const unsigned kNoAuthTagLength = static_cast<unsigned>(-1);
void CommonInit(const char* cipher_type,
const EVP_CIPHER* cipher,
const unsigned char* key,
int key_len,
const unsigned char* iv,
int iv_len,
unsigned int auth_tag_len);
void Init(const char* cipher_type,
const ArrayBufferOrViewContents<unsigned char>& key_buf,
unsigned int auth_tag_len);
void InitIv(const char* cipher_type,
const ByteSource& key_buf,
const ArrayBufferOrViewContents<unsigned char>& iv_buf,
unsigned int auth_tag_len);
bool InitAuthenticated(const char* cipher_type, int iv_len,
unsigned int auth_tag_len);
bool CheckCCMMessageLength(int message_len);
UpdateResult Update(const char* data, size_t len, AllocatedBuffer* out);
bool Final(AllocatedBuffer* out);
bool SetAutoPadding(bool auto_padding);
bool IsAuthenticatedMode() const;
bool SetAAD(
const ArrayBufferOrViewContents<unsigned char>& data,
int plaintext_len);
bool MaybePassAuthTagToOpenSSL();
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Init(const v8::FunctionCallbackInfo<v8::Value>& args);
static void InitIv(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Update(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Final(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetAutoPadding(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetAuthTag(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetAuthTag(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetAAD(const v8::FunctionCallbackInfo<v8::Value>& args);
CipherBase(Environment* env, v8::Local<v8::Object> wrap, CipherKind kind);
private:
DeleteFnPtr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free> ctx_;
const CipherKind kind_;
AuthTagState auth_tag_state_;
unsigned int auth_tag_len_;
char auth_tag_[EVP_GCM_TLS_TAG_LEN];
bool pending_auth_failed_;
int max_message_size_;
};
class PublicKeyCipher {
public:
typedef int (*EVP_PKEY_cipher_init_t)(EVP_PKEY_CTX* ctx);
typedef int (*EVP_PKEY_cipher_t)(EVP_PKEY_CTX* ctx,
unsigned char* out, size_t* outlen,
const unsigned char* in, size_t inlen);
enum Operation {
kPublic,
kPrivate
};
template <Operation operation,
EVP_PKEY_cipher_init_t EVP_PKEY_cipher_init,
EVP_PKEY_cipher_t EVP_PKEY_cipher>
static bool Cipher(Environment* env,
const ManagedEVPPKey& pkey,
int padding,
const EVP_MD* digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
AllocatedBuffer* out);
template <Operation operation,
EVP_PKEY_cipher_init_t EVP_PKEY_cipher_init,
EVP_PKEY_cipher_t EVP_PKEY_cipher>
static void Cipher(const v8::FunctionCallbackInfo<v8::Value>& args);
};
enum WebCryptoCipherMode {
kWebCryptoCipherEncrypt,
kWebCryptoCipherDecrypt
};
enum class WebCryptoCipherStatus {
ERR_OK,
ERR_INVALID_KEY_TYPE,
ERR_FAILED
};
// CipherJob is a base implementation class for implementations of
// one-shot sync and async ciphers. It has been added primarily to
// support the AES and RSA ciphers underlying the WebCrypt API.
//
// See the crypto_aes and crypto_rsa headers for examples of how to
// use CipherJob.
template <typename CipherTraits>
class CipherJob final : public CryptoJob<CipherTraits> {
public:
using AdditionalParams = typename CipherTraits::AdditionalParameters;
static void New(const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args.IsConstructCall());
CryptoJobMode mode = GetCryptoJobMode(args[0]);
CHECK(args[1]->IsUint32()); // Cipher Mode
uint32_t cmode = args[1].As<v8::Uint32>()->Value();
CHECK_LE(cmode, WebCryptoCipherMode::kWebCryptoCipherDecrypt);
WebCryptoCipherMode cipher_mode = static_cast<WebCryptoCipherMode>(cmode);
CHECK(args[2]->IsObject()); // KeyObject
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args[2]);
CHECK_NOT_NULL(key);
ArrayBufferOrViewContents<char> data(args[3]); // data to operate on
if (!data.CheckSizeInt32())
return THROW_ERR_OUT_OF_RANGE(env, "data is too large");
AdditionalParams params;
if (CipherTraits::AdditionalConfig(mode, args, 4, cipher_mode, &params)
.IsNothing()) {
// The CipherTraits::AdditionalConfig is responsible for
// calling an appropriate THROW_CRYPTO_* variant reporting
// whatever error caused initialization to fail.
return;
}
new CipherJob<CipherTraits>(
env,
args.This(),
mode,
key,
cipher_mode,
data,
std::move(params));
}
static void Initialize(
Environment* env,
v8::Local<v8::Object> target) {
CryptoJob<CipherTraits>::Initialize(New, env, target);
}
CipherJob(
Environment* env,
v8::Local<v8::Object> object,
CryptoJobMode mode,
KeyObjectHandle* key,
WebCryptoCipherMode cipher_mode,
const ArrayBufferOrViewContents<char>& data,
AdditionalParams&& params)
: CryptoJob<CipherTraits>(
env,
object,
AsyncWrap::PROVIDER_CIPHERREQUEST,
mode,
std::move(params)),
key_(key->Data()),
cipher_mode_(cipher_mode),
in_(mode == kCryptoJobAsync
? data.ToCopy()
: data.ToByteSource()) {}
std::shared_ptr<KeyObjectData> key() const { return key_; }
WebCryptoCipherMode cipher_mode() const { return cipher_mode_; }
void DoThreadPoolWork() override {
switch (CipherTraits::DoCipher(
AsyncWrap::env(),
key(),
cipher_mode_,
*CryptoJob<CipherTraits>::params(),
in_,
&out_)) {
case WebCryptoCipherStatus::ERR_OK:
// Success!
break;
case WebCryptoCipherStatus::ERR_INVALID_KEY_TYPE:
// Fall through
// TODO(@jasnell): Separate error for this
case WebCryptoCipherStatus::ERR_FAILED: {
CryptoErrorVector* errors = CryptoJob<CipherTraits>::errors();
errors->Capture();
if (errors->empty())
errors->push_back(std::string("Cipher job failed."));
}
}
}
v8::Maybe<bool> ToResult(
v8::Local<v8::Value>* err,
v8::Local<v8::Value>* result) override {
Environment* env = AsyncWrap::env();
CryptoErrorVector* errors = CryptoJob<CipherTraits>::errors();
if (out_.size() > 0) {
CHECK(errors->empty());
*err = v8::Undefined(env->isolate());
*result = out_.ToArrayBuffer(env);
return v8::Just(!result->IsEmpty());
}
if (errors->empty())
errors->Capture();
CHECK(!errors->empty());
*result = v8::Undefined(env->isolate());
return v8::Just(errors->ToException(env).ToLocal(err));
}
SET_SELF_SIZE(CipherJob)
void MemoryInfo(MemoryTracker* tracker) const override {
if (CryptoJob<CipherTraits>::mode() == kCryptoJobAsync)
tracker->TrackFieldWithSize("in", in_.size());
tracker->TrackFieldWithSize("out", out_.size());
CryptoJob<CipherTraits>::MemoryInfo(tracker);
}
private:
std::shared_ptr<KeyObjectData> key_;
WebCryptoCipherMode cipher_mode_;
ByteSource in_;
ByteSource out_;
};
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_CIPHER_H_

View File

@ -29,7 +29,6 @@
namespace node {
namespace crypto {
inline ClientHelloParser::ClientHelloParser()
: state_(kEnded),
onhello_cb_(nullptr),

View File

@ -24,7 +24,6 @@
namespace node {
namespace crypto {
void ClientHelloParser::Parse(const uint8_t* data, size_t avail) {
switch (state_) {
case kWaiting:

View File

@ -29,7 +29,6 @@
namespace node {
namespace crypto {
// Parse the client hello so we can do async session resumption. OpenSSL's
// session resumption uses synchronous callbacks, see SSL_CTX_sess_set_get_cb
// and get_session_cb.

View File

@ -8,6 +8,7 @@
#include "node_internals.h"
#include "node_url.h"
#include "string_bytes.h"
#include "memory_tracker-inl.h"
#include "v8.h"
#include <openssl/ec.h>
@ -39,7 +40,6 @@ using v8::Undefined;
using v8::Value;
namespace crypto {
static constexpr int X509_NAME_FLAGS =
ASN1_STRFLGS_ESC_CTRL |
ASN1_STRFLGS_UTF8_CONVERT |

View File

@ -13,7 +13,6 @@
namespace node {
namespace crypto {
// OPENSSL_free is a macro, so we need a wrapper function.
struct OpenSSLBufferDeleter {
void operator()(char* pointer) const { OPENSSL_free(pointer); }

1284
src/crypto/crypto_context.cc Normal file

File diff suppressed because it is too large Load Diff

125
src/crypto/crypto_context.h Normal file
View File

@ -0,0 +1,125 @@
#ifndef SRC_CRYPTO_CRYPTO_CONTEXT_H_
#define SRC_CRYPTO_CRYPTO_CONTEXT_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_util.h"
#include "base_object.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
namespace node {
namespace crypto {
// A maxVersion of 0 means "any", but OpenSSL may support TLS versions that
// Node.js doesn't, so pin the max to what we do support.
constexpr int kMaxSupportedVersion = TLS1_3_VERSION;
void GetRootCertificates(
const v8::FunctionCallbackInfo<v8::Value>& args);
void IsExtraRootCertsFileLoaded(
const v8::FunctionCallbackInfo<v8::Value>& args);
class SecureContext final : public BaseObject {
public:
~SecureContext() override;
static void Initialize(Environment* env, v8::Local<v8::Object> target);
SSL_CTX* operator*() const { return ctx_.get(); }
// TODO(joyeecheung): track the memory used by OpenSSL types
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(SecureContext)
SET_SELF_SIZE(SecureContext)
SSLCtxPointer ctx_;
X509Pointer cert_;
X509Pointer issuer_;
#ifndef OPENSSL_NO_ENGINE
bool client_cert_engine_provided_ = false;
EnginePointer private_key_engine_;
#endif // !OPENSSL_NO_ENGINE
static const int kMaxSessionSize = 10 * 1024;
// See TicketKeyCallback
static const int kTicketKeyReturnIndex = 0;
static const int kTicketKeyHMACIndex = 1;
static const int kTicketKeyAESIndex = 2;
static const int kTicketKeyNameIndex = 3;
static const int kTicketKeyIVIndex = 4;
unsigned char ticket_key_name_[16];
unsigned char ticket_key_aes_[16];
unsigned char ticket_key_hmac_[16];
protected:
// OpenSSL structures are opaque. This is sizeof(SSL_CTX) for OpenSSL 1.1.1b:
static const int64_t kExternalSize = 1024;
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Init(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetKey(const v8::FunctionCallbackInfo<v8::Value>& args);
#ifndef OPENSSL_NO_ENGINE
static void SetEngineKey(const v8::FunctionCallbackInfo<v8::Value>& args);
#endif // !OPENSSL_NO_ENGINE
static void SetCert(const v8::FunctionCallbackInfo<v8::Value>& args);
static void AddCACert(const v8::FunctionCallbackInfo<v8::Value>& args);
static void AddCRL(const v8::FunctionCallbackInfo<v8::Value>& args);
static void AddRootCerts(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetCipherSuites(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetCiphers(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetSigalgs(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetECDHCurve(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetDHParam(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetOptions(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetSessionIdContext(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetSessionTimeout(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetMinProto(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetMaxProto(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetMinProto(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetMaxProto(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Close(const v8::FunctionCallbackInfo<v8::Value>& args);
static void LoadPKCS12(const v8::FunctionCallbackInfo<v8::Value>& args);
#ifndef OPENSSL_NO_ENGINE
static void SetClientCertEngine(
const v8::FunctionCallbackInfo<v8::Value>& args);
#endif // !OPENSSL_NO_ENGINE
static void GetTicketKeys(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetTicketKeys(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetFreeListLength(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void EnableTicketKeyCallback(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void CtxGetter(const v8::FunctionCallbackInfo<v8::Value>& info);
template <bool primary>
static void GetCertificate(const v8::FunctionCallbackInfo<v8::Value>& args);
static int TicketKeyCallback(SSL* ssl,
unsigned char* name,
unsigned char* iv,
EVP_CIPHER_CTX* ectx,
HMAC_CTX* hctx,
int enc);
static int TicketCompatibilityCallback(SSL* ssl,
unsigned char* name,
unsigned char* iv,
EVP_CIPHER_CTX* ectx,
HMAC_CTX* hctx,
int enc);
SecureContext(Environment* env, v8::Local<v8::Object> wrap);
void Reset();
};
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_CONTEXT_H_

677
src/crypto/crypto_dh.cc Normal file
View File

@ -0,0 +1,677 @@
#include "crypto/crypto_dh.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_groups.h"
#include "allocated_buffer-inl.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
namespace node {
using v8::ConstructorBehavior;
using v8::DontDelete;
using v8::FunctionCallback;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Int32;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::Nothing;
using v8::Object;
using v8::PropertyAttribute;
using v8::ReadOnly;
using v8::SideEffectType;
using v8::Signature;
using v8::String;
using v8::Value;
namespace crypto {
namespace {
static void ZeroPadDiffieHellmanSecret(size_t remainder_size,
char* data,
size_t length) {
// DH_size returns number of bytes in a prime number.
// DH_compute_key returns number of bytes in a remainder of exponent, which
// may have less bytes than a prime number. Therefore add 0-padding to the
// allocated buffer.
const size_t prime_size = length;
if (remainder_size != prime_size) {
CHECK_LT(remainder_size, prime_size);
const size_t padding = prime_size - remainder_size;
memmove(data + padding, data, remainder_size);
memset(data, 0, padding);
}
}
static void ZeroPadDiffieHellmanSecret(size_t remainder_size,
AllocatedBuffer* ret) {
ZeroPadDiffieHellmanSecret(remainder_size, ret->data(), ret->size());
}
} // namespace
DiffieHellman::DiffieHellman(Environment* env, Local<Object> wrap)
: BaseObject(env, wrap), verifyError_(0) {
MakeWeak();
}
void DiffieHellman::Initialize(Environment* env, Local<Object> target) {
auto make = [&] (Local<String> name, FunctionCallback callback) {
Local<FunctionTemplate> t = env->NewFunctionTemplate(callback);
const PropertyAttribute attributes =
static_cast<PropertyAttribute>(ReadOnly | DontDelete);
t->InstanceTemplate()->SetInternalFieldCount(
DiffieHellman::kInternalFieldCount);
t->Inherit(BaseObject::GetConstructorTemplate(env));
env->SetProtoMethod(t, "generateKeys", GenerateKeys);
env->SetProtoMethod(t, "computeSecret", ComputeSecret);
env->SetProtoMethodNoSideEffect(t, "getPrime", GetPrime);
env->SetProtoMethodNoSideEffect(t, "getGenerator", GetGenerator);
env->SetProtoMethodNoSideEffect(t, "getPublicKey", GetPublicKey);
env->SetProtoMethodNoSideEffect(t, "getPrivateKey", GetPrivateKey);
env->SetProtoMethod(t, "setPublicKey", SetPublicKey);
env->SetProtoMethod(t, "setPrivateKey", SetPrivateKey);
Local<FunctionTemplate> verify_error_getter_templ =
FunctionTemplate::New(env->isolate(),
DiffieHellman::VerifyErrorGetter,
Local<Value>(),
Signature::New(env->isolate(), t),
/* length */ 0,
ConstructorBehavior::kThrow,
SideEffectType::kHasNoSideEffect);
t->InstanceTemplate()->SetAccessorProperty(
env->verify_error_string(),
verify_error_getter_templ,
Local<FunctionTemplate>(),
attributes);
target->Set(env->context(),
name,
t->GetFunction(env->context()).ToLocalChecked()).Check();
};
make(FIXED_ONE_BYTE_STRING(env->isolate(), "DiffieHellman"), New);
make(FIXED_ONE_BYTE_STRING(env->isolate(), "DiffieHellmanGroup"),
DiffieHellmanGroup);
env->SetMethodNoSideEffect(target, "statelessDH", DiffieHellman::Stateless);
DHKeyPairGenJob::Initialize(env, target);
DHKeyExportJob::Initialize(env, target);
DHBitsJob::Initialize(env, target);
}
bool DiffieHellman::Init(int primeLength, int g) {
dh_.reset(DH_new());
if (!DH_generate_parameters_ex(dh_.get(), primeLength, g, nullptr))
return false;
return VerifyContext();
}
void DiffieHellman::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("dh", dh_ ? kSizeOf_DH : 0);
}
bool DiffieHellman::Init(const char* p, int p_len, int g) {
dh_.reset(DH_new());
if (p_len <= 0) {
BNerr(BN_F_BN_GENERATE_PRIME_EX, BN_R_BITS_TOO_SMALL);
return false;
}
if (g <= 1) {
DHerr(DH_F_DH_BUILTIN_GENPARAMS, DH_R_BAD_GENERATOR);
return false;
}
BIGNUM* bn_p =
BN_bin2bn(reinterpret_cast<const unsigned char*>(p), p_len, nullptr);
BIGNUM* bn_g = BN_new();
if (!BN_set_word(bn_g, g) ||
!DH_set0_pqg(dh_.get(), bn_p, nullptr, bn_g)) {
BN_free(bn_p);
BN_free(bn_g);
return false;
}
return VerifyContext();
}
bool DiffieHellman::Init(const char* p, int p_len, const char* g, int g_len) {
dh_.reset(DH_new());
if (p_len <= 0) {
BNerr(BN_F_BN_GENERATE_PRIME_EX, BN_R_BITS_TOO_SMALL);
return false;
}
if (g_len <= 0) {
DHerr(DH_F_DH_BUILTIN_GENPARAMS, DH_R_BAD_GENERATOR);
return false;
}
BIGNUM* bn_g =
BN_bin2bn(reinterpret_cast<const unsigned char*>(g), g_len, nullptr);
if (BN_is_zero(bn_g) || BN_is_one(bn_g)) {
BN_free(bn_g);
DHerr(DH_F_DH_BUILTIN_GENPARAMS, DH_R_BAD_GENERATOR);
return false;
}
BIGNUM* bn_p =
BN_bin2bn(reinterpret_cast<const unsigned char*>(p), p_len, nullptr);
if (!DH_set0_pqg(dh_.get(), bn_p, nullptr, bn_g)) {
BN_free(bn_p);
BN_free(bn_g);
return false;
}
return VerifyContext();
}
inline const modp_group* FindDiffieHellmanGroup(const char* name) {
for (const modp_group& group : modp_groups) {
if (StringEqualNoCase(name, group.name))
return &group;
}
return nullptr;
}
void DiffieHellman::DiffieHellmanGroup(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
DiffieHellman* diffieHellman = new DiffieHellman(env, args.This());
CHECK_EQ(args.Length(), 1);
THROW_AND_RETURN_IF_NOT_STRING(env, args[0], "Group name");
bool initialized = false;
const node::Utf8Value group_name(env->isolate(), args[0]);
const modp_group* group = FindDiffieHellmanGroup(*group_name);
if (group == nullptr)
return THROW_ERR_CRYPTO_UNKNOWN_DH_GROUP(env);
initialized = diffieHellman->Init(group->prime,
group->prime_size,
group->gen);
if (!initialized)
THROW_ERR_CRYPTO_INITIALIZATION_FAILED(env);
}
void DiffieHellman::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
DiffieHellman* diffieHellman =
new DiffieHellman(env, args.This());
bool initialized = false;
if (args.Length() == 2) {
if (args[0]->IsInt32()) {
if (args[1]->IsInt32()) {
initialized = diffieHellman->Init(args[0].As<Int32>()->Value(),
args[1].As<Int32>()->Value());
}
} else {
ArrayBufferOrViewContents<char> arg0(args[0]);
if (UNLIKELY(!arg0.CheckSizeInt32()))
return THROW_ERR_OUT_OF_RANGE(env, "prime is too big");
if (args[1]->IsInt32()) {
initialized = diffieHellman->Init(arg0.data(),
arg0.size(),
args[1].As<Int32>()->Value());
} else {
ArrayBufferOrViewContents<char> arg1(args[1]);
if (UNLIKELY(!arg1.CheckSizeInt32()))
return THROW_ERR_OUT_OF_RANGE(env, "generator is too big");
initialized = diffieHellman->Init(arg0.data(), arg0.size(),
arg1.data(), arg1.size());
}
}
}
if (!initialized) {
return ThrowCryptoError(env, ERR_get_error(), "Initialization failed");
}
}
void DiffieHellman::GenerateKeys(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
DiffieHellman* diffieHellman;
ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.Holder());
if (!DH_generate_key(diffieHellman->dh_.get())) {
return ThrowCryptoError(env, ERR_get_error(), "Key generation failed");
}
const BIGNUM* pub_key;
DH_get0_key(diffieHellman->dh_.get(), &pub_key, nullptr);
const int size = BN_num_bytes(pub_key);
CHECK_GE(size, 0);
AllocatedBuffer data = AllocatedBuffer::AllocateManaged(env, size);
CHECK_EQ(size,
BN_bn2binpad(
pub_key, reinterpret_cast<unsigned char*>(data.data()), size));
args.GetReturnValue().Set(data.ToBuffer().ToLocalChecked());
}
void DiffieHellman::GetField(const FunctionCallbackInfo<Value>& args,
const BIGNUM* (*get_field)(const DH*),
const char* err_if_null) {
Environment* env = Environment::GetCurrent(args);
DiffieHellman* dh;
ASSIGN_OR_RETURN_UNWRAP(&dh, args.Holder());
const BIGNUM* num = get_field(dh->dh_.get());
if (num == nullptr)
return THROW_ERR_CRYPTO_INVALID_STATE(env, err_if_null);
const int size = BN_num_bytes(num);
CHECK_GE(size, 0);
AllocatedBuffer data = AllocatedBuffer::AllocateManaged(env, size);
CHECK_EQ(
size,
BN_bn2binpad(num, reinterpret_cast<unsigned char*>(data.data()), size));
args.GetReturnValue().Set(data.ToBuffer().ToLocalChecked());
}
void DiffieHellman::GetPrime(const FunctionCallbackInfo<Value>& args) {
GetField(args, [](const DH* dh) -> const BIGNUM* {
const BIGNUM* p;
DH_get0_pqg(dh, &p, nullptr, nullptr);
return p;
}, "p is null");
}
void DiffieHellman::GetGenerator(const FunctionCallbackInfo<Value>& args) {
GetField(args, [](const DH* dh) -> const BIGNUM* {
const BIGNUM* g;
DH_get0_pqg(dh, nullptr, nullptr, &g);
return g;
}, "g is null");
}
void DiffieHellman::GetPublicKey(const FunctionCallbackInfo<Value>& args) {
GetField(args, [](const DH* dh) -> const BIGNUM* {
const BIGNUM* pub_key;
DH_get0_key(dh, &pub_key, nullptr);
return pub_key;
}, "No public key - did you forget to generate one?");
}
void DiffieHellman::GetPrivateKey(const FunctionCallbackInfo<Value>& args) {
GetField(args, [](const DH* dh) -> const BIGNUM* {
const BIGNUM* priv_key;
DH_get0_key(dh, nullptr, &priv_key);
return priv_key;
}, "No private key - did you forget to generate one?");
}
void DiffieHellman::ComputeSecret(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
DiffieHellman* diffieHellman;
ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.Holder());
ClearErrorOnReturn clear_error_on_return;
CHECK_EQ(args.Length(), 1);
ArrayBufferOrViewContents<unsigned char> key_buf(args[0]);
if (UNLIKELY(!key_buf.CheckSizeInt32()))
return THROW_ERR_OUT_OF_RANGE(env, "secret is too big");
BignumPointer key(BN_bin2bn(key_buf.data(), key_buf.size(), nullptr));
AllocatedBuffer ret =
AllocatedBuffer::AllocateManaged(env, DH_size(diffieHellman->dh_.get()));
int size = DH_compute_key(reinterpret_cast<unsigned char*>(ret.data()),
key.get(),
diffieHellman->dh_.get());
if (size == -1) {
int checkResult;
int checked;
checked = DH_check_pub_key(diffieHellman->dh_.get(),
key.get(),
&checkResult);
if (!checked) {
return ThrowCryptoError(env, ERR_get_error(), "Invalid Key");
} else if (checkResult) {
if (checkResult & DH_CHECK_PUBKEY_TOO_SMALL) {
return THROW_ERR_CRYPTO_INVALID_KEYLEN(env,
"Supplied key is too small");
} else if (checkResult & DH_CHECK_PUBKEY_TOO_LARGE) {
return THROW_ERR_CRYPTO_INVALID_KEYLEN(env,
"Supplied key is too large");
}
}
return THROW_ERR_CRYPTO_INVALID_KEYTYPE(env);
}
CHECK_GE(size, 0);
ZeroPadDiffieHellmanSecret(static_cast<size_t>(size), &ret);
args.GetReturnValue().Set(ret.ToBuffer().ToLocalChecked());
}
void DiffieHellman::SetKey(const FunctionCallbackInfo<Value>& args,
int (*set_field)(DH*, BIGNUM*), const char* what) {
Environment* env = Environment::GetCurrent(args);
DiffieHellman* dh;
ASSIGN_OR_RETURN_UNWRAP(&dh, args.Holder());
CHECK_EQ(args.Length(), 1);
ArrayBufferOrViewContents<unsigned char> buf(args[0]);
if (UNLIKELY(!buf.CheckSizeInt32()))
return THROW_ERR_OUT_OF_RANGE(env, "buf is too big");
BIGNUM* num = BN_bin2bn(buf.data(), buf.size(), nullptr);
CHECK_NOT_NULL(num);
CHECK_EQ(1, set_field(dh->dh_.get(), num));
}
void DiffieHellman::SetPublicKey(const FunctionCallbackInfo<Value>& args) {
SetKey(args,
[](DH* dh, BIGNUM* num) { return DH_set0_key(dh, num, nullptr); },
"Public key");
}
void DiffieHellman::SetPrivateKey(const FunctionCallbackInfo<Value>& args) {
SetKey(args,
[](DH* dh, BIGNUM* num) { return DH_set0_key(dh, nullptr, num); },
"Private key");
}
void DiffieHellman::VerifyErrorGetter(const FunctionCallbackInfo<Value>& args) {
HandleScope scope(args.GetIsolate());
DiffieHellman* diffieHellman;
ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.Holder());
args.GetReturnValue().Set(diffieHellman->verifyError_);
}
bool DiffieHellman::VerifyContext() {
int codes;
if (!DH_check(dh_.get(), &codes))
return false;
verifyError_ = codes;
return true;
}
// The input arguments to DhKeyPairGenJob can vary
// 1. CryptoJobMode
// and either
// 2. Group name (as a string)
// or
// 2. Prime or Prime Length
// 3. Generator
// Followed by the public and private key encoding parameters:
// * Public format
// * Public type
// * Private format
// * Private type
// * Cipher
// * Passphrase
Maybe<bool> DhKeyGenTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int* offset,
DhKeyPairGenConfig* params) {
Environment* env = Environment::GetCurrent(args);
if (args[*offset]->IsString()) {
Utf8Value group_name(env->isolate(), args[*offset]);
const modp_group* group = FindDiffieHellmanGroup(*group_name);
if (group == nullptr) {
THROW_ERR_CRYPTO_UNKNOWN_DH_GROUP(env);
return Nothing<bool>();
}
params->params.prime_fixed_value = BignumPointer(
BN_bin2bn(reinterpret_cast<const unsigned char*>(group->prime),
group->prime_size, nullptr));
params->params.generator = group->gen;
*offset += 1;
} else {
if (args[*offset]->IsInt32()) {
int size = args[*offset].As<Int32>()->Value();
if (size < 0) {
THROW_ERR_OUT_OF_RANGE(env, "Invalid prime size");
return Nothing<bool>();
}
params->params.prime_size = size;
} else {
ArrayBufferOrViewContents<unsigned char> input(args[*offset]);
if (UNLIKELY(!input.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "prime is too big");
return Nothing<bool>();
}
params->params.prime_fixed_value = BignumPointer(
BN_bin2bn(input.data(), input.size(), nullptr));
}
CHECK(args[*offset + 1]->IsInt32());
params->params.generator = args[*offset + 1].As<Int32>()->Value();
*offset += 2;
}
return Just(true);
}
EVPKeyCtxPointer DhKeyGenTraits::Setup(DhKeyPairGenConfig* params) {
EVPKeyPointer key_params;
if (params->params.prime_fixed_value) {
DHPointer dh(DH_new());
if (!dh)
return EVPKeyCtxPointer();
BIGNUM* prime = params->params.prime_fixed_value.get();
BignumPointer bn_g(BN_new());
if (!BN_set_word(bn_g.get(), params->params.generator) ||
!DH_set0_pqg(dh.get(), prime, nullptr, bn_g.get()))
return EVPKeyCtxPointer();
params->params.prime_fixed_value.release();
bn_g.release();
key_params = EVPKeyPointer(EVP_PKEY_new());
CHECK(key_params);
EVP_PKEY_assign_DH(key_params.get(), dh.release());
} else {
EVPKeyCtxPointer param_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_DH, nullptr));
EVP_PKEY* raw_params = nullptr;
if (!param_ctx ||
EVP_PKEY_paramgen_init(param_ctx.get()) <= 0 ||
EVP_PKEY_CTX_set_dh_paramgen_prime_len(
param_ctx.get(),
params->params.prime_size) <= 0 ||
EVP_PKEY_CTX_set_dh_paramgen_generator(
param_ctx.get(),
params->params.generator) <= 0 ||
EVP_PKEY_paramgen(param_ctx.get(), &raw_params) <= 0) {
return EVPKeyCtxPointer();
}
key_params = EVPKeyPointer(raw_params);
}
EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(key_params.get(), nullptr));
if (!ctx || EVP_PKEY_keygen_init(ctx.get()) <= 0)
return EVPKeyCtxPointer();
return ctx;
}
Maybe<bool> DHKeyExportTraits::AdditionalConfig(
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
DHKeyExportConfig* params) {
return Just(true);
}
WebCryptoKeyExportStatus DHKeyExportTraits::DoExport(
std::shared_ptr<KeyObjectData> key_data,
WebCryptoKeyFormat format,
const DHKeyExportConfig& params,
ByteSource* out) {
CHECK_NE(key_data->GetKeyType(), kKeyTypeSecret);
switch (format) {
case kWebCryptoKeyFormatPKCS8:
if (key_data->GetKeyType() != kKeyTypePrivate)
return WebCryptoKeyExportStatus::ERR_INVALID_KEY_TYPE;
return PKEY_PKCS8_Export(key_data.get(), out);
case kWebCryptoKeyFormatSPKI:
if (key_data->GetKeyType() != kKeyTypePublic)
return WebCryptoKeyExportStatus::ERR_INVALID_KEY_TYPE;
return PKEY_SPKI_Export(key_data.get(), out);
default:
UNREACHABLE();
}
}
namespace {
AllocatedBuffer StatelessDiffieHellman(
Environment* env,
ManagedEVPPKey our_key,
ManagedEVPPKey their_key) {
size_t out_size;
EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(our_key.get(), nullptr));
if (!ctx ||
EVP_PKEY_derive_init(ctx.get()) <= 0 ||
EVP_PKEY_derive_set_peer(ctx.get(), their_key.get()) <= 0 ||
EVP_PKEY_derive(ctx.get(), nullptr, &out_size) <= 0)
return AllocatedBuffer();
AllocatedBuffer result = AllocatedBuffer::AllocateManaged(env, out_size);
CHECK_NOT_NULL(result.data());
unsigned char* data = reinterpret_cast<unsigned char*>(result.data());
if (EVP_PKEY_derive(ctx.get(), data, &out_size) <= 0)
return AllocatedBuffer();
ZeroPadDiffieHellmanSecret(out_size, &result);
return result;
}
// The version of StatelessDiffieHellman that returns an AllocatedBuffer
// is not threadsafe because of the AllocatedBuffer allocation of a
// v8::BackingStore (it'll cause much crashing if we call it from a
// libuv worker thread). This version allocates a ByteSource instead,
// which we can convert into a v8::BackingStore later.
// TODO(@jasnell): Eliminate the code duplication between these two
// versions of the function.
ByteSource StatelessDiffieHellmanThreadsafe(
Environment* env,
ManagedEVPPKey our_key,
ManagedEVPPKey their_key) {
size_t out_size;
EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(our_key.get(), nullptr));
if (!ctx ||
EVP_PKEY_derive_init(ctx.get()) <= 0 ||
EVP_PKEY_derive_set_peer(ctx.get(), their_key.get()) <= 0 ||
EVP_PKEY_derive(ctx.get(), nullptr, &out_size) <= 0)
return ByteSource();
char* buf = MallocOpenSSL<char>(out_size);
ByteSource out = ByteSource::Allocated(buf, out_size);
if (EVP_PKEY_derive(
ctx.get(),
reinterpret_cast<unsigned char*>(buf),
&out_size) <= 0) {
return ByteSource();
}
ZeroPadDiffieHellmanSecret(out_size, buf, out.size());
return out;
}
} // namespace
void DiffieHellman::Stateless(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsObject() && args[1]->IsObject());
KeyObjectHandle* our_key_object;
ASSIGN_OR_RETURN_UNWRAP(&our_key_object, args[0].As<Object>());
CHECK_EQ(our_key_object->Data()->GetKeyType(), kKeyTypePrivate);
KeyObjectHandle* their_key_object;
ASSIGN_OR_RETURN_UNWRAP(&their_key_object, args[1].As<Object>());
CHECK_NE(their_key_object->Data()->GetKeyType(), kKeyTypeSecret);
ManagedEVPPKey our_key = our_key_object->Data()->GetAsymmetricKey();
ManagedEVPPKey their_key = their_key_object->Data()->GetAsymmetricKey();
AllocatedBuffer out = StatelessDiffieHellman(env, our_key, their_key);
if (out.size() == 0)
return ThrowCryptoError(env, ERR_get_error(), "diffieHellman failed");
args.GetReturnValue().Set(out.ToBuffer().ToLocalChecked());
}
Maybe<bool> DHBitsTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
DHBitsConfig* params) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[offset]->IsObject()); // public key
CHECK(args[offset + 1]->IsObject()); // private key
KeyObjectHandle* private_key;
KeyObjectHandle* public_key;
ASSIGN_OR_RETURN_UNWRAP(&public_key, args[offset], Nothing<bool>());
ASSIGN_OR_RETURN_UNWRAP(&private_key, args[offset + 1], Nothing<bool>());
if (private_key->Data()->GetKeyType() != kKeyTypePrivate ||
public_key->Data()->GetKeyType() != kKeyTypePublic) {
THROW_ERR_CRYPTO_INVALID_KEYTYPE(env);
return Nothing<bool>();
}
params->public_key = public_key->Data();
params->private_key = private_key->Data();
return Just(true);
}
Maybe<bool> DHBitsTraits::EncodeOutput(
Environment* env,
const DHBitsConfig& params,
ByteSource* out,
v8::Local<v8::Value>* result) {
*result = out->ToArrayBuffer(env);
return Just(!result->IsEmpty());
}
bool DHBitsTraits::DeriveBits(
Environment* env,
const DHBitsConfig& params,
ByteSource* out) {
*out = StatelessDiffieHellmanThreadsafe(
env,
params.private_key->GetAsymmetricKey(),
params.public_key->GetAsymmetricKey());
return true;
}
Maybe<bool> GetDhKeyDetail(
Environment* env,
std::shared_ptr<KeyObjectData> key,
Local<Object> target) {
ManagedEVPPKey pkey = key->GetAsymmetricKey();
CHECK_EQ(EVP_PKEY_id(pkey.get()), EVP_PKEY_DH);
return Just(true);
}
} // namespace crypto
} // namespace node

157
src/crypto/crypto_dh.h Normal file
View File

@ -0,0 +1,157 @@
#ifndef SRC_CRYPTO_CRYPTO_DH_H_
#define SRC_CRYPTO_CRYPTO_DH_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_keys.h"
#include "crypto/crypto_keygen.h"
#include "crypto/crypto_util.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
#include <vector>
namespace node {
namespace crypto {
class DiffieHellman : public BaseObject {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
bool Init(int primeLength, int g);
bool Init(const char* p, int p_len, int g);
bool Init(const char* p, int p_len, const char* g, int g_len);
static void Stateless(const v8::FunctionCallbackInfo<v8::Value>& args);
protected:
static void DiffieHellmanGroup(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GenerateKeys(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPrime(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetGenerator(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPrivateKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ComputeSecret(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetPublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetPrivateKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void VerifyErrorGetter(
const v8::FunctionCallbackInfo<v8::Value>& args);
DiffieHellman(Environment* env, v8::Local<v8::Object> wrap);
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(DiffieHellman)
SET_SELF_SIZE(DiffieHellman)
private:
static void GetField(const v8::FunctionCallbackInfo<v8::Value>& args,
const BIGNUM* (*get_field)(const DH*),
const char* err_if_null);
static void SetKey(const v8::FunctionCallbackInfo<v8::Value>& args,
int (*set_field)(DH*, BIGNUM*), const char* what);
bool VerifyContext();
int verifyError_;
DHPointer dh_;
};
struct DhKeyPairParams final : public MemoryRetainer {
// TODO(tniessen): Use std::variant instead.
// Diffie-Hellman can either generate keys using a fixed prime, or by first
// generating a random prime of a given size (in bits). Only one of both
// options may be specified.
BignumPointer prime_fixed_value;
unsigned int prime_size;
unsigned int generator;
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(DhKeyPairParams)
SET_SELF_SIZE(DhKeyPairParams)
};
using DhKeyPairGenConfig = KeyPairGenConfig<DhKeyPairParams>;
struct DhKeyGenTraits final {
using AdditionalParameters = DhKeyPairGenConfig;
static constexpr const char* JobName = "DhKeyPairGenJob";
static EVPKeyCtxPointer Setup(DhKeyPairGenConfig* params);
static v8::Maybe<bool> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
DhKeyPairGenConfig* params);
};
using DHKeyPairGenJob = KeyGenJob<KeyPairGenTraits<DhKeyGenTraits>>;
struct DHKeyExportConfig final : public MemoryRetainer {
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(DHKeyExportConfig)
SET_SELF_SIZE(DHKeyExportConfig)
};
struct DHKeyExportTraits final {
static constexpr const char* JobName = "DHKeyExportJob";
using AdditionalParameters = DHKeyExportConfig;
static v8::Maybe<bool> AdditionalConfig(
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
DHKeyExportConfig* config);
static WebCryptoKeyExportStatus DoExport(
std::shared_ptr<KeyObjectData> key_data,
WebCryptoKeyFormat format,
const DHKeyExportConfig& params,
ByteSource* out);
};
using DHKeyExportJob = KeyExportJob<DHKeyExportTraits>;
struct DHBitsConfig final : public MemoryRetainer {
std::shared_ptr<KeyObjectData> private_key;
std::shared_ptr<KeyObjectData> public_key;
SET_NO_MEMORY_INFO();
SET_MEMORY_INFO_NAME(DHBitsConfig);
SET_SELF_SIZE(DHBitsConfig);
};
struct DHBitsTraits final {
using AdditionalParameters = DHBitsConfig;
static constexpr const char* JobName = "DHBitsJob";
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_DERIVEBITSREQUEST;
static v8::Maybe<bool> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
DHBitsConfig* params);
static bool DeriveBits(
Environment* env,
const DHBitsConfig& params,
ByteSource* out_);
static v8::Maybe<bool> EncodeOutput(
Environment* env,
const DHBitsConfig& params,
ByteSource* out,
v8::Local<v8::Value>* result);
};
using DHBitsJob = DeriveBitsJob<DHBitsTraits>;
v8::Maybe<bool> GetDhKeyDetail(
Environment* env,
std::shared_ptr<KeyObjectData> key,
v8::Local<v8::Object> target);
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_DH_H_

272
src/crypto/crypto_dsa.cc Normal file
View File

@ -0,0 +1,272 @@
#include "crypto/crypto_dsa.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "async_wrap-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
#include <openssl/bn.h>
#include <openssl/dsa.h>
#include <cstdio>
namespace node {
using v8::FunctionCallbackInfo;
using v8::Int32;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::Nothing;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Value;
namespace crypto {
EVPKeyCtxPointer DsaKeyGenTraits::Setup(DsaKeyPairGenConfig* params) {
EVPKeyCtxPointer param_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_DSA, nullptr));
EVP_PKEY* raw_params = nullptr;
if (!param_ctx ||
EVP_PKEY_paramgen_init(param_ctx.get()) <= 0 ||
EVP_PKEY_CTX_set_dsa_paramgen_bits(
param_ctx.get(),
params->params.modulus_bits) <= 0) {
return EVPKeyCtxPointer();
}
if (params->params.divisor_bits != -1) {
if (EVP_PKEY_CTX_ctrl(
param_ctx.get(),
EVP_PKEY_DSA,
EVP_PKEY_OP_PARAMGEN,
EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS,
params->params.divisor_bits,
nullptr) <= 0) {
return EVPKeyCtxPointer();
}
}
if (EVP_PKEY_paramgen(param_ctx.get(), &raw_params) <= 0)
return EVPKeyCtxPointer();
EVPKeyPointer key_params(raw_params);
EVPKeyCtxPointer key_ctx(EVP_PKEY_CTX_new(key_params.get(), nullptr));
if (!key_ctx || EVP_PKEY_keygen_init(key_ctx.get()) <= 0)
return EVPKeyCtxPointer();
return key_ctx;
}
// Input arguments for DsaKeyPairGenJob
// 1. CryptoJobMode
// 2. Modulus Bits
// 3. Divisor Bits
// 4. Public Format
// 5. Public Type
// 6. Private Format
// 7. Private Type
// 8. Cipher
// 9. Passphrase
Maybe<bool> DsaKeyGenTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int* offset,
DsaKeyPairGenConfig* params) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[*offset]->IsUint32()); // modulus bits
CHECK(args[*offset + 1]->IsInt32()); // divisor bits
params->params.modulus_bits = args[*offset].As<Uint32>()->Value();
params->params.divisor_bits = args[*offset + 1].As<Int32>()->Value();
if (params->params.divisor_bits < -1) {
char msg[1024];
snprintf(msg, sizeof(msg), "invalid value for divisor_bits");
THROW_ERR_OUT_OF_RANGE(env, msg);
return Nothing<bool>();
}
*offset += 2;
return Just(true);
}
Maybe<bool> DSAKeyExportTraits::AdditionalConfig(
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
DSAKeyExportConfig* params) {
return Just(true);
}
WebCryptoKeyExportStatus DSAKeyExportTraits::DoExport(
std::shared_ptr<KeyObjectData> key_data,
WebCryptoKeyFormat format,
const DSAKeyExportConfig& params,
ByteSource* out) {
CHECK_NE(key_data->GetKeyType(), kKeyTypeSecret);
switch (format) {
case kWebCryptoKeyFormatRaw:
// Not supported for RSA keys of either type
return WebCryptoKeyExportStatus::ERR_FAILED;
case kWebCryptoKeyFormatPKCS8:
if (key_data->GetKeyType() != kKeyTypePrivate)
return WebCryptoKeyExportStatus::ERR_INVALID_KEY_TYPE;
return PKEY_PKCS8_Export(key_data.get(), out);
case kWebCryptoKeyFormatSPKI:
if (key_data->GetKeyType() != kKeyTypePublic)
return WebCryptoKeyExportStatus::ERR_INVALID_KEY_TYPE;
return PKEY_SPKI_Export(key_data.get(), out);
default:
UNREACHABLE();
}
}
Maybe<bool> ExportJWKDsaKey(
Environment* env,
std::shared_ptr<KeyObjectData> key,
Local<Object> target) {
ManagedEVPPKey pkey = key->GetAsymmetricKey();
CHECK_EQ(EVP_PKEY_id(pkey.get()), EVP_PKEY_DSA);
DSA* dsa = EVP_PKEY_get0_DSA(pkey.get());
CHECK_NOT_NULL(dsa);
const BIGNUM* y;
const BIGNUM* x;
const BIGNUM* p;
const BIGNUM* q;
const BIGNUM* g;
DSA_get0_key(dsa, &y, &x);
DSA_get0_pqg(dsa, &p, &q, &g);
if (target->Set(
env->context(),
env->jwk_kty_string(),
env->jwk_dsa_string()).IsNothing()) {
return Nothing<bool>();
}
if (SetEncodedValue(env, target, env->jwk_y_string(), y).IsNothing() ||
SetEncodedValue(env, target, env->jwk_p_string(), p).IsNothing() ||
SetEncodedValue(env, target, env->jwk_q_string(), q).IsNothing() ||
SetEncodedValue(env, target, env->jwk_g_string(), g).IsNothing()) {
return Nothing<bool>();
}
if (key->GetKeyType() == kKeyTypePrivate &&
SetEncodedValue(env, target, env->jwk_x_string(), x).IsNothing()) {
return Nothing<bool>();
}
return Just(true);
}
std::shared_ptr<KeyObjectData> ImportJWKDsaKey(
Environment* env,
Local<Object> jwk,
const FunctionCallbackInfo<Value>& args,
unsigned int offset) {
Local<Value> y_value;
Local<Value> p_value;
Local<Value> q_value;
Local<Value> g_value;
Local<Value> x_value;
if (!jwk->Get(env->context(), env->jwk_y_string()).ToLocal(&y_value) ||
!jwk->Get(env->context(), env->jwk_p_string()).ToLocal(&p_value) ||
!jwk->Get(env->context(), env->jwk_q_string()).ToLocal(&q_value) ||
!jwk->Get(env->context(), env->jwk_g_string()).ToLocal(&g_value) ||
!jwk->Get(env->context(), env->jwk_x_string()).ToLocal(&x_value)) {
return std::shared_ptr<KeyObjectData>();
}
if (!y_value->IsString() ||
!p_value->IsString() ||
!q_value->IsString() ||
!q_value->IsString() ||
(!x_value->IsUndefined() && !x_value->IsString())) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JSK DSA key");
return std::shared_ptr<KeyObjectData>();
}
KeyType type = x_value->IsString() ? kKeyTypePrivate : kKeyTypePublic;
DsaPointer dsa(DSA_new());
ByteSource y = ByteSource::FromEncodedString(env, y_value.As<String>());
ByteSource p = ByteSource::FromEncodedString(env, p_value.As<String>());
ByteSource q = ByteSource::FromEncodedString(env, q_value.As<String>());
ByteSource g = ByteSource::FromEncodedString(env, g_value.As<String>());
if (!DSA_set0_key(dsa.get(), y.ToBN().release(), nullptr) ||
!DSA_set0_pqg(dsa.get(),
p.ToBN().release(),
q.ToBN().release(),
g.ToBN().release())) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JSK DSA key");
return std::shared_ptr<KeyObjectData>();
}
if (type == kKeyTypePrivate) {
ByteSource x = ByteSource::FromEncodedString(env, x_value.As<String>());
if (!DSA_set0_key(dsa.get(), nullptr, x.ToBN().release())) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JSK DSA key");
return std::shared_ptr<KeyObjectData>();
}
}
EVPKeyPointer pkey(EVP_PKEY_new());
CHECK_EQ(EVP_PKEY_set1_DSA(pkey.get(), dsa.get()), 1);
return KeyObjectData::CreateAsymmetric(type, ManagedEVPPKey(std::move(pkey)));
}
Maybe<bool> GetDsaKeyDetail(
Environment* env,
std::shared_ptr<KeyObjectData> key,
Local<Object> target) {
const BIGNUM* p; // Modulus length
const BIGNUM* q; // Divisor length
ManagedEVPPKey pkey = key->GetAsymmetricKey();
int type = EVP_PKEY_id(pkey.get());
CHECK(type == EVP_PKEY_DSA);
DSA* dsa = EVP_PKEY_get0_DSA(pkey.get());
CHECK_NOT_NULL(dsa);
DSA_get0_pqg(dsa, &p, &q, nullptr);
size_t modulus_length = BN_num_bytes(p) * CHAR_BIT;
size_t divisor_length = BN_num_bytes(q) * CHAR_BIT;
if (target->Set(
env->context(),
env->modulus_length_string(),
Number::New(env->isolate(), modulus_length)).IsNothing() ||
target->Set(
env->context(),
env->divisor_length_string(),
Number::New(env->isolate(), divisor_length)).IsNothing()) {
return Nothing<bool>();
}
return Just(true);
}
namespace DSAAlg {
void Initialize(Environment* env, Local<Object> target) {
DsaKeyPairGenJob::Initialize(env, target);
DSAKeyExportJob::Initialize(env, target);
}
} // namespace DSAAlg
} // namespace crypto
} // namespace node

87
src/crypto/crypto_dsa.h Normal file
View File

@ -0,0 +1,87 @@
#ifndef SRC_CRYPTO_CRYPTO_DSA_H_
#define SRC_CRYPTO_CRYPTO_DSA_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_keys.h"
#include "crypto/crypto_keygen.h"
#include "crypto/crypto_util.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
namespace node {
namespace crypto {
struct DsaKeyPairParams final : public MemoryRetainer {
unsigned int modulus_bits;
int divisor_bits;
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(DsaKeyPairParams);
SET_SELF_SIZE(DsaKeyPairParams);
};
using DsaKeyPairGenConfig = KeyPairGenConfig<DsaKeyPairParams>;
struct DsaKeyGenTraits final {
using AdditionalParameters = DsaKeyPairGenConfig;
static constexpr const char* JobName = "DsaKeyPairGenJob";
static EVPKeyCtxPointer Setup(DsaKeyPairGenConfig* params);
static v8::Maybe<bool> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
DsaKeyPairGenConfig* params);
};
using DsaKeyPairGenJob = KeyGenJob<KeyPairGenTraits<DsaKeyGenTraits>>;
struct DSAKeyExportConfig final : public MemoryRetainer {
SET_NO_MEMORY_INFO();
SET_MEMORY_INFO_NAME(DSAKeyExportConfig);
SET_SELF_SIZE(DSAKeyExportConfig);
};
struct DSAKeyExportTraits final {
static constexpr const char* JobName = "DSAKeyExportJob";
using AdditionalParameters = DSAKeyExportConfig;
static v8::Maybe<bool> AdditionalConfig(
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
DSAKeyExportConfig* config);
static WebCryptoKeyExportStatus DoExport(
std::shared_ptr<KeyObjectData> key_data,
WebCryptoKeyFormat format,
const DSAKeyExportConfig& params,
ByteSource* out);
};
using DSAKeyExportJob = KeyExportJob<DSAKeyExportTraits>;
v8::Maybe<bool> ExportJWKDsaKey(
Environment* env,
std::shared_ptr<KeyObjectData> key,
v8::Local<v8::Object> target);
std::shared_ptr<KeyObjectData> ImportJWKDsaKey(
Environment* env,
v8::Local<v8::Object> jwk,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset);
v8::Maybe<bool> GetDsaKeyDetail(
Environment* env,
std::shared_ptr<KeyObjectData> key,
v8::Local<v8::Object> target);
namespace DSAAlg {
void Initialize(Environment* env, v8::Local<v8::Object> target);
} // namespace DSAAlg
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_DSA_H_

814
src/crypto/crypto_ecdh.cc Normal file
View File

@ -0,0 +1,814 @@
#include "crypto/crypto_ecdh.h"
#include "crypto/crypto_common.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer-inl.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node_buffer.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
#include <openssl/bn.h>
#include <openssl/ec.h>
#include <openssl/ecdh.h>
namespace node {
using v8::Array;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Int32;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::Nothing;
using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Value;
namespace crypto {
namespace {
int GetCurveFromName(const char* name) {
int nid = EC_curve_nist2nid(name);
if (nid == NID_undef)
nid = OBJ_sn2nid(name);
return nid;
}
} // namespace
void ECDH::Initialize(Environment* env, Local<Object> target) {
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
t->Inherit(BaseObject::GetConstructorTemplate(env));
t->InstanceTemplate()->SetInternalFieldCount(ECDH::kInternalFieldCount);
env->SetProtoMethod(t, "generateKeys", GenerateKeys);
env->SetProtoMethod(t, "computeSecret", ComputeSecret);
env->SetProtoMethodNoSideEffect(t, "getPublicKey", GetPublicKey);
env->SetProtoMethodNoSideEffect(t, "getPrivateKey", GetPrivateKey);
env->SetProtoMethod(t, "setPublicKey", SetPublicKey);
env->SetProtoMethod(t, "setPrivateKey", SetPrivateKey);
target->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "ECDH"),
t->GetFunction(env->context()).ToLocalChecked()).Check();
env->SetMethodNoSideEffect(target, "ECDHConvertKey", ECDH::ConvertKey);
env->SetMethodNoSideEffect(target, "getCurves", ECDH::GetCurves);
ECDHBitsJob::Initialize(env, target);
ECKeyPairGenJob::Initialize(env, target);
ECKeyExportJob::Initialize(env, target);
NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE);
NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
}
void ECDH::GetCurves(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
const size_t num_curves = EC_get_builtin_curves(nullptr, 0);
if (num_curves) {
std::vector<EC_builtin_curve> curves(num_curves);
if (EC_get_builtin_curves(curves.data(), num_curves)) {
std::vector<Local<Value>> arr(num_curves);
for (size_t i = 0; i < num_curves; i++)
arr[i] = OneByteString(env->isolate(), OBJ_nid2sn(curves[i].nid));
args.GetReturnValue().Set(
Array::New(env->isolate(), arr.data(), arr.size()));
return;
}
}
args.GetReturnValue().Set(Array::New(env->isolate()));
}
ECDH::ECDH(Environment* env, Local<Object> wrap, ECKeyPointer&& key)
: BaseObject(env, wrap),
key_(std::move(key)),
group_(EC_KEY_get0_group(key_.get())) {
MakeWeak();
CHECK_NOT_NULL(group_);
}
void ECDH::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("key", key_ ? kSizeOf_EC_KEY : 0);
}
ECDH::~ECDH() {}
void ECDH::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
MarkPopErrorOnReturn mark_pop_error_on_return;
// TODO(indutny): Support raw curves?
CHECK(args[0]->IsString());
node::Utf8Value curve(env->isolate(), args[0]);
int nid = OBJ_sn2nid(*curve);
if (nid == NID_undef)
return THROW_ERR_CRYPTO_INVALID_CURVE(env);
ECKeyPointer key(EC_KEY_new_by_curve_name(nid));
if (!key)
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to create key using named curve");
new ECDH(env, args.This(), std::move(key));
}
void ECDH::GenerateKeys(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ECDH* ecdh;
ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder());
if (!EC_KEY_generate_key(ecdh->key_.get()))
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to generate key");
}
ECPointPointer ECDH::BufferToPoint(Environment* env,
const EC_GROUP* group,
Local<Value> buf) {
int r;
ECPointPointer pub(EC_POINT_new(group));
if (!pub) {
THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to allocate EC_POINT for a public key");
return pub;
}
ArrayBufferOrViewContents<unsigned char> input(buf);
if (UNLIKELY(!input.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "buffer is too big");
return ECPointPointer();
}
r = EC_POINT_oct2point(
group,
pub.get(),
input.data(),
input.size(),
nullptr);
if (!r)
return ECPointPointer();
return pub;
}
void ECDH::ComputeSecret(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(IsAnyByteSource(args[0]));
ECDH* ecdh;
ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder());
MarkPopErrorOnReturn mark_pop_error_on_return;
if (!ecdh->IsKeyPairValid())
return THROW_ERR_CRYPTO_INVALID_KEYPAIR(env);
ECPointPointer pub(
ECDH::BufferToPoint(env,
ecdh->group_,
args[0]));
if (!pub) {
args.GetReturnValue().Set(
FIXED_ONE_BYTE_STRING(env->isolate(),
"ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY"));
return;
}
// NOTE: field_size is in bits
int field_size = EC_GROUP_get_degree(ecdh->group_);
size_t out_len = (field_size + 7) / 8;
AllocatedBuffer out = AllocatedBuffer::AllocateManaged(env, out_len);
int r = ECDH_compute_key(
out.data(), out_len, pub.get(), ecdh->key_.get(), nullptr);
if (!r)
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to compute ECDH key");
Local<Object> buf = out.ToBuffer().ToLocalChecked();
args.GetReturnValue().Set(buf);
}
void ECDH::GetPublicKey(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
// Conversion form
CHECK_EQ(args.Length(), 1);
ECDH* ecdh;
ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder());
const EC_GROUP* group = EC_KEY_get0_group(ecdh->key_.get());
const EC_POINT* pub = EC_KEY_get0_public_key(ecdh->key_.get());
if (pub == nullptr)
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to get ECDH public key");
CHECK(args[0]->IsUint32());
uint32_t val = args[0].As<Uint32>()->Value();
point_conversion_form_t form = static_cast<point_conversion_form_t>(val);
const char* error;
Local<Object> buf;
if (!ECPointToBuffer(env, group, pub, form, &error).ToLocal(&buf))
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, error);
args.GetReturnValue().Set(buf);
}
void ECDH::GetPrivateKey(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ECDH* ecdh;
ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder());
const BIGNUM* b = EC_KEY_get0_private_key(ecdh->key_.get());
if (b == nullptr)
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to get ECDH private key");
const int size = BN_num_bytes(b);
AllocatedBuffer out = AllocatedBuffer::AllocateManaged(env, size);
CHECK_EQ(size, BN_bn2binpad(b,
reinterpret_cast<unsigned char*>(out.data()),
size));
Local<Object> buf = out.ToBuffer().ToLocalChecked();
args.GetReturnValue().Set(buf);
}
void ECDH::SetPrivateKey(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ECDH* ecdh;
ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder());
ArrayBufferOrViewContents<unsigned char> priv_buffer(args[0]);
if (UNLIKELY(!priv_buffer.CheckSizeInt32()))
return THROW_ERR_OUT_OF_RANGE(env, "key is too big");
BignumPointer priv(BN_bin2bn(
priv_buffer.data(), priv_buffer.size(), nullptr));
if (!priv) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to convert Buffer to BN");
}
if (!ecdh->IsKeyValidForCurve(priv)) {
return THROW_ERR_CRYPTO_INVALID_KEYTYPE(env,
"Private key is not valid for specified curve.");
}
ECKeyPointer new_key(EC_KEY_dup(ecdh->key_.get()));
CHECK(new_key);
int result = EC_KEY_set_private_key(new_key.get(), priv.get());
priv.reset();
if (!result) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to convert BN to a private key");
}
MarkPopErrorOnReturn mark_pop_error_on_return;
USE(&mark_pop_error_on_return);
const BIGNUM* priv_key = EC_KEY_get0_private_key(new_key.get());
CHECK_NOT_NULL(priv_key);
ECPointPointer pub(EC_POINT_new(ecdh->group_));
CHECK(pub);
if (!EC_POINT_mul(ecdh->group_, pub.get(), priv_key,
nullptr, nullptr, nullptr)) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to generate ECDH public key");
}
if (!EC_KEY_set_public_key(new_key.get(), pub.get()))
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to set generated public key");
EC_KEY_copy(ecdh->key_.get(), new_key.get());
ecdh->group_ = EC_KEY_get0_group(ecdh->key_.get());
}
void ECDH::SetPublicKey(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ECDH* ecdh;
ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder());
CHECK(IsAnyByteSource(args[0]));
MarkPopErrorOnReturn mark_pop_error_on_return;
ECPointPointer pub(
ECDH::BufferToPoint(env,
ecdh->group_,
args[0]));
if (!pub) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to convert Buffer to EC_POINT");
}
int r = EC_KEY_set_public_key(ecdh->key_.get(), pub.get());
if (!r) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to set EC_POINT as the public key");
}
}
bool ECDH::IsKeyValidForCurve(const BignumPointer& private_key) {
CHECK(group_);
CHECK(private_key);
// Private keys must be in the range [1, n-1].
// Ref: Section 3.2.1 - http://www.secg.org/sec1-v2.pdf
if (BN_cmp(private_key.get(), BN_value_one()) < 0) {
return false;
}
BignumPointer order(BN_new());
CHECK(order);
return EC_GROUP_get_order(group_, order.get(), nullptr) &&
BN_cmp(private_key.get(), order.get()) < 0;
}
bool ECDH::IsKeyPairValid() {
MarkPopErrorOnReturn mark_pop_error_on_return;
USE(&mark_pop_error_on_return);
return 1 == EC_KEY_check_key(key_.get());
}
// Convert the input public key to compressed, uncompressed, or hybrid formats.
void ECDH::ConvertKey(const FunctionCallbackInfo<Value>& args) {
MarkPopErrorOnReturn mark_pop_error_on_return;
Environment* env = Environment::GetCurrent(args);
CHECK_EQ(args.Length(), 3);
CHECK(IsAnyByteSource(args[0]));
ArrayBufferOrViewContents<char> args0(args[0]);
if (UNLIKELY(!args0.CheckSizeInt32()))
return THROW_ERR_OUT_OF_RANGE(env, "key is too big");
if (args0.size() == 0)
return args.GetReturnValue().SetEmptyString();
node::Utf8Value curve(env->isolate(), args[1]);
int nid = OBJ_sn2nid(*curve);
if (nid == NID_undef)
return THROW_ERR_CRYPTO_INVALID_CURVE(env);
ECGroupPointer group(
EC_GROUP_new_by_curve_name(nid));
if (group == nullptr)
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get EC_GROUP");
ECPointPointer pub(
ECDH::BufferToPoint(env,
group.get(),
args[0]));
if (pub == nullptr) {
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"Failed to convert Buffer to EC_POINT");
}
CHECK(args[2]->IsUint32());
uint32_t val = args[2].As<Uint32>()->Value();
point_conversion_form_t form = static_cast<point_conversion_form_t>(val);
const char* error;
Local<Object> buf;
if (!ECPointToBuffer(env, group.get(), pub.get(), form, &error).ToLocal(&buf))
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, error);
args.GetReturnValue().Set(buf);
}
Maybe<bool> ECDHBitsTraits::EncodeOutput(
Environment* env,
const ECDHBitsConfig& params,
ByteSource* out,
v8::Local<v8::Value>* result) {
*result = out->ToArrayBuffer(env);
return Just(!result->IsEmpty());
}
Maybe<bool> ECDHBitsTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
ECDHBitsConfig* params) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[offset]->IsString()); // curve name
CHECK(args[offset + 1]->IsObject()); // public key
CHECK(args[offset + 2]->IsObject()); // private key
KeyObjectHandle* private_key;
KeyObjectHandle* public_key;
Utf8Value name(env->isolate(), args[offset]);
ASSIGN_OR_RETURN_UNWRAP(&public_key, args[offset + 1], Nothing<bool>());
ASSIGN_OR_RETURN_UNWRAP(&private_key, args[offset + 2], Nothing<bool>());
if (private_key->Data()->GetKeyType() != kKeyTypePrivate ||
public_key->Data()->GetKeyType() != kKeyTypePublic) {
THROW_ERR_CRYPTO_INVALID_KEYTYPE(env);
return Nothing<bool>();
}
params->private_key = ECKeyPointer(
EC_KEY_dup(
EVP_PKEY_get1_EC_KEY(private_key->Data()->GetAsymmetricKey().get())));
if (!params->private_key) {
THROW_ERR_CRYPTO_INVALID_KEYTYPE(env);
return Nothing<bool>();
}
params->public_key = ECKeyPointer(
EC_KEY_dup(
EVP_PKEY_get1_EC_KEY(public_key->Data()->GetAsymmetricKey().get())));
if (!params->public_key) {
THROW_ERR_CRYPTO_INVALID_KEYTYPE(env);
return Nothing<bool>();
}
params->group = EC_KEY_get0_group(params->private_key.get());
return Just(true);
}
bool ECDHBitsTraits::DeriveBits(
Environment* env,
const ECDHBitsConfig& params,
ByteSource* out) {
if (params.group == nullptr)
return false;
CHECK_EQ(EC_KEY_check_key(params.private_key.get()), 1);
CHECK_EQ(EC_KEY_check_key(params.public_key.get()), 1);
const EC_POINT* pub = EC_KEY_get0_public_key(params.public_key.get());
int field_size = EC_GROUP_get_degree(params.group);
size_t len = (field_size + 7) / 8;
char* data = MallocOpenSSL<char>(len);
ByteSource buf = ByteSource::Allocated(data, len);
if (ECDH_compute_key(
data,
len,
pub,
params.private_key.get(),
nullptr) <= 0) {
return false;
}
*out = std::move(buf);
return true;
}
EVPKeyCtxPointer EcKeyGenTraits::Setup(EcKeyPairGenConfig* params) {
EVPKeyCtxPointer param_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr));
EVP_PKEY* raw_params = nullptr;
if (!param_ctx ||
EVP_PKEY_paramgen_init(param_ctx.get()) <= 0 ||
EVP_PKEY_CTX_set_ec_paramgen_curve_nid(
param_ctx.get(), params->params.curve_nid) <= 0 ||
EVP_PKEY_CTX_set_ec_param_enc(
param_ctx.get(), params->params.param_encoding) <= 0 ||
EVP_PKEY_paramgen(param_ctx.get(), &raw_params) <= 0) {
return EVPKeyCtxPointer();
}
EVPKeyPointer key_params(raw_params);
EVPKeyCtxPointer key_ctx(EVP_PKEY_CTX_new(key_params.get(), nullptr));
if (!key_ctx || EVP_PKEY_keygen_init(key_ctx.get()) <= 0)
return EVPKeyCtxPointer();
return key_ctx;
}
// EcKeyPairGenJob input arguments
// 1. CryptoJobMode
// 2. Curve Name
// 3. Param Encoding
// 4. Public Format
// 5. Public Type
// 6. Private Format
// 7. Private Type
// 8. Cipher
// 9. Passphrase
Maybe<bool> EcKeyGenTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int* offset,
EcKeyPairGenConfig* params) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[*offset]->IsString()); // curve name
CHECK(args[*offset + 1]->IsInt32()); // param encoding
Utf8Value curve_name(env->isolate(), args[*offset]);
params->params.curve_nid = GetCurveFromName(*curve_name);
if (params->params.curve_nid == NID_undef) {
THROW_ERR_CRYPTO_INVALID_CURVE(env);
return Nothing<bool>();
}
params->params.param_encoding = args[*offset + 1].As<Int32>()->Value();
if (params->params.param_encoding != OPENSSL_EC_NAMED_CURVE &&
params->params.param_encoding != OPENSSL_EC_EXPLICIT_CURVE) {
THROW_ERR_OUT_OF_RANGE(env, "Invalid param_encoding specified");
return Nothing<bool>();
}
*offset += 2;
return Just(true);
}
namespace {
WebCryptoKeyExportStatus EC_Raw_Export(
KeyObjectData* key_data,
const ECKeyExportConfig& params,
ByteSource* out) {
CHECK(key_data->GetAsymmetricKey());
EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(key_data->GetAsymmetricKey().get());
CHECK_NOT_NULL(ec_key);
const EC_GROUP* group = EC_KEY_get0_group(ec_key);
const EC_POINT* point = EC_KEY_get0_public_key(ec_key);
point_conversion_form_t form = POINT_CONVERSION_UNCOMPRESSED;
// Get the allocated data size...
size_t len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr);
if (len == 0)
return WebCryptoKeyExportStatus::ERR_FAILED;
unsigned char* data = MallocOpenSSL<unsigned char>(len);
size_t check_len = EC_POINT_point2oct(group, point, form, data, len, nullptr);
if (check_len == 0)
return WebCryptoKeyExportStatus::ERR_FAILED;
CHECK_EQ(len, check_len);
*out = ByteSource::Allocated(reinterpret_cast<char*>(data), len);
return WebCryptoKeyExportStatus::ERR_OK;
}
} // namespace
Maybe<bool> ECKeyExportTraits::AdditionalConfig(
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
ECKeyExportConfig* params) {
return Just(true);
}
WebCryptoKeyExportStatus ECKeyExportTraits::DoExport(
std::shared_ptr<KeyObjectData> key_data,
WebCryptoKeyFormat format,
const ECKeyExportConfig& params,
ByteSource* out) {
CHECK_NE(key_data->GetKeyType(), kKeyTypeSecret);
switch (format) {
case kWebCryptoKeyFormatRaw:
if (key_data->GetKeyType() != kKeyTypePublic)
return WebCryptoKeyExportStatus::ERR_INVALID_KEY_TYPE;
return EC_Raw_Export(key_data.get(), params, out);
case kWebCryptoKeyFormatPKCS8:
if (key_data->GetKeyType() != kKeyTypePrivate)
return WebCryptoKeyExportStatus::ERR_INVALID_KEY_TYPE;
return PKEY_PKCS8_Export(key_data.get(), out);
case kWebCryptoKeyFormatSPKI:
if (key_data->GetKeyType() != kKeyTypePublic)
return WebCryptoKeyExportStatus::ERR_INVALID_KEY_TYPE;
return PKEY_SPKI_Export(key_data.get(), out);
default:
UNREACHABLE();
}
}
Maybe<bool> ExportJWKEcKey(
Environment* env,
std::shared_ptr<KeyObjectData> key,
Local<Object> target) {
ManagedEVPPKey pkey = key->GetAsymmetricKey();
CHECK_EQ(EVP_PKEY_id(pkey.get()), EVP_PKEY_EC);
EC_KEY* ec = EVP_PKEY_get0_EC_KEY(pkey.get());
CHECK_NOT_NULL(ec);
const EC_POINT* pub = EC_KEY_get0_public_key(ec);
const EC_GROUP* group = EC_KEY_get0_group(ec);
int degree_bits = EC_GROUP_get_degree(group);
int degree_bytes =
(degree_bits / CHAR_BIT) + (7 + (degree_bits % CHAR_BIT)) / 8;
BignumPointer x(BN_new());
BignumPointer y(BN_new());
EC_POINT_get_affine_coordinates(group, pub, x.get(), y.get(), nullptr);
if (target->Set(
env->context(),
env->jwk_kty_string(),
env->jwk_ec_string()).IsNothing()) {
return Nothing<bool>();
}
if (SetEncodedValue(
env,
target,
env->jwk_x_string(),
x.get(),
degree_bytes).IsNothing() ||
SetEncodedValue(
env,
target,
env->jwk_y_string(),
y.get(),
degree_bytes).IsNothing()) {
return Nothing<bool>();
}
if (key->GetKeyType() == kKeyTypePrivate) {
const BIGNUM* pvt = EC_KEY_get0_private_key(ec);
return SetEncodedValue(
env,
target,
env->jwk_d_string(),
pvt,
degree_bytes);
}
return Just(true);
}
std::shared_ptr<KeyObjectData> ImportJWKEcKey(
Environment* env,
Local<Object> jwk,
const FunctionCallbackInfo<Value>& args,
unsigned int offset) {
CHECK(args[offset]->IsString()); // curve name
Utf8Value curve(env->isolate(), args[offset].As<String>());
int nid = GetCurveFromName(*curve);
if (nid == NID_undef) { // Unknown curve
THROW_ERR_CRYPTO_INVALID_CURVE(env);
return std::shared_ptr<KeyObjectData>();
}
Local<Value> x_value;
Local<Value> y_value;
Local<Value> d_value;
if (!jwk->Get(env->context(), env->jwk_x_string()).ToLocal(&x_value) ||
!jwk->Get(env->context(), env->jwk_y_string()).ToLocal(&y_value) ||
!jwk->Get(env->context(), env->jwk_d_string()).ToLocal(&d_value)) {
return std::shared_ptr<KeyObjectData>();
}
if (!x_value->IsString() ||
!y_value->IsString() ||
(!d_value->IsUndefined() && !d_value->IsString())) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JSK EC key");
return std::shared_ptr<KeyObjectData>();
}
KeyType type = d_value->IsString() ? kKeyTypePrivate : kKeyTypePublic;
ECKeyPointer ec(EC_KEY_new_by_curve_name(nid));
if (!ec) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JSK EC key");
return std::shared_ptr<KeyObjectData>();
}
ByteSource x = ByteSource::FromEncodedString(env, x_value.As<String>());
ByteSource y = ByteSource::FromEncodedString(env, y_value.As<String>());
if (!EC_KEY_set_public_key_affine_coordinates(
ec.get(),
x.ToBN().get(),
y.ToBN().get())) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JSK EC key");
return std::shared_ptr<KeyObjectData>();
}
if (type == kKeyTypePrivate) {
ByteSource d = ByteSource::FromEncodedString(env, d_value.As<String>());
if (!EC_KEY_set_private_key(ec.get(), d.ToBN().get())) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JSK EC key");
return std::shared_ptr<KeyObjectData>();
}
}
EVPKeyPointer pkey(EVP_PKEY_new());
CHECK_EQ(EVP_PKEY_set1_EC_KEY(pkey.get(), ec.get()), 1);
return KeyObjectData::CreateAsymmetric(type, ManagedEVPPKey(std::move(pkey)));
}
Maybe<bool> GetEcKeyDetail(
Environment* env,
std::shared_ptr<KeyObjectData> key,
Local<Object> target) {
ManagedEVPPKey pkey = key->GetAsymmetricKey();
CHECK_EQ(EVP_PKEY_id(pkey.get()), EVP_PKEY_EC);
EC_KEY* ec = EVP_PKEY_get0_EC_KEY(pkey.get());
CHECK_NOT_NULL(ec);
const EC_GROUP* group = EC_KEY_get0_group(ec);
int nid = EC_GROUP_get_curve_name(group);
return target->Set(
env->context(),
env->named_curve_string(),
OneByteString(env->isolate(), OBJ_nid2sn(nid)));
}
// WebCrypto requires a different format for ECDSA signatures than
// what OpenSSL produces, so we need to convert between them. The
// implementation here is a adapted from Chromium's impl here:
// https://github.com/chromium/chromium/blob/7af6cfd/components/webcrypto/algorithms/ecdsa.cc
size_t GroupOrderSize(ManagedEVPPKey key) {
EC_KEY* ec = EVP_PKEY_get0_EC_KEY(key.get());
CHECK_NOT_NULL(ec);
const EC_GROUP* group = EC_KEY_get0_group(ec);
BignumPointer order(BN_new());
CHECK(EC_GROUP_get_order(group, order.get(), nullptr));
return BN_num_bytes(order.get());
}
ByteSource ConvertToWebCryptoSignature(
ManagedEVPPKey key,
const ByteSource& signature) {
const unsigned char* data =
reinterpret_cast<const unsigned char*>(signature.get());
EcdsaSigPointer ecsig(d2i_ECDSA_SIG(nullptr, &data, signature.size()));
if (!ecsig)
return ByteSource();
size_t order_size_bytes = GroupOrderSize(key);
char* outdata = MallocOpenSSL<char>(order_size_bytes * 2);
ByteSource out = ByteSource::Allocated(outdata, order_size_bytes * 2);
unsigned char* ptr = reinterpret_cast<unsigned char*>(outdata);
const BIGNUM* pr;
const BIGNUM* ps;
ECDSA_SIG_get0(ecsig.get(), &pr, &ps);
if (!BN_bn2binpad(pr, ptr, order_size_bytes) ||
!BN_bn2binpad(ps, ptr + order_size_bytes, order_size_bytes)) {
return ByteSource();
}
return out;
}
ByteSource ConvertFromWebCryptoSignature(
ManagedEVPPKey key,
const ByteSource& signature) {
size_t order_size_bytes = GroupOrderSize(key);
// If the size of the signature is incorrect, verification
// will fail.
if (signature.size() != 2 * order_size_bytes)
return ByteSource(); // Empty!
EcdsaSigPointer ecsig(ECDSA_SIG_new());
if (!ecsig)
return ByteSource();
BignumPointer r(BN_new());
BignumPointer s(BN_new());
const unsigned char* sig = signature.data<unsigned char>();
if (!BN_bin2bn(sig, order_size_bytes, r.get()) ||
!BN_bin2bn(sig + order_size_bytes, order_size_bytes, s.get()) ||
!ECDSA_SIG_set0(ecsig.get(), r.release(), s.release())) {
return ByteSource();
}
int size = i2d_ECDSA_SIG(ecsig.get(), nullptr);
char* data = MallocOpenSSL<char>(size);
unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
CHECK_EQ(i2d_ECDSA_SIG(ecsig.get(), &ptr), size);
return ByteSource::Allocated(data, size);
}
} // namespace crypto
} // namespace node

170
src/crypto/crypto_ecdh.h Normal file
View File

@ -0,0 +1,170 @@
#ifndef SRC_CRYPTO_CRYPTO_ECDH_H_
#define SRC_CRYPTO_CRYPTO_ECDH_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_keys.h"
#include "crypto/crypto_keygen.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer.h"
#include "async_wrap.h"
#include "base_object.h"
#include "env.h"
#include "memory_tracker.h"
#include "node_internals.h"
#include "v8.h"
namespace node {
namespace crypto {
class ECDH final : public BaseObject {
public:
~ECDH() override;
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static ECPointPointer BufferToPoint(Environment* env,
const EC_GROUP* group,
v8::Local<v8::Value> buf);
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(ECDH)
SET_SELF_SIZE(ECDH)
static void ConvertKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetCurves(const v8::FunctionCallbackInfo<v8::Value>& args);
protected:
ECDH(Environment* env, v8::Local<v8::Object> wrap, ECKeyPointer&& key);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GenerateKeys(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ComputeSecret(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPrivateKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetPrivateKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetPublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);
bool IsKeyPairValid();
bool IsKeyValidForCurve(const BignumPointer& private_key);
ECKeyPointer key_;
const EC_GROUP* group_;
};
struct ECDHBitsConfig final : public MemoryRetainer {
ECKeyPointer private_key;
ECKeyPointer public_key;
const EC_GROUP* group = nullptr;
SET_NO_MEMORY_INFO();
SET_MEMORY_INFO_NAME(ECDHBitsConfig);
SET_SELF_SIZE(ECDHBitsConfig);
};
struct ECDHBitsTraits final {
using AdditionalParameters = ECDHBitsConfig;
static constexpr const char* JobName = "ECDHBitsJob";
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_DERIVEBITSREQUEST;
static v8::Maybe<bool> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
ECDHBitsConfig* params);
static bool DeriveBits(
Environment* env,
const ECDHBitsConfig& params,
ByteSource* out_);
static v8::Maybe<bool> EncodeOutput(
Environment* env,
const ECDHBitsConfig& params,
ByteSource* out,
v8::Local<v8::Value>* result);
};
using ECDHBitsJob = DeriveBitsJob<ECDHBitsTraits>;
struct EcKeyPairParams final : public MemoryRetainer {
int curve_nid;
int param_encoding;
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(EcKeyPairParams)
SET_SELF_SIZE(EcKeyPairParams)
};
using EcKeyPairGenConfig = KeyPairGenConfig<EcKeyPairParams>;
struct EcKeyGenTraits final {
using AdditionalParameters = EcKeyPairGenConfig;
static constexpr const char* JobName = "EcKeyPairGenJob";
static EVPKeyCtxPointer Setup(EcKeyPairGenConfig* params);
static v8::Maybe<bool> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
EcKeyPairGenConfig* params);
};
using ECKeyPairGenJob = KeyGenJob<KeyPairGenTraits<EcKeyGenTraits>>;
// There is currently no additional information that the
// ECKeyExport needs to collect, but we need to provide
// the base struct anyway.
struct ECKeyExportConfig final : public MemoryRetainer {
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(ECKeyExportConfig)
SET_SELF_SIZE(ECKeyExportConfig)
};
struct ECKeyExportTraits final {
static constexpr const char* JobName = "ECKeyExportJob";
using AdditionalParameters = ECKeyExportConfig;
static v8::Maybe<bool> AdditionalConfig(
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
ECKeyExportConfig* config);
static WebCryptoKeyExportStatus DoExport(
std::shared_ptr<KeyObjectData> key_data,
WebCryptoKeyFormat format,
const ECKeyExportConfig& params,
ByteSource* out);
};
using ECKeyExportJob = KeyExportJob<ECKeyExportTraits>;
v8::Maybe<bool> ExportJWKEcKey(
Environment* env,
std::shared_ptr<KeyObjectData> key,
v8::Local<v8::Object> target);
std::shared_ptr<KeyObjectData> ImportJWKEcKey(
Environment* env,
v8::Local<v8::Object> jwk,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset);
v8::Maybe<bool> GetEcKeyDetail(
Environment* env,
std::shared_ptr<KeyObjectData> key,
v8::Local<v8::Object> target);
ByteSource ConvertToWebCryptoSignature(
ManagedEVPPKey key,
const ByteSource& signature);
ByteSource ConvertFromWebCryptoSignature(
ManagedEVPPKey key,
const ByteSource& signature);
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_ECDH_H_

314
src/crypto/crypto_hash.cc Normal file
View File

@ -0,0 +1,314 @@
#include "crypto/crypto_hash.h"
#include "allocated_buffer-inl.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "string_bytes.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
#include <cstdio>
namespace node {
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Object;
using v8::Uint32;
using v8::Value;
namespace crypto {
Hash::Hash(Environment* env, Local<Object> wrap)
: BaseObject(env, wrap),
mdctx_(nullptr),
has_md_(false),
md_value_(nullptr) {
MakeWeak();
}
void Hash::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("mdctx", mdctx_ ? kSizeOf_EVP_MD_CTX : 0);
tracker->TrackFieldWithSize("md", md_len_);
}
void Hash::GetHashes(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CipherPushContext ctx(env);
EVP_MD_do_all_sorted(array_push_back<EVP_MD>, &ctx);
args.GetReturnValue().Set(ctx.ToJSArray());
}
void Hash::Initialize(Environment* env, Local<Object> target) {
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
t->InstanceTemplate()->SetInternalFieldCount(
Hash::kInternalFieldCount);
t->Inherit(BaseObject::GetConstructorTemplate(env));
env->SetProtoMethod(t, "update", HashUpdate);
env->SetProtoMethod(t, "digest", HashDigest);
target->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "Hash"),
t->GetFunction(env->context()).ToLocalChecked()).Check();
env->SetMethodNoSideEffect(target, "getHashes", GetHashes);
HashJob::Initialize(env, target);
}
Hash::~Hash() {
if (md_value_ != nullptr)
OPENSSL_clear_free(md_value_, md_len_);
}
void Hash::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
const Hash* orig = nullptr;
const EVP_MD* md = nullptr;
if (args[0]->IsObject()) {
ASSIGN_OR_RETURN_UNWRAP(&orig, args[0].As<Object>());
md = EVP_MD_CTX_md(orig->mdctx_.get());
} else {
const Utf8Value hash_type(env->isolate(), args[0]);
md = EVP_get_digestbyname(*hash_type);
}
Maybe<unsigned int> xof_md_len = Nothing<unsigned int>();
if (!args[1]->IsUndefined()) {
CHECK(args[1]->IsUint32());
xof_md_len = Just<unsigned int>(args[1].As<Uint32>()->Value());
}
Hash* hash = new Hash(env, args.This());
if (md == nullptr || !hash->HashInit(md, xof_md_len)) {
return ThrowCryptoError(env, ERR_get_error(),
"Digest method not supported");
}
if (orig != nullptr &&
0 >= EVP_MD_CTX_copy(hash->mdctx_.get(), orig->mdctx_.get())) {
return ThrowCryptoError(env, ERR_get_error(), "Digest copy error");
}
}
bool Hash::HashInit(const EVP_MD* md, Maybe<unsigned int> xof_md_len) {
mdctx_.reset(EVP_MD_CTX_new());
if (!mdctx_ || EVP_DigestInit_ex(mdctx_.get(), md, nullptr) <= 0) {
mdctx_.reset();
return false;
}
md_len_ = EVP_MD_size(md);
if (xof_md_len.IsJust() && xof_md_len.FromJust() != md_len_) {
// This is a little hack to cause createHash to fail when an incorrect
// hashSize option was passed for a non-XOF hash function.
if ((EVP_MD_flags(md) & EVP_MD_FLAG_XOF) == 0) {
EVPerr(EVP_F_EVP_DIGESTFINALXOF, EVP_R_NOT_XOF_OR_INVALID_LENGTH);
return false;
}
md_len_ = xof_md_len.FromJust();
}
return true;
}
bool Hash::HashUpdate(const char* data, size_t len) {
if (!mdctx_)
return false;
EVP_DigestUpdate(mdctx_.get(), data, len);
return true;
}
void Hash::HashUpdate(const FunctionCallbackInfo<Value>& args) {
Decode<Hash>(args, [](Hash* hash, const FunctionCallbackInfo<Value>& args,
const char* data, size_t size) {
Environment* env = Environment::GetCurrent(args);
if (UNLIKELY(size > INT_MAX))
return THROW_ERR_OUT_OF_RANGE(env, "data is too long");
bool r = hash->HashUpdate(data, size);
args.GetReturnValue().Set(r);
});
}
void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Hash* hash;
ASSIGN_OR_RETURN_UNWRAP(&hash, args.Holder());
enum encoding encoding = BUFFER;
if (args.Length() >= 1) {
encoding = ParseEncoding(env->isolate(), args[0], BUFFER);
}
// TODO(tniessen): SHA3_squeeze does not work for zero-length outputs on all
// platforms and will cause a segmentation fault if called. This workaround
// causes hash.digest() to correctly return an empty buffer / string.
// See https://github.com/openssl/openssl/issues/9431.
if (!hash->has_md_ && hash->md_len_ == 0) {
hash->has_md_ = true;
}
if (!hash->has_md_) {
// Some hash algorithms such as SHA3 do not support calling
// EVP_DigestFinal_ex more than once, however, Hash._flush
// and Hash.digest can both be used to retrieve the digest,
// so we need to cache it.
// See https://github.com/nodejs/node/issues/28245.
hash->md_value_ = MallocOpenSSL<unsigned char>(hash->md_len_);
size_t default_len = EVP_MD_CTX_size(hash->mdctx_.get());
int ret;
if (hash->md_len_ == default_len) {
ret = EVP_DigestFinal_ex(hash->mdctx_.get(), hash->md_value_,
&hash->md_len_);
} else {
ret = EVP_DigestFinalXOF(hash->mdctx_.get(), hash->md_value_,
hash->md_len_);
}
if (ret != 1) {
OPENSSL_free(hash->md_value_);
hash->md_value_ = nullptr;
return ThrowCryptoError(env, ERR_get_error());
}
hash->has_md_ = true;
}
Local<Value> error;
MaybeLocal<Value> rc =
StringBytes::Encode(env->isolate(),
reinterpret_cast<const char*>(hash->md_value_),
hash->md_len_,
encoding,
&error);
if (rc.IsEmpty()) {
CHECK(!error.IsEmpty());
env->isolate()->ThrowException(error);
return;
}
args.GetReturnValue().Set(rc.ToLocalChecked());
}
HashConfig::HashConfig(HashConfig&& other) noexcept
: mode(other.mode),
in(std::move(other.in)),
digest(other.digest),
length(other.length) {}
HashConfig& HashConfig::operator=(HashConfig&& other) noexcept {
if (&other == this) return *this;
this->~HashConfig();
return *new (this) HashConfig(std::move(other));
}
void HashConfig::MemoryInfo(MemoryTracker* tracker) const {
// If the Job is sync, then the HashConfig does not own the data.
if (mode == kCryptoJobAsync)
tracker->TrackFieldWithSize("in", in.size());
}
Maybe<bool> HashTraits::EncodeOutput(
Environment* env,
const HashConfig& params,
ByteSource* out,
v8::Local<v8::Value>* result) {
*result = out->ToArrayBuffer(env);
return Just(!result->IsEmpty());
}
Maybe<bool> HashTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
HashConfig* params) {
Environment* env = Environment::GetCurrent(args);
params->mode = mode;
CHECK(args[offset]->IsString()); // Hash algorithm
Utf8Value digest(env->isolate(), args[offset]);
params->digest = EVP_get_digestbyname(*digest);
if (UNLIKELY(params->digest == nullptr)) {
char msg[1024];
snprintf(msg, sizeof(msg), "Invalid digest: %s", *digest);
THROW_ERR_CRYPTO_INVALID_DIGEST(env);
return Nothing<bool>();
}
ArrayBufferOrViewContents<char> data(args[offset + 1]);
if (UNLIKELY(!data.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "data is too big");
return Nothing<bool>();
}
params->in = mode == kCryptoJobAsync
? data.ToCopy()
: data.ToByteSource();
unsigned int expected = EVP_MD_size(params->digest);
params->length = expected;
if (UNLIKELY(args[offset + 2]->IsUint32())) {
// length is expressed in terms of bits
params->length =
static_cast<uint32_t>(args[offset + 2]
.As<Uint32>()->Value()) / CHAR_BIT;
if (params->length != expected) {
if ((EVP_MD_flags(params->digest) & EVP_MD_FLAG_XOF) == 0) {
THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Digest method not supported");
return Nothing<bool>();
}
}
}
return Just(true);
}
bool HashTraits::DeriveBits(
Environment* env,
const HashConfig& params,
ByteSource* out) {
EVPMDPointer ctx(EVP_MD_CTX_new());
if (UNLIKELY(!ctx ||
EVP_DigestInit_ex(ctx.get(), params.digest, nullptr) <= 0 ||
EVP_DigestUpdate(
ctx.get(),
params.in.get(),
params.in.size()) <= 0)) {
return false;
}
if (LIKELY(params.length > 0)) {
unsigned int length = params.length;
char* data = MallocOpenSSL<char>(length);
ByteSource buf = ByteSource::Allocated(data, length);
unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
size_t expected = EVP_MD_CTX_size(ctx.get());
int ret = (length == expected)
? EVP_DigestFinal_ex(ctx.get(), ptr, &length)
: EVP_DigestFinalXOF(ctx.get(), ptr, length);
if (UNLIKELY(ret != 1))
return false;
*out = std::move(buf);
}
return true;
}
} // namespace crypto
} // namespace node

92
src/crypto/crypto_hash.h Normal file
View File

@ -0,0 +1,92 @@
#ifndef SRC_CRYPTO_CRYPTO_HASH_H_
#define SRC_CRYPTO_CRYPTO_HASH_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer.h"
#include "base_object.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
namespace node {
namespace crypto {
class Hash final : public BaseObject {
public:
~Hash() override;
static void Initialize(Environment* env, v8::Local<v8::Object> target);
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(Hash)
SET_SELF_SIZE(Hash)
bool HashInit(const EVP_MD* md, v8::Maybe<unsigned int> xof_md_len);
bool HashUpdate(const char* data, size_t len);
static void GetHashes(const v8::FunctionCallbackInfo<v8::Value>& args);
protected:
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void HashUpdate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void HashDigest(const v8::FunctionCallbackInfo<v8::Value>& args);
Hash(Environment* env, v8::Local<v8::Object> wrap);
private:
EVPMDPointer mdctx_;
bool has_md_;
unsigned int md_len_;
unsigned char* md_value_;
};
struct HashConfig final : public MemoryRetainer {
CryptoJobMode mode;
ByteSource in;
const EVP_MD* digest;
unsigned int length;
HashConfig() = default;
explicit HashConfig(HashConfig&& other) noexcept;
HashConfig& operator=(HashConfig&& other) noexcept;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(HashConfig);
SET_SELF_SIZE(HashConfig);
};
struct HashTraits final {
using AdditionalParameters = HashConfig;
static constexpr const char* JobName = "HashJob";
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_HASHREQUEST;
static v8::Maybe<bool> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
HashConfig* params);
static bool DeriveBits(
Environment* env,
const HashConfig& params,
ByteSource* out);
static v8::Maybe<bool> EncodeOutput(
Environment* env,
const HashConfig& params,
ByteSource* out,
v8::Local<v8::Value>* result);
};
using HashJob = DeriveBitsJob<HashTraits>;
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_HASH_H_

147
src/crypto/crypto_hkdf.cc Normal file
View File

@ -0,0 +1,147 @@
#include "crypto/crypto_hkdf.h"
#include "crypto/crypto_keys.h"
#include "allocated_buffer-inl.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
namespace node {
using v8::FunctionCallbackInfo;
using v8::Just;
using v8::Maybe;
using v8::Nothing;
using v8::Uint32;
using v8::Value;
namespace crypto {
HKDFConfig::HKDFConfig(HKDFConfig&& other) noexcept
: mode(other.mode),
length(other.length),
digest(other.digest),
key(other.key),
salt(std::move(other.salt)),
info(std::move(other.info)) {}
HKDFConfig& HKDFConfig::operator=(HKDFConfig&& other) noexcept {
if (&other == this) return *this;
this->~HKDFConfig();
return *new (this) HKDFConfig(std::move(other));
}
Maybe<bool> HKDFTraits::EncodeOutput(
Environment* env,
const HKDFConfig& params,
ByteSource* out,
v8::Local<v8::Value>* result) {
*result = out->ToArrayBuffer(env);
return Just(!result->IsEmpty());
}
Maybe<bool> HKDFTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
HKDFConfig* params) {
Environment* env = Environment::GetCurrent(args);
params->mode = mode;
CHECK(args[offset]->IsString()); // Hash
CHECK(args[offset + 1]->IsObject()); // Key
CHECK(IsAnyByteSource(args[offset + 2])); // Salt
CHECK(IsAnyByteSource(args[offset + 3])); // Info
CHECK(args[offset + 4]->IsUint32()); // Length
Utf8Value hash(env->isolate(), args[offset]);
params->digest = EVP_get_digestbyname(*hash);
if (params->digest == nullptr) {
THROW_ERR_CRYPTO_INVALID_DIGEST(env);
return Nothing<bool>();
}
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args[offset + 1], Nothing<bool>());
params->key = key->Data();
ArrayBufferOrViewContents<char> salt(args[offset + 2]);
ArrayBufferOrViewContents<char> info(args[offset + 3]);
if (UNLIKELY(!salt.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "salt is too big");
return Nothing<bool>();
}
if (UNLIKELY(!info.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "info is too big");
return Nothing<bool>();
}
params->salt = mode == kCryptoJobAsync
? salt.ToCopy()
: salt.ToByteSource();
params->info = mode == kCryptoJobAsync
? info.ToCopy()
: info.ToByteSource();
params->length = args[offset + 4].As<Uint32>()->Value();
size_t max_length = EVP_MD_size(params->digest) * kMaxDigestMultiplier;
if (params->length > max_length) {
THROW_ERR_CRYPTO_INVALID_KEYLEN(env);
return Nothing<bool>();
}
return Just(true);
}
bool HKDFTraits::DeriveBits(
Environment* env,
const HKDFConfig& params,
ByteSource* out) {
EVPKeyCtxPointer ctx =
EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr));
if (!ctx ||
!EVP_PKEY_derive_init(ctx.get()) ||
!EVP_PKEY_CTX_hkdf_mode(
ctx.get(), EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND) ||
!EVP_PKEY_CTX_set_hkdf_md(ctx.get(), params.digest) ||
!EVP_PKEY_CTX_set1_hkdf_salt(
ctx.get(),
params.salt.get(),
params.salt.size()) ||
!EVP_PKEY_CTX_set1_hkdf_key(
ctx.get(),
params.key->GetSymmetricKey(),
params.key->GetSymmetricKeySize()) ||
!EVP_PKEY_CTX_add1_hkdf_info(
ctx.get(),
params.info.get(),
params.info.size())) {
return false;
}
size_t length = params.length;
char* data = MallocOpenSSL<char>(length);
ByteSource buf = ByteSource::Allocated(data, length);
unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
if (EVP_PKEY_derive(ctx.get(), ptr, &length) <= 0)
return false;
*out = std::move(buf);
return true;
}
void HKDFConfig::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("key", key);
// If the job is sync, then the HKDFConfig does not own the data
if (mode == kCryptoJobAsync) {
tracker->TrackFieldWithSize("salt", salt.size());
tracker->TrackFieldWithSize("info", info.size());
}
}
} // namespace crypto
} // namespace node

66
src/crypto/crypto_hkdf.h Normal file
View File

@ -0,0 +1,66 @@
#ifndef SRC_CRYPTO_CRYPTO_HKDF_H_
#define SRC_CRYPTO_CRYPTO_HKDF_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer.h"
#include "async_wrap.h"
#include "base_object.h"
#include "v8.h"
namespace node {
namespace crypto {
static constexpr size_t kMaxDigestMultiplier = 255;
struct HKDFConfig final : public MemoryRetainer {
CryptoJobMode mode;
size_t length;
const EVP_MD* digest;
std::shared_ptr<KeyObjectData> key;
ByteSource salt;
ByteSource info;
HKDFConfig() = default;
explicit HKDFConfig(HKDFConfig&& other) noexcept;
HKDFConfig& operator=(HKDFConfig&& other) noexcept;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(HKDFConfig);
SET_SELF_SIZE(HKDFConfig);
};
struct HKDFTraits final {
using AdditionalParameters = HKDFConfig;
static constexpr const char* JobName = "HKDFJob";
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_DERIVEBITSREQUEST;
static v8::Maybe<bool> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
HKDFConfig* params);
static bool DeriveBits(
Environment* env,
const HKDFConfig& params,
ByteSource* out);
static v8::Maybe<bool> EncodeOutput(
Environment* env,
const HKDFConfig& params,
ByteSource* out,
v8::Local<v8::Value>* result);
};
using HKDFJob = DeriveBitsJob<HKDFTraits>;
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_HKDF_H_

275
src/crypto/crypto_hmac.cc Normal file
View File

@ -0,0 +1,275 @@
#include "crypto/crypto_hmac.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_sig.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer-inl.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node_buffer.h"
#include "string_bytes.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
namespace node {
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Object;
using v8::Uint32;
using v8::Value;
namespace crypto {
Hmac::Hmac(Environment* env, Local<Object> wrap)
: BaseObject(env, wrap),
ctx_(nullptr) {
MakeWeak();
}
void Hmac::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("context", ctx_ ? kSizeOf_HMAC_CTX : 0);
}
void Hmac::Initialize(Environment* env, Local<Object> target) {
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
t->InstanceTemplate()->SetInternalFieldCount(
Hmac::kInternalFieldCount);
t->Inherit(BaseObject::GetConstructorTemplate(env));
env->SetProtoMethod(t, "init", HmacInit);
env->SetProtoMethod(t, "update", HmacUpdate);
env->SetProtoMethod(t, "digest", HmacDigest);
target->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "Hmac"),
t->GetFunction(env->context()).ToLocalChecked()).Check();
HmacJob::Initialize(env, target);
}
void Hmac::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
new Hmac(env, args.This());
}
void Hmac::HmacInit(const char* hash_type, const char* key, int key_len) {
HandleScope scope(env()->isolate());
const EVP_MD* md = EVP_get_digestbyname(hash_type);
if (md == nullptr)
return THROW_ERR_CRYPTO_INVALID_DIGEST(env());
if (key_len == 0) {
key = "";
}
ctx_.reset(HMAC_CTX_new());
if (!ctx_ || !HMAC_Init_ex(ctx_.get(), key, key_len, md, nullptr)) {
ctx_.reset();
return ThrowCryptoError(env(), ERR_get_error());
}
}
void Hmac::HmacInit(const FunctionCallbackInfo<Value>& args) {
Hmac* hmac;
ASSIGN_OR_RETURN_UNWRAP(&hmac, args.Holder());
Environment* env = hmac->env();
const node::Utf8Value hash_type(env->isolate(), args[0]);
ByteSource key = ByteSource::FromSecretKeyBytes(env, args[1]);
hmac->HmacInit(*hash_type, key.get(), key.size());
}
bool Hmac::HmacUpdate(const char* data, size_t len) {
return ctx_ && HMAC_Update(ctx_.get(),
reinterpret_cast<const unsigned char*>(data),
len) == 1;
}
void Hmac::HmacUpdate(const FunctionCallbackInfo<Value>& args) {
Decode<Hmac>(args, [](Hmac* hmac, const FunctionCallbackInfo<Value>& args,
const char* data, size_t size) {
Environment* env = Environment::GetCurrent(args);
if (UNLIKELY(size > INT_MAX))
return THROW_ERR_OUT_OF_RANGE(env, "data is too long");
bool r = hmac->HmacUpdate(data, size);
args.GetReturnValue().Set(r);
});
}
void Hmac::HmacDigest(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Hmac* hmac;
ASSIGN_OR_RETURN_UNWRAP(&hmac, args.Holder());
enum encoding encoding = BUFFER;
if (args.Length() >= 1) {
encoding = ParseEncoding(env->isolate(), args[0], BUFFER);
}
unsigned char md_value[EVP_MAX_MD_SIZE];
unsigned int md_len = 0;
if (hmac->ctx_) {
HMAC_Final(hmac->ctx_.get(), md_value, &md_len);
hmac->ctx_.reset();
}
Local<Value> error;
MaybeLocal<Value> rc =
StringBytes::Encode(env->isolate(),
reinterpret_cast<const char*>(md_value),
md_len,
encoding,
&error);
if (rc.IsEmpty()) {
CHECK(!error.IsEmpty());
env->isolate()->ThrowException(error);
return;
}
args.GetReturnValue().Set(rc.ToLocalChecked());
}
HmacConfig::HmacConfig(HmacConfig&& other) noexcept
: job_mode(other.job_mode),
mode(other.mode),
key(std::move(other.key)),
data(std::move(other.data)),
signature(std::move(other.signature)),
digest(other.digest) {}
HmacConfig& HmacConfig::operator=(HmacConfig&& other) noexcept {
if (&other == this) return *this;
this->~HmacConfig();
return *new (this) HmacConfig(std::move(other));
}
void HmacConfig::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("key", key.get());
// If the job is sync, then the HmacConfig does not own the data
if (job_mode == kCryptoJobAsync) {
tracker->TrackFieldWithSize("data", data.size());
tracker->TrackFieldWithSize("signature", signature.size());
}
}
Maybe<bool> HmacTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
HmacConfig* params) {
Environment* env = Environment::GetCurrent(args);
params->job_mode = mode;
CHECK(args[offset]->IsUint32()); // SignConfiguration::Mode
params->mode =
static_cast<SignConfiguration::Mode>(args[offset].As<Uint32>()->Value());
CHECK(args[offset + 1]->IsString()); // Hash
CHECK(args[offset + 2]->IsObject()); // Key
Utf8Value digest(env->isolate(), args[offset + 1]);
params->digest = EVP_get_digestbyname(*digest);
if (params->digest == nullptr) {
THROW_ERR_CRYPTO_INVALID_DIGEST(env);
return Nothing<bool>();
}
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args[offset + 2], Nothing<bool>());
params->key = key->Data();
ArrayBufferOrViewContents<char> data(args[offset + 3]);
if (UNLIKELY(!data.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "data is too big");
return Nothing<bool>();
}
params->data = mode == kCryptoJobAsync
? data.ToCopy()
: data.ToByteSource();
if (!args[offset + 4]->IsUndefined()) {
ArrayBufferOrViewContents<char> signature(args[offset + 4]);
if (UNLIKELY(!signature.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "signature is too big");
return Nothing<bool>();
}
params->signature = mode == kCryptoJobAsync
? signature.ToCopy()
: signature.ToByteSource();
}
return Just(true);
}
bool HmacTraits::DeriveBits(
Environment* env,
const HmacConfig& params,
ByteSource* out) {
HMACCtxPointer ctx(HMAC_CTX_new());
if (!ctx ||
!HMAC_Init_ex(
ctx.get(),
params.key->GetSymmetricKey(),
params.key->GetSymmetricKeySize(),
params.digest,
nullptr)) {
return false;
}
if (!HMAC_Update(
ctx.get(),
params.data.data<unsigned char>(),
params.data.size())) {
return false;
}
char* data = MallocOpenSSL<char>(EVP_MAX_MD_SIZE);
ByteSource buf = ByteSource::Allocated(data, EVP_MAX_MD_SIZE);
unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
unsigned int len;
if (!HMAC_Final(ctx.get(), ptr, &len)) {
return false;
}
buf.Resize(len);
*out = std::move(buf);
return true;
}
Maybe<bool> HmacTraits::EncodeOutput(
Environment* env,
const HmacConfig& params,
ByteSource* out,
Local<Value>* result) {
switch (params.mode) {
case SignConfiguration::kSign:
*result = out->ToArrayBuffer(env);
break;
case SignConfiguration::kVerify:
*result =
out->size() > 0 &&
out->size() == params.signature.size() &&
memcmp(out->get(), params.signature.get(), out->size()) == 0
? v8::True(env->isolate())
: v8::False(env->isolate());
break;
default:
UNREACHABLE();
}
return Just(!result->IsEmpty());
}
} // namespace crypto
} // namespace node

94
src/crypto/crypto_hmac.h Normal file
View File

@ -0,0 +1,94 @@
#ifndef SRC_CRYPTO_CRYPTO_HMAC_H_
#define SRC_CRYPTO_CRYPTO_HMAC_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_keys.h"
#include "crypto/crypto_sig.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer.h"
#include "base_object.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
namespace node {
namespace crypto {
class Hmac : public BaseObject {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(Hmac)
SET_SELF_SIZE(Hmac)
protected:
void HmacInit(const char* hash_type, const char* key, int key_len);
bool HmacUpdate(const char* data, size_t len);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void HmacInit(const v8::FunctionCallbackInfo<v8::Value>& args);
static void HmacUpdate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void HmacDigest(const v8::FunctionCallbackInfo<v8::Value>& args);
Hmac(Environment* env, v8::Local<v8::Object> wrap);
static void Sign(const v8::FunctionCallbackInfo<v8::Value>& args);
private:
HMACCtxPointer ctx_;
};
struct HmacConfig final : public MemoryRetainer {
CryptoJobMode job_mode;
SignConfiguration::Mode mode;
std::shared_ptr<KeyObjectData> key;
ByteSource data;
ByteSource signature;
const EVP_MD* digest;
HmacConfig() = default;
explicit HmacConfig(HmacConfig&& other) noexcept;
HmacConfig& operator=(HmacConfig&& other) noexcept;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(HmacConfig);
SET_SELF_SIZE(HmacConfig);
};
struct HmacTraits final {
using AdditionalParameters = HmacConfig;
static constexpr const char* JobName = "HmacJob";
// TODO(@jasnell): Sign request vs. Verify request
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_SIGNREQUEST;
static v8::Maybe<bool> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
HmacConfig* params);
static bool DeriveBits(
Environment* env,
const HmacConfig& params,
ByteSource* out);
static v8::Maybe<bool> EncodeOutput(
Environment* env,
const HmacConfig& params,
ByteSource* out,
v8::Local<v8::Value>* result);
};
using HmacJob = DeriveBitsJob<HmacTraits>;
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_HMAC_H_

107
src/crypto/crypto_keygen.cc Normal file
View File

@ -0,0 +1,107 @@
#include "crypto/crypto_keygen.h"
#include "allocated_buffer-inl.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
#include <cmath>
namespace node {
using v8::FunctionCallbackInfo;
using v8::Int32;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::Nothing;
using v8::Object;
using v8::Uint32;
using v8::Value;
namespace crypto {
// NidKeyPairGenJob input arguments:
// 1. CryptoJobMode
// 2. NID
// 3. Public Format
// 4. Public Type
// 5. Private Format
// 6. Private Type
// 7. Cipher
// 8. Passphrase
Maybe<bool> NidKeyPairGenTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int* offset,
NidKeyPairGenConfig* params) {
CHECK(args[*offset]->IsInt32());
params->params.id = args[*offset].As<Int32>()->Value();
*offset += 1;
return Just(true);
}
EVPKeyCtxPointer NidKeyPairGenTraits::Setup(NidKeyPairGenConfig* params) {
EVPKeyCtxPointer ctx =
EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(params->params.id, nullptr));
if (!ctx || EVP_PKEY_keygen_init(ctx.get()) <= 0)
return EVPKeyCtxPointer();
return ctx;
}
void SecretKeyGenConfig::MemoryInfo(MemoryTracker* tracker) const {
if (out != nullptr)
tracker->TrackFieldWithSize("out", length);
}
Maybe<bool> SecretKeyGenTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int* offset,
SecretKeyGenConfig* params) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[*offset]->IsUint32());
params->length = std::trunc(args[*offset].As<Uint32>()->Value() / CHAR_BIT);
if (params->length > INT_MAX) {
char msg[1024];
snprintf(msg, sizeof(msg),
"length must be less than or equal to %lu bits",
static_cast<size_t>(INT_MAX) * CHAR_BIT);
THROW_ERR_OUT_OF_RANGE(env, msg);
return Nothing<bool>();
}
*offset += 1;
return Just(true);
}
KeyGenJobStatus SecretKeyGenTraits::DoKeyGen(
Environment* env,
SecretKeyGenConfig* params) {
CHECK_LE(params->length, INT_MAX);
params->out = MallocOpenSSL<char>(params->length);
EntropySource(reinterpret_cast<unsigned char*>(params->out), params->length);
return KeyGenJobStatus::ERR_OK;
}
Maybe<bool> SecretKeyGenTraits::EncodeKey(
Environment* env,
SecretKeyGenConfig* params,
Local<Value>* result) {
ByteSource out = ByteSource::Allocated(params->out, params->length);
std::shared_ptr<KeyObjectData> data =
KeyObjectData::CreateSecret(std::move(out));
return Just(KeyObjectHandle::Create(env, data).ToLocal(result));
}
namespace Keygen {
void Initialize(Environment* env, Local<Object> target) {
NidKeyPairGenJob::Initialize(env, target);
SecretKeyGenJob::Initialize(env, target);
}
} // namespace Keygen
} // namespace crypto
} // namespace node

292
src/crypto/crypto_keygen.h Normal file
View File

@ -0,0 +1,292 @@
#ifndef SRC_CRYPTO_CRYPTO_KEYGEN_H_
#define SRC_CRYPTO_CRYPTO_KEYGEN_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer.h"
#include "async_wrap.h"
#include "base_object.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
namespace node {
namespace crypto {
namespace Keygen {
void Initialize(Environment* env, v8::Local<v8::Object> target);
} // namespace Keygen
enum class KeyGenJobStatus {
ERR_OK,
ERR_FAILED
};
// A Base CryptoJob for generating secret keys or key pairs.
// The KeyGenTraits is largely responsible for the details of
// the implementation, while KeyGenJob handles the common
// mechanisms.
template <typename KeyGenTraits>
class KeyGenJob final : public CryptoJob<KeyGenTraits> {
public:
using AdditionalParams = typename KeyGenTraits::AdditionalParameters;
static void New(const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args.IsConstructCall());
CryptoJobMode mode = GetCryptoJobMode(args[0]);
unsigned int offset = 1;
AdditionalParams params;
if (KeyGenTraits::AdditionalConfig(mode, args, &offset, &params)
.IsNothing()) {
// The KeyGenTraits::AdditionalConfig is responsible for
// calling an appropriate THROW_CRYPTO_* variant reporting
// whatever error caused initialization to fail.
return;
}
new KeyGenJob<KeyGenTraits>(env, args.This(), mode, std::move(params));
}
static void Initialize(
Environment* env,
v8::Local<v8::Object> target) {
CryptoJob<KeyGenTraits>::Initialize(New, env, target);
}
KeyGenJob(
Environment* env,
v8::Local<v8::Object> object,
CryptoJobMode mode,
AdditionalParams&& params)
: CryptoJob<KeyGenTraits>(
env,
object,
KeyGenTraits::Provider,
mode,
std::move(params)) {}
void DoThreadPoolWork() override {
// Make sure the the CSPRNG is properly seeded so the results are secure
CheckEntropy();
AdditionalParams* params = CryptoJob<KeyGenTraits>::params();
switch (KeyGenTraits::DoKeyGen(AsyncWrap::env(), params)) {
case KeyGenJobStatus::ERR_OK:
status_ = KeyGenJobStatus::ERR_OK;
// Success!
break;
case KeyGenJobStatus::ERR_FAILED: {
CryptoErrorVector* errors = CryptoJob<KeyGenTraits>::errors();
errors->Capture();
if (errors->empty())
errors->push_back(std::string("Key generation job failed"));
}
}
}
v8::Maybe<bool> ToResult(
v8::Local<v8::Value>* err,
v8::Local<v8::Value>* result) override {
Environment* env = AsyncWrap::env();
CryptoErrorVector* errors = CryptoJob<KeyGenTraits>::errors();
AdditionalParams* params = CryptoJob<KeyGenTraits>::params();
if (status_ == KeyGenJobStatus::ERR_OK &&
LIKELY(!KeyGenTraits::EncodeKey(env, params, result).IsNothing())) {
*err = Undefined(env->isolate());
return v8::Just(true);
}
if (errors->empty())
errors->Capture();
CHECK(!errors->empty());
*result = Undefined(env->isolate());
return v8::Just(errors->ToException(env).ToLocal(err));
}
SET_SELF_SIZE(KeyGenJob);
private:
KeyGenJobStatus status_ = KeyGenJobStatus::ERR_FAILED;
};
// A Base KeyGenTraits for Key Pair generation algorithms.
template <typename KeyPairAlgorithmTraits>
struct KeyPairGenTraits final {
using AdditionalParameters =
typename KeyPairAlgorithmTraits::AdditionalParameters;
static const AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_KEYPAIRGENREQUEST;
static constexpr const char* JobName = KeyPairAlgorithmTraits::JobName;
static v8::Maybe<bool> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
AdditionalParameters* params) {
// Notice that offset is a pointer. Each of the AdditionalConfig,
// GetPublicKeyEncodingFromJs, and GetPrivateKeyEncodingFromJs
// functions will update the value of the offset as they successfully
// process input parameters. This allows each job to have a variable
// number of input parameters specific to each job type.
if (KeyPairAlgorithmTraits::AdditionalConfig(mode, args, offset, params)
.IsNothing()) {
return v8::Just(false);
}
params->public_key_encoding = ManagedEVPPKey::GetPublicKeyEncodingFromJs(
args,
offset,
kKeyContextGenerate);
auto private_key_encoding =
ManagedEVPPKey::GetPrivateKeyEncodingFromJs(
args,
offset,
kKeyContextGenerate);
if (!private_key_encoding.IsEmpty())
params->private_key_encoding = private_key_encoding.Release();
return v8::Just(true);
}
static KeyGenJobStatus DoKeyGen(
Environment* env,
AdditionalParameters* params) {
EVPKeyCtxPointer ctx = KeyPairAlgorithmTraits::Setup(params);
if (!ctx || EVP_PKEY_keygen_init(ctx.get()) <= 0)
return KeyGenJobStatus::ERR_FAILED;
// Generate the key
EVP_PKEY* pkey = nullptr;
if (!EVP_PKEY_keygen(ctx.get(), &pkey))
return KeyGenJobStatus::ERR_FAILED;
params->key = ManagedEVPPKey(EVPKeyPointer(pkey));
return KeyGenJobStatus::ERR_OK;
}
static v8::Maybe<bool> EncodeKey(
Environment* env,
AdditionalParameters* params,
v8::Local<v8::Value>* result) {
v8::Local<v8::Value> keys[2];
if (ManagedEVPPKey::ToEncodedPublicKey(
env,
std::move(params->key),
params->public_key_encoding,
&keys[0]).IsNothing() ||
ManagedEVPPKey::ToEncodedPrivateKey(
env,
std::move(params->key),
params->private_key_encoding,
&keys[1]).IsNothing()) {
return v8::Nothing<bool>();
}
*result = v8::Array::New(env->isolate(), keys, arraysize(keys));
return v8::Just(true);
}
};
struct SecretKeyGenConfig final : public MemoryRetainer {
size_t length; // Expressed a a number of bits
char* out = nullptr; // Placeholder for the generated key bytes
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(SecretKeyGenConfig)
SET_SELF_SIZE(SecretKeyGenConfig)
};
struct SecretKeyGenTraits final {
using AdditionalParameters = SecretKeyGenConfig;
static const AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_KEYGENREQUEST;
static constexpr const char* JobName = "SecretKeyGenJob";
static v8::Maybe<bool> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
SecretKeyGenConfig* params);
static KeyGenJobStatus DoKeyGen(
Environment* env,
SecretKeyGenConfig* params);
static v8::Maybe<bool> EncodeKey(
Environment* env,
SecretKeyGenConfig* params,
v8::Local<v8::Value>* result);
};
template <typename AlgorithmParams>
struct KeyPairGenConfig final : public MemoryRetainer {
PublicKeyEncodingConfig public_key_encoding;
PrivateKeyEncodingConfig private_key_encoding;
ManagedEVPPKey key;
AlgorithmParams params;
KeyPairGenConfig() = default;
explicit KeyPairGenConfig(KeyPairGenConfig&& other) noexcept
: public_key_encoding(other.public_key_encoding),
private_key_encoding(
std::forward<PrivateKeyEncodingConfig>(
other.private_key_encoding)),
key(std::move(other.key)),
params(std::move(other.params)) {}
KeyPairGenConfig& operator=(KeyPairGenConfig&& other) noexcept {
if (&other == this) return *this;
this->~KeyPairGenConfig();
return *new (this) KeyPairGenConfig(std::move(other));
}
void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackField("key", key);
tracker->TrackFieldWithSize("private_key_encoding.passphrase",
private_key_encoding.passphrase_.size());
tracker->TrackField("params", params);
}
SET_MEMORY_INFO_NAME(KeyPairGenConfig)
SET_SELF_SIZE(KeyPairGenConfig)
};
struct NidKeyPairParams final : public MemoryRetainer {
int id;
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(NidKeyPairParams)
SET_SELF_SIZE(NidKeyPairParams)
};
using NidKeyPairGenConfig = KeyPairGenConfig<NidKeyPairParams>;
struct NidKeyPairGenTraits final {
using AdditionalParameters = NidKeyPairGenConfig;
static constexpr const char* JobName = "NidKeyPairGenJob";
static EVPKeyCtxPointer Setup(NidKeyPairGenConfig* params);
static v8::Maybe<bool> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
NidKeyPairGenConfig* params);
};
using NidKeyPairGenJob = KeyGenJob<KeyPairGenTraits<NidKeyPairGenTraits>>;
using SecretKeyGenJob = KeyGenJob<SecretKeyGenTraits>;
} // namespace crypto
} // namespace node
#endif // !defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_KEYGEN_H_

1304
src/crypto/crypto_keys.cc Normal file

File diff suppressed because it is too large Load Diff

401
src/crypto/crypto_keys.h Normal file
View File

@ -0,0 +1,401 @@
#ifndef SRC_CRYPTO_CRYPTO_KEYS_H_
#define SRC_CRYPTO_CRYPTO_KEYS_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_util.h"
#include "base_object.h"
#include "env.h"
#include "memory_tracker.h"
#include "node_buffer.h"
#include "node_worker.h"
#include "v8.h"
#include <openssl/evp.h>
#include <memory>
#include <string>
namespace node {
namespace crypto {
enum PKEncodingType {
// RSAPublicKey / RSAPrivateKey according to PKCS#1.
kKeyEncodingPKCS1,
// PrivateKeyInfo or EncryptedPrivateKeyInfo according to PKCS#8.
kKeyEncodingPKCS8,
// SubjectPublicKeyInfo according to X.509.
kKeyEncodingSPKI,
// ECPrivateKey according to SEC1.
kKeyEncodingSEC1
};
enum PKFormatType {
kKeyFormatDER,
kKeyFormatPEM
};
enum KeyType {
kKeyTypeSecret,
kKeyTypePublic,
kKeyTypePrivate
};
enum KeyEncodingContext {
kKeyContextInput,
kKeyContextExport,
kKeyContextGenerate
};
enum class ParseKeyResult {
kParseKeyOk,
kParseKeyNotRecognized,
kParseKeyNeedPassphrase,
kParseKeyFailed
};
struct AsymmetricKeyEncodingConfig {
bool output_key_object_ = false;
PKFormatType format_ = kKeyFormatDER;
v8::Maybe<PKEncodingType> type_ = v8::Nothing<PKEncodingType>();
};
using PublicKeyEncodingConfig = AsymmetricKeyEncodingConfig;
struct PrivateKeyEncodingConfig : public AsymmetricKeyEncodingConfig {
const EVP_CIPHER* cipher_;
ByteSource passphrase_;
};
// This uses the built-in reference counter of OpenSSL to manage an EVP_PKEY
// which is slightly more efficient than using a shared pointer and easier to
// use.
class ManagedEVPPKey : public MemoryRetainer {
public:
ManagedEVPPKey() = default;
explicit ManagedEVPPKey(EVPKeyPointer&& pkey);
ManagedEVPPKey(const ManagedEVPPKey& that);
ManagedEVPPKey& operator=(const ManagedEVPPKey& that);
operator bool() const;
EVP_PKEY* get() const;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(ManagedEVPPKey)
SET_SELF_SIZE(ManagedEVPPKey)
static PublicKeyEncodingConfig GetPublicKeyEncodingFromJs(
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
KeyEncodingContext context);
static NonCopyableMaybe<PrivateKeyEncodingConfig> GetPrivateKeyEncodingFromJs(
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
KeyEncodingContext context);
static ManagedEVPPKey GetParsedKey(Environment* env,
EVPKeyPointer&& pkey,
ParseKeyResult ret,
const char* default_msg);
static ManagedEVPPKey GetPublicOrPrivateKeyFromJs(
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset);
static ManagedEVPPKey GetPrivateKeyFromJs(
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
bool allow_key_object);
static v8::Maybe<bool> ToEncodedPublicKey(
Environment* env,
ManagedEVPPKey key,
const PublicKeyEncodingConfig& config,
v8::Local<v8::Value>* out);
static v8::Maybe<bool> ToEncodedPrivateKey(
Environment* env,
ManagedEVPPKey key,
const PrivateKeyEncodingConfig& config,
v8::Local<v8::Value>* out);
private:
size_t size_of_private_key() const;
size_t size_of_public_key() const;
EVPKeyPointer pkey_;
};
// Objects of this class can safely be shared among threads.
class KeyObjectData : public MemoryRetainer {
public:
static std::shared_ptr<KeyObjectData> CreateSecret(
const ArrayBufferOrViewContents<char>& buf);
static std::shared_ptr<KeyObjectData> CreateSecret(ByteSource key);
static std::shared_ptr<KeyObjectData> CreateAsymmetric(
KeyType type,
const ManagedEVPPKey& pkey);
KeyType GetKeyType() const;
// These functions allow unprotected access to the raw key material and should
// only be used to implement cryptographic operations requiring the key.
ManagedEVPPKey GetAsymmetricKey() const;
const char* GetSymmetricKey() const;
size_t GetSymmetricKeySize() const;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(KeyObjectData);
SET_SELF_SIZE(KeyObjectData);
private:
explicit KeyObjectData(ByteSource symmetric_key);
KeyObjectData(
KeyType type,
const ManagedEVPPKey& pkey);
const KeyType key_type_;
const ByteSource symmetric_key_;
const unsigned int symmetric_key_len_;
const ManagedEVPPKey asymmetric_key_;
};
class KeyObjectHandle : public BaseObject {
public:
static v8::Local<v8::Function> Initialize(Environment* env);
static v8::MaybeLocal<v8::Object> Create(Environment* env,
std::shared_ptr<KeyObjectData> data);
// TODO(tniessen): track the memory used by OpenSSL types
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(KeyObjectHandle)
SET_SELF_SIZE(KeyObjectHandle)
const std::shared_ptr<KeyObjectData>& Data();
protected:
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Init(const v8::FunctionCallbackInfo<v8::Value>& args);
static void InitECRaw(const v8::FunctionCallbackInfo<v8::Value>& args);
static void InitJWK(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetKeyDetail(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ExportJWK(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetAsymmetricKeyType(
const v8::FunctionCallbackInfo<v8::Value>& args);
v8::Local<v8::Value> GetAsymmetricKeyType() const;
static void GetSymmetricKeySize(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void Export(const v8::FunctionCallbackInfo<v8::Value>& args);
v8::Local<v8::Value> ExportSecretKey() const;
v8::MaybeLocal<v8::Value> ExportPublicKey(
const PublicKeyEncodingConfig& config) const;
v8::MaybeLocal<v8::Value> ExportPrivateKey(
const PrivateKeyEncodingConfig& config) const;
KeyObjectHandle(Environment* env,
v8::Local<v8::Object> wrap);
private:
std::shared_ptr<KeyObjectData> data_;
};
class NativeKeyObject : public BaseObject {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void CreateNativeKeyObjectClass(
const v8::FunctionCallbackInfo<v8::Value>& args);
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(NativeKeyObject)
SET_SELF_SIZE(NativeKeyObject)
class KeyObjectTransferData : public worker::TransferData {
public:
explicit KeyObjectTransferData(const std::shared_ptr<KeyObjectData>& data)
: data_(data) {}
BaseObjectPtr<BaseObject> Deserialize(
Environment* env,
v8::Local<v8::Context> context,
std::unique_ptr<worker::TransferData> self) override;
SET_MEMORY_INFO_NAME(KeyObjectTransferData)
SET_SELF_SIZE(KeyObjectTransferData)
SET_NO_MEMORY_INFO()
private:
std::shared_ptr<KeyObjectData> data_;
};
BaseObject::TransferMode GetTransferMode() const override;
std::unique_ptr<worker::TransferData> CloneForMessaging() const override;
private:
NativeKeyObject(Environment* env,
v8::Local<v8::Object> wrap,
const std::shared_ptr<KeyObjectData>& handle_data)
: BaseObject(env, wrap),
handle_data_(handle_data) {
MakeWeak();
}
std::shared_ptr<KeyObjectData> handle_data_;
};
enum WebCryptoKeyFormat {
kWebCryptoKeyFormatRaw,
kWebCryptoKeyFormatPKCS8,
kWebCryptoKeyFormatSPKI,
kWebCryptoKeyFormatJWK
};
enum class WebCryptoKeyExportStatus {
ERR_OK,
ERR_INVALID_KEY_TYPE,
ERR_FAILED
};
template <typename KeyExportTraits>
class KeyExportJob final : public CryptoJob<KeyExportTraits> {
public:
using AdditionalParams = typename KeyExportTraits::AdditionalParameters;
static void New(const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args.IsConstructCall());
CryptoJobMode mode = GetCryptoJobMode(args[0]);
CHECK(args[1]->IsUint32()); // Export Type
CHECK(args[2]->IsObject()); // KeyObject
WebCryptoKeyFormat format =
static_cast<WebCryptoKeyFormat>(args[1].As<v8::Uint32>()->Value());
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args[2]);
CHECK_NOT_NULL(key);
AdditionalParams params;
if (KeyExportTraits::AdditionalConfig(args, 3, &params).IsNothing()) {
// The KeyExportTraits::AdditionalConfig is responsible for
// calling an appropriate THROW_CRYPTO_* variant reporting
// whatever error caused initialization to fail.
return;
}
new KeyExportJob<KeyExportTraits>(
env,
args.This(),
mode,
key->Data(),
format,
std::move(params));
}
static void Initialize(
Environment* env,
v8::Local<v8::Object> target) {
CryptoJob<KeyExportTraits>::Initialize(New, env, target);
}
KeyExportJob(
Environment* env,
v8::Local<v8::Object> object,
CryptoJobMode mode,
std::shared_ptr<KeyObjectData> key,
WebCryptoKeyFormat format,
AdditionalParams&& params)
: CryptoJob<KeyExportTraits>(
env,
object,
AsyncWrap::PROVIDER_KEYEXPORTREQUEST,
mode,
std::move(params)),
key_(key),
format_(format) {}
WebCryptoKeyFormat format() const { return format_; }
void DoThreadPoolWork() override {
switch (KeyExportTraits::DoExport(
key_,
format_,
*CryptoJob<KeyExportTraits>::params(),
&out_)) {
case WebCryptoKeyExportStatus::ERR_OK:
// Success!
break;
case WebCryptoKeyExportStatus::ERR_INVALID_KEY_TYPE:
// Fall through
// TODO(@jasnell): Separate error for this
case WebCryptoKeyExportStatus::ERR_FAILED: {
CryptoErrorVector* errors = CryptoJob<KeyExportTraits>::errors();
errors->Capture();
if (errors->empty())
errors->push_back("Key export failed.");
}
}
}
v8::Maybe<bool> ToResult(
v8::Local<v8::Value>* err,
v8::Local<v8::Value>* result) override {
Environment* env = AsyncWrap::env();
CryptoErrorVector* errors = CryptoJob<KeyExportTraits>::errors();
if (out_.size() > 0) {
CHECK(errors->empty());
*err = v8::Undefined(env->isolate());
*result = out_.ToArrayBuffer(env);
return v8::Just(!result->IsEmpty());
}
if (errors->empty())
errors->Capture();
CHECK(!errors->empty());
*result = v8::Undefined(env->isolate());
return v8::Just(errors->ToException(env).ToLocal(err));
}
SET_SELF_SIZE(KeyExportJob)
void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackFieldWithSize("out", out_.size());
CryptoJob<KeyExportTraits>::MemoryInfo(tracker);
}
private:
std::shared_ptr<KeyObjectData> key_;
WebCryptoKeyFormat format_;
ByteSource out_;
};
WebCryptoKeyExportStatus PKEY_SPKI_Export(
KeyObjectData* key_data,
ByteSource* out);
WebCryptoKeyExportStatus PKEY_PKCS8_Export(
KeyObjectData* key_data,
ByteSource* out);
namespace Keys {
void Initialize(Environment* env, v8::Local<v8::Object> target);
} // namespace Keys
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_KEYS_H_

149
src/crypto/crypto_pbkdf2.cc Normal file
View File

@ -0,0 +1,149 @@
#include "crypto/crypto_pbkdf2.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer-inl.h"
#include "async_wrap-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node_buffer.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
namespace node {
using v8::FunctionCallbackInfo;
using v8::Int32;
using v8::Just;
using v8::Maybe;
using v8::Nothing;
using v8::Value;
namespace crypto {
PBKDF2Config::PBKDF2Config(PBKDF2Config&& other) noexcept
: mode(other.mode),
pass(std::move(other.pass)),
salt(std::move(other.salt)),
iterations(other.iterations),
length(other.length),
digest(other.digest) {}
PBKDF2Config& PBKDF2Config::operator=(PBKDF2Config&& other) noexcept {
if (&other == this) return *this;
this->~PBKDF2Config();
return *new (this) PBKDF2Config(std::move(other));
}
void PBKDF2Config::MemoryInfo(MemoryTracker* tracker) const {
// The the job is sync, the PBKDF2Config does not own the data
if (mode == kCryptoJobAsync) {
tracker->TrackFieldWithSize("pass", pass.size());
tracker->TrackFieldWithSize("salt", salt.size());
}
}
Maybe<bool> PBKDF2Traits::EncodeOutput(
Environment* env,
const PBKDF2Config& params,
ByteSource* out,
v8::Local<v8::Value>* result) {
*result = out->ToArrayBuffer(env);
return Just(!result->IsEmpty());
}
// The input arguments for the job are:
// 1. CryptoJobMode
// 2. The password
// 3. The salt
// 4. The number of iterations
// 5. The number of bytes to generate
// 6. The digest algorithm name
Maybe<bool> PBKDF2Traits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
PBKDF2Config* params) {
Environment* env = Environment::GetCurrent(args);
params->mode = mode;
ArrayBufferOrViewContents<char> pass(args[offset]);
ArrayBufferOrViewContents<char> salt(args[offset + 1]);
if (UNLIKELY(!pass.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "pass is too large");
return Nothing<bool>();
}
if (UNLIKELY(!salt.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "salt is too large");
return Nothing<bool>();
}
params->pass = mode == kCryptoJobAsync
? pass.ToCopy()
: pass.ToByteSource();
params->salt = mode == kCryptoJobAsync
? salt.ToCopy()
: salt.ToByteSource();
CHECK(args[offset + 2]->IsInt32()); // iteration_count
CHECK(args[offset + 3]->IsInt32()); // length
CHECK(args[offset + 4]->IsString()); // digest_name
params->iterations = args[offset + 2].As<Int32>()->Value();
if (params->iterations < 0) {
char msg[1024];
snprintf(msg, sizeof(msg), "iterations must be <= %d", INT_MAX);
THROW_ERR_OUT_OF_RANGE(env, msg);
return Nothing<bool>();
}
params->length = args[offset + 3].As<Int32>()->Value();
if (params->length < 0) {
char msg[1024];
snprintf(msg, sizeof(msg), "length must be <= %d", INT_MAX);
THROW_ERR_OUT_OF_RANGE(env, msg);
return Nothing<bool>();
}
Utf8Value name(args.GetIsolate(), args[offset + 4]);
params->digest = EVP_get_digestbyname(*name);
if (params->digest == nullptr) {
char errmsg[1024];
snprintf(errmsg, sizeof(errmsg), "Invalid digest: %s", *name);
THROW_ERR_CRYPTO_INVALID_DIGEST(env, errmsg);
return Nothing<bool>();
}
return Just(true);
}
bool PBKDF2Traits::DeriveBits(
Environment* env,
const PBKDF2Config& params,
ByteSource* out) {
char* data = MallocOpenSSL<char>(params.length);
ByteSource buf = ByteSource::Allocated(data, params.length);
unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
// Both pass and salt may be zero length here.
// The generated bytes are stored in buf, which is
// assigned to out on success.
if (PKCS5_PBKDF2_HMAC(
params.pass.get(),
params.pass.size(),
params.salt.data<unsigned char>(),
params.salt.size(),
params.iterations,
params.digest,
params.length,
ptr) <= 0) {
return false;
}
*out = std::move(buf);
return true;
}
} // namespace crypto
} // namespace node

View File

@ -0,0 +1,76 @@
#ifndef SRC_CRYPTO_CRYPTO_PBKDF2_H_
#define SRC_CRYPTO_CRYPTO_PBKDF2_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_util.h"
#include "async_wrap.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
namespace node {
namespace crypto {
// PBKDF2 is a pseudo-random key derivation scheme defined
// in https://tools.ietf.org/html/rfc8018
//
// The algorithm takes as input a password and salt
// (both of which may, but should not be zero-length),
// a number of iterations, a hash digest algorithm, and
// an output length.
//
// The salt should be as unique as possible, and should
// be at least 16 bytes in length.
//
// The iteration count should be as high as possible.
struct PBKDF2Config final : public MemoryRetainer {
CryptoJobMode mode;
ByteSource pass;
ByteSource salt;
int32_t iterations;
int32_t length;
const EVP_MD* digest = nullptr;
PBKDF2Config() = default;
explicit PBKDF2Config(PBKDF2Config&& other) noexcept;
PBKDF2Config& operator=(PBKDF2Config&& other) noexcept;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(PBKDF2Config);
SET_SELF_SIZE(PBKDF2Config);
};
struct PBKDF2Traits final {
using AdditionalParameters = PBKDF2Config;
static constexpr const char* JobName = "PBKDF2Job";
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_PBKDF2REQUEST;
static v8::Maybe<bool> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
PBKDF2Config* params);
static bool DeriveBits(
Environment* env,
const PBKDF2Config& params,
ByteSource* out);
static v8::Maybe<bool> EncodeOutput(
Environment* env,
const PBKDF2Config& params,
ByteSource* out,
v8::Local<v8::Value>* result);
};
using PBKDF2Job = DeriveBitsJob<PBKDF2Traits>;
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_PBKDF2_H_

View File

@ -0,0 +1,73 @@
#include "crypto/crypto_random.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer-inl.h"
#include "async_wrap-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
namespace node {
using v8::FunctionCallbackInfo;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::Nothing;
using v8::Object;
using v8::Uint32;
using v8::Value;
namespace crypto {
Maybe<bool> RandomBytesTraits::EncodeOutput(
Environment* env,
const RandomBytesConfig& params,
ByteSource* unused,
v8::Local<v8::Value>* result) {
*result = v8::Undefined(env->isolate());
return Just(!result->IsEmpty());
}
Maybe<bool> RandomBytesTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
RandomBytesConfig* params) {
Environment* env = Environment::GetCurrent(args);
CHECK(IsAnyByteSource(args[offset])); // Buffer to fill
CHECK(args[offset + 1]->IsUint32()); // Offset
CHECK(args[offset + 2]->IsUint32()); // Size
ArrayBufferOrViewContents<unsigned char> in(args[offset]);
const uint32_t byte_offset = args[offset + 1].As<Uint32>()->Value();
const uint32_t size = args[offset + 2].As<Uint32>()->Value();
CHECK_GE(byte_offset + size, byte_offset); // Overflow check.
CHECK_LE(byte_offset + size, in.size()); // Bounds check.
if (UNLIKELY(size > INT_MAX)) {
THROW_ERR_OUT_OF_RANGE(env, "buffer is too large");
return Nothing<bool>();
}
params->buffer = in.data() + byte_offset;
params->size = size;
return Just(true);
}
bool RandomBytesTraits::DeriveBits(
Environment* env,
const RandomBytesConfig& params,
ByteSource* unused) {
CheckEntropy(); // Ensure that OpenSSL's PRNG is properly seeded.
return RAND_bytes(params.buffer, params.size) != 0;
}
namespace Random {
void Initialize(Environment* env, Local<Object> target) {
RandomBytesJob::Initialize(env, target);
}
} // namespace Random
} // namespace crypto
} // namespace node

View File

@ -0,0 +1,57 @@
#ifndef SRC_CRYPTO_CRYPTO_RANDOM_H_
#define SRC_CRYPTO_CRYPTO_RANDOM_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_util.h"
#include "base_object.h"
#include "allocated_buffer.h"
#include "env.h"
#include "memory_tracker.h"
#include "node_internals.h"
#include "v8.h"
namespace node {
namespace crypto {
struct RandomBytesConfig final : public MemoryRetainer {
unsigned char* buffer;
size_t size;
SET_NO_MEMORY_INFO();
SET_MEMORY_INFO_NAME(RandomBytesConfig);
SET_SELF_SIZE(RandomBytesConfig);
};
struct RandomBytesTraits final {
using AdditionalParameters = RandomBytesConfig;
static constexpr const char* JobName = "RandomBytesJob";
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_RANDOMBYTESREQUEST;
static v8::Maybe<bool> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
RandomBytesConfig* params);
static bool DeriveBits(
Environment* env,
const RandomBytesConfig& params,
ByteSource* out_);
static v8::Maybe<bool> EncodeOutput(
Environment* env,
const RandomBytesConfig& params,
ByteSource* unused,
v8::Local<v8::Value>* result);
};
using RandomBytesJob = DeriveBitsJob<RandomBytesTraits>;
namespace Random {
void Initialize(Environment* env, v8::Local<v8::Object> target);
} // namespace Random
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_RANDOM_H_

550
src/crypto/crypto_rsa.cc Normal file
View File

@ -0,0 +1,550 @@
#include "crypto/crypto_rsa.h"
#include "crypto/crypto_bio.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer-inl.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
#include <openssl/bn.h>
#include <openssl/rsa.h>
namespace node {
using v8::FunctionCallbackInfo;
using v8::Int32;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::Nothing;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Value;
namespace crypto {
EVPKeyCtxPointer RsaKeyGenTraits::Setup(RsaKeyPairGenConfig* params) {
EVPKeyCtxPointer ctx(
EVP_PKEY_CTX_new_id(
params->params.variant == kKeyVariantRSA_PSS
? EVP_PKEY_RSA_PSS
: EVP_PKEY_RSA,
nullptr));
if (EVP_PKEY_keygen_init(ctx.get()) <= 0)
return EVPKeyCtxPointer();
if (EVP_PKEY_CTX_set_rsa_keygen_bits(
ctx.get(),
params->params.modulus_bits) <= 0) {
return EVPKeyCtxPointer();
}
// 0x10001 is the default RSA exponent.
if (params->params.exponent != 0x10001) {
BignumPointer bn(BN_new());
CHECK_NOT_NULL(bn.get());
CHECK(BN_set_word(bn.get(), params->params.exponent));
// EVP_CTX accepts ownership of bn on success.
if (EVP_PKEY_CTX_set_rsa_keygen_pubexp(ctx.get(), bn.get()) <= 0)
return EVPKeyCtxPointer();
bn.release();
}
if (params->params.variant == kKeyVariantRSA_PSS) {
if (params->params.md != nullptr &&
EVP_PKEY_CTX_set_rsa_pss_keygen_md(ctx.get(), params->params.md) <= 0) {
return EVPKeyCtxPointer();
}
if (params->params.mgf1_md != nullptr &&
EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md(
ctx.get(),
params->params.mgf1_md) <= 0) {
return EVPKeyCtxPointer();
}
if (params->params.saltlen >= 0 &&
EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen(
ctx.get(),
params->params.saltlen) <= 0) {
return EVPKeyCtxPointer();
}
}
return ctx;
}
// Input parameters to the RsaKeyGenJob:
// For key variants RSA-OAEP and RSA-SSA-PKCS1-v1_5
// 1. CryptoJobMode
// 2. Key Variant
// 3. Modulus Bits
// 4. Public Exponent
// 5. Public Format
// 6. Public Type
// 7. Private Format
// 8. Private Type
// 9. Cipher
// 10. Passphrase
//
// For RSA-PSS variant
// 1. CryptoJobMode
// 2. Key Variant
// 3. Modulus Bits
// 4. Public Exponent
// 5. Digest
// 6. mgf1 Digest
// 7. Salt length
// 8. Public Format
// 9. Public Type
// 10. Private Format
// 11. Private Type
// 12. Cipher
// 13. Passphrase
Maybe<bool> RsaKeyGenTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int* offset,
RsaKeyPairGenConfig* params) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[*offset]->IsUint32()); // Variant
CHECK(args[*offset + 1]->IsUint32()); // Modulus bits
CHECK(args[*offset + 2]->IsUint32()); // Exponent
params->params.variant =
static_cast<RSAKeyVariant>(args[*offset].As<Uint32>()->Value());
CHECK_IMPLIES(params->params.variant != kKeyVariantRSA_PSS,
args.Length() == 10);
CHECK_IMPLIES(params->params.variant == kKeyVariantRSA_PSS,
args.Length() == 13);
params->params.modulus_bits = args[*offset + 1].As<Uint32>()->Value();
params->params.exponent = args[*offset + 2].As<Uint32>()->Value();
*offset += 3;
if (params->params.variant == kKeyVariantRSA_PSS) {
if (!args[*offset]->IsUndefined()) {
CHECK(args[*offset]->IsString());
Utf8Value digest(env->isolate(), args[*offset]);
params->params.md = EVP_get_digestbyname(*digest);
if (params->params.md == nullptr) {
char msg[1024];
snprintf(msg, sizeof(msg), "md specifies an invalid digest");
THROW_ERR_CRYPTO_INVALID_DIGEST(env, msg);
return Nothing<bool>();
}
}
if (!args[*offset + 1]->IsUndefined()) {
CHECK(args[*offset + 1]->IsString());
Utf8Value digest(env->isolate(), args[*offset + 1]);
params->params.mgf1_md = EVP_get_digestbyname(*digest);
if (params->params.mgf1_md == nullptr) {
char msg[1024];
snprintf(msg, sizeof(msg), "mgf1_md specifies an invalid digest");
THROW_ERR_CRYPTO_INVALID_DIGEST(env, msg);
return Nothing<bool>();
}
}
if (!args[*offset + 2]->IsUndefined()) {
CHECK(args[*offset + 2]->IsInt32());
params->params.saltlen = args[*offset + 2].As<Int32>()->Value();
if (params->params.saltlen < 0) {
char msg[1024];
snprintf(msg, sizeof(msg), "salt length is out of range");
THROW_ERR_OUT_OF_RANGE(env, msg);
return Nothing<bool>();
}
}
*offset += 3;
}
return Just(true);
}
namespace {
WebCryptoKeyExportStatus RSA_JWK_Export(
KeyObjectData* key_data,
const RSAKeyExportConfig& params,
ByteSource* out) {
return WebCryptoKeyExportStatus::ERR_FAILED;
}
template <PublicKeyCipher::EVP_PKEY_cipher_init_t init,
PublicKeyCipher::EVP_PKEY_cipher_t cipher>
WebCryptoCipherStatus RSA_Cipher(
Environment* env,
KeyObjectData* key_data,
const RSACipherConfig& params,
const ByteSource& in,
ByteSource* out) {
CHECK_NE(key_data->GetKeyType(), kKeyTypeSecret);
EVPKeyCtxPointer ctx(
EVP_PKEY_CTX_new(key_data->GetAsymmetricKey().get(), nullptr));
if (!ctx || init(ctx.get()) <= 0)
return WebCryptoCipherStatus::ERR_FAILED;
if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), params.padding) <= 0) {
return WebCryptoCipherStatus::ERR_FAILED;
}
if (params.digest != nullptr &&
(EVP_PKEY_CTX_set_rsa_oaep_md(ctx.get(), params.digest) <= 0 ||
EVP_PKEY_CTX_set_rsa_mgf1_md(ctx.get(), params.digest) <= 0)) {
return WebCryptoCipherStatus::ERR_FAILED;
}
size_t label_len = params.label.size();
if (label_len > 0) {
void* label = OPENSSL_memdup(params.label.get(), label_len);
CHECK_NOT_NULL(label);
if (EVP_PKEY_CTX_set0_rsa_oaep_label(ctx.get(), label, label_len) <= 0) {
OPENSSL_free(label);
return WebCryptoCipherStatus::ERR_FAILED;
}
}
size_t out_len = 0;
if (cipher(
ctx.get(),
nullptr,
&out_len,
in.data<unsigned char>(),
in.size()) <= 0) {
return WebCryptoCipherStatus::ERR_FAILED;
}
char* data = MallocOpenSSL<char>(out_len);
ByteSource buf = ByteSource::Allocated(data, out_len);
unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
if (cipher(
ctx.get(),
ptr,
&out_len,
in.data<unsigned char>(),
in.size()) <= 0) {
return WebCryptoCipherStatus::ERR_FAILED;
}
buf.Resize(out_len);
*out = std::move(buf);
return WebCryptoCipherStatus::ERR_OK;
}
} // namespace
Maybe<bool> RSAKeyExportTraits::AdditionalConfig(
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
RSAKeyExportConfig* params) {
CHECK(args[offset]->IsUint32()); // RSAKeyVariant
params->variant =
static_cast<RSAKeyVariant>(args[offset].As<Uint32>()->Value());
return Just(true);
}
WebCryptoKeyExportStatus RSAKeyExportTraits::DoExport(
std::shared_ptr<KeyObjectData> key_data,
WebCryptoKeyFormat format,
const RSAKeyExportConfig& params,
ByteSource* out) {
CHECK_NE(key_data->GetKeyType(), kKeyTypeSecret);
switch (format) {
case kWebCryptoKeyFormatRaw:
// Not supported for RSA keys of either type
return WebCryptoKeyExportStatus::ERR_FAILED;
case kWebCryptoKeyFormatJWK:
return RSA_JWK_Export(key_data.get(), params, out);
case kWebCryptoKeyFormatPKCS8:
if (key_data->GetKeyType() != kKeyTypePrivate)
return WebCryptoKeyExportStatus::ERR_INVALID_KEY_TYPE;
return PKEY_PKCS8_Export(key_data.get(), out);
case kWebCryptoKeyFormatSPKI:
if (key_data->GetKeyType() != kKeyTypePublic)
return WebCryptoKeyExportStatus::ERR_INVALID_KEY_TYPE;
return PKEY_SPKI_Export(key_data.get(), out);
default:
UNREACHABLE();
}
}
RSACipherConfig::RSACipherConfig(RSACipherConfig&& other) noexcept
: mode(other.mode),
label(std::move(other.label)),
padding(other.padding),
digest(other.digest) {}
void RSACipherConfig::MemoryInfo(MemoryTracker* tracker) const {
if (mode == kCryptoJobAsync)
tracker->TrackFieldWithSize("label", label.size());
}
Maybe<bool> RSACipherTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
WebCryptoCipherMode cipher_mode,
RSACipherConfig* params) {
Environment* env = Environment::GetCurrent(args);
params->mode = mode;
params->padding = RSA_PKCS1_OAEP_PADDING;
CHECK(args[offset]->IsUint32());
RSAKeyVariant variant =
static_cast<RSAKeyVariant>(args[offset].As<Uint32>()->Value());
switch (variant) {
case kKeyVariantRSA_OAEP: {
CHECK(args[offset + 1]->IsString()); // digest
Utf8Value digest(env->isolate(), args[offset + 1]);
params->digest = EVP_get_digestbyname(*digest);
if (params->digest == nullptr) {
THROW_ERR_CRYPTO_INVALID_DIGEST(env);
return Nothing<bool>();
}
if (IsAnyByteSource(args[offset + 2])) {
ArrayBufferOrViewContents<char> label(args[offset + 2]);
if (UNLIKELY(!label.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "label is too big");
return Nothing<bool>();
}
params->label = label.ToCopy();
}
break;
}
default:
THROW_ERR_CRYPTO_INVALID_KEYTYPE(env);
return Nothing<bool>();
}
return Just(true);
}
WebCryptoCipherStatus RSACipherTraits::DoCipher(
Environment* env,
std::shared_ptr<KeyObjectData> key_data,
WebCryptoCipherMode cipher_mode,
const RSACipherConfig& params,
const ByteSource& in,
ByteSource* out) {
switch (cipher_mode) {
case kWebCryptoCipherEncrypt:
CHECK_EQ(key_data->GetKeyType(), kKeyTypePublic);
return RSA_Cipher<EVP_PKEY_encrypt_init, EVP_PKEY_encrypt>(
env, key_data.get(), params, in, out);
case kWebCryptoCipherDecrypt:
CHECK_EQ(key_data->GetKeyType(), kKeyTypePrivate);
return RSA_Cipher<EVP_PKEY_decrypt_init, EVP_PKEY_decrypt>(
env, key_data.get(), params, in, out);
}
return WebCryptoCipherStatus::ERR_FAILED;
}
Maybe<bool> ExportJWKRsaKey(
Environment* env,
std::shared_ptr<KeyObjectData> key,
Local<Object> target) {
ManagedEVPPKey pkey = key->GetAsymmetricKey();
int type = EVP_PKEY_id(pkey.get());
CHECK(type == EVP_PKEY_RSA || type == EVP_PKEY_RSA_PSS);
RSA* rsa = EVP_PKEY_get0_RSA(pkey.get());
CHECK_NOT_NULL(rsa);
const BIGNUM* n;
const BIGNUM* e;
const BIGNUM* d;
const BIGNUM* p;
const BIGNUM* q;
const BIGNUM* dp;
const BIGNUM* dq;
const BIGNUM* qi;
RSA_get0_key(rsa, &n, &e, &d);
if (target->Set(
env->context(),
env->jwk_kty_string(),
env->jwk_rsa_string()).IsNothing()) {
return Nothing<bool>();
}
if (SetEncodedValue(env, target, env->jwk_n_string(), n).IsNothing() ||
SetEncodedValue(env, target, env->jwk_e_string(), e).IsNothing()) {
return Nothing<bool>();
}
if (key->GetKeyType() == kKeyTypePrivate) {
RSA_get0_factors(rsa, &p, &q);
RSA_get0_crt_params(rsa, &dp, &dq, &qi);
if (SetEncodedValue(env, target, env->jwk_d_string(), d).IsNothing() ||
SetEncodedValue(env, target, env->jwk_p_string(), p).IsNothing() ||
SetEncodedValue(env, target, env->jwk_q_string(), q).IsNothing() ||
SetEncodedValue(env, target, env->jwk_dp_string(), dp).IsNothing() ||
SetEncodedValue(env, target, env->jwk_dq_string(), dq).IsNothing() ||
SetEncodedValue(env, target, env->jwk_qi_string(), qi).IsNothing()) {
return Nothing<bool>();
}
}
return Just(true);
}
std::shared_ptr<KeyObjectData> ImportJWKRsaKey(
Environment* env,
Local<Object> jwk,
const FunctionCallbackInfo<Value>& args,
unsigned int offset) {
Local<Value> n_value;
Local<Value> e_value;
Local<Value> d_value;
if (!jwk->Get(env->context(), env->jwk_n_string()).ToLocal(&n_value) ||
!jwk->Get(env->context(), env->jwk_e_string()).ToLocal(&e_value) ||
!jwk->Get(env->context(), env->jwk_d_string()).ToLocal(&d_value) ||
!n_value->IsString() ||
!e_value->IsString()) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JSK RSA key");
return std::shared_ptr<KeyObjectData>();
}
if (!d_value->IsUndefined() && !d_value->IsString()) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JSK RSA key");
return std::shared_ptr<KeyObjectData>();
}
KeyType type = d_value->IsString() ? kKeyTypePrivate : kKeyTypePublic;
RsaPointer rsa(RSA_new());
ByteSource n = ByteSource::FromEncodedString(env, n_value.As<String>());
ByteSource e = ByteSource::FromEncodedString(env, e_value.As<String>());
if (!RSA_set0_key(
rsa.get(),
n.ToBN().release(),
e.ToBN().release(),
nullptr)) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JSK RSA key");
return std::shared_ptr<KeyObjectData>();
}
if (type == kKeyTypePrivate) {
Local<Value> p_value;
Local<Value> q_value;
Local<Value> dp_value;
Local<Value> dq_value;
Local<Value> qi_value;
if (!jwk->Get(env->context(), env->jwk_p_string()).ToLocal(&p_value) ||
!jwk->Get(env->context(), env->jwk_q_string()).ToLocal(&q_value) ||
!jwk->Get(env->context(), env->jwk_dp_string()).ToLocal(&dp_value) ||
!jwk->Get(env->context(), env->jwk_dq_string()).ToLocal(&dq_value) ||
!jwk->Get(env->context(), env->jwk_qi_string()).ToLocal(&qi_value)) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JSK RSA key");
return std::shared_ptr<KeyObjectData>();
}
if (!p_value->IsString() ||
!q_value->IsString() ||
!dp_value->IsString() ||
!dq_value->IsString() ||
!qi_value->IsString()) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JSK RSA key");
return std::shared_ptr<KeyObjectData>();
}
ByteSource d = ByteSource::FromEncodedString(env, d_value.As<String>());
ByteSource q = ByteSource::FromEncodedString(env, q_value.As<String>());
ByteSource p = ByteSource::FromEncodedString(env, p_value.As<String>());
ByteSource dp = ByteSource::FromEncodedString(env, dp_value.As<String>());
ByteSource dq = ByteSource::FromEncodedString(env, dq_value.As<String>());
ByteSource qi = ByteSource::FromEncodedString(env, qi_value.As<String>());
if (!RSA_set0_key(rsa.get(), nullptr, nullptr, d.ToBN().release()) ||
!RSA_set0_factors(rsa.get(), p.ToBN().release(), q.ToBN().release()) ||
!RSA_set0_crt_params(
rsa.get(),
dp.ToBN().release(),
dq.ToBN().release(),
qi.ToBN().release())) {
THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JSK RSA key");
return std::shared_ptr<KeyObjectData>();
}
}
EVPKeyPointer pkey(EVP_PKEY_new());
CHECK_EQ(EVP_PKEY_set1_RSA(pkey.get(), rsa.get()), 1);
return KeyObjectData::CreateAsymmetric(type, ManagedEVPPKey(std::move(pkey)));
}
Maybe<bool> GetRsaKeyDetail(
Environment* env,
std::shared_ptr<KeyObjectData> key,
Local<Object> target) {
const BIGNUM* e; // Public Exponent
const BIGNUM* n; // Modulus
ManagedEVPPKey pkey = key->GetAsymmetricKey();
int type = EVP_PKEY_id(pkey.get());
CHECK(type == EVP_PKEY_RSA || type == EVP_PKEY_RSA_PSS);
RSA* rsa = EVP_PKEY_get0_RSA(pkey.get());
CHECK_NOT_NULL(rsa);
RSA_get0_key(rsa, &n, &e, nullptr);
size_t modulus_length = BN_num_bytes(n) * CHAR_BIT;
if (target->Set(
env->context(),
env->modulus_length_string(),
Number::New(env->isolate(), modulus_length)).IsNothing()) {
return Nothing<bool>();
}
int len = BN_num_bytes(e);
AllocatedBuffer public_exponent = AllocatedBuffer::AllocateManaged(env, len);
unsigned char* data =
reinterpret_cast<unsigned char*>(public_exponent.data());
CHECK_EQ(BN_bn2binpad(e, data, len), len);
return target->Set(
env->context(),
env->public_exponent_string(),
public_exponent.ToArrayBuffer());
}
namespace RSAAlg {
void Initialize(Environment* env, Local<Object> target) {
RSAKeyPairGenJob::Initialize(env, target);
RSAKeyExportJob::Initialize(env, target);
RSACipherJob::Initialize(env, target);
NODE_DEFINE_CONSTANT(target, kKeyVariantRSA_SSA_PKCS1_V1_5);
NODE_DEFINE_CONSTANT(target, kKeyVariantRSA_PSS);
NODE_DEFINE_CONSTANT(target, kKeyVariantRSA_OAEP);
}
} // namespace RSAAlg
} // namespace crypto
} // namespace node

140
src/crypto/crypto_rsa.h Normal file
View File

@ -0,0 +1,140 @@
#ifndef SRC_CRYPTO_CRYPTO_RSA_H_
#define SRC_CRYPTO_CRYPTO_RSA_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_cipher.h"
#include "crypto/crypto_keygen.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
namespace node {
namespace crypto {
enum RSAKeyVariant {
kKeyVariantRSA_SSA_PKCS1_V1_5,
kKeyVariantRSA_PSS,
kKeyVariantRSA_OAEP
};
struct RsaKeyPairParams final : public MemoryRetainer {
RSAKeyVariant variant;
unsigned int modulus_bits;
unsigned int exponent;
// The following used for RSA-PSS
const EVP_MD* md = nullptr;
const EVP_MD* mgf1_md = nullptr;
int saltlen = 0;
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(RsaKeyPairParams);
SET_SELF_SIZE(RsaKeyPairParams);
};
using RsaKeyPairGenConfig = KeyPairGenConfig<RsaKeyPairParams>;
struct RsaKeyGenTraits final {
using AdditionalParameters = RsaKeyPairGenConfig;
static constexpr const char* JobName = "RsaKeyPairGenJob";
static EVPKeyCtxPointer Setup(RsaKeyPairGenConfig* params);
static v8::Maybe<bool> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int* offset,
RsaKeyPairGenConfig* params);
};
using RSAKeyPairGenJob = KeyGenJob<KeyPairGenTraits<RsaKeyGenTraits>>;
struct RSAKeyExportConfig final : public MemoryRetainer {
RSAKeyVariant variant = kKeyVariantRSA_SSA_PKCS1_V1_5;
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(RSAKeyExportConfig)
SET_SELF_SIZE(RSAKeyExportConfig)
};
struct RSAKeyExportTraits final {
static constexpr const char* JobName = "RSAKeyExportJob";
using AdditionalParameters = RSAKeyExportConfig;
static v8::Maybe<bool> AdditionalConfig(
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
RSAKeyExportConfig* config);
static WebCryptoKeyExportStatus DoExport(
std::shared_ptr<KeyObjectData> key_data,
WebCryptoKeyFormat format,
const RSAKeyExportConfig& params,
ByteSource* out);
};
using RSAKeyExportJob = KeyExportJob<RSAKeyExportTraits>;
struct RSACipherConfig final : public MemoryRetainer {
CryptoJobMode mode;
ByteSource label;
int padding = 0;
const EVP_MD* digest = nullptr;
RSACipherConfig() = default;
RSACipherConfig(RSACipherConfig&& other) noexcept;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(RSACipherConfig);
SET_SELF_SIZE(RSACipherConfig);
};
struct RSACipherTraits final {
static constexpr const char* JobName = "RSACipherJob";
using AdditionalParameters = RSACipherConfig;
static v8::Maybe<bool> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
WebCryptoCipherMode cipher_mode,
RSACipherConfig* config);
static WebCryptoCipherStatus DoCipher(
Environment* env,
std::shared_ptr<KeyObjectData> key_data,
WebCryptoCipherMode cipher_mode,
const RSACipherConfig& params,
const ByteSource& in,
ByteSource* out);
};
using RSACipherJob = CipherJob<RSACipherTraits>;
v8::Maybe<bool> ExportJWKRsaKey(
Environment* env,
std::shared_ptr<KeyObjectData> key,
v8::Local<v8::Object> target);
std::shared_ptr<KeyObjectData> ImportJWKRsaKey(
Environment* env,
v8::Local<v8::Object> jwk,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset);
v8::Maybe<bool> GetRsaKeyDetail(
Environment* env,
std::shared_ptr<KeyObjectData> key,
v8::Local<v8::Object> target);
namespace RSAAlg {
void Initialize(Environment* env, v8::Local<v8::Object> target);
} // namespace RSAAlg
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_RSA_H_

153
src/crypto/crypto_scrypt.cc Normal file
View File

@ -0,0 +1,153 @@
#include "crypto/crypto_scrypt.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer-inl.h"
#include "async_wrap-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node_buffer.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
namespace node {
using v8::FunctionCallbackInfo;
using v8::Int32;
using v8::Just;
using v8::Maybe;
using v8::Nothing;
using v8::Uint32;
using v8::Value;
namespace crypto {
#ifndef OPENSSL_NO_SCRYPT
ScryptConfig::ScryptConfig(ScryptConfig&& other) noexcept
: mode(other.mode),
pass(std::move(other.pass)),
salt(std::move(other.salt)),
N(other.N),
r(other.r),
p(other.p),
length(other.length) {}
ScryptConfig& ScryptConfig::operator=(ScryptConfig&& other) noexcept {
if (&other == this) return *this;
this->~ScryptConfig();
return *new (this) ScryptConfig(std::move(other));
}
void ScryptConfig::MemoryInfo(MemoryTracker* tracker) const {
if (mode == kCryptoJobAsync) {
tracker->TrackFieldWithSize("pass", pass.size());
tracker->TrackFieldWithSize("salt", salt.size());
}
}
Maybe<bool> ScryptTraits::EncodeOutput(
Environment* env,
const ScryptConfig& params,
ByteSource* out,
v8::Local<v8::Value>* result) {
*result = out->ToArrayBuffer(env);
return Just(!result->IsEmpty());
}
Maybe<bool> ScryptTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
ScryptConfig* params) {
Environment* env = Environment::GetCurrent(args);
params->mode = mode;
ArrayBufferOrViewContents<char> pass(args[offset]);
ArrayBufferOrViewContents<char> salt(args[offset + 1]);
if (UNLIKELY(!pass.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "pass is too large");
return Nothing<bool>();
}
if (UNLIKELY(!salt.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "salt is too large");
return Nothing<bool>();
}
params->pass = mode == kCryptoJobAsync
? pass.ToCopy()
: pass.ToByteSource();
params->salt = mode == kCryptoJobAsync
? salt.ToCopy()
: salt.ToByteSource();
CHECK(args[offset + 2]->IsUint32()); // N
CHECK(args[offset + 3]->IsUint32()); // r
CHECK(args[offset + 4]->IsUint32()); // p
CHECK(args[offset + 5]->IsNumber()); // maxmem
CHECK(args[offset + 6]->IsInt32()); // length
params->N = args[offset + 2].As<Uint32>()->Value();
params->r = args[offset + 3].As<Uint32>()->Value();
params->p = args[offset + 4].As<Uint32>()->Value();
params->maxmem = args[offset + 5]->IntegerValue(env->context()).ToChecked();
if (EVP_PBE_scrypt(
nullptr,
0,
nullptr,
0,
params->N,
params->r,
params->p,
params->maxmem,
nullptr,
0) != 1) {
THROW_ERR_CRYPTO_INVALID_SCRYPT_PARAMS(env);
return Nothing<bool>();
}
params->length = args[offset + 6].As<Int32>()->Value();
if (params->length < 0) {
char msg[1024];
snprintf(msg, sizeof(msg), "length must be <= %d", INT_MAX);
THROW_ERR_OUT_OF_RANGE(env, msg);
return Nothing<bool>();
}
return Just(true);
}
bool ScryptTraits::DeriveBits(
Environment* env,
const ScryptConfig& params,
ByteSource* out) {
char* data = MallocOpenSSL<char>(params.length);
ByteSource buf = ByteSource::Allocated(data, params.length);
unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
// Botht the pass and salt may be zero-length at this point
if (!EVP_PBE_scrypt(
params.pass.get(),
params.pass.size(),
params.salt.data<unsigned char>(),
params.salt.size(),
params.N,
params.r,
params.p,
params.maxmem,
ptr,
params.length)) {
return false;
}
*out = std::move(buf);
return true;
}
#endif // !OPENSSL_NO_SCRYPT
} // namespace crypto
} // namespace node

View File

@ -0,0 +1,87 @@
#ifndef SRC_CRYPTO_CRYPTO_SCRYPT_H_
#define SRC_CRYPTO_CRYPTO_SCRYPT_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_util.h"
#include "env.h"
#include "memory_tracker.h"
#include "v8.h"
namespace node {
namespace crypto {
#ifndef OPENSSL_NO_SCRYPT
// Scrypt is a password-based key derivation algorithm
// defined in https://tools.ietf.org/html/rfc7914
// It takes as input a password, a salt value, and a
// handful of additional parameters that control the
// cost of the operation. In this case, the higher
// the cost, the better the result. The length parameter
// defines the number of bytes that are generated.
// The salt must be as random as possible and should be
// at least 16 bytes in length.
struct ScryptConfig final : public MemoryRetainer {
CryptoJobMode mode;
ByteSource pass;
ByteSource salt;
uint32_t N;
uint32_t r;
uint32_t p;
uint64_t maxmem;
int32_t length;
ScryptConfig() = default;
explicit ScryptConfig(ScryptConfig&& other) noexcept;
ScryptConfig& operator=(ScryptConfig&& other) noexcept;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(ScryptConfig);
SET_SELF_SIZE(ScryptConfig);
};
struct ScryptTraits final {
using AdditionalParameters = ScryptConfig;
static constexpr const char* JobName = "ScryptJob";
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_SCRYPTREQUEST;
static v8::Maybe<bool> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
ScryptConfig* params);
static bool DeriveBits(
Environment* env,
const ScryptConfig& params,
ByteSource* out);
static v8::Maybe<bool> EncodeOutput(
Environment* env,
const ScryptConfig& params,
ByteSource* out,
v8::Local<v8::Value>* result);
};
using ScryptJob = DeriveBitsJob<ScryptTraits>;
#else
// If there is no Scrypt support, ScryptJob becomes a non-op
struct ScryptJob {
static void Initialize(
Environment* env,
v8::Local<v8::Object> target) {}
}
#endif // !OPENSSL_NO_SCRIPT
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_SCRYPT_H_

881
src/crypto/crypto_sig.cc Normal file
View File

@ -0,0 +1,881 @@
#include "crypto/crypto_sig.h"
#include "crypto/crypto_ecdh.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer-inl.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
namespace node {
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Int32;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::Nothing;
using v8::Object;
using v8::Uint32;
using v8::Value;
namespace crypto {
namespace {
bool ValidateDSAParameters(EVP_PKEY* key) {
#ifdef NODE_FIPS_MODE
/* Validate DSA2 parameters from FIPS 186-4 */
if (FIPS_mode() && EVP_PKEY_DSA == EVP_PKEY_base_id(key)) {
DSA* dsa = EVP_PKEY_get0_DSA(key);
const BIGNUM* p;
DSA_get0_pqg(dsa, &p, nullptr, nullptr);
size_t L = BN_num_bits(p);
const BIGNUM* q;
DSA_get0_pqg(dsa, nullptr, &q, nullptr);
size_t N = BN_num_bits(q);
return (L == 1024 && N == 160) ||
(L == 2048 && N == 224) ||
(L == 2048 && N == 256) ||
(L == 3072 && N == 256);
}
#endif // NODE_FIPS_MODE
return true;
}
bool ApplyRSAOptions(const ManagedEVPPKey& pkey,
EVP_PKEY_CTX* pkctx,
int padding,
const Maybe<int>& salt_len) {
if (EVP_PKEY_id(pkey.get()) == EVP_PKEY_RSA ||
EVP_PKEY_id(pkey.get()) == EVP_PKEY_RSA2 ||
EVP_PKEY_id(pkey.get()) == EVP_PKEY_RSA_PSS) {
if (EVP_PKEY_CTX_set_rsa_padding(pkctx, padding) <= 0)
return false;
if (padding == RSA_PKCS1_PSS_PADDING && salt_len.IsJust()) {
if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkctx, salt_len.FromJust()) <= 0)
return false;
}
}
return true;
}
AllocatedBuffer Node_SignFinal(Environment* env,
EVPMDPointer&& mdctx,
const ManagedEVPPKey& pkey,
int padding,
Maybe<int> pss_salt_len) {
unsigned char m[EVP_MAX_MD_SIZE];
unsigned int m_len;
if (!EVP_DigestFinal_ex(mdctx.get(), m, &m_len))
return AllocatedBuffer();
int signed_sig_len = EVP_PKEY_size(pkey.get());
CHECK_GE(signed_sig_len, 0);
size_t sig_len = static_cast<size_t>(signed_sig_len);
AllocatedBuffer sig = AllocatedBuffer::AllocateManaged(env, sig_len);
unsigned char* ptr = reinterpret_cast<unsigned char*>(sig.data());
EVPKeyCtxPointer pkctx(EVP_PKEY_CTX_new(pkey.get(), nullptr));
if (pkctx &&
EVP_PKEY_sign_init(pkctx.get()) &&
ApplyRSAOptions(pkey, pkctx.get(), padding, pss_salt_len) &&
EVP_PKEY_CTX_set_signature_md(pkctx.get(), EVP_MD_CTX_md(mdctx.get())) &&
EVP_PKEY_sign(pkctx.get(), ptr, &sig_len, m, m_len)) {
sig.Resize(sig_len);
return sig;
}
return AllocatedBuffer();
}
int GetDefaultSignPadding(const ManagedEVPPKey& key) {
return EVP_PKEY_id(key.get()) == EVP_PKEY_RSA_PSS ? RSA_PKCS1_PSS_PADDING :
RSA_PKCS1_PADDING;
}
unsigned int GetBytesOfRS(const ManagedEVPPKey& pkey) {
int bits, base_id = EVP_PKEY_base_id(pkey.get());
if (base_id == EVP_PKEY_DSA) {
DSA* dsa_key = EVP_PKEY_get0_DSA(pkey.get());
// Both r and s are computed mod q, so their width is limited by that of q.
bits = BN_num_bits(DSA_get0_q(dsa_key));
} else if (base_id == EVP_PKEY_EC) {
EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(pkey.get());
const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key);
bits = EC_GROUP_order_bits(ec_group);
} else {
return kNoDsaSignature;
}
return (bits + 7) / 8;
}
// Returns the maximum size of each of the integers (r, s) of the DSA signature.
AllocatedBuffer ConvertSignatureToP1363(Environment* env,
const ManagedEVPPKey& pkey,
AllocatedBuffer&& signature) {
unsigned int n = GetBytesOfRS(pkey);
if (n == kNoDsaSignature)
return std::move(signature);
const unsigned char* sig_data =
reinterpret_cast<unsigned char*>(signature.data());
ECDSASigPointer asn1_sig(d2i_ECDSA_SIG(nullptr, &sig_data, signature.size()));
if (!asn1_sig)
return AllocatedBuffer();
AllocatedBuffer buf = AllocatedBuffer::AllocateManaged(env, 2 * n);
unsigned char* data = reinterpret_cast<unsigned char*>(buf.data());
const BIGNUM* r = ECDSA_SIG_get0_r(asn1_sig.get());
const BIGNUM* s = ECDSA_SIG_get0_s(asn1_sig.get());
CHECK_EQ(n, static_cast<unsigned int>(BN_bn2binpad(r, data, n)));
CHECK_EQ(n, static_cast<unsigned int>(BN_bn2binpad(s, data + n, n)));
return buf;
}
ByteSource ConvertSignatureToDER(
const ManagedEVPPKey& pkey,
const ArrayBufferOrViewContents<char>& signature) {
unsigned int n = GetBytesOfRS(pkey);
if (n == kNoDsaSignature)
return signature.ToByteSource();
const unsigned char* sig_data =
reinterpret_cast<const unsigned char*>(signature.data());
if (signature.size() != 2 * n)
return ByteSource();
ECDSASigPointer asn1_sig(ECDSA_SIG_new());
CHECK(asn1_sig);
BIGNUM* r = BN_new();
CHECK_NOT_NULL(r);
BIGNUM* s = BN_new();
CHECK_NOT_NULL(s);
CHECK_EQ(r, BN_bin2bn(sig_data, n, r));
CHECK_EQ(s, BN_bin2bn(sig_data + n, n, s));
CHECK_EQ(1, ECDSA_SIG_set0(asn1_sig.get(), r, s));
unsigned char* data = nullptr;
int len = i2d_ECDSA_SIG(asn1_sig.get(), &data);
if (len <= 0)
return ByteSource();
CHECK_NOT_NULL(data);
return ByteSource::Allocated(reinterpret_cast<char*>(data), len);
}
void CheckThrow(Environment* env, SignBase::Error error) {
HandleScope scope(env->isolate());
switch (error) {
case SignBase::Error::kSignUnknownDigest:
return THROW_ERR_CRYPTO_INVALID_DIGEST(env);
case SignBase::Error::kSignNotInitialised:
return THROW_ERR_CRYPTO_INVALID_STATE(env, "Not initialised");
case SignBase::Error::kSignMalformedSignature:
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Malformed signature");
case SignBase::Error::kSignInit:
case SignBase::Error::kSignUpdate:
case SignBase::Error::kSignPrivateKey:
case SignBase::Error::kSignPublicKey:
{
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
if (err)
return ThrowCryptoError(env, err);
switch (error) {
case SignBase::Error::kSignInit:
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"EVP_SignInit_ex failed");
case SignBase::Error::kSignUpdate:
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"EVP_SignUpdate failed");
case SignBase::Error::kSignPrivateKey:
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"PEM_read_bio_PrivateKey failed");
case SignBase::Error::kSignPublicKey:
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"PEM_read_bio_PUBKEY failed");
default:
ABORT();
}
}
case SignBase::Error::kSignOk:
return;
}
}
} // namespace
SignBase::Error SignBase::Init(const char* sign_type) {
CHECK_NULL(mdctx_);
// Historically, "dss1" and "DSS1" were DSA aliases for SHA-1
// exposed through the public API.
if (strcmp(sign_type, "dss1") == 0 ||
strcmp(sign_type, "DSS1") == 0) {
sign_type = "SHA1";
}
const EVP_MD* md = EVP_get_digestbyname(sign_type);
if (md == nullptr)
return kSignUnknownDigest;
mdctx_.reset(EVP_MD_CTX_new());
if (!mdctx_ || !EVP_DigestInit_ex(mdctx_.get(), md, nullptr)) {
mdctx_.reset();
return kSignInit;
}
return kSignOk;
}
SignBase::Error SignBase::Update(const char* data, size_t len) {
if (mdctx_ == nullptr)
return kSignNotInitialised;
if (!EVP_DigestUpdate(mdctx_.get(), data, len))
return kSignUpdate;
return kSignOk;
}
SignBase::SignBase(Environment* env, Local<Object> wrap)
: BaseObject(env, wrap) {}
void SignBase::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("mdctx", mdctx_ ? kSizeOf_EVP_MD_CTX : 0);
}
Sign::Sign(Environment* env, Local<Object> wrap) : SignBase(env, wrap) {
MakeWeak();
}
void Sign::Initialize(Environment* env, Local<Object> target) {
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
t->InstanceTemplate()->SetInternalFieldCount(
SignBase::kInternalFieldCount);
t->Inherit(BaseObject::GetConstructorTemplate(env));
env->SetProtoMethod(t, "init", SignInit);
env->SetProtoMethod(t, "update", SignUpdate);
env->SetProtoMethod(t, "sign", SignFinal);
target->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "Sign"),
t->GetFunction(env->context()).ToLocalChecked()).Check();
env->SetMethod(target, "signOneShot", Sign::SignSync);
SignJob::Initialize(env, target);
constexpr int kSignJobModeSign = SignConfiguration::kSign;
constexpr int kSignJobModeVerify = SignConfiguration::kVerify;
NODE_DEFINE_CONSTANT(target, kSignJobModeSign);
NODE_DEFINE_CONSTANT(target, kSignJobModeVerify);
NODE_DEFINE_CONSTANT(target, RSA_PKCS1_PSS_PADDING);
}
void Sign::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
new Sign(env, args.This());
}
void Sign::SignInit(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Sign* sign;
ASSIGN_OR_RETURN_UNWRAP(&sign, args.Holder());
const node::Utf8Value sign_type(args.GetIsolate(), args[0]);
crypto::CheckThrow(env, sign->Init(*sign_type));
}
void Sign::SignUpdate(const FunctionCallbackInfo<Value>& args) {
Decode<Sign>(args, [](Sign* sign, const FunctionCallbackInfo<Value>& args,
const char* data, size_t size) {
Environment* env = Environment::GetCurrent(args);
if (UNLIKELY(size > INT_MAX))
return THROW_ERR_OUT_OF_RANGE(env, "data is too long");
Error err = sign->Update(data, size);
crypto::CheckThrow(sign->env(), err);
});
}
Sign::SignResult Sign::SignFinal(
const ManagedEVPPKey& pkey,
int padding,
const Maybe<int>& salt_len,
DSASigEnc dsa_sig_enc) {
if (!mdctx_)
return SignResult(kSignNotInitialised);
EVPMDPointer mdctx = std::move(mdctx_);
if (!ValidateDSAParameters(pkey.get()))
return SignResult(kSignPrivateKey);
AllocatedBuffer buffer =
Node_SignFinal(env(), std::move(mdctx), pkey, padding, salt_len);
Error error = buffer.data() == nullptr ? kSignPrivateKey : kSignOk;
if (error == kSignOk && dsa_sig_enc == kSigEncP1363) {
buffer = ConvertSignatureToP1363(env(), pkey, std::move(buffer));
CHECK_NOT_NULL(buffer.data());
}
return SignResult(error, std::move(buffer));
}
void Sign::SignFinal(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Sign* sign;
ASSIGN_OR_RETURN_UNWRAP(&sign, args.Holder());
ClearErrorOnReturn clear_error_on_return;
unsigned int offset = 0;
ManagedEVPPKey key = ManagedEVPPKey::GetPrivateKeyFromJs(args, &offset, true);
if (!key)
return;
int padding = GetDefaultSignPadding(key);
if (!args[offset]->IsUndefined()) {
CHECK(args[offset]->IsInt32());
padding = args[offset].As<Int32>()->Value();
}
Maybe<int> salt_len = Nothing<int>();
if (!args[offset + 1]->IsUndefined()) {
CHECK(args[offset + 1]->IsInt32());
salt_len = Just<int>(args[offset + 1].As<Int32>()->Value());
}
CHECK(args[offset + 2]->IsInt32());
DSASigEnc dsa_sig_enc =
static_cast<DSASigEnc>(args[offset + 2].As<Int32>()->Value());
SignResult ret = sign->SignFinal(
key,
padding,
salt_len,
dsa_sig_enc);
if (ret.error != kSignOk)
return crypto::CheckThrow(env, ret.error);
args.GetReturnValue().Set(ret.signature.ToBuffer().ToLocalChecked());
}
Verify::Verify(Environment* env, Local<Object> wrap)
: SignBase(env, wrap) {
MakeWeak();
}
void Verify::Initialize(Environment* env, Local<Object> target) {
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
t->InstanceTemplate()->SetInternalFieldCount(
SignBase::kInternalFieldCount);
t->Inherit(BaseObject::GetConstructorTemplate(env));
env->SetProtoMethod(t, "init", VerifyInit);
env->SetProtoMethod(t, "update", VerifyUpdate);
env->SetProtoMethod(t, "verify", VerifyFinal);
target->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "Verify"),
t->GetFunction(env->context()).ToLocalChecked()).Check();
env->SetMethod(target, "verifyOneShot", Verify::VerifySync);
}
void Verify::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
new Verify(env, args.This());
}
void Verify::VerifyInit(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Verify* verify;
ASSIGN_OR_RETURN_UNWRAP(&verify, args.Holder());
const node::Utf8Value verify_type(args.GetIsolate(), args[0]);
crypto::CheckThrow(env, verify->Init(*verify_type));
}
void Verify::VerifyUpdate(const FunctionCallbackInfo<Value>& args) {
Decode<Verify>(args, [](Verify* verify,
const FunctionCallbackInfo<Value>& args,
const char* data, size_t size) {
Environment* env = Environment::GetCurrent(args);
if (UNLIKELY(size > INT_MAX))
return THROW_ERR_OUT_OF_RANGE(env, "data is too long");
Error err = verify->Update(data, size);
crypto::CheckThrow(verify->env(), err);
});
}
SignBase::Error Verify::VerifyFinal(const ManagedEVPPKey& pkey,
const ByteSource& sig,
int padding,
const Maybe<int>& saltlen,
bool* verify_result) {
if (!mdctx_)
return kSignNotInitialised;
unsigned char m[EVP_MAX_MD_SIZE];
unsigned int m_len;
*verify_result = false;
EVPMDPointer mdctx = std::move(mdctx_);
if (!EVP_DigestFinal_ex(mdctx.get(), m, &m_len))
return kSignPublicKey;
EVPKeyCtxPointer pkctx(EVP_PKEY_CTX_new(pkey.get(), nullptr));
if (pkctx &&
EVP_PKEY_verify_init(pkctx.get()) > 0 &&
ApplyRSAOptions(pkey, pkctx.get(), padding, saltlen) &&
EVP_PKEY_CTX_set_signature_md(pkctx.get(),
EVP_MD_CTX_md(mdctx.get())) > 0) {
const unsigned char* s = reinterpret_cast<const unsigned char*>(sig.get());
const int r = EVP_PKEY_verify(pkctx.get(), s, sig.size(), m, m_len);
*verify_result = r == 1;
}
return kSignOk;
}
void Verify::VerifyFinal(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ClearErrorOnReturn clear_error_on_return;
Verify* verify;
ASSIGN_OR_RETURN_UNWRAP(&verify, args.Holder());
unsigned int offset = 0;
ManagedEVPPKey pkey =
ManagedEVPPKey::GetPublicOrPrivateKeyFromJs(args, &offset);
if (!pkey)
return;
ArrayBufferOrViewContents<char> hbuf(args[offset]);
if (UNLIKELY(!hbuf.CheckSizeInt32()))
return THROW_ERR_OUT_OF_RANGE(env, "buffer is too big");
int padding = GetDefaultSignPadding(pkey);
if (!args[offset + 1]->IsUndefined()) {
CHECK(args[offset + 1]->IsInt32());
padding = args[offset + 1].As<Int32>()->Value();
}
Maybe<int> salt_len = Nothing<int>();
if (!args[offset + 2]->IsUndefined()) {
CHECK(args[offset + 2]->IsInt32());
salt_len = Just<int>(args[offset + 2].As<Int32>()->Value());
}
CHECK(args[offset + 3]->IsInt32());
DSASigEnc dsa_sig_enc =
static_cast<DSASigEnc>(args[offset + 3].As<Int32>()->Value());
ByteSource signature = hbuf.ToByteSource();
if (dsa_sig_enc == kSigEncP1363) {
signature = ConvertSignatureToDER(pkey, hbuf);
if (signature.get() == nullptr)
return crypto::CheckThrow(env, Error::kSignMalformedSignature);
}
bool verify_result;
Error err = verify->VerifyFinal(pkey, signature, padding,
salt_len, &verify_result);
if (err != kSignOk)
return crypto::CheckThrow(env, err);
args.GetReturnValue().Set(verify_result);
}
void Sign::SignSync(const FunctionCallbackInfo<Value>& args) {
ClearErrorOnReturn clear_error_on_return;
Environment* env = Environment::GetCurrent(args);
unsigned int offset = 0;
ManagedEVPPKey key = ManagedEVPPKey::GetPrivateKeyFromJs(args, &offset, true);
if (!key)
return;
if (!ValidateDSAParameters(key.get()))
return crypto::CheckThrow(env, SignBase::Error::kSignPrivateKey);
ArrayBufferOrViewContents<char> data(args[offset]);
if (UNLIKELY(!data.CheckSizeInt32()))
return THROW_ERR_OUT_OF_RANGE(env, "data is too big");
const EVP_MD* md;
if (args[offset + 1]->IsNullOrUndefined()) {
md = nullptr;
} else {
const node::Utf8Value sign_type(args.GetIsolate(), args[offset + 1]);
md = EVP_get_digestbyname(*sign_type);
if (md == nullptr)
return crypto::CheckThrow(env, SignBase::Error::kSignUnknownDigest);
}
int rsa_padding = GetDefaultSignPadding(key);
if (!args[offset + 2]->IsUndefined()) {
CHECK(args[offset + 2]->IsInt32());
rsa_padding = args[offset + 2].As<Int32>()->Value();
}
Maybe<int> rsa_salt_len = Nothing<int>();
if (!args[offset + 3]->IsUndefined()) {
CHECK(args[offset + 3]->IsInt32());
rsa_salt_len = Just<int>(args[offset + 3].As<Int32>()->Value());
}
CHECK(args[offset + 4]->IsInt32());
DSASigEnc dsa_sig_enc =
static_cast<DSASigEnc>(args[offset + 4].As<Int32>()->Value());
EVP_PKEY_CTX* pkctx = nullptr;
EVPMDPointer mdctx(EVP_MD_CTX_new());
if (!mdctx ||
!EVP_DigestSignInit(mdctx.get(), &pkctx, md, nullptr, key.get())) {
return crypto::CheckThrow(env, SignBase::Error::kSignInit);
}
if (!ApplyRSAOptions(key, pkctx, rsa_padding, rsa_salt_len))
return crypto::CheckThrow(env, SignBase::Error::kSignPrivateKey);
const unsigned char* input =
reinterpret_cast<const unsigned char*>(data.data());
size_t sig_len;
if (!EVP_DigestSign(mdctx.get(), nullptr, &sig_len, input, data.size()))
return crypto::CheckThrow(env, SignBase::Error::kSignPrivateKey);
AllocatedBuffer signature = AllocatedBuffer::AllocateManaged(env, sig_len);
if (!EVP_DigestSign(mdctx.get(),
reinterpret_cast<unsigned char*>(signature.data()),
&sig_len,
input,
data.size())) {
return crypto::CheckThrow(env, SignBase::Error::kSignPrivateKey);
}
signature.Resize(sig_len);
if (dsa_sig_enc == kSigEncP1363) {
signature = ConvertSignatureToP1363(env, key, std::move(signature));
}
args.GetReturnValue().Set(signature.ToBuffer().ToLocalChecked());
}
void Verify::VerifySync(const FunctionCallbackInfo<Value>& args) {
ClearErrorOnReturn clear_error_on_return;
Environment* env = Environment::GetCurrent(args);
unsigned int offset = 0;
ManagedEVPPKey key =
ManagedEVPPKey::GetPublicOrPrivateKeyFromJs(args, &offset);
if (!key)
return;
ArrayBufferOrViewContents<char> sig(args[offset]);
ArrayBufferOrViewContents<char> data(args[offset + 1]);
if (UNLIKELY(!sig.CheckSizeInt32()))
return THROW_ERR_OUT_OF_RANGE(env, "sig is too big");
if (UNLIKELY(!data.CheckSizeInt32()))
return THROW_ERR_OUT_OF_RANGE(env, "data is too big");
const EVP_MD* md;
if (args[offset + 2]->IsNullOrUndefined()) {
md = nullptr;
} else {
const node::Utf8Value sign_type(args.GetIsolate(), args[offset + 2]);
md = EVP_get_digestbyname(*sign_type);
if (md == nullptr)
return crypto::CheckThrow(env, SignBase::Error::kSignUnknownDigest);
}
int rsa_padding = GetDefaultSignPadding(key);
if (!args[offset + 3]->IsUndefined()) {
CHECK(args[offset + 3]->IsInt32());
rsa_padding = args[offset + 3].As<Int32>()->Value();
}
Maybe<int> rsa_salt_len = Nothing<int>();
if (!args[offset + 4]->IsUndefined()) {
CHECK(args[offset + 4]->IsInt32());
rsa_salt_len = Just<int>(args[offset + 4].As<Int32>()->Value());
}
CHECK(args[offset + 5]->IsInt32());
DSASigEnc dsa_sig_enc =
static_cast<DSASigEnc>(args[offset + 5].As<Int32>()->Value());
EVP_PKEY_CTX* pkctx = nullptr;
EVPMDPointer mdctx(EVP_MD_CTX_new());
if (!mdctx ||
!EVP_DigestVerifyInit(mdctx.get(), &pkctx, md, nullptr, key.get())) {
return crypto::CheckThrow(env, SignBase::Error::kSignInit);
}
if (!ApplyRSAOptions(key, pkctx, rsa_padding, rsa_salt_len))
return crypto::CheckThrow(env, SignBase::Error::kSignPublicKey);
ByteSource sig_bytes = ByteSource::Foreign(sig.data(), sig.size());
if (dsa_sig_enc == kSigEncP1363) {
sig_bytes = ConvertSignatureToDER(key, sig);
if (!sig_bytes)
return crypto::CheckThrow(env, SignBase::Error::kSignMalformedSignature);
}
bool verify_result;
const int r = EVP_DigestVerify(
mdctx.get(),
sig_bytes.data<unsigned char>(),
sig_bytes.size(),
reinterpret_cast<const unsigned char*>(data.data()),
data.size());
switch (r) {
case 1:
verify_result = true;
break;
case 0:
verify_result = false;
break;
default:
return crypto::CheckThrow(env, SignBase::Error::kSignPublicKey);
}
args.GetReturnValue().Set(verify_result);
}
SignConfiguration::SignConfiguration(SignConfiguration&& other) noexcept
: job_mode(other.job_mode),
mode(other.mode),
key(std::move(other.key)),
data(std::move(other.data)),
signature(std::move(other.signature)),
digest(other.digest),
flags(other.flags),
padding(other.padding),
salt_length(other.salt_length) {}
SignConfiguration& SignConfiguration::operator=(
SignConfiguration&& other) noexcept {
if (&other == this) return *this;
this->~SignConfiguration();
return *new (this) SignConfiguration(std::move(other));
}
void SignConfiguration::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("key", key.get());
if (job_mode == kCryptoJobAsync) {
tracker->TrackFieldWithSize("data", data.size());
tracker->TrackFieldWithSize("signature", signature.size());
}
}
Maybe<bool> SignTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
SignConfiguration* params) {
Environment* env = Environment::GetCurrent(args);
params->job_mode = mode;
CHECK(args[offset]->IsUint32()); // Sign Mode
CHECK(args[offset + 1]->IsObject()); // Key
params->mode =
static_cast<SignConfiguration::Mode>(args[offset].As<Uint32>()->Value());
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args[offset + 1], Nothing<bool>());
params->key = key->Data();
ArrayBufferOrViewContents<char> data(args[offset + 2]);
if (UNLIKELY(!data.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "data is too big");
return Nothing<bool>();
}
params->data = mode == kCryptoJobAsync
? data.ToCopy()
: data.ToByteSource();
if (args[offset + 3]->IsString()) {
Utf8Value digest(env->isolate(), args[offset + 3]);
params->digest = EVP_get_digestbyname(*digest);
if (params->digest == nullptr) {
THROW_ERR_CRYPTO_INVALID_DIGEST(env);
return Nothing<bool>();
}
}
if (args[offset + 4]->IsUint32()) { // Salt length
params->flags |= SignConfiguration::kHasSaltLength;
params->salt_length = args[offset + 4].As<Uint32>()->Value();
}
if (args[offset + 5]->IsUint32()) { // Padding
params->flags |= SignConfiguration::kHasPadding;
params->padding = args[offset + 5].As<Uint32>()->Value();
}
if (params->mode == SignConfiguration::kVerify) {
ArrayBufferOrViewContents<char> signature(args[offset + 6]);
if (UNLIKELY(!signature.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "signature is too big");
return Nothing<bool>();
}
// If this is an EC key (assuming ECDSA) we need to convert the
// the signature from WebCrypto format into DER format...
if (EVP_PKEY_id(params->key->GetAsymmetricKey().get()) == EVP_PKEY_EC) {
params->signature =
ConvertFromWebCryptoSignature(
params->key->GetAsymmetricKey(),
signature.ToByteSource());
} else {
params->signature = mode == kCryptoJobAsync
? signature.ToCopy()
: signature.ToByteSource();
}
}
return Just(true);
}
bool SignTraits::DeriveBits(
Environment* env,
const SignConfiguration& params,
ByteSource* out) {
EVPMDPointer context(EVP_MD_CTX_new());
EVP_PKEY_CTX* ctx = nullptr;
switch (params.mode) {
case SignConfiguration::kSign:
CHECK_EQ(params.key->GetKeyType(), kKeyTypePrivate);
if (!EVP_DigestSignInit(
context.get(),
&ctx,
params.digest,
nullptr,
params.key->GetAsymmetricKey().get())) {
return false;
}
break;
case SignConfiguration::kVerify:
CHECK_EQ(params.key->GetKeyType(), kKeyTypePublic);
if (!EVP_DigestVerifyInit(
context.get(),
&ctx,
params.digest,
nullptr,
params.key->GetAsymmetricKey().get())) {
return false;
}
break;
}
int padding = params.flags & SignConfiguration::kHasPadding
? params.padding
: GetDefaultSignPadding(params.key->GetAsymmetricKey());
Maybe<int> salt_length = params.flags & SignConfiguration::kHasSaltLength
? Just<int>(params.salt_length) : Nothing<int>();
if (!ApplyRSAOptions(
params.key->GetAsymmetricKey(),
ctx,
padding,
salt_length)) {
return false;
}
switch (params.mode) {
case SignConfiguration::kSign: {
size_t len;
if (!EVP_DigestSignUpdate(
context.get(),
params.data.data<unsigned char>(),
params.data.size()) ||
!EVP_DigestSignFinal(context.get(), nullptr, &len)) {
return false;
}
char* data = MallocOpenSSL<char>(len);
ByteSource buf = ByteSource::Allocated(data, len);
unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
if (!EVP_DigestSignFinal(context.get(), ptr, &len))
return false;
// If this is an EC key (assuming ECDSA) we have to
// convert the signature in to the proper format.
if (EVP_PKEY_id(params.key->GetAsymmetricKey().get()) == EVP_PKEY_EC) {
*out = ConvertToWebCryptoSignature(params.key->GetAsymmetricKey(), buf);
} else {
buf.Resize(len);
*out = std::move(buf);
}
break;
}
case SignConfiguration::kVerify: {
char* data = MallocOpenSSL<char>(1);
data[0] = 0;
*out = ByteSource::Allocated(data, 1);
if (!EVP_DigestVerifyUpdate(
context.get(),
params.data.data<unsigned char>(),
params.data.size())) {
return false;
}
if (EVP_DigestVerifyFinal(
context.get(),
params.signature.data<unsigned char>(),
params.signature.size()) == 1) {
data[0] = 1;
}
}
}
return true;
}
Maybe<bool> SignTraits::EncodeOutput(
Environment* env,
const SignConfiguration& params,
ByteSource* out,
Local<Value>* result) {
switch (params.mode) {
case SignConfiguration::kSign:
*result = out->ToArrayBuffer(env);
break;
case SignConfiguration::kVerify:
*result = out->get()[0] == 1
? v8::True(env->isolate())
: v8::False(env->isolate());
break;
default:
UNREACHABLE();
}
return Just(!result->IsEmpty());
}
} // namespace crypto
} // namespace node

165
src/crypto/crypto_sig.h Normal file
View File

@ -0,0 +1,165 @@
#ifndef SRC_CRYPTO_CRYPTO_SIG_H_
#define SRC_CRYPTO_CRYPTO_SIG_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer.h"
#include "base_object.h"
#include "env.h"
#include "memory_tracker.h"
namespace node {
namespace crypto {
static const unsigned int kNoDsaSignature = static_cast<unsigned int>(-1);
enum DSASigEnc {
kSigEncDER, kSigEncP1363
};
class SignBase : public BaseObject {
public:
typedef enum {
kSignOk,
kSignUnknownDigest,
kSignInit,
kSignNotInitialised,
kSignUpdate,
kSignPrivateKey,
kSignPublicKey,
kSignMalformedSignature
} Error;
SignBase(Environment* env, v8::Local<v8::Object> wrap);
Error Init(const char* sign_type);
Error Update(const char* data, size_t len);
// TODO(joyeecheung): track the memory used by OpenSSL types
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(SignBase)
SET_SELF_SIZE(SignBase)
protected:
EVPMDPointer mdctx_;
};
class Sign : public SignBase {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
struct SignResult {
Error error;
AllocatedBuffer signature;
explicit SignResult(
Error err,
AllocatedBuffer&& sig = AllocatedBuffer())
: error(err), signature(std::move(sig)) {}
};
SignResult SignFinal(
const ManagedEVPPKey& pkey,
int padding,
const v8::Maybe<int>& saltlen,
DSASigEnc dsa_sig_enc);
static void SignSync(const v8::FunctionCallbackInfo<v8::Value>& args);
protected:
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SignInit(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SignUpdate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SignFinal(const v8::FunctionCallbackInfo<v8::Value>& args);
Sign(Environment* env, v8::Local<v8::Object> wrap);
};
class Verify : public SignBase {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
Error VerifyFinal(const ManagedEVPPKey& key,
const ByteSource& sig,
int padding,
const v8::Maybe<int>& saltlen,
bool* verify_result);
static void VerifySync(const v8::FunctionCallbackInfo<v8::Value>& args);
protected:
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void VerifyInit(const v8::FunctionCallbackInfo<v8::Value>& args);
static void VerifyUpdate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void VerifyFinal(const v8::FunctionCallbackInfo<v8::Value>& args);
Verify(Environment* env, v8::Local<v8::Object> wrap);
};
struct SignConfiguration final : public MemoryRetainer {
enum Mode {
kSign,
kVerify
};
enum Flags {
kHasNone = 0,
kHasSaltLength = 1,
kHasPadding = 2
};
CryptoJobMode job_mode;
Mode mode;
std::shared_ptr<KeyObjectData> key;
ByteSource data;
ByteSource signature;
const EVP_MD* digest = nullptr;
int flags = SignConfiguration::kHasNone;
int padding = 0;
int salt_length = 0;
SignConfiguration() = default;
explicit SignConfiguration(SignConfiguration&& other) noexcept;
SignConfiguration& operator=(SignConfiguration&& other) noexcept;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(SignConfiguration);
SET_SELF_SIZE(SignConfiguration);
};
struct SignTraits final {
using AdditionalParameters = SignConfiguration;
static constexpr const char* JobName = "SignJob";
// TODO(@jasnell): Sign request vs. Verify request
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_SIGNREQUEST;
static v8::Maybe<bool> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
SignConfiguration* params);
static bool DeriveBits(
Environment* env,
const SignConfiguration& params,
ByteSource* out);
static v8::Maybe<bool> EncodeOutput(
Environment* env,
const SignConfiguration& params,
ByteSource* out,
v8::Local<v8::Value>* result);
};
using SignJob = DeriveBitsJob<SignTraits>;
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_SIG_H_

128
src/crypto/crypto_spkac.cc Normal file
View File

@ -0,0 +1,128 @@
#include "crypto/crypto_spkac.h"
#include "crypto/crypto_common.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node.h"
#include "v8.h"
namespace node {
using v8::FunctionCallbackInfo;
using v8::Local;
using v8::Object;
using v8::Value;
namespace crypto {
namespace SPKAC {
bool VerifySpkac(const ArrayBufferOrViewContents<char>& input) {
NetscapeSPKIPointer spki(
NETSCAPE_SPKI_b64_decode(input.data(), input.size()));
if (!spki)
return false;
EVPKeyPointer pkey(X509_PUBKEY_get(spki->spkac->pubkey));
if (!pkey)
return false;
return NETSCAPE_SPKI_verify(spki.get(), pkey.get()) > 0;
}
void VerifySpkac(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ArrayBufferOrViewContents<char> input(args[0]);
if (input.size() == 0)
return args.GetReturnValue().SetEmptyString();
if (UNLIKELY(!input.CheckSizeInt32()))
return THROW_ERR_OUT_OF_RANGE(env, "spkac is too large");
args.GetReturnValue().Set(VerifySpkac(input));
}
AllocatedBuffer ExportPublicKey(Environment* env,
const ArrayBufferOrViewContents<char>& input,
size_t* size) {
BIOPointer bio(BIO_new(BIO_s_mem()));
if (!bio) return AllocatedBuffer();
NetscapeSPKIPointer spki(
NETSCAPE_SPKI_b64_decode(input.data(), input.size()));
if (!spki) return AllocatedBuffer();
EVPKeyPointer pkey(NETSCAPE_SPKI_get_pubkey(spki.get()));
if (!pkey) return AllocatedBuffer();
if (PEM_write_bio_PUBKEY(bio.get(), pkey.get()) <= 0)
return AllocatedBuffer();
BUF_MEM* ptr;
BIO_get_mem_ptr(bio.get(), &ptr);
*size = ptr->length;
AllocatedBuffer buf = AllocatedBuffer::AllocateManaged(env, *size);
memcpy(buf.data(), ptr->data, *size);
return buf;
}
void ExportPublicKey(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ArrayBufferOrViewContents<char> input(args[0]);
if (input.size() == 0)
return args.GetReturnValue().SetEmptyString();
if (UNLIKELY(!input.CheckSizeInt32()))
return THROW_ERR_OUT_OF_RANGE(env, "spkac is too large");
size_t pkey_size;
AllocatedBuffer pkey = ExportPublicKey(env, input, &pkey_size);
if (pkey.data() == nullptr)
return args.GetReturnValue().SetEmptyString();
args.GetReturnValue().Set(pkey.ToBuffer().ToLocalChecked());
}
OpenSSLBuffer ExportChallenge(const ArrayBufferOrViewContents<char>& input) {
NetscapeSPKIPointer sp(
NETSCAPE_SPKI_b64_decode(input.data(), input.size()));
if (!sp)
return nullptr;
unsigned char* buf = nullptr;
ASN1_STRING_to_UTF8(&buf, sp->spkac->challenge);
return OpenSSLBuffer(reinterpret_cast<char*>(buf));
}
void ExportChallenge(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ArrayBufferOrViewContents<char> input(args[0]);
if (input.size() == 0)
return args.GetReturnValue().SetEmptyString();
if (UNLIKELY(!input.CheckSizeInt32()))
return THROW_ERR_OUT_OF_RANGE(env, "spkac is too large");
OpenSSLBuffer cert = ExportChallenge(input);
if (!cert)
return args.GetReturnValue().SetEmptyString();
Local<Value> outString =
Encode(env->isolate(), cert.get(), strlen(cert.get()), BUFFER);
args.GetReturnValue().Set(outString);
}
void Initialize(Environment* env, Local<Object> target) {
env->SetMethodNoSideEffect(target, "certVerifySpkac", VerifySpkac);
env->SetMethodNoSideEffect(target, "certExportPublicKey", ExportPublicKey);
env->SetMethodNoSideEffect(target, "certExportChallenge", ExportChallenge);
}
} // namespace SPKAC
} // namespace crypto
} // namespace node

20
src/crypto/crypto_spkac.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef SRC_CRYPTO_CRYPTO_SPKAC_H_
#define SRC_CRYPTO_CRYPTO_SPKAC_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "env.h"
#include "v8.h"
#include <openssl/evp.h>
namespace node {
namespace crypto {
namespace SPKAC {
void Initialize(Environment* env, v8::Local<v8::Object>);
} // namespace SPKAC
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_SPKAC_H_

860
src/crypto/crypto_ssl.cc Normal file
View File

@ -0,0 +1,860 @@
#include "crypto/crypto_ssl.h"
#include "crypto/crypto_clienthello-inl.h"
#include "crypto/crypto_common.h"
#include "crypto/crypto_util.h"
#include "allocated_buffer-inl.h"
#include "async_wrap-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node_buffer.h"
#include "tls_wrap.h"
#include "v8.h"
namespace node {
using v8::Array;
using v8::ArrayBufferView;
using v8::Boolean;
using v8::Context;
using v8::Exception;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::MaybeLocal;
using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Value;
namespace crypto {
// Just to generate static methods
template void SSLWrap<TLSWrap>::AddMethods(Environment* env,
Local<FunctionTemplate> t);
template void SSLWrap<TLSWrap>::ConfigureSecureContext(SecureContext* sc);
template int SSLWrap<TLSWrap>::SetCACerts(SecureContext* sc);
template void SSLWrap<TLSWrap>::MemoryInfo(MemoryTracker* tracker) const;
template SSL_SESSION* SSLWrap<TLSWrap>::GetSessionCallback(
SSL* s,
const unsigned char* key,
int len,
int* copy);
template int SSLWrap<TLSWrap>::NewSessionCallback(SSL* s,
SSL_SESSION* sess);
template void SSLWrap<TLSWrap>::KeylogCallback(const SSL* s,
const char* line);
template void SSLWrap<TLSWrap>::OnClientHello(
void* arg,
const ClientHelloParser::ClientHello& hello);
template int SSLWrap<TLSWrap>::TLSExtStatusCallback(SSL* s, void* arg);
template void SSLWrap<TLSWrap>::DestroySSL();
template int SSLWrap<TLSWrap>::SSLCertCallback(SSL* s, void* arg);
template void SSLWrap<TLSWrap>::WaitForCertCb(CertCb cb, void* arg);
template int SSLWrap<TLSWrap>::SelectALPNCallback(
SSL* s,
const unsigned char** out,
unsigned char* outlen,
const unsigned char* in,
unsigned int inlen,
void* arg);
template <class Base>
void SSLWrap<Base>::AddMethods(Environment* env, Local<FunctionTemplate> t) {
HandleScope scope(env->isolate());
env->SetProtoMethodNoSideEffect(t, "getPeerCertificate", GetPeerCertificate);
env->SetProtoMethodNoSideEffect(t, "getCertificate", GetCertificate);
env->SetProtoMethodNoSideEffect(t, "getFinished", GetFinished);
env->SetProtoMethodNoSideEffect(t, "getPeerFinished", GetPeerFinished);
env->SetProtoMethodNoSideEffect(t, "getSession", GetSession);
env->SetProtoMethod(t, "setSession", SetSession);
env->SetProtoMethod(t, "loadSession", LoadSession);
env->SetProtoMethodNoSideEffect(t, "isSessionReused", IsSessionReused);
env->SetProtoMethodNoSideEffect(t, "verifyError", VerifyError);
env->SetProtoMethodNoSideEffect(t, "getCipher", GetCipher);
env->SetProtoMethodNoSideEffect(t, "getSharedSigalgs", GetSharedSigalgs);
env->SetProtoMethodNoSideEffect(
t, "exportKeyingMaterial", ExportKeyingMaterial);
env->SetProtoMethod(t, "endParser", EndParser);
env->SetProtoMethod(t, "certCbDone", CertCbDone);
env->SetProtoMethod(t, "renegotiate", Renegotiate);
env->SetProtoMethodNoSideEffect(t, "getTLSTicket", GetTLSTicket);
env->SetProtoMethod(t, "newSessionDone", NewSessionDone);
env->SetProtoMethod(t, "setOCSPResponse", SetOCSPResponse);
env->SetProtoMethod(t, "requestOCSP", RequestOCSP);
env->SetProtoMethodNoSideEffect(t, "getEphemeralKeyInfo",
GetEphemeralKeyInfo);
env->SetProtoMethodNoSideEffect(t, "getProtocol", GetProtocol);
env->SetProtoMethod(t, "setMaxSendFragment", SetMaxSendFragment);
env->SetProtoMethodNoSideEffect(t, "getALPNNegotiatedProtocol",
GetALPNNegotiatedProto);
env->SetProtoMethod(t, "setALPNProtocols", SetALPNProtocols);
}
template <class Base>
void SSLWrap<Base>::ConfigureSecureContext(SecureContext* sc) {
// OCSP stapling
SSL_CTX_set_tlsext_status_cb(sc->ctx_.get(), TLSExtStatusCallback);
SSL_CTX_set_tlsext_status_arg(sc->ctx_.get(), nullptr);
}
template <class Base>
SSL_SESSION* SSLWrap<Base>::GetSessionCallback(SSL* s,
const unsigned char* key,
int len,
int* copy) {
Base* w = static_cast<Base*>(SSL_get_app_data(s));
*copy = 0;
return w->next_sess_.release();
}
template <class Base>
int SSLWrap<Base>::NewSessionCallback(SSL* s, SSL_SESSION* sess) {
Base* w = static_cast<Base*>(SSL_get_app_data(s));
Environment* env = w->ssl_env();
HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());
if (!w->session_callbacks_)
return 0;
// Check if session is small enough to be stored
int size = i2d_SSL_SESSION(sess, nullptr);
if (size > SecureContext::kMaxSessionSize)
return 0;
// Serialize session
Local<Object> session = Buffer::New(env, size).ToLocalChecked();
unsigned char* session_data = reinterpret_cast<unsigned char*>(
Buffer::Data(session));
memset(session_data, 0, size);
i2d_SSL_SESSION(sess, &session_data);
unsigned int session_id_length;
const unsigned char* session_id_data = SSL_SESSION_get_id(sess,
&session_id_length);
Local<Object> session_id = Buffer::Copy(
env,
reinterpret_cast<const char*>(session_id_data),
session_id_length).ToLocalChecked();
Local<Value> argv[] = { session_id, session };
// On servers, we pause the handshake until callback of 'newSession', which
// calls NewSessionDoneCb(). On clients, there is no callback to wait for.
if (w->is_server())
w->awaiting_new_session_ = true;
w->MakeCallback(env->onnewsession_string(), arraysize(argv), argv);
return 0;
}
template <class Base>
void SSLWrap<Base>::KeylogCallback(const SSL* s, const char* line) {
Base* w = static_cast<Base*>(SSL_get_app_data(s));
Environment* env = w->ssl_env();
HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());
const size_t size = strlen(line);
Local<Value> line_bf = Buffer::Copy(env, line, 1 + size).ToLocalChecked();
char* data = Buffer::Data(line_bf);
data[size] = '\n';
w->MakeCallback(env->onkeylog_string(), 1, &line_bf);
}
template <class Base>
void SSLWrap<Base>::OnClientHello(void* arg,
const ClientHelloParser::ClientHello& hello) {
Base* w = static_cast<Base*>(arg);
Environment* env = w->ssl_env();
HandleScope handle_scope(env->isolate());
Local<Context> context = env->context();
Context::Scope context_scope(context);
Local<Object> hello_obj = Object::New(env->isolate());
Local<Object> buff = Buffer::Copy(
env,
reinterpret_cast<const char*>(hello.session_id()),
hello.session_size()).ToLocalChecked();
hello_obj->Set(context, env->session_id_string(), buff).Check();
if (hello.servername() == nullptr) {
hello_obj->Set(context,
env->servername_string(),
String::Empty(env->isolate())).Check();
} else {
Local<String> servername = OneByteString(env->isolate(),
hello.servername(),
hello.servername_size());
hello_obj->Set(context, env->servername_string(), servername).Check();
}
hello_obj->Set(context,
env->tls_ticket_string(),
Boolean::New(env->isolate(), hello.has_ticket())).Check();
Local<Value> argv[] = { hello_obj };
w->MakeCallback(env->onclienthello_string(), arraysize(argv), argv);
}
template <class Base>
void SSLWrap<Base>::GetPeerCertificate(
const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
Environment* env = w->ssl_env();
bool abbreviated = args.Length() < 1 || !args[0]->IsTrue();
Local<Value> ret;
if (GetPeerCert(env, w->ssl_, abbreviated, w->is_server()).ToLocal(&ret))
args.GetReturnValue().Set(ret);
}
template <class Base>
void SSLWrap<Base>::GetCertificate(
const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
Environment* env = w->ssl_env();
Local<Value> ret;
if (GetCert(env, w->ssl_).ToLocal(&ret))
args.GetReturnValue().Set(ret);
}
template <class Base>
void SSLWrap<Base>::GetFinished(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
// We cannot just pass nullptr to SSL_get_finished()
// because it would further be propagated to memcpy(),
// where the standard requirements as described in ISO/IEC 9899:2011
// sections 7.21.2.1, 7.21.1.2, and 7.1.4, would be violated.
// Thus, we use a dummy byte.
char dummy[1];
size_t len = SSL_get_finished(w->ssl_.get(), dummy, sizeof dummy);
if (len == 0)
return;
AllocatedBuffer buf = AllocatedBuffer::AllocateManaged(env, len);
CHECK_EQ(len, SSL_get_finished(w->ssl_.get(), buf.data(), len));
args.GetReturnValue().Set(buf.ToBuffer().ToLocalChecked());
}
template <class Base>
void SSLWrap<Base>::GetPeerFinished(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
// We cannot just pass nullptr to SSL_get_peer_finished()
// because it would further be propagated to memcpy(),
// where the standard requirements as described in ISO/IEC 9899:2011
// sections 7.21.2.1, 7.21.1.2, and 7.1.4, would be violated.
// Thus, we use a dummy byte.
char dummy[1];
size_t len = SSL_get_peer_finished(w->ssl_.get(), dummy, sizeof dummy);
if (len == 0)
return;
AllocatedBuffer buf = AllocatedBuffer::AllocateManaged(env, len);
CHECK_EQ(len, SSL_get_peer_finished(w->ssl_.get(), buf.data(), len));
args.GetReturnValue().Set(buf.ToBuffer().ToLocalChecked());
}
template <class Base>
void SSLWrap<Base>::GetSession(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
SSL_SESSION* sess = SSL_get_session(w->ssl_.get());
if (sess == nullptr)
return;
int slen = i2d_SSL_SESSION(sess, nullptr);
if (slen <= 0)
return; // Invalid or malformed session.
AllocatedBuffer sbuf = AllocatedBuffer::AllocateManaged(env, slen);
unsigned char* p = reinterpret_cast<unsigned char*>(sbuf.data());
CHECK_LT(0, i2d_SSL_SESSION(sess, &p));
args.GetReturnValue().Set(sbuf.ToBuffer().ToLocalChecked());
}
template <class Base>
void SSLWrap<Base>::SetSession(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
if (args.Length() < 1)
return THROW_ERR_MISSING_ARGS(env, "Session argument is mandatory");
THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "Session");
SSLSessionPointer sess = GetTLSSession(args[0]);
if (sess == nullptr)
return;
if (!SetTLSSession(w->ssl_, sess))
return env->ThrowError("SSL_set_session error");
}
template <class Base>
void SSLWrap<Base>::LoadSession(const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
// TODO(@sam-github) check arg length and types in js, and CHECK in c++
if (args.Length() >= 1 && Buffer::HasInstance(args[0])) {
ArrayBufferViewContents<unsigned char> sbuf(args[0]);
const unsigned char* p = sbuf.data();
SSL_SESSION* sess = d2i_SSL_SESSION(nullptr, &p, sbuf.length());
// Setup next session and move hello to the BIO buffer
w->next_sess_.reset(sess);
}
}
template <class Base>
void SSLWrap<Base>::IsSessionReused(const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
bool yes = SSL_session_reused(w->ssl_.get());
args.GetReturnValue().Set(yes);
}
template <class Base>
void SSLWrap<Base>::EndParser(const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
w->hello_parser_.End();
}
template <class Base>
void SSLWrap<Base>::Renegotiate(const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
ClearErrorOnReturn clear_error_on_return;
if (SSL_renegotiate(w->ssl_.get()) != 1) {
return ThrowCryptoError(w->ssl_env(), ERR_get_error());
}
}
template <class Base>
void SSLWrap<Base>::GetTLSTicket(const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
Environment* env = w->ssl_env();
SSL_SESSION* sess = SSL_get_session(w->ssl_.get());
if (sess == nullptr)
return;
const unsigned char* ticket;
size_t length;
SSL_SESSION_get0_ticket(sess, &ticket, &length);
if (ticket == nullptr)
return;
Local<Object> buff = Buffer::Copy(
env, reinterpret_cast<const char*>(ticket), length).ToLocalChecked();
args.GetReturnValue().Set(buff);
}
template <class Base>
void SSLWrap<Base>::NewSessionDone(const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
w->awaiting_new_session_ = false;
w->NewSessionDoneCb();
}
template <class Base>
void SSLWrap<Base>::SetOCSPResponse(const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
Environment* env = w->env();
if (args.Length() < 1)
return THROW_ERR_MISSING_ARGS(env, "OCSP response argument is mandatory");
THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "OCSP response");
w->ocsp_response_.Reset(args.GetIsolate(), args[0].As<ArrayBufferView>());
}
template <class Base>
void SSLWrap<Base>::RequestOCSP(const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
SSL_set_tlsext_status_type(w->ssl_.get(), TLSEXT_STATUSTYPE_ocsp);
}
template <class Base>
void SSLWrap<Base>::GetEphemeralKeyInfo(
const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
Environment* env = Environment::GetCurrent(args);
CHECK(w->ssl_);
// tmp key is available on only client
if (w->is_server())
return args.GetReturnValue().SetNull();
Local<Object> ret;
if (GetEphemeralKey(env, w->ssl_).ToLocal(&ret))
args.GetReturnValue().Set(ret);
// TODO(@sam-github) semver-major: else return ThrowCryptoError(env,
// ERR_get_error())
}
template <class Base>
void SSLWrap<Base>::SetMaxSendFragment(
const FunctionCallbackInfo<Value>& args) {
CHECK(args.Length() >= 1 && args[0]->IsNumber());
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
int rv = SSL_set_max_send_fragment(
w->ssl_.get(),
args[0]->Int32Value(w->ssl_env()->context()).FromJust());
args.GetReturnValue().Set(rv);
}
template <class Base>
void SSLWrap<Base>::VerifyError(const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
// XXX(bnoordhuis) The UNABLE_TO_GET_ISSUER_CERT error when there is no
// peer certificate is questionable but it's compatible with what was
// here before.
long x509_verify_error = // NOLINT(runtime/int)
VerifyPeerCertificate(w->ssl_, X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT);
if (x509_verify_error == X509_V_OK)
return args.GetReturnValue().SetNull();
const char* reason = X509_verify_cert_error_string(x509_verify_error);
const char* code = reason;
code = X509ErrorCode(x509_verify_error);
Isolate* isolate = args.GetIsolate();
Local<String> reason_string = OneByteString(isolate, reason);
Local<Value> exception_value = Exception::Error(reason_string);
Local<Object> exception_object =
exception_value->ToObject(isolate->GetCurrentContext()).ToLocalChecked();
exception_object->Set(w->env()->context(), w->env()->code_string(),
OneByteString(isolate, code)).Check();
args.GetReturnValue().Set(exception_object);
}
template <class Base>
void SSLWrap<Base>::GetCipher(const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
Environment* env = w->ssl_env();
const SSL_CIPHER* c = SSL_get_current_cipher(w->ssl_.get());
if (c == nullptr)
return;
Local<Object> ret;
if (GetCipherInfo(env, w->ssl_).ToLocal(&ret))
args.GetReturnValue().Set(ret);
}
template <class Base>
void SSLWrap<Base>::GetSharedSigalgs(const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
Environment* env = w->ssl_env();
SSL* ssl = w->ssl_.get();
int nsig = SSL_get_shared_sigalgs(ssl, 0, nullptr, nullptr, nullptr, nullptr,
nullptr);
MaybeStackBuffer<Local<Value>, 16> ret_arr(nsig);
for (int i = 0; i < nsig; i++) {
int hash_nid;
int sign_nid;
std::string sig_with_md;
SSL_get_shared_sigalgs(ssl, i, &sign_nid, &hash_nid, nullptr, nullptr,
nullptr);
switch (sign_nid) {
case EVP_PKEY_RSA:
sig_with_md = "RSA+";
break;
case EVP_PKEY_RSA_PSS:
sig_with_md = "RSA-PSS+";
break;
case EVP_PKEY_DSA:
sig_with_md = "DSA+";
break;
case EVP_PKEY_EC:
sig_with_md = "ECDSA+";
break;
case NID_ED25519:
sig_with_md = "Ed25519+";
break;
case NID_ED448:
sig_with_md = "Ed448+";
break;
#ifndef OPENSSL_NO_GOST
case NID_id_GostR3410_2001:
sig_with_md = "gost2001+";
break;
case NID_id_GostR3410_2012_256:
sig_with_md = "gost2012_256+";
break;
case NID_id_GostR3410_2012_512:
sig_with_md = "gost2012_512+";
break;
#endif // !OPENSSL_NO_GOST
default:
const char* sn = OBJ_nid2sn(sign_nid);
if (sn != nullptr) {
sig_with_md = std::string(sn) + "+";
} else {
sig_with_md = "UNDEF+";
}
break;
}
const char* sn_hash = OBJ_nid2sn(hash_nid);
if (sn_hash != nullptr) {
sig_with_md += std::string(sn_hash);
} else {
sig_with_md += "UNDEF";
}
ret_arr[i] = OneByteString(env->isolate(), sig_with_md.c_str());
}
args.GetReturnValue().Set(
Array::New(env->isolate(), ret_arr.out(), ret_arr.length()));
}
template <class Base>
void SSLWrap<Base>::ExportKeyingMaterial(
const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsInt32());
CHECK(args[1]->IsString());
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
Environment* env = w->ssl_env();
uint32_t olen = args[0].As<Uint32>()->Value();
node::Utf8Value label(env->isolate(), args[1]);
AllocatedBuffer out = AllocatedBuffer::AllocateManaged(env, olen);
ByteSource context;
bool use_context = !args[2]->IsUndefined();
if (use_context)
context = ByteSource::FromBuffer(args[2]);
if (SSL_export_keying_material(w->ssl_.get(),
reinterpret_cast<unsigned char*>(out.data()),
olen,
*label,
label.length(),
reinterpret_cast<const unsigned char*>(
context.get()),
context.size(),
use_context) != 1) {
return ThrowCryptoError(env, ERR_get_error(), "SSL_export_keying_material");
}
args.GetReturnValue().Set(out.ToBuffer().ToLocalChecked());
}
template <class Base>
void SSLWrap<Base>::GetProtocol(const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
const char* tls_version = SSL_get_version(w->ssl_.get());
args.GetReturnValue().Set(OneByteString(args.GetIsolate(), tls_version));
}
template <class Base>
int SSLWrap<Base>::SelectALPNCallback(SSL* s,
const unsigned char** out,
unsigned char* outlen,
const unsigned char* in,
unsigned int inlen,
void* arg) {
Base* w = static_cast<Base*>(SSL_get_app_data(s));
Environment* env = w->env();
HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());
Local<Value> alpn_buffer =
w->object()->GetPrivate(
env->context(),
env->alpn_buffer_private_symbol()).ToLocalChecked();
ArrayBufferViewContents<unsigned char> alpn_protos(alpn_buffer);
int status = SSL_select_next_proto(const_cast<unsigned char**>(out), outlen,
alpn_protos.data(), alpn_protos.length(),
in, inlen);
// According to 3.2. Protocol Selection of RFC7301, fatal
// no_application_protocol alert shall be sent but OpenSSL 1.0.2 does not
// support it yet. See
// https://rt.openssl.org/Ticket/Display.html?id=3463&user=guest&pass=guest
return status == OPENSSL_NPN_NEGOTIATED ? SSL_TLSEXT_ERR_OK
: SSL_TLSEXT_ERR_NOACK;
}
template <class Base>
void SSLWrap<Base>::GetALPNNegotiatedProto(
const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
const unsigned char* alpn_proto;
unsigned int alpn_proto_len;
SSL_get0_alpn_selected(w->ssl_.get(), &alpn_proto, &alpn_proto_len);
Local<Value> result;
if (alpn_proto_len == 0) {
result = False(args.GetIsolate());
} else if (alpn_proto_len == sizeof("h2") - 1 &&
0 == memcmp(alpn_proto, "h2", sizeof("h2") - 1)) {
result = w->env()->h2_string();
} else if (alpn_proto_len == sizeof("http/1.1") - 1 &&
0 == memcmp(alpn_proto, "http/1.1", sizeof("http/1.1") - 1)) {
result = w->env()->http_1_1_string();
} else {
result = OneByteString(args.GetIsolate(), alpn_proto, alpn_proto_len);
}
args.GetReturnValue().Set(result);
}
template <class Base>
void SSLWrap<Base>::SetALPNProtocols(const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
Environment* env = w->env();
if (args.Length() < 1 || !Buffer::HasInstance(args[0]))
return env->ThrowTypeError("Must give a Buffer as first argument");
if (w->is_client()) {
CHECK(SetALPN(w->ssl_, args[0]));
} else {
CHECK(
w->object()->SetPrivate(
env->context(),
env->alpn_buffer_private_symbol(),
args[0]).FromJust());
// Server should select ALPN protocol from list of advertised by client
SSL_CTX_set_alpn_select_cb(SSL_get_SSL_CTX(w->ssl_.get()),
SelectALPNCallback,
nullptr);
}
}
template <class Base>
int SSLWrap<Base>::TLSExtStatusCallback(SSL* s, void* arg) {
Base* w = static_cast<Base*>(SSL_get_app_data(s));
Environment* env = w->env();
HandleScope handle_scope(env->isolate());
if (w->is_client()) {
// Incoming response
Local<Value> arg;
MaybeLocal<Value> ret = GetSSLOCSPResponse(env, s, Null(env->isolate()));
if (ret.ToLocal(&arg))
w->MakeCallback(env->onocspresponse_string(), 1, &arg);
// No async acceptance is possible, so always return 1 to accept the
// response. The listener for 'OCSPResponse' event has no control over
// return value, but it can .destroy() the connection if the response is not
// acceptable.
return 1;
} else {
// Outgoing response
if (w->ocsp_response_.IsEmpty())
return SSL_TLSEXT_ERR_NOACK;
Local<ArrayBufferView> obj = PersistentToLocal::Default(env->isolate(),
w->ocsp_response_);
size_t len = obj->ByteLength();
// OpenSSL takes control of the pointer after accepting it
unsigned char* data = MallocOpenSSL<unsigned char>(len);
obj->CopyContents(data, len);
if (!SSL_set_tlsext_status_ocsp_resp(s, data, len))
OPENSSL_free(data);
w->ocsp_response_.Reset();
return SSL_TLSEXT_ERR_OK;
}
}
template <class Base>
void SSLWrap<Base>::WaitForCertCb(CertCb cb, void* arg) {
cert_cb_ = cb;
cert_cb_arg_ = arg;
}
template <class Base>
int SSLWrap<Base>::SSLCertCallback(SSL* s, void* arg) {
Base* w = static_cast<Base*>(SSL_get_app_data(s));
if (!w->is_server())
return 1;
if (!w->is_waiting_cert_cb())
return 1;
if (w->cert_cb_running_)
// Not an error. Suspend handshake with SSL_ERROR_WANT_X509_LOOKUP, and
// handshake will continue after certcb is done.
return -1;
Environment* env = w->env();
Local<Context> context = env->context();
HandleScope handle_scope(env->isolate());
Context::Scope context_scope(context);
w->cert_cb_running_ = true;
Local<Object> info = Object::New(env->isolate());
const char* servername = GetServerName(s);
if (servername == nullptr) {
info->Set(context,
env->servername_string(),
String::Empty(env->isolate())).Check();
} else {
Local<String> str = OneByteString(env->isolate(), servername,
strlen(servername));
info->Set(context, env->servername_string(), str).Check();
}
const bool ocsp = (SSL_get_tlsext_status_type(s) == TLSEXT_STATUSTYPE_ocsp);
info->Set(context, env->ocsp_request_string(),
Boolean::New(env->isolate(), ocsp)).Check();
Local<Value> argv[] = { info };
w->MakeCallback(env->oncertcb_string(), arraysize(argv), argv);
if (!w->cert_cb_running_)
return 1;
// Performing async action, wait...
return -1;
}
template <class Base>
void SSLWrap<Base>::CertCbDone(const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
Environment* env = w->env();
CHECK(w->is_waiting_cert_cb() && w->cert_cb_running_);
Local<Object> object = w->object();
Local<Value> ctx = object->Get(env->context(),
env->sni_context_string()).ToLocalChecked();
Local<FunctionTemplate> cons = env->secure_context_constructor_template();
if (cons->HasInstance(ctx)) {
SecureContext* sc = Unwrap<SecureContext>(ctx.As<Object>());
CHECK_NOT_NULL(sc);
// Store the SNI context for later use.
w->sni_context_ = BaseObjectPtr<SecureContext>(sc);
if (UseSNIContext(w->ssl_, w->sni_context_) && !w->SetCACerts(sc)) {
// Not clear why sometimes we throw error, and sometimes we call
// onerror(). Both cause .destroy(), but onerror does a bit more.
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
return ThrowCryptoError(env, err, "CertCbDone");
}
} else if (ctx->IsObject()) {
// Failure: incorrect SNI context object
Local<Value> err = Exception::TypeError(env->sni_context_err_string());
w->MakeCallback(env->onerror_string(), 1, &err);
return;
}
CertCb cb;
void* arg;
cb = w->cert_cb_;
arg = w->cert_cb_arg_;
w->cert_cb_running_ = false;
w->cert_cb_ = nullptr;
w->cert_cb_arg_ = nullptr;
cb(arg);
}
template <class Base>
void SSLWrap<Base>::DestroySSL() {
if (!ssl_)
return;
env_->isolate()->AdjustAmountOfExternalAllocatedMemory(-kExternalSize);
ssl_.reset();
}
template <class Base>
int SSLWrap<Base>::SetCACerts(SecureContext* sc) {
int err = SSL_set1_verify_cert_store(ssl_.get(),
SSL_CTX_get_cert_store(sc->ctx_.get()));
if (err != 1)
return err;
STACK_OF(X509_NAME)* list = SSL_dup_CA_list(
SSL_CTX_get_client_CA_list(sc->ctx_.get()));
// NOTE: `SSL_set_client_CA_list` takes the ownership of `list`
SSL_set_client_CA_list(ssl_.get(), list);
return 1;
}
template <class Base>
void SSLWrap<Base>::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("ocsp_response", ocsp_response_);
tracker->TrackField("sni_context", sni_context_);
}
} // namespace crypto
} // namespace node

146
src/crypto/crypto_ssl.h Normal file
View File

@ -0,0 +1,146 @@
#ifndef SRC_CRYPTO_CRYPTO_SSL_H_
#define SRC_CRYPTO_CRYPTO_SSL_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_context.h"
#include "crypto/crypto_clienthello.h"
#include "crypto/crypto_util.h"
#include "env.h"
#include "v8.h"
namespace node {
namespace crypto {
// SSLWrap implicitly depends on the inheriting class' handle having an
// internal pointer to the Base class.
template <class Base>
class SSLWrap {
public:
enum Kind {
kClient,
kServer
};
SSLWrap(Environment* env, SecureContext* sc, Kind kind)
: env_(env),
kind_(kind),
next_sess_(nullptr),
session_callbacks_(false),
awaiting_new_session_(false),
cert_cb_(nullptr),
cert_cb_arg_(nullptr),
cert_cb_running_(false) {
ssl_.reset(SSL_new(sc->ctx_.get()));
CHECK(ssl_);
env_->isolate()->AdjustAmountOfExternalAllocatedMemory(kExternalSize);
}
virtual ~SSLWrap() {
DestroySSL();
}
inline void enable_session_callbacks() { session_callbacks_ = true; }
inline bool is_server() const { return kind_ == kServer; }
inline bool is_client() const { return kind_ == kClient; }
inline bool is_awaiting_new_session() const { return awaiting_new_session_; }
inline bool is_waiting_cert_cb() const { return cert_cb_ != nullptr; }
void MemoryInfo(MemoryTracker* tracker) const;
protected:
typedef void (*CertCb)(void* arg);
// OpenSSL structures are opaque. Estimate SSL memory size for OpenSSL 1.1.1b:
// SSL: 6224
// SSL->SSL3_STATE: 1040
// ...some buffers: 42 * 1024
// NOTE: Actually it is much more than this
static const int64_t kExternalSize = 6224 + 1040 + 42 * 1024;
static void ConfigureSecureContext(SecureContext* sc);
static void AddMethods(Environment* env, v8::Local<v8::FunctionTemplate> t);
static SSL_SESSION* GetSessionCallback(SSL* s,
const unsigned char* key,
int len,
int* copy);
static int NewSessionCallback(SSL* s, SSL_SESSION* sess);
static void KeylogCallback(const SSL* s, const char* line);
static void OnClientHello(void* arg,
const ClientHelloParser::ClientHello& hello);
static void GetPeerCertificate(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetCertificate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetFinished(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPeerFinished(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetSession(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetSession(const v8::FunctionCallbackInfo<v8::Value>& args);
static void LoadSession(const v8::FunctionCallbackInfo<v8::Value>& args);
static void IsSessionReused(const v8::FunctionCallbackInfo<v8::Value>& args);
static void VerifyError(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetCipher(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetSharedSigalgs(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ExportKeyingMaterial(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void EndParser(const v8::FunctionCallbackInfo<v8::Value>& args);
static void CertCbDone(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Renegotiate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetTLSTicket(const v8::FunctionCallbackInfo<v8::Value>& args);
static void NewSessionDone(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetOCSPResponse(const v8::FunctionCallbackInfo<v8::Value>& args);
static void RequestOCSP(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetEphemeralKeyInfo(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetProtocol(const v8::FunctionCallbackInfo<v8::Value>& args);
#ifdef SSL_set_max_send_fragment
static void SetMaxSendFragment(
const v8::FunctionCallbackInfo<v8::Value>& args);
#endif // SSL_set_max_send_fragment
static void GetALPNNegotiatedProto(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetALPNProtocols(const v8::FunctionCallbackInfo<v8::Value>& args);
static int SelectALPNCallback(SSL* s,
const unsigned char** out,
unsigned char* outlen,
const unsigned char* in,
unsigned int inlen,
void* arg);
static int TLSExtStatusCallback(SSL* s, void* arg);
static int SSLCertCallback(SSL* s, void* arg);
void DestroySSL();
void WaitForCertCb(CertCb cb, void* arg);
int SetCACerts(SecureContext* sc);
inline Environment* ssl_env() const {
return env_;
}
Environment* const env_;
Kind kind_;
SSLSessionPointer next_sess_;
SSLPointer ssl_;
bool session_callbacks_;
bool awaiting_new_session_;
// SSL_set_cert_cb
CertCb cert_cb_;
void* cert_cb_arg_;
bool cert_cb_running_;
ClientHelloParser hello_parser_;
v8::Global<v8::ArrayBufferView> ocsp_response_;
BaseObjectPtr<SecureContext> sni_context_;
friend class SecureContext;
};
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_SSL_H_

View File

@ -0,0 +1,55 @@
#include "crypto/crypto_timing.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "node_errors.h"
#include "v8.h"
#include "node.h"
#include <openssl/crypto.h>
namespace node {
using v8::FunctionCallbackInfo;
using v8::Local;
using v8::Object;
using v8::Value;
namespace crypto {
namespace Timing {
void TimingSafeEqual(const FunctionCallbackInfo<Value>& args) {
// Moving the type checking into JS leads to test failures, most likely due
// to V8 inlining certain parts of the wrapper. Therefore, keep them in C++.
// Refs: https://github.com/nodejs/node/issues/34073.
Environment* env = Environment::GetCurrent(args);
if (!IsAnyByteSource(args[0])) {
THROW_ERR_INVALID_ARG_TYPE(
env, "The \"buf1\" argument must be an instance of "
"ArrayBuffer, Buffer, TypedArray, or DataView.");
return;
}
if (!IsAnyByteSource(args[1])) {
THROW_ERR_INVALID_ARG_TYPE(
env, "The \"buf2\" argument must be an instance of "
"ArrayBuffer, Buffer, TypedArray, or DataView.");
return;
}
ArrayBufferOrViewContents<char> buf1(args[0]);
ArrayBufferOrViewContents<char> buf2(args[1]);
if (buf1.size() != buf2.size()) {
THROW_ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH(env);
return;
}
return args.GetReturnValue().Set(
CRYPTO_memcmp(buf1.data(), buf2.data(), buf1.size()) == 0);
}
void Initialize(Environment* env, Local<Object> target) {
env->SetMethodNoSideEffect(target, "timingSafeEqual", TimingSafeEqual);
}
} // namespace Timing
} // namespace crypto
} // namespace node

View File

@ -0,0 +1,20 @@
#ifndef SRC_CRYPTO_CRYPTO_TIMING_H_
#define SRC_CRYPTO_CRYPTO_TIMING_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "env.h"
#include "v8.h"
namespace node {
namespace crypto {
namespace Timing {
void Initialize(Environment* env, v8::Local<v8::Object> target);
} // namespace Timing
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_TIMING_H_

600
src/crypto/crypto_util.cc Normal file
View File

@ -0,0 +1,600 @@
#include "crypto/crypto_util.h"
#include "crypto/crypto_bio.h"
#include "crypto/crypto_keys.h"
#include "allocated_buffer-inl.h"
#include "async_wrap-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node_buffer.h"
#include "node_options-inl.h"
#include "string_bytes.h"
#include "threadpoolwork-inl.h"
#include "util-inl.h"
#include "v8.h"
#include "math.h"
namespace node {
using v8::ArrayBuffer;
using v8::BackingStore;
using v8::Context;
using v8::Exception;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Isolate;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::NewStringType;
using v8::Nothing;
using v8::Object;
using v8::String;
using v8::Value;
namespace crypto {
int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) {
// From https://www.openssl.org/docs/man1.1.1/man3/SSL_verify_cb:
//
// If VerifyCallback returns 1, the verification process is continued. If
// VerifyCallback always returns 1, the TLS/SSL handshake will not be
// terminated with respect to verification failures and the connection will
// be established. The calling process can however retrieve the error code
// of the last verification error using SSL_get_verify_result(3) or by
// maintaining its own error storage managed by VerifyCallback.
//
// Since we cannot perform I/O quickly enough with X509_STORE_CTX_ APIs in
// this callback, we ignore all preverify_ok errors and let the handshake
// continue. It is imperative that the user use Connection::VerifyError after
// the 'secure' callback has been made.
return 1;
}
void CheckEntropy() {
for (;;) {
int status = RAND_status();
CHECK_GE(status, 0); // Cannot fail.
if (status != 0)
break;
// Give up, RAND_poll() not supported.
if (RAND_poll() == 0)
break;
}
}
bool EntropySource(unsigned char* buffer, size_t length) {
// Ensure that OpenSSL's PRNG is properly seeded.
CheckEntropy();
// RAND_bytes() can return 0 to indicate that the entropy data is not truly
// random. That's okay, it's still better than V8's stock source of entropy,
// which is /dev/urandom on UNIX platforms and the current time on Windows.
return RAND_bytes(buffer, length) != -1;
}
int PasswordCallback(char* buf, int size, int rwflag, void* u) {
const char* passphrase = static_cast<char*>(u);
if (passphrase != nullptr) {
size_t buflen = static_cast<size_t>(size);
size_t len = strlen(passphrase);
if (buflen < len)
return -1;
memcpy(buf, passphrase, len);
return len;
}
return -1;
}
// This callback is used to avoid the default passphrase callback in OpenSSL
// which will typically prompt for the passphrase. The prompting is designed
// for the OpenSSL CLI, but works poorly for Node.js because it involves
// synchronous interaction with the controlling terminal, something we never
// want, and use this function to avoid it.
int NoPasswordCallback(char* buf, int size, int rwflag, void* u) {
return 0;
}
void InitCryptoOnce() {
#ifndef OPENSSL_IS_BORINGSSL
OPENSSL_INIT_SETTINGS* settings = OPENSSL_INIT_new();
// --openssl-config=...
if (!per_process::cli_options->openssl_config.empty()) {
const char* conf = per_process::cli_options->openssl_config.c_str();
OPENSSL_INIT_set_config_filename(settings, conf);
}
OPENSSL_init_ssl(0, settings);
OPENSSL_INIT_free(settings);
settings = nullptr;
#endif
#ifdef NODE_FIPS_MODE
/* Override FIPS settings in cnf file, if needed. */
unsigned long err = 0; // NOLINT(runtime/int)
if (per_process::cli_options->enable_fips_crypto ||
per_process::cli_options->force_fips_crypto) {
if (0 == FIPS_mode() && !FIPS_mode_set(1)) {
err = ERR_get_error();
}
}
if (0 != err) {
fprintf(stderr,
"openssl fips failed: %s\n",
ERR_error_string(err, nullptr));
UNREACHABLE();
}
#endif // NODE_FIPS_MODE
// Turn off compression. Saves memory and protects against CRIME attacks.
// No-op with OPENSSL_NO_COMP builds of OpenSSL.
sk_SSL_COMP_zero(SSL_COMP_get_compression_methods());
#ifndef OPENSSL_NO_ENGINE
ERR_load_ENGINE_strings();
ENGINE_load_builtin_engines();
#endif // !OPENSSL_NO_ENGINE
NodeBIO::GetMethod();
}
#ifdef NODE_FIPS_MODE
void GetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(FIPS_mode() ? 1 : 0);
}
void SetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
CHECK(!per_process::cli_options->force_fips_crypto);
Environment* env = Environment::GetCurrent(args);
bool enable = args[0]->BooleanValue(env->isolate());
if (enable == FIPS_mode())
return; // No action needed.
if (!FIPS_mode_set(enable)) {
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
return ThrowCryptoError(env, err);
}
}
#endif /* NODE_FIPS_MODE */
void CryptoErrorVector::Capture() {
clear();
while (auto err = ERR_get_error()) {
char buf[256];
ERR_error_string_n(err, buf, sizeof(buf));
push_back(buf);
}
std::reverse(begin(), end());
}
MaybeLocal<Value> CryptoErrorVector::ToException(
Environment* env,
Local<String> exception_string) const {
if (exception_string.IsEmpty()) {
CryptoErrorVector copy(*this);
if (copy.empty()) copy.push_back("no error"); // But possibly a bug...
// Use last element as the error message, everything else goes
// into the .opensslErrorStack property on the exception object.
auto exception_string =
String::NewFromUtf8(env->isolate(), copy.back().data(),
NewStringType::kNormal, copy.back().size())
.ToLocalChecked();
copy.pop_back();
return copy.ToException(env, exception_string);
}
Local<Value> exception_v = Exception::Error(exception_string);
CHECK(!exception_v.IsEmpty());
if (!empty()) {
CHECK(exception_v->IsObject());
Local<Object> exception = exception_v.As<Object>();
Maybe<bool> ok = exception->Set(env->context(),
env->openssl_error_stack(),
ToV8Value(env->context(), *this).ToLocalChecked());
if (ok.IsNothing())
return MaybeLocal<Value>();
}
return exception_v;
}
ByteSource::ByteSource(ByteSource&& other) noexcept
: data_(other.data_),
allocated_data_(other.allocated_data_),
size_(other.size_) {
other.allocated_data_ = nullptr;
}
ByteSource::~ByteSource() {
OPENSSL_clear_free(allocated_data_, size_);
}
void ByteSource::reset() {
OPENSSL_clear_free(allocated_data_, size_);
data_ = nullptr;
size_ = 0;
}
ByteSource& ByteSource::operator=(ByteSource&& other) noexcept {
if (&other != this) {
OPENSSL_clear_free(allocated_data_, size_);
data_ = other.data_;
allocated_data_ = other.allocated_data_;
other.allocated_data_ = nullptr;
size_ = other.size_;
}
return *this;
}
std::unique_ptr<BackingStore> ByteSource::ReleaseToBackingStore() {
CHECK_NOT_NULL(allocated_data_);
std::unique_ptr<BackingStore> ptr = ArrayBuffer::NewBackingStore(
allocated_data_,
size(),
[](void* data, size_t length, void* deleter_data) {
OPENSSL_clear_free(deleter_data, length);
}, allocated_data_);
CHECK(ptr);
allocated_data_ = nullptr;
data_ = nullptr;
size_ = 0;
return ptr;
}
Local<ArrayBuffer> ByteSource::ToArrayBuffer(Environment* env) {
std::unique_ptr<BackingStore> store = ReleaseToBackingStore();
return ArrayBuffer::New(env->isolate(), std::move(store));
}
const char* ByteSource::get() const {
return data_;
}
size_t ByteSource::size() const {
return size_;
}
ByteSource ByteSource::FromBIO(const BIOPointer& bio) {
CHECK(bio);
BUF_MEM* bptr;
BIO_get_mem_ptr(bio.get(), &bptr);
char* data = MallocOpenSSL<char>(bptr->length);
memcpy(data, bptr->data, bptr->length);
return Allocated(data, bptr->length);
}
ByteSource ByteSource::FromEncodedString(Environment* env,
Local<String> key,
enum encoding enc) {
size_t length = 0;
size_t actual = 0;
char* data = nullptr;
if (StringBytes::Size(env->isolate(), key, enc).To(&length) && length > 0) {
data = MallocOpenSSL<char>(length);
actual = StringBytes::Write(env->isolate(), data, length, key, enc);
CHECK(actual <= length);
if (actual == 0) {
OPENSSL_clear_free(data, length);
data = nullptr;
} else if (actual < length) {
data = reinterpret_cast<char*>(OPENSSL_realloc(data, actual));
}
}
return Allocated(data, actual);
}
ByteSource ByteSource::FromStringOrBuffer(Environment* env,
Local<Value> value) {
return IsAnyByteSource(value) ? FromBuffer(value)
: FromString(env, value.As<String>());
}
ByteSource ByteSource::FromString(Environment* env, Local<String> str,
bool ntc) {
CHECK(str->IsString());
size_t size = str->Utf8Length(env->isolate());
size_t alloc_size = ntc ? size + 1 : size;
char* data = MallocOpenSSL<char>(alloc_size);
int opts = String::NO_OPTIONS;
if (!ntc) opts |= String::NO_NULL_TERMINATION;
str->WriteUtf8(env->isolate(), data, alloc_size, nullptr, opts);
return Allocated(data, size);
}
ByteSource ByteSource::FromBuffer(Local<Value> buffer, bool ntc) {
ArrayBufferOrViewContents<char> buf(buffer);
return ntc ? buf.ToNullTerminatedCopy() : buf.ToByteSource();
}
ByteSource ByteSource::FromSecretKeyBytes(
Environment* env,
Local<Value> value) {
// A key can be passed as a string, buffer or KeyObject with type 'secret'.
// If it is a string, we need to convert it to a buffer. We are not doing that
// in JS to avoid creating an unprotected copy on the heap.
return value->IsString() || IsAnyByteSource(value) ?
ByteSource::FromStringOrBuffer(env, value) :
ByteSource::FromSymmetricKeyObjectHandle(value);
}
ByteSource ByteSource::NullTerminatedCopy(Environment* env,
Local<Value> value) {
return Buffer::HasInstance(value) ? FromBuffer(value, true)
: FromString(env, value.As<String>(), true);
}
ByteSource ByteSource::FromSymmetricKeyObjectHandle(Local<Value> handle) {
CHECK(handle->IsObject());
KeyObjectHandle* key = Unwrap<KeyObjectHandle>(handle.As<Object>());
CHECK_NOT_NULL(key);
return Foreign(key->Data()->GetSymmetricKey(),
key->Data()->GetSymmetricKeySize());
}
ByteSource::ByteSource(const char* data, char* allocated_data, size_t size)
: data_(data),
allocated_data_(allocated_data),
size_(size) {}
ByteSource ByteSource::Allocated(char* data, size_t size) {
return ByteSource(data, data, size);
}
ByteSource ByteSource::Foreign(const char* data, size_t size) {
return ByteSource(data, nullptr, size);
}
namespace error {
Maybe<bool> Decorate(Environment* env, Local<Object> obj,
unsigned long err) { // NOLINT(runtime/int)
if (err == 0) return Just(true); // No decoration necessary.
const char* ls = ERR_lib_error_string(err);
const char* fs = ERR_func_error_string(err);
const char* rs = ERR_reason_error_string(err);
Isolate* isolate = env->isolate();
Local<Context> context = isolate->GetCurrentContext();
if (ls != nullptr) {
if (obj->Set(context, env->library_string(),
OneByteString(isolate, ls)).IsNothing()) {
return Nothing<bool>();
}
}
if (fs != nullptr) {
if (obj->Set(context, env->function_string(),
OneByteString(isolate, fs)).IsNothing()) {
return Nothing<bool>();
}
}
if (rs != nullptr) {
if (obj->Set(context, env->reason_string(),
OneByteString(isolate, rs)).IsNothing()) {
return Nothing<bool>();
}
// SSL has no API to recover the error name from the number, so we
// transform reason strings like "this error" to "ERR_SSL_THIS_ERROR",
// which ends up being close to the original error macro name.
std::string reason(rs);
for (auto& c : reason) {
if (c == ' ')
c = '_';
else
c = ToUpper(c);
}
#define OSSL_ERROR_CODES_MAP(V) \
V(SYS) \
V(BN) \
V(RSA) \
V(DH) \
V(EVP) \
V(BUF) \
V(OBJ) \
V(PEM) \
V(DSA) \
V(X509) \
V(ASN1) \
V(CONF) \
V(CRYPTO) \
V(EC) \
V(SSL) \
V(BIO) \
V(PKCS7) \
V(X509V3) \
V(PKCS12) \
V(RAND) \
V(DSO) \
V(ENGINE) \
V(OCSP) \
V(UI) \
V(COMP) \
V(ECDSA) \
V(ECDH) \
V(OSSL_STORE) \
V(FIPS) \
V(CMS) \
V(TS) \
V(HMAC) \
V(CT) \
V(ASYNC) \
V(KDF) \
V(SM2) \
V(USER) \
#define V(name) case ERR_LIB_##name: lib = #name "_"; break;
const char* lib = "";
const char* prefix = "OSSL_";
switch (ERR_GET_LIB(err)) { OSSL_ERROR_CODES_MAP(V) }
#undef V
#undef OSSL_ERROR_CODES_MAP
// Don't generate codes like "ERR_OSSL_SSL_".
if (lib && strcmp(lib, "SSL_") == 0)
prefix = "";
// All OpenSSL reason strings fit in a single 80-column macro definition,
// all prefix lengths are <= 10, and ERR_OSSL_ is 9, so 128 is more than
// sufficient.
char code[128];
snprintf(code, sizeof(code), "ERR_%s%s%s", prefix, lib, reason.c_str());
if (obj->Set(env->isolate()->GetCurrentContext(),
env->code_string(),
OneByteString(env->isolate(), code)).IsNothing())
return Nothing<bool>();
}
return Just(true);
}
} // namespace error
void ThrowCryptoError(Environment* env,
unsigned long err, // NOLINT(runtime/int)
// Default, only used if there is no SSL `err` which can
// be used to create a long-style message string.
const char* message) {
char message_buffer[128] = {0};
if (err != 0 || message == nullptr) {
ERR_error_string_n(err, message_buffer, sizeof(message_buffer));
message = message_buffer;
}
HandleScope scope(env->isolate());
Local<String> exception_string =
String::NewFromUtf8(env->isolate(), message).ToLocalChecked();
CryptoErrorVector errors;
errors.Capture();
Local<Value> exception;
if (!errors.ToException(env, exception_string).ToLocal(&exception))
return;
Local<Object> obj;
if (!exception->ToObject(env->context()).ToLocal(&obj))
return;
if (error::Decorate(env, obj, err).IsNothing())
return;
env->isolate()->ThrowException(exception);
}
#ifndef OPENSSL_NO_ENGINE
EnginePointer LoadEngineById(const char* id, CryptoErrorVector* errors) {
MarkPopErrorOnReturn mark_pop_error_on_return;
EnginePointer engine(ENGINE_by_id(id));
if (!engine) {
// Engine not found, try loading dynamically.
engine = EnginePointer(ENGINE_by_id("dynamic"));
if (engine) {
if (!ENGINE_ctrl_cmd_string(engine.get(), "SO_PATH", id, 0) ||
!ENGINE_ctrl_cmd_string(engine.get(), "LOAD", nullptr, 0)) {
engine.reset();
}
}
}
if (!engine && errors != nullptr) {
if (ERR_get_error() != 0) {
errors->Capture();
} else {
errors->push_back(std::string("Engine \"") + id + "\" was not found");
}
}
return engine;
}
bool SetEngine(const char* id, uint32_t flags, CryptoErrorVector* errors) {
ClearErrorOnReturn clear_error_on_return;
EnginePointer engine = LoadEngineById(id, errors);
if (!engine)
return false;
if (!ENGINE_set_default(engine.get(), flags)) {
if (errors != nullptr)
errors->Capture();
return false;
}
return true;
}
void SetEngine(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args.Length() >= 2 && args[0]->IsString());
uint32_t flags;
if (!args[1]->Uint32Value(env->context()).To(&flags)) return;
const node::Utf8Value engine_id(env->isolate(), args[0]);
args.GetReturnValue().Set(SetEngine(*engine_id, flags));
}
#endif // !OPENSSL_NO_ENGINE
MaybeLocal<Value> EncodeBignum(
Environment* env,
const BIGNUM* bn,
int size,
Local<Value>* error) {
std::vector<uint8_t> buf(size);
CHECK_EQ(BN_bn2binpad(bn, buf.data(), size), size);
return StringBytes::Encode(
env->isolate(),
reinterpret_cast<const char*>(buf.data()),
buf.size(),
BASE64URL,
error);
}
Maybe<bool> SetEncodedValue(
Environment* env,
Local<Object> target,
Local<String> name,
const BIGNUM* bn,
int size) {
Local<Value> value;
Local<Value> error;
CHECK_NOT_NULL(bn);
if (size == 0)
size = BN_num_bytes(bn);
if (!EncodeBignum(env, bn, size, &error).ToLocal(&value)) {
if (!error.IsEmpty())
env->isolate()->ThrowException(error);
return Nothing<bool>();
}
return target->Set(env->context(), name, value);
}
CryptoJobMode GetCryptoJobMode(v8::Local<v8::Value> args) {
CHECK(args->IsUint32());
uint32_t mode = args.As<v8::Uint32>()->Value();
CHECK_LE(mode, kCryptoJobSync);
return static_cast<CryptoJobMode>(mode);
}
namespace Util {
void Initialize(Environment* env, Local<Object> target) {
#ifndef OPENSSL_NO_ENGINE
env->SetMethod(target, "setEngine", SetEngine);
#endif // !OPENSSL_NO_ENGINE
#ifdef NODE_FIPS_MODE
env->SetMethodNoSideEffect(target, "getFipsCrypto", GetFipsCrypto);
env->SetMethod(target, "setFipsCrypto", SetFipsCrypto);
#endif
NODE_DEFINE_CONSTANT(target, kCryptoJobAsync);
NODE_DEFINE_CONSTANT(target, kCryptoJobSync);
}
} // namespace Util
} // namespace crypto
} // namespace node

674
src/crypto/crypto_util.h Normal file
View File

@ -0,0 +1,674 @@
#ifndef SRC_CRYPTO_CRYPTO_UTIL_H_
#define SRC_CRYPTO_CRYPTO_UTIL_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "env.h"
#include "async_wrap.h"
#include "allocated_buffer.h"
#include "node_errors.h"
#include "node_internals.h"
#include "util.h"
#include "v8.h"
#include "string_bytes.h"
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/ec.h>
#include <openssl/kdf.h>
#include <openssl/rsa.h>
#include <openssl/dsa.h>
#include <openssl/ssl.h>
#ifndef OPENSSL_NO_ENGINE
# include <openssl/engine.h>
#endif // !OPENSSL_NO_ENGINE
#include <algorithm>
#include <memory>
#include <string>
#include <vector>
#include <climits>
#include <cstdio>
namespace node {
namespace crypto {
// Currently known sizes of commonly used OpenSSL struct sizes.
// OpenSSL considers it's various structs to be opaque and the
// sizes may change from one version of OpenSSL to another, so
// these values should not be trusted to remain static. These
// are provided to allow for some close to reasonable memory
// tracking.
constexpr size_t kSizeOf_DH = 144;
constexpr size_t kSizeOf_EC_KEY = 80;
constexpr size_t kSizeOf_EVP_CIPHER_CTX = 168;
constexpr size_t kSizeOf_EVP_MD_CTX = 48;
constexpr size_t kSizeOf_EVP_PKEY = 72;
constexpr size_t kSizeOf_EVP_PKEY_CTX = 80;
constexpr size_t kSizeOf_HMAC_CTX = 32;
// Define smart pointers for the most commonly used OpenSSL types:
using X509Pointer = DeleteFnPtr<X509, X509_free>;
using BIOPointer = DeleteFnPtr<BIO, BIO_free_all>;
using SSLCtxPointer = DeleteFnPtr<SSL_CTX, SSL_CTX_free>;
using SSLSessionPointer = DeleteFnPtr<SSL_SESSION, SSL_SESSION_free>;
using SSLPointer = DeleteFnPtr<SSL, SSL_free>;
using PKCS8Pointer = DeleteFnPtr<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free>;
using EVPKeyPointer = DeleteFnPtr<EVP_PKEY, EVP_PKEY_free>;
using EVPKeyCtxPointer = DeleteFnPtr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>;
using EVPMDPointer = DeleteFnPtr<EVP_MD_CTX, EVP_MD_CTX_free>;
using RSAPointer = DeleteFnPtr<RSA, RSA_free>;
using ECPointer = DeleteFnPtr<EC_KEY, EC_KEY_free>;
using BignumPointer = DeleteFnPtr<BIGNUM, BN_free>;
using NetscapeSPKIPointer = DeleteFnPtr<NETSCAPE_SPKI, NETSCAPE_SPKI_free>;
using ECGroupPointer = DeleteFnPtr<EC_GROUP, EC_GROUP_free>;
using ECPointPointer = DeleteFnPtr<EC_POINT, EC_POINT_free>;
using ECKeyPointer = DeleteFnPtr<EC_KEY, EC_KEY_free>;
using DHPointer = DeleteFnPtr<DH, DH_free>;
using ECDSASigPointer = DeleteFnPtr<ECDSA_SIG, ECDSA_SIG_free>;
using HMACCtxPointer = DeleteFnPtr<HMAC_CTX, HMAC_CTX_free>;
using CipherCtxPointer = DeleteFnPtr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
using RsaPointer = DeleteFnPtr<RSA, RSA_free>;
using DsaPointer = DeleteFnPtr<DSA, DSA_free>;
using EcdsaSigPointer = DeleteFnPtr<ECDSA_SIG, ECDSA_SIG_free>;
// Our custom implementation of the certificate verify callback
// used when establishing a TLS handshake. Because we cannot perform
// I/O quickly enough with X509_STORE_CTX_ APIs in this callback,
// we ignore preverify_ok errors here and let the handshake continue.
// In other words, this VerifyCallback is a non-op. It is imperative
// that the user user Connection::VerifyError after the `secure`
// callback has been made.
extern int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx);
void InitCryptoOnce();
void InitCrypto(v8::Local<v8::Object> target);
extern void UseExtraCaCerts(const std::string& file);
// Forcibly clear OpenSSL's error stack on return. This stops stale errors
// from popping up later in the lifecycle of crypto operations where they
// would cause spurious failures. It's a rather blunt method, though.
// ERR_clear_error() isn't necessarily cheap either.
struct ClearErrorOnReturn {
~ClearErrorOnReturn() { ERR_clear_error(); }
};
// Pop errors from OpenSSL's error stack that were added
// between when this was constructed and destructed.
struct MarkPopErrorOnReturn {
MarkPopErrorOnReturn() { ERR_set_mark(); }
~MarkPopErrorOnReturn() { ERR_pop_to_mark(); }
};
// Ensure that OpenSSL has enough entropy (at least 256 bits) for its PRNG.
// The entropy pool starts out empty and needs to fill up before the PRNG
// can be used securely. Once the pool is filled, it never dries up again;
// its contents is stirred and reused when necessary.
//
// OpenSSL normally fills the pool automatically but not when someone starts
// generating random numbers before the pool is full: in that case OpenSSL
// keeps lowering the entropy estimate to thwart attackers trying to guess
// the initial state of the PRNG.
//
// When that happens, we will have to wait until enough entropy is available.
// That should normally never take longer than a few milliseconds.
//
// OpenSSL draws from /dev/random and /dev/urandom. While /dev/random may
// block pending "true" randomness, /dev/urandom is a CSPRNG that doesn't
// block under normal circumstances.
//
// The only time when /dev/urandom may conceivably block is right after boot,
// when the whole system is still low on entropy. That's not something we can
// do anything about.
void CheckEntropy();
// Generate length bytes of random data. If this returns false, the data
// may not be truly random but it's still generally good enough.
bool EntropySource(unsigned char* buffer, size_t length);
int PasswordCallback(char* buf, int size, int rwflag, void* u);
int NoPasswordCallback(char* buf, int size, int rwflag, void* u);
// Decode is used by the various stream-based crypto utilities to decode
// string input.
template <typename T>
void Decode(const v8::FunctionCallbackInfo<v8::Value>& args,
void (*callback)(T*, const v8::FunctionCallbackInfo<v8::Value>&,
const char*, size_t)) {
T* ctx;
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
if (args[0]->IsString()) {
StringBytes::InlineDecoder decoder;
Environment* env = Environment::GetCurrent(args);
enum encoding enc = ParseEncoding(env->isolate(), args[1], UTF8);
if (decoder.Decode(env, args[0].As<v8::String>(), enc).IsNothing())
return;
callback(ctx, args, decoder.out(), decoder.size());
} else {
ArrayBufferViewContents<char> buf(args[0]);
callback(ctx, args, buf.data(), buf.length());
}
}
// Utility struct used to harvest error information from openssl's error stack
struct CryptoErrorVector : public std::vector<std::string> {
void Capture();
v8::MaybeLocal<v8::Value> ToException(
Environment* env,
v8::Local<v8::String> exception_string = v8::Local<v8::String>()) const;
};
template <typename T>
T* MallocOpenSSL(size_t count) {
void* mem = OPENSSL_malloc(MultiplyWithOverflowCheck(count, sizeof(T)));
CHECK_IMPLIES(mem == nullptr, count == 0);
return static_cast<T*>(mem);
}
template <typename T>
T* ReallocOpenSSL(T* buf, size_t count) {
void* mem = OPENSSL_realloc(buf, MultiplyWithOverflowCheck(count, sizeof(T)));
CHECK_IMPLIES(mem == nullptr, count == 0);
return static_cast<T*>(mem);
}
// A helper class representing a read-only byte array. When deallocated, its
// contents are zeroed.
class ByteSource {
public:
ByteSource() = default;
ByteSource(ByteSource&& other) noexcept;
~ByteSource();
ByteSource& operator=(ByteSource&& other) noexcept;
const char* get() const;
template <typename T>
const T* data() const { return reinterpret_cast<const T*>(get()); }
size_t size() const;
operator bool() const { return data_ != nullptr; }
BignumPointer ToBN() const {
return BignumPointer(BN_bin2bn(
reinterpret_cast<const unsigned char*>(get()),
size(),
nullptr));
}
// Creates a v8::BackingStore that takes over responsibility for
// any allocated data. The ByteSource will be reset with size = 0
// after being called.
std::unique_ptr<v8::BackingStore> ReleaseToBackingStore();
v8::Local<v8::ArrayBuffer> ToArrayBuffer(Environment* env);
void reset();
// Allows an Allocated ByteSource to be truncated.
void Resize(size_t newsize) {
CHECK_LE(newsize, size_);
CHECK_NOT_NULL(allocated_data_);
char* new_data_ = ReallocOpenSSL<char>(allocated_data_, newsize);
data_ = allocated_data_ = new_data_;
size_ = newsize;
}
static ByteSource Allocated(char* data, size_t size);
static ByteSource Foreign(const char* data, size_t size);
static ByteSource FromEncodedString(Environment* env,
v8::Local<v8::String> value,
enum encoding enc = BASE64);
static ByteSource FromStringOrBuffer(Environment* env,
v8::Local<v8::Value> value);
static ByteSource FromString(Environment* env,
v8::Local<v8::String> str,
bool ntc = false);
static ByteSource FromBuffer(v8::Local<v8::Value> buffer,
bool ntc = false);
static ByteSource FromBIO(const BIOPointer& bio);
static ByteSource NullTerminatedCopy(Environment* env,
v8::Local<v8::Value> value);
static ByteSource FromSymmetricKeyObjectHandle(v8::Local<v8::Value> handle);
ByteSource(const ByteSource&) = delete;
ByteSource& operator=(const ByteSource&) = delete;
static ByteSource FromSecretKeyBytes(
Environment* env, v8::Local<v8::Value> value);
private:
const char* data_ = nullptr;
char* allocated_data_ = nullptr;
size_t size_ = 0;
ByteSource(const char* data, char* allocated_data, size_t size);
};
enum CryptoJobMode {
kCryptoJobAsync,
kCryptoJobSync
};
CryptoJobMode GetCryptoJobMode(v8::Local<v8::Value> args);
template <typename CryptoJobTraits>
class CryptoJob : public AsyncWrap, public ThreadPoolWork {
public:
using AdditionalParams = typename CryptoJobTraits::AdditionalParameters;
explicit CryptoJob(
Environment* env,
v8::Local<v8::Object> object,
AsyncWrap::ProviderType type,
CryptoJobMode mode,
AdditionalParams&& params)
: AsyncWrap(env, object, type),
ThreadPoolWork(env),
mode_(mode),
params_(std::move(params)) {
// If the CryptoJob is async, then the instance will be
// cleaned up when AfterThreadPoolWork is called.
if (mode == kCryptoJobSync) MakeWeak();
}
bool IsNotIndicativeOfMemoryLeakAtExit() const override {
// CryptoJobs run a work in the libuv thread pool and may still
// exist when the event loop empties and starts to exit.
return true;
}
void AfterThreadPoolWork(int status) override {
Environment* env = AsyncWrap::env();
CHECK_EQ(mode_, kCryptoJobAsync);
CHECK(status == 0 || status == UV_ECANCELED);
std::unique_ptr<CryptoJob> ptr(this);
// If the job was canceled do not execute the callback.
// TODO(@jasnell): We should likely revisit skipping the
// callback on cancel as that could leave the JS in a pending
// state (e.g. unresolved promises...)
if (status == UV_ECANCELED) return;
v8::HandleScope handle_scope(env->isolate());
v8::Context::Scope context_scope(env->context());
v8::Local<v8::Value> args[2];
if (ptr->ToResult(&args[0], &args[1]).FromJust())
ptr->MakeCallback(env->ondone_string(), arraysize(args), args);
}
virtual v8::Maybe<bool> ToResult(
v8::Local<v8::Value>* err,
v8::Local<v8::Value>* result) = 0;
CryptoJobMode mode() const { return mode_; }
CryptoErrorVector* errors() { return &errors_; }
AdditionalParams* params() { return &params_; }
std::string MemoryInfoName() const override {
return CryptoJobTraits::JobName;
}
void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackField("params", params_);
tracker->TrackField("errors", errors_);
}
static void Run(const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
CryptoJob<CryptoJobTraits>* job;
ASSIGN_OR_RETURN_UNWRAP(&job, args.Holder());
if (job->mode() == kCryptoJobAsync)
return job->ScheduleWork();
v8::Local<v8::Value> ret[2];
env->PrintSyncTrace();
job->DoThreadPoolWork();
if (job->ToResult(&ret[0], &ret[1]).FromJust()) {
args.GetReturnValue().Set(
v8::Array::New(env->isolate(), ret, arraysize(ret)));
}
}
static void Initialize(
v8::FunctionCallback new_fn,
Environment* env,
v8::Local<v8::Object> target) {
v8::Local<v8::FunctionTemplate> job = env->NewFunctionTemplate(new_fn);
v8::Local<v8::String> class_name =
OneByteString(env->isolate(), CryptoJobTraits::JobName);
job->SetClassName(class_name);
job->Inherit(AsyncWrap::GetConstructorTemplate(env));
job->InstanceTemplate()->SetInternalFieldCount(
AsyncWrap::kInternalFieldCount);
env->SetProtoMethod(job, "run", Run);
target->Set(
env->context(),
class_name,
job->GetFunction(env->context()).ToLocalChecked()).Check();
}
private:
const CryptoJobMode mode_;
CryptoErrorVector errors_;
AdditionalParams params_;
};
template <typename DeriveBitsTraits>
class DeriveBitsJob final : public CryptoJob<DeriveBitsTraits> {
public:
using AdditionalParams = typename DeriveBitsTraits::AdditionalParameters;
static void New(const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
CryptoJobMode mode = GetCryptoJobMode(args[0]);
AdditionalParams params;
if (DeriveBitsTraits::AdditionalConfig(mode, args, 1, &params)
.IsNothing()) {
// The DeriveBitsTraits::AdditionalConfig is responsible for
// calling an appropriate THROW_CRYPTO_* variant reporting
// whatever error caused initialization to fail.
return;
}
new DeriveBitsJob(env, args.This(), mode, std::move(params));
}
static void Initialize(
Environment* env,
v8::Local<v8::Object> target) {
CryptoJob<DeriveBitsTraits>::Initialize(New, env, target);
}
DeriveBitsJob(
Environment* env,
v8::Local<v8::Object> object,
CryptoJobMode mode,
AdditionalParams&& params)
: CryptoJob<DeriveBitsTraits>(
env,
object,
DeriveBitsTraits::Provider,
mode,
std::move(params)) {}
void DoThreadPoolWork() override {
if (!DeriveBitsTraits::DeriveBits(
AsyncWrap::env(),
*CryptoJob<DeriveBitsTraits>::params(), &out_)) {
CryptoErrorVector* errors = CryptoJob<DeriveBitsTraits>::errors();
errors->Capture();
if (errors->empty())
errors->push_back("Deriving bits failed");
return;
}
success_ = true;
}
v8::Maybe<bool> ToResult(
v8::Local<v8::Value>* err,
v8::Local<v8::Value>* result) override {
Environment* env = AsyncWrap::env();
CryptoErrorVector* errors = CryptoJob<DeriveBitsTraits>::errors();
if (success_) {
CHECK(errors->empty());
*err = v8::Undefined(env->isolate());
return DeriveBitsTraits::EncodeOutput(
env,
*CryptoJob<DeriveBitsTraits>::params(),
&out_,
result);
}
if (errors->empty())
errors->Capture();
CHECK(!errors->empty());
*result = v8::Undefined(env->isolate());
return v8::Just(errors->ToException(env).ToLocal(err));
}
SET_SELF_SIZE(DeriveBitsJob);
void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackFieldWithSize("out", out_.size());
CryptoJob<DeriveBitsTraits>::MemoryInfo(tracker);
}
private:
ByteSource out_;
bool success_ = false;
};
void ThrowCryptoError(Environment* env,
unsigned long err, // NOLINT(runtime/int)
const char* message = nullptr);
#ifndef OPENSSL_NO_ENGINE
struct EnginePointer {
ENGINE* engine = nullptr;
bool finish_on_exit = false;
inline EnginePointer() = default;
inline explicit EnginePointer(ENGINE* engine_, bool finish_on_exit_ = false)
: engine(engine_),
finish_on_exit(finish_on_exit_) {}
inline EnginePointer(EnginePointer&& other) noexcept
: engine(other.engine),
finish_on_exit(other.finish_on_exit) {
other.release();
}
inline ~EnginePointer() { reset(); }
inline EnginePointer& operator=(EnginePointer&& other) noexcept {
if (this == &other) return *this;
this->~EnginePointer();
return *new (this) EnginePointer(std::move(other));
}
inline operator bool() const { return engine != nullptr; }
inline ENGINE* get() { return engine; }
inline void reset(ENGINE* engine_ = nullptr, bool finish_on_exit_ = false) {
if (engine != nullptr) {
if (finish_on_exit)
ENGINE_finish(engine);
ENGINE_free(engine);
}
engine = engine_;
finish_on_exit = finish_on_exit_;
}
inline ENGINE* release() {
ENGINE* ret = engine;
engine = nullptr;
finish_on_exit = false;
return ret;
}
};
EnginePointer LoadEngineById(const char* id, CryptoErrorVector* errors);
bool SetEngine(
const char* id,
uint32_t flags,
CryptoErrorVector* errors = nullptr);
void SetEngine(const v8::FunctionCallbackInfo<v8::Value>& args);
#endif // !OPENSSL_NO_ENGINE
#ifdef NODE_FIPS_MODE
void GetFipsCrypto(const v8::FunctionCallbackInfo<v8::Value>& args);
void SetFipsCrypto(const v8::FunctionCallbackInfo<v8::Value>& args);
#endif /* NODE_FIPS_MODE */
class CipherPushContext {
public:
inline explicit CipherPushContext(Environment* env) : env_(env) {}
inline void push_back(const char* str) {
list_.emplace_back(OneByteString(env_->isolate(), str));
}
inline v8::Local<v8::Array> ToJSArray() {
return v8::Array::New(env_->isolate(), list_.data(), list_.size());
}
private:
std::vector<v8::Local<v8::Value>> list_;
Environment* env_;
};
template <class TypeName>
void array_push_back(const TypeName* md,
const char* from,
const char* to,
void* arg) {
static_cast<CipherPushContext*>(arg)->push_back(from);
}
inline bool IsAnyByteSource(v8::Local<v8::Value> arg) {
return arg->IsArrayBufferView() ||
arg->IsArrayBuffer() ||
arg->IsSharedArrayBuffer();
}
template <typename T>
class ArrayBufferOrViewContents {
public:
ArrayBufferOrViewContents() = default;
inline explicit ArrayBufferOrViewContents(v8::Local<v8::Value> buf) {
CHECK(IsAnyByteSource(buf));
if (buf->IsArrayBufferView()) {
auto view = buf.As<v8::ArrayBufferView>();
offset_ = view->ByteOffset();
length_ = view->ByteLength();
store_ = view->Buffer()->GetBackingStore();
} else if (buf->IsArrayBuffer()) {
auto ab = buf.As<v8::ArrayBuffer>();
offset_ = 0;
length_ = ab->ByteLength();
store_ = ab->GetBackingStore();
} else {
auto sab = buf.As<v8::SharedArrayBuffer>();
offset_ = 0;
length_ = sab->ByteLength();
store_ = sab->GetBackingStore();
}
}
inline const T* data() const {
// Ideally, these would return nullptr if IsEmpty() or length_ is zero,
// but some of the openssl API react badly if given a nullptr even when
// length is zero, so we have to return something.
if (size() == 0)
return &buf;
return reinterpret_cast<T*>(store_->Data()) + offset_;
}
inline T* data() {
// Ideally, these would return nullptr if IsEmpty() or length_ is zero,
// but some of the openssl API react badly if given a nullptr even when
// length is zero, so we have to return something.
if (size() == 0)
return &buf;
return reinterpret_cast<T*>(store_->Data()) + offset_;
}
inline size_t size() const { return length_; }
// In most cases, input buffer sizes passed in to openssl need to
// be limited to <= INT_MAX. This utility method helps us check.
inline bool CheckSizeInt32() { return size() <= INT_MAX; }
inline ByteSource ToByteSource() const {
return ByteSource::Foreign(data(), size());
}
inline ByteSource ToCopy() const {
if (size() == 0) return ByteSource();
char* buf = MallocOpenSSL<char>(size());
CHECK_NOT_NULL(buf);
memcpy(buf, data(), size());
return ByteSource::Allocated(buf, size());
}
inline ByteSource ToNullTerminatedCopy() const {
if (size() == 0) return ByteSource();
char* buf = MallocOpenSSL<char>(size() + 1);
CHECK_NOT_NULL(buf);
buf[size()] = 0;
memcpy(buf, data(), size());
return ByteSource::Allocated(buf, size());
}
template <typename M>
void CopyTo(M* dest, size_t len) const {
static_assert(sizeof(M) == 1, "sizeof(M) must equal 1");
len = std::min(len, size());
if (len > 0 && data() != nullptr)
memcpy(dest, data(), len);
}
private:
T buf = 0;
size_t offset_ = 0;
size_t length_ = 0;
std::shared_ptr<v8::BackingStore> store_;
};
template <typename T>
std::vector<T> CopyBuffer(const ArrayBufferOrViewContents<T>& buf) {
std::vector<T> vec;
vec->resize(buf.size());
if (vec->size() > 0 && buf.data() != nullptr)
memcpy(vec->data(), buf.data(), vec->size());
return vec;
}
template <typename T>
std::vector<T> CopyBuffer(v8::Local<v8::Value> buf) {
return CopyBuffer(ArrayBufferOrViewContents<T>(buf));
}
v8::MaybeLocal<v8::Value> EncodeBignum(
Environment* env,
const BIGNUM* bn,
v8::Local<v8::Value>* error);
v8::Maybe<bool> SetEncodedValue(
Environment* env,
v8::Local<v8::Object> target,
v8::Local<v8::String> name,
const BIGNUM* bn,
int size = 0);
namespace Util {
void Initialize(Environment* env, v8::Local<v8::Object> target);
} // namespace Util
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_UTIL_H_

View File

@ -215,6 +215,7 @@ constexpr size_t kFsStatsBufferLength =
V(destroyed_string, "destroyed") \
V(detached_string, "detached") \
V(dh_string, "DH") \
V(divisor_length_string, "divisorLength") \
V(dns_a_string, "A") \
V(dns_aaaa_string, "AAAA") \
V(dns_cname_string, "CNAME") \
@ -277,6 +278,23 @@ constexpr size_t kFsStatsBufferLength =
V(isclosing_string, "isClosing") \
V(issuer_string, "issuer") \
V(issuercert_string, "issuerCertificate") \
V(jwk_d_string, "d") \
V(jwk_dp_string, "dp") \
V(jwk_dq_string, "dq") \
V(jwk_dsa_string, "DSA") \
V(jwk_e_string, "e") \
V(jwk_ec_string, "EC") \
V(jwk_g_string, "g") \
V(jwk_k_string, "k") \
V(jwk_p_string, "p") \
V(jwk_q_string, "q") \
V(jwk_qi_string, "qi") \
V(jwk_kty_string, "kty") \
V(jwk_n_string, "n") \
V(jwk_oct_string, "oct") \
V(jwk_rsa_string, "RSA") \
V(jwk_x_string, "x") \
V(jwk_y_string, "y") \
V(kill_signal_string, "killSignal") \
V(kind_string, "kind") \
V(length_string, "length") \
@ -290,7 +308,9 @@ constexpr size_t kFsStatsBufferLength =
V(minttl_string, "minttl") \
V(module_string, "module") \
V(modulus_string, "modulus") \
V(modulus_length_string, "modulusLength") \
V(name_string, "name") \
V(named_curve_string, "namedCurve") \
V(netmask_string, "netmask") \
V(next_string, "next") \
V(nistcurve_string, "nistCurve") \
@ -339,6 +359,7 @@ constexpr size_t kFsStatsBufferLength =
V(promise_string, "promise") \
V(psk_string, "psk") \
V(pubkey_string, "pubkey") \
V(public_exponent_string, "publicExponent") \
V(query_string, "query") \
V(http3_alpn_string, "h3-29") \
V(rate_string, "rate") \

View File

@ -3,11 +3,10 @@
#include "inspector_socket_server.h"
#include "inspector/main_thread_interface.h"
#include "inspector/node_string.h"
#include "allocated_buffer-inl.h" // Inlined functions needed by node_crypto.h.
#include "crypto/crypto_util.h"
#include "base_object-inl.h"
#include "debug_utils-inl.h"
#include "node.h"
#include "node_crypto.h"
#include "node_internals.h"
#include "node_mutex.h"
#include "v8-inspector.h"

View File

@ -635,7 +635,18 @@ inline void NODE_SET_PROTOTYPE_METHOD(v8::Local<v8::FunctionTemplate> recv,
#define NODE_SET_PROTOTYPE_METHOD node::NODE_SET_PROTOTYPE_METHOD
// BINARY is a deprecated alias of LATIN1.
enum encoding {ASCII, UTF8, BASE64, UCS2, BINARY, HEX, BUFFER, LATIN1 = BINARY};
// BASE64URL is not currently exposed to the JavaScript side.
enum encoding {
ASCII,
UTF8,
BASE64,
UCS2,
BINARY,
HEX,
BUFFER,
BASE64URL,
LATIN1 = BINARY
};
NODE_EXTERN enum encoding ParseEncoding(
v8::Isolate* isolate,

File diff suppressed because it is too large Load Diff

View File

@ -24,842 +24,32 @@
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
// ClientHelloParser
#include "crypto/crypto_clienthello.h"
#include "allocated_buffer.h"
#include "env.h"
#include "base_object.h"
#include "util.h"
#include "node_messaging.h"
#include "v8.h"
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/bn.h>
#include <openssl/dh.h>
#include <openssl/ec.h>
#include <openssl/rsa.h>
namespace node {
namespace crypto {
// Forcibly clear OpenSSL's error stack on return. This stops stale errors
// from popping up later in the lifecycle of crypto operations where they
// would cause spurious failures. It's a rather blunt method, though.
// ERR_clear_error() isn't necessarily cheap either.
struct ClearErrorOnReturn {
~ClearErrorOnReturn() { ERR_clear_error(); }
};
// Pop errors from OpenSSL's error stack that were added
// between when this was constructed and destructed.
struct MarkPopErrorOnReturn {
MarkPopErrorOnReturn() { ERR_set_mark(); }
~MarkPopErrorOnReturn() { ERR_pop_to_mark(); }
};
// Define smart pointers for the most commonly used OpenSSL types:
using X509Pointer = DeleteFnPtr<X509, X509_free>;
using BIOPointer = DeleteFnPtr<BIO, BIO_free_all>;
using SSLCtxPointer = DeleteFnPtr<SSL_CTX, SSL_CTX_free>;
using SSLSessionPointer = DeleteFnPtr<SSL_SESSION, SSL_SESSION_free>;
using SSLPointer = DeleteFnPtr<SSL, SSL_free>;
using PKCS8Pointer = DeleteFnPtr<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free>;
using EVPKeyPointer = DeleteFnPtr<EVP_PKEY, EVP_PKEY_free>;
using EVPKeyCtxPointer = DeleteFnPtr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>;
using EVPMDPointer = DeleteFnPtr<EVP_MD_CTX, EVP_MD_CTX_free>;
using RSAPointer = DeleteFnPtr<RSA, RSA_free>;
using ECPointer = DeleteFnPtr<EC_KEY, EC_KEY_free>;
using BignumPointer = DeleteFnPtr<BIGNUM, BN_free>;
using NetscapeSPKIPointer = DeleteFnPtr<NETSCAPE_SPKI, NETSCAPE_SPKI_free>;
using ECGroupPointer = DeleteFnPtr<EC_GROUP, EC_GROUP_free>;
using ECPointPointer = DeleteFnPtr<EC_POINT, EC_POINT_free>;
using ECKeyPointer = DeleteFnPtr<EC_KEY, EC_KEY_free>;
using DHPointer = DeleteFnPtr<DH, DH_free>;
using ECDSASigPointer = DeleteFnPtr<ECDSA_SIG, ECDSA_SIG_free>;
extern int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx);
extern void UseExtraCaCerts(const std::string& file);
void InitCryptoOnce();
class SecureContext final : public BaseObject {
public:
~SecureContext() override;
static void Initialize(Environment* env, v8::Local<v8::Object> target);
SSL_CTX* operator*() const { return ctx_.get(); }
// TODO(joyeecheung): track the memory used by OpenSSL types
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(SecureContext)
SET_SELF_SIZE(SecureContext)
SSLCtxPointer ctx_;
X509Pointer cert_;
X509Pointer issuer_;
#ifndef OPENSSL_NO_ENGINE
bool client_cert_engine_provided_ = false;
std::unique_ptr<ENGINE, std::function<void(ENGINE*)>> private_key_engine_;
#endif // !OPENSSL_NO_ENGINE
static const int kMaxSessionSize = 10 * 1024;
// See TicketKeyCallback
static const int kTicketKeyReturnIndex = 0;
static const int kTicketKeyHMACIndex = 1;
static const int kTicketKeyAESIndex = 2;
static const int kTicketKeyNameIndex = 3;
static const int kTicketKeyIVIndex = 4;
unsigned char ticket_key_name_[16];
unsigned char ticket_key_aes_[16];
unsigned char ticket_key_hmac_[16];
protected:
// OpenSSL structures are opaque. This is sizeof(SSL_CTX) for OpenSSL 1.1.1b:
static const int64_t kExternalSize = 1024;
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Init(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetKey(const v8::FunctionCallbackInfo<v8::Value>& args);
#ifndef OPENSSL_NO_ENGINE
static void SetEngineKey(const v8::FunctionCallbackInfo<v8::Value>& args);
#endif // !OPENSSL_NO_ENGINE
static void SetCert(const v8::FunctionCallbackInfo<v8::Value>& args);
static void AddCACert(const v8::FunctionCallbackInfo<v8::Value>& args);
static void AddCRL(const v8::FunctionCallbackInfo<v8::Value>& args);
static void AddRootCerts(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetCipherSuites(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetCiphers(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetSigalgs(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetECDHCurve(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetDHParam(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetOptions(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetSessionIdContext(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetSessionTimeout(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetMinProto(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetMaxProto(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetMinProto(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetMaxProto(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Close(const v8::FunctionCallbackInfo<v8::Value>& args);
static void LoadPKCS12(const v8::FunctionCallbackInfo<v8::Value>& args);
#ifndef OPENSSL_NO_ENGINE
static void SetClientCertEngine(
const v8::FunctionCallbackInfo<v8::Value>& args);
#endif // !OPENSSL_NO_ENGINE
static void GetTicketKeys(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetTicketKeys(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetFreeListLength(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void EnableTicketKeyCallback(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void CtxGetter(const v8::FunctionCallbackInfo<v8::Value>& info);
template <bool primary>
static void GetCertificate(const v8::FunctionCallbackInfo<v8::Value>& args);
static int TicketKeyCallback(SSL* ssl,
unsigned char* name,
unsigned char* iv,
EVP_CIPHER_CTX* ectx,
HMAC_CTX* hctx,
int enc);
static int TicketCompatibilityCallback(SSL* ssl,
unsigned char* name,
unsigned char* iv,
EVP_CIPHER_CTX* ectx,
HMAC_CTX* hctx,
int enc);
SecureContext(Environment* env, v8::Local<v8::Object> wrap);
void Reset();
};
// SSLWrap implicitly depends on the inheriting class' handle having an
// internal pointer to the Base class.
template <class Base>
class SSLWrap {
public:
enum Kind {
kClient,
kServer
};
SSLWrap(Environment* env, SecureContext* sc, Kind kind)
: env_(env),
kind_(kind),
next_sess_(nullptr),
session_callbacks_(false),
awaiting_new_session_(false),
cert_cb_(nullptr),
cert_cb_arg_(nullptr),
cert_cb_running_(false) {
ssl_.reset(SSL_new(sc->ctx_.get()));
CHECK(ssl_);
env_->isolate()->AdjustAmountOfExternalAllocatedMemory(kExternalSize);
}
virtual ~SSLWrap() {
DestroySSL();
}
inline void enable_session_callbacks() { session_callbacks_ = true; }
inline bool is_server() const { return kind_ == kServer; }
inline bool is_client() const { return kind_ == kClient; }
inline bool is_awaiting_new_session() const { return awaiting_new_session_; }
inline bool is_waiting_cert_cb() const { return cert_cb_ != nullptr; }
void MemoryInfo(MemoryTracker* tracker) const;
protected:
typedef void (*CertCb)(void* arg);
// OpenSSL structures are opaque. Estimate SSL memory size for OpenSSL 1.1.1b:
// SSL: 6224
// SSL->SSL3_STATE: 1040
// ...some buffers: 42 * 1024
// NOTE: Actually it is much more than this
static const int64_t kExternalSize = 6224 + 1040 + 42 * 1024;
static void ConfigureSecureContext(SecureContext* sc);
static void AddMethods(Environment* env, v8::Local<v8::FunctionTemplate> t);
static SSL_SESSION* GetSessionCallback(SSL* s,
const unsigned char* key,
int len,
int* copy);
static int NewSessionCallback(SSL* s, SSL_SESSION* sess);
static void KeylogCallback(const SSL* s, const char* line);
static void OnClientHello(void* arg,
const ClientHelloParser::ClientHello& hello);
static void GetPeerCertificate(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetCertificate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetFinished(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPeerFinished(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetSession(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetSession(const v8::FunctionCallbackInfo<v8::Value>& args);
static void LoadSession(const v8::FunctionCallbackInfo<v8::Value>& args);
static void IsSessionReused(const v8::FunctionCallbackInfo<v8::Value>& args);
static void VerifyError(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetCipher(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetSharedSigalgs(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ExportKeyingMaterial(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void EndParser(const v8::FunctionCallbackInfo<v8::Value>& args);
static void CertCbDone(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Renegotiate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetTLSTicket(const v8::FunctionCallbackInfo<v8::Value>& args);
static void NewSessionDone(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetOCSPResponse(const v8::FunctionCallbackInfo<v8::Value>& args);
static void RequestOCSP(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetEphemeralKeyInfo(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetProtocol(const v8::FunctionCallbackInfo<v8::Value>& args);
#ifdef SSL_set_max_send_fragment
static void SetMaxSendFragment(
const v8::FunctionCallbackInfo<v8::Value>& args);
#endif // SSL_set_max_send_fragment
static void GetALPNNegotiatedProto(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetALPNProtocols(const v8::FunctionCallbackInfo<v8::Value>& args);
static int SelectALPNCallback(SSL* s,
const unsigned char** out,
unsigned char* outlen,
const unsigned char* in,
unsigned int inlen,
void* arg);
static int TLSExtStatusCallback(SSL* s, void* arg);
static int SSLCertCallback(SSL* s, void* arg);
void DestroySSL();
void WaitForCertCb(CertCb cb, void* arg);
int SetCACerts(SecureContext* sc);
inline Environment* ssl_env() const {
return env_;
}
Environment* const env_;
Kind kind_;
SSLSessionPointer next_sess_;
SSLPointer ssl_;
bool session_callbacks_;
bool awaiting_new_session_;
// SSL_set_cert_cb
CertCb cert_cb_;
void* cert_cb_arg_;
bool cert_cb_running_;
ClientHelloParser hello_parser_;
v8::Global<v8::ArrayBufferView> ocsp_response_;
BaseObjectPtr<SecureContext> sni_context_;
friend class SecureContext;
};
// A helper class representing a read-only byte array. When deallocated, its
// contents are zeroed.
class ByteSource {
public:
ByteSource() = default;
ByteSource(ByteSource&& other);
~ByteSource();
ByteSource& operator=(ByteSource&& other);
const char* get() const;
size_t size() const;
inline operator bool() const {
return data_ != nullptr;
}
static ByteSource Allocated(char* data, size_t size);
static ByteSource Foreign(const char* data, size_t size);
static ByteSource FromStringOrBuffer(Environment* env,
v8::Local<v8::Value> value);
static ByteSource FromString(Environment* env,
v8::Local<v8::String> str,
bool ntc = false);
static ByteSource FromBuffer(v8::Local<v8::Value> buffer,
bool ntc = false);
static ByteSource NullTerminatedCopy(Environment* env,
v8::Local<v8::Value> value);
static ByteSource FromSymmetricKeyObjectHandle(v8::Local<v8::Value> handle);
ByteSource(const ByteSource&) = delete;
ByteSource& operator=(const ByteSource&) = delete;
private:
const char* data_ = nullptr;
char* allocated_data_ = nullptr;
size_t size_ = 0;
ByteSource(const char* data, char* allocated_data, size_t size);
};
enum PKEncodingType {
// RSAPublicKey / RSAPrivateKey according to PKCS#1.
kKeyEncodingPKCS1,
// PrivateKeyInfo or EncryptedPrivateKeyInfo according to PKCS#8.
kKeyEncodingPKCS8,
// SubjectPublicKeyInfo according to X.509.
kKeyEncodingSPKI,
// ECPrivateKey according to SEC1.
kKeyEncodingSEC1
};
enum PKFormatType {
kKeyFormatDER,
kKeyFormatPEM
};
struct AsymmetricKeyEncodingConfig {
bool output_key_object_;
PKFormatType format_;
v8::Maybe<PKEncodingType> type_ = v8::Nothing<PKEncodingType>();
};
typedef AsymmetricKeyEncodingConfig PublicKeyEncodingConfig;
struct PrivateKeyEncodingConfig : public AsymmetricKeyEncodingConfig {
const EVP_CIPHER* cipher_;
ByteSource passphrase_;
};
enum KeyType {
kKeyTypeSecret,
kKeyTypePublic,
kKeyTypePrivate
};
// This uses the built-in reference counter of OpenSSL to manage an EVP_PKEY
// which is slightly more efficient than using a shared pointer and easier to
// use.
class ManagedEVPPKey {
public:
ManagedEVPPKey() = default;
explicit ManagedEVPPKey(EVPKeyPointer&& pkey);
ManagedEVPPKey(const ManagedEVPPKey& that);
ManagedEVPPKey& operator=(const ManagedEVPPKey& that);
operator bool() const;
EVP_PKEY* get() const;
private:
EVPKeyPointer pkey_;
};
// Objects of this class can safely be shared among threads.
class KeyObjectData {
public:
static std::shared_ptr<KeyObjectData> CreateSecret(
v8::Local<v8::ArrayBufferView> abv);
static std::shared_ptr<KeyObjectData> CreateAsymmetric(
KeyType type, const ManagedEVPPKey& pkey);
KeyType GetKeyType() const;
// These functions allow unprotected access to the raw key material and should
// only be used to implement cryptographic operations requiring the key.
ManagedEVPPKey GetAsymmetricKey() const;
const char* GetSymmetricKey() const;
size_t GetSymmetricKeySize() const;
private:
KeyObjectData(std::unique_ptr<char, std::function<void(char*)>> symmetric_key,
unsigned int symmetric_key_len)
: key_type_(KeyType::kKeyTypeSecret),
symmetric_key_(std::move(symmetric_key)),
symmetric_key_len_(symmetric_key_len),
asymmetric_key_() {}
KeyObjectData(KeyType type, const ManagedEVPPKey& pkey)
: key_type_(type),
symmetric_key_(),
symmetric_key_len_(0),
asymmetric_key_{pkey} {}
const KeyType key_type_;
const std::unique_ptr<char, std::function<void(char*)>> symmetric_key_;
const unsigned int symmetric_key_len_;
const ManagedEVPPKey asymmetric_key_;
};
class KeyObjectHandle : public BaseObject {
public:
static v8::Local<v8::Function> Initialize(Environment* env);
static v8::MaybeLocal<v8::Object> Create(Environment* env,
std::shared_ptr<KeyObjectData> data);
// TODO(tniessen): track the memory used by OpenSSL types
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(KeyObjectHandle)
SET_SELF_SIZE(KeyObjectHandle)
const std::shared_ptr<KeyObjectData>& Data();
protected:
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Init(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetAsymmetricKeyType(
const v8::FunctionCallbackInfo<v8::Value>& args);
v8::Local<v8::Value> GetAsymmetricKeyType() const;
static void GetSymmetricKeySize(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void Export(const v8::FunctionCallbackInfo<v8::Value>& args);
v8::Local<v8::Value> ExportSecretKey() const;
v8::MaybeLocal<v8::Value> ExportPublicKey(
const PublicKeyEncodingConfig& config) const;
v8::MaybeLocal<v8::Value> ExportPrivateKey(
const PrivateKeyEncodingConfig& config) const;
KeyObjectHandle(Environment* env,
v8::Local<v8::Object> wrap);
private:
std::shared_ptr<KeyObjectData> data_;
};
class NativeKeyObject : public BaseObject {
public:
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(NativeKeyObject)
SET_SELF_SIZE(NativeKeyObject)
class KeyObjectTransferData : public worker::TransferData {
public:
explicit KeyObjectTransferData(const std::shared_ptr<KeyObjectData>& data)
: data_(data) {}
BaseObjectPtr<BaseObject> Deserialize(
Environment* env,
v8::Local<v8::Context> context,
std::unique_ptr<worker::TransferData> self) override;
SET_MEMORY_INFO_NAME(KeyObjectTransferData)
SET_SELF_SIZE(KeyObjectTransferData)
SET_NO_MEMORY_INFO()
private:
std::shared_ptr<KeyObjectData> data_;
};
BaseObject::TransferMode GetTransferMode() const override;
std::unique_ptr<worker::TransferData> CloneForMessaging() const override;
private:
NativeKeyObject(Environment* env,
v8::Local<v8::Object> wrap,
const std::shared_ptr<KeyObjectData>& handle_data)
: BaseObject(env, wrap),
handle_data_(handle_data) {
MakeWeak();
}
std::shared_ptr<KeyObjectData> handle_data_;
};
class CipherBase : public BaseObject {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
// TODO(joyeecheung): track the memory used by OpenSSL types
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(CipherBase)
SET_SELF_SIZE(CipherBase)
protected:
enum CipherKind {
kCipher,
kDecipher
};
enum UpdateResult {
kSuccess,
kErrorMessageSize,
kErrorState
};
enum AuthTagState {
kAuthTagUnknown,
kAuthTagKnown,
kAuthTagPassedToOpenSSL
};
static const unsigned kNoAuthTagLength = static_cast<unsigned>(-1);
void CommonInit(const char* cipher_type,
const EVP_CIPHER* cipher,
const unsigned char* key,
int key_len,
const unsigned char* iv,
int iv_len,
unsigned int auth_tag_len);
void Init(const char* cipher_type,
const char* key_buf,
int key_buf_len,
unsigned int auth_tag_len);
void InitIv(const char* cipher_type,
const unsigned char* key,
int key_len,
const unsigned char* iv,
int iv_len,
unsigned int auth_tag_len);
bool InitAuthenticated(const char* cipher_type, int iv_len,
unsigned int auth_tag_len);
bool CheckCCMMessageLength(int message_len);
UpdateResult Update(const char* data, int len, AllocatedBuffer* out);
bool Final(AllocatedBuffer* out);
bool SetAutoPadding(bool auto_padding);
bool IsAuthenticatedMode() const;
bool SetAAD(const char* data, unsigned int len, int plaintext_len);
bool MaybePassAuthTagToOpenSSL();
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Init(const v8::FunctionCallbackInfo<v8::Value>& args);
static void InitIv(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Update(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Final(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetAutoPadding(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetAuthTag(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetAuthTag(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetAAD(const v8::FunctionCallbackInfo<v8::Value>& args);
CipherBase(Environment* env, v8::Local<v8::Object> wrap, CipherKind kind);
private:
DeleteFnPtr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free> ctx_;
const CipherKind kind_;
AuthTagState auth_tag_state_;
unsigned int auth_tag_len_;
char auth_tag_[EVP_GCM_TLS_TAG_LEN];
bool pending_auth_failed_;
int max_message_size_;
};
class Hmac : public BaseObject {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
// TODO(joyeecheung): track the memory used by OpenSSL types
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(Hmac)
SET_SELF_SIZE(Hmac)
protected:
void HmacInit(const char* hash_type, const char* key, int key_len);
bool HmacUpdate(const char* data, int len);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void HmacInit(const v8::FunctionCallbackInfo<v8::Value>& args);
static void HmacUpdate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void HmacDigest(const v8::FunctionCallbackInfo<v8::Value>& args);
Hmac(Environment* env, v8::Local<v8::Object> wrap);
private:
DeleteFnPtr<HMAC_CTX, HMAC_CTX_free> ctx_;
};
class Hash final : public BaseObject {
public:
~Hash() override;
static void Initialize(Environment* env, v8::Local<v8::Object> target);
// TODO(joyeecheung): track the memory used by OpenSSL types
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(Hash)
SET_SELF_SIZE(Hash)
bool HashInit(const EVP_MD* md, v8::Maybe<unsigned int> xof_md_len);
bool HashUpdate(const char* data, int len);
protected:
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void HashUpdate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void HashDigest(const v8::FunctionCallbackInfo<v8::Value>& args);
Hash(Environment* env, v8::Local<v8::Object> wrap);
private:
EVPMDPointer mdctx_;
bool has_md_;
unsigned int md_len_;
unsigned char* md_value_;
};
class SignBase : public BaseObject {
public:
typedef enum {
kSignOk,
kSignUnknownDigest,
kSignInit,
kSignNotInitialised,
kSignUpdate,
kSignPrivateKey,
kSignPublicKey,
kSignMalformedSignature
} Error;
SignBase(Environment* env, v8::Local<v8::Object> wrap);
Error Init(const char* sign_type);
Error Update(const char* data, int len);
// TODO(joyeecheung): track the memory used by OpenSSL types
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(SignBase)
SET_SELF_SIZE(SignBase)
protected:
void CheckThrow(Error error);
EVPMDPointer mdctx_;
};
enum DSASigEnc {
kSigEncDER, kSigEncP1363
};
class Sign : public SignBase {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
struct SignResult {
Error error;
AllocatedBuffer signature;
explicit SignResult(
Error err,
AllocatedBuffer&& sig = AllocatedBuffer())
: error(err), signature(std::move(sig)) {}
};
SignResult SignFinal(
const ManagedEVPPKey& pkey,
int padding,
const v8::Maybe<int>& saltlen,
DSASigEnc dsa_sig_enc);
protected:
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SignInit(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SignUpdate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SignFinal(const v8::FunctionCallbackInfo<v8::Value>& args);
Sign(Environment* env, v8::Local<v8::Object> wrap);
};
class Verify : public SignBase {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
Error VerifyFinal(const ManagedEVPPKey& key,
const ByteSource& sig,
int padding,
const v8::Maybe<int>& saltlen,
bool* verify_result);
protected:
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void VerifyInit(const v8::FunctionCallbackInfo<v8::Value>& args);
static void VerifyUpdate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void VerifyFinal(const v8::FunctionCallbackInfo<v8::Value>& args);
Verify(Environment* env, v8::Local<v8::Object> wrap);
};
class PublicKeyCipher {
public:
typedef int (*EVP_PKEY_cipher_init_t)(EVP_PKEY_CTX* ctx);
typedef int (*EVP_PKEY_cipher_t)(EVP_PKEY_CTX* ctx,
unsigned char* out, size_t* outlen,
const unsigned char* in, size_t inlen);
enum Operation {
kPublic,
kPrivate
};
template <Operation operation,
EVP_PKEY_cipher_init_t EVP_PKEY_cipher_init,
EVP_PKEY_cipher_t EVP_PKEY_cipher>
static bool Cipher(Environment* env,
const ManagedEVPPKey& pkey,
int padding,
const EVP_MD* digest,
const void* oaep_label,
size_t oaep_label_size,
const unsigned char* data,
int len,
AllocatedBuffer* out);
template <Operation operation,
EVP_PKEY_cipher_init_t EVP_PKEY_cipher_init,
EVP_PKEY_cipher_t EVP_PKEY_cipher>
static void Cipher(const v8::FunctionCallbackInfo<v8::Value>& args);
};
class DiffieHellman : public BaseObject {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
bool Init(int primeLength, int g);
bool Init(const char* p, int p_len, int g);
bool Init(const char* p, int p_len, const char* g, int g_len);
protected:
static void DiffieHellmanGroup(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GenerateKeys(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPrime(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetGenerator(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPrivateKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ComputeSecret(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetPublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetPrivateKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void VerifyErrorGetter(
const v8::FunctionCallbackInfo<v8::Value>& args);
DiffieHellman(Environment* env, v8::Local<v8::Object> wrap);
// TODO(joyeecheung): track the memory used by OpenSSL types
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(DiffieHellman)
SET_SELF_SIZE(DiffieHellman)
private:
static void GetField(const v8::FunctionCallbackInfo<v8::Value>& args,
const BIGNUM* (*get_field)(const DH*),
const char* err_if_null);
static void SetKey(const v8::FunctionCallbackInfo<v8::Value>& args,
int (*set_field)(DH*, BIGNUM*), const char* what);
bool VerifyContext();
int verifyError_;
DHPointer dh_;
};
class ECDH final : public BaseObject {
public:
~ECDH() override;
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static ECPointPointer BufferToPoint(Environment* env,
const EC_GROUP* group,
v8::Local<v8::Value> buf);
// TODO(joyeecheung): track the memory used by OpenSSL types
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(ECDH)
SET_SELF_SIZE(ECDH)
protected:
ECDH(Environment* env, v8::Local<v8::Object> wrap, ECKeyPointer&& key);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GenerateKeys(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ComputeSecret(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPrivateKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetPrivateKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetPublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);
bool IsKeyPairValid();
bool IsKeyValidForCurve(const BignumPointer& private_key);
ECKeyPointer key_;
const EC_GROUP* group_;
};
bool EntropySource(unsigned char* buffer, size_t length);
#ifndef OPENSSL_NO_ENGINE
void SetEngine(const v8::FunctionCallbackInfo<v8::Value>& args);
#endif // !OPENSSL_NO_ENGINE
void InitCrypto(v8::Local<v8::Object> target);
void ThrowCryptoError(Environment* env,
unsigned long err, // NOLINT(runtime/int)
const char* message = nullptr);
template <typename T>
inline T* MallocOpenSSL(size_t count) {
void* mem = OPENSSL_malloc(MultiplyWithOverflowCheck(count, sizeof(T)));
CHECK_IMPLIES(mem == nullptr, count == 0);
return static_cast<T*>(mem);
}
} // namespace crypto
} // namespace node
// All of the crypto definitions previously contained in this header
// have been split across multiple headers in src/crypto. This header
// remains for convenience for any code that still imports it. New
// code should include the relevant src/crypto headers directly.
#include "crypto/crypto_aes.h"
#include "crypto/crypto_bio.h"
#include "crypto/crypto_cipher.h"
#include "crypto/crypto_context.h"
#include "crypto/crypto_dh.h"
#include "crypto/crypto_dsa.h"
#include "crypto/crypto_ecdh.h"
#include "crypto/crypto_groups.h"
#include "crypto/crypto_hash.h"
#include "crypto/crypto_hkdf.h"
#include "crypto/crypto_hmac.h"
#include "crypto/crypto_keygen.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_pbkdf2.h"
#include "crypto/crypto_random.h"
#include "crypto/crypto_rsa.h"
#include "crypto/crypto_scrypt.h"
#include "crypto/crypto_sig.h"
#include "crypto/crypto_spkac.h"
#include "crypto/crypto_ssl.h"
#include "crypto/crypto_timing.h"
#include "crypto/crypto_util.h"
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

View File

@ -33,9 +33,26 @@ void OnFatalError(const char* location, const char* message);
V(ERR_BUFFER_TOO_LARGE, Error) \
V(ERR_CONSTRUCT_CALL_REQUIRED, TypeError) \
V(ERR_CONSTRUCT_CALL_INVALID, TypeError) \
V(ERR_CRYPTO_INITIALIZATION_FAILED, Error) \
V(ERR_CRYPTO_INVALID_AUTH_TAG, TypeError) \
V(ERR_CRYPTO_INVALID_COUNTER, TypeError) \
V(ERR_CRYPTO_INVALID_CURVE, TypeError) \
V(ERR_CRYPTO_INVALID_DIGEST, TypeError) \
V(ERR_CRYPTO_INVALID_IV, TypeError) \
V(ERR_CRYPTO_INVALID_JWK, TypeError) \
V(ERR_CRYPTO_INVALID_KEYLEN, RangeError) \
V(ERR_CRYPTO_INVALID_KEYPAIR, RangeError) \
V(ERR_CRYPTO_INVALID_KEYTYPE, RangeError) \
V(ERR_CRYPTO_INVALID_MESSAGELEN, RangeError) \
V(ERR_CRYPTO_INVALID_SCRYPT_PARAMS, RangeError) \
V(ERR_CRYPTO_INVALID_STATE, Error) \
V(ERR_CRYPTO_INVALID_TAG_LENGTH, RangeError) \
V(ERR_CRYPTO_OPERATION_FAILED, Error) \
V(ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH, RangeError) \
V(ERR_CRYPTO_UNKNOWN_CIPHER, Error) \
V(ERR_CRYPTO_UNKNOWN_DH_GROUP, Error) \
V(ERR_CRYPTO_UNSUPPORTED_OPERATION, Error) \
V(ERR_CRYPTO_JOB_INIT_FAILED, Error) \
V(ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE, Error) \
V(ERR_INVALID_ARG_VALUE, TypeError) \
V(ERR_OSSL_EVP_INVALID_DIGEST, Error) \
@ -89,10 +106,27 @@ void OnFatalError(const char* location, const char* message);
"Buffer is not available for the current Context") \
V(ERR_CONSTRUCT_CALL_INVALID, "Constructor cannot be called") \
V(ERR_CONSTRUCT_CALL_REQUIRED, "Cannot call constructor without `new`") \
V(ERR_CRYPTO_INITIALIZATION_FAILED, "Initialization failed") \
V(ERR_CRYPTO_INVALID_AUTH_TAG, "Invalid authentication tag") \
V(ERR_CRYPTO_INVALID_COUNTER, "Invalid counter") \
V(ERR_CRYPTO_INVALID_CURVE, "Invalid EC curve name") \
V(ERR_CRYPTO_INVALID_DIGEST, "Invalid digest") \
V(ERR_CRYPTO_INVALID_IV, "Invalid initialization vector") \
V(ERR_CRYPTO_INVALID_JWK, "Invalid JWK format") \
V(ERR_CRYPTO_INVALID_KEYLEN, "Invalid key length") \
V(ERR_CRYPTO_INVALID_KEYPAIR, "Invalid key pair") \
V(ERR_CRYPTO_INVALID_KEYTYPE, "Invalid key type") \
V(ERR_CRYPTO_INVALID_MESSAGELEN, "Invalid message length") \
V(ERR_CRYPTO_INVALID_SCRYPT_PARAMS, "Invalid scrypt params") \
V(ERR_CRYPTO_INVALID_STATE, "Invalid state") \
V(ERR_CRYPTO_INVALID_TAG_LENGTH, "Invalid taglength") \
V(ERR_CRYPTO_OPERATION_FAILED, "Operation failed") \
V(ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH, \
"Input buffers must have the same byte length") \
V(ERR_CRYPTO_UNKNOWN_CIPHER, "Unknown cipher") \
V(ERR_CRYPTO_UNKNOWN_DH_GROUP, "Unknown DH group") \
V(ERR_CRYPTO_UNSUPPORTED_OPERATION, "Unsupported crypto operation") \
V(ERR_CRYPTO_JOB_INIT_FAILED, "Failed to initialize crypto job config") \
V(ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE, \
"Context not associated with Node.js environment") \
V(ERR_INVALID_TRANSFER_OBJECT, "Found invalid object in transferList") \

View File

@ -93,6 +93,7 @@ void NativeModuleLoader::InitializeModuleCategories() {
#if !HAVE_OPENSSL
"crypto",
"crypto/promises",
"https",
"http2",
"tls",

View File

@ -60,7 +60,7 @@ void PromiseRejectCallback(PromiseRejectMessage message) {
Environment* env = Environment::GetCurrent(isolate);
if (env == nullptr) return;
if (env == nullptr || !env->can_call_into_js()) return;
Local<Function> callback = env->promise_reject_callback();
// The promise is rejected before JS land calls SetPromiseRejectCallback

View File

@ -1,7 +1,7 @@
#include "debug_utils-inl.h"
#include "node.h"
#include "env-inl.h"
#include "node_crypto.h" // SecureContext
#include "crypto/crypto_context.h"
#include "crypto/crypto_common.h"
#include "node_errors.h"
#include "node_process.h"

View File

@ -1,6 +1,8 @@
#include "node_quic_crypto.h"
#include "env-inl.h"
#include "node_crypto.h"
#include "crypto/crypto_util.h"
#include "crypto/crypto_context.h"
#include "crypto/crypto_common.h"
#include "node_process.h"
#include "node_quic_session-inl.h"

View File

@ -358,6 +358,8 @@ size_t StringBytes::Write(Isolate* isolate,
break;
}
case BASE64URL:
// Fall through
case BASE64:
if (str->IsExternalOneByte()) {
auto ext = str->GetExternalOneByteStringResource();
@ -425,6 +427,8 @@ Maybe<size_t> StringBytes::StorageSize(Isolate* isolate,
data_size = str->Length() * sizeof(uint16_t);
break;
case BASE64URL:
// Fall through
case BASE64:
data_size = base64_decoded_size_fast(str->Length());
break;
@ -466,6 +470,8 @@ Maybe<size_t> StringBytes::Size(Isolate* isolate,
case UCS2:
return Just(str->Length() * sizeof(uint16_t));
case BASE64URL:
// Fall through
case BASE64: {
String::Value value(isolate, str);
return Just(base64_decoded_size(*value, value.length()));
@ -691,6 +697,20 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate,
return ExternOneByteString::New(isolate, dst, dlen, error);
}
case BASE64URL: {
size_t dlen = base64_encoded_size(buflen, Base64Mode::URL);
char* dst = node::UncheckedMalloc(dlen);
if (dst == nullptr) {
*error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate);
return MaybeLocal<Value>();
}
size_t written = base64_encode(buf, buflen, dst, dlen, Base64Mode::URL);
CHECK_EQ(written, dlen);
return ExternOneByteString::New(isolate, dst, dlen, error);
}
case HEX: {
size_t dlen = buflen * 2;
char* dst = node::UncheckedMalloc(dlen);

View File

@ -25,10 +25,10 @@
#include "debug_utils-inl.h"
#include "memory_tracker-inl.h"
#include "node_buffer.h" // Buffer
#include "node_crypto.h" // SecureContext
#include "crypto/crypto_context.h"
#include "crypto/crypto_util.h"
#include "crypto/crypto_bio.h" // NodeBIO
// ClientHelloParser
#include "crypto/crypto_clienthello-inl.h"
#include "crypto/crypto_clienthello-inl.h" // ClientHelloParser
#include "node_errors.h"
#include "stream_base-inl.h"
#include "util-inl.h"

View File

@ -25,6 +25,7 @@
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "node_crypto.h" // SSLWrap
#include "crypto/crypto_ssl.h"
#include "allocated_buffer.h"
#include "async_wrap.h"

View File

@ -44,6 +44,20 @@ TEST(Base64Test, Encode) {
"IGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVtLg==");
}
TEST(Base64Test, EncodeURL) {
auto test = [](const char* string, const char* base64_string) {
const size_t len = strlen(base64_string);
char* const buffer = new char[len + 1];
buffer[len] = 0;
base64_encode(string, strlen(string), buffer, len, node::Base64Mode::URL);
EXPECT_STREQ(base64_string, buffer);
delete[] buffer;
};
test("\x68\xd9\x16\x25\x5c\x1e\x40\x92\x2d\xfb", "aNkWJVweQJIt-w");
test("\xac\xc7\x93\xaa\x83\x6f\xc3\xe3\x3f\x75", "rMeTqoNvw-M_dQ");
}
TEST(Base64Test, Decode) {
auto test = [](const char* base64_string, const char* string) {
const size_t len = strlen(string);
@ -75,6 +89,7 @@ TEST(Base64Test, Decode) {
test("YWJj ZGVm", "abcdef");
test("Y W J j Z G V m", "abcdef");
test("Y W\n JjZ \nG Vm", "abcdef");
test("rMeTqoNvw-M_dQ", "\xac\xc7\x93\xaa\x83\x6f\xc3\xe3\x3f\x75");
const char* text =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "

144
test/fixtures/crypto/aes_cbc.js vendored Normal file
View File

@ -0,0 +1,144 @@
'use strict';
module.exports = function() {
const kPlaintext =
Buffer.from('546869732073706563696669636174696f6e206465736372696265' +
'732061204a6176615363726970742041504920666f722070657266' +
'6f726d696e672062617369632063727970746f6772617068696320' +
'6f7065726174696f6e7320696e20776562206170706c6963617469' +
'6f6e732c20737563682061732068617368696e672c207369676e61' +
'747572652067656e65726174696f6e20616e642076657269666963' +
'6174696f6e2c20616e6420656e6372797074696f6e20616e642064' +
'656372797074696f6e2e204164646974696f6e616c6c792c206974' +
'2064657363726962657320616e2041504920666f72206170706c69' +
'636174696f6e7320746f2067656e657261746520616e642f6f7220' +
'6d616e61676520746865206b6579696e67206d6174657269616c20' +
'6e656365737361727920746f20706572666f726d20746865736520' +
'6f7065726174696f6e732e205573657320666f7220746869732041' +
'50492072616e67652066726f6d2075736572206f72207365727669' +
'63652061757468656e7469636174696f6e2c20646f63756d656e74' +
'206f7220636f6465207369676e696e672c20616e64207468652063' +
'6f6e666964656e7469616c69747920616e6420696e746567726974' +
'79206f6620636f6d6d756e69636174696f6e732e', 'hex');
const kKeyBytes = {
'128': Buffer.from('dec0d4fcbf3c4741c892dabd1cd4c04e', 'hex'),
'256': Buffer.from('67693823fb1d58073f91ece9cc3af910e5532616a4d27b1' +
'3eb7b74d8000bbf30', 'hex')
}
const iv = Buffer.from('55aaf89ba89413d54ea727a76c27a284', 'hex');
const kCipherText = {
'128': Buffer.from(
'237f03fee70872e78faec148ddbd01bd77cb96e3381ef4ece2afea17a7afd37' +
'ccbe461df9c4d58aea6bbbae1b05cfab1e129877cd756c6867c319a3ce05da5' +
'0cbef5f1a4f7dce345f269d06cdec1df00e2d927a04e93bf2699e8ceddfe19b' +
'9f907b5d76862a3c2a167a1eda70af2255002ffad60146aaa6e5026887f1055' +
'f44eac386a0373823aba81ecfffbb270189f52fc01b2845c287d12877440b21' +
'fae577272da4e6f00effc4f3f773a764e37f92482e1cd0d4c61d6faaee84367' +
'd3b2ce2081bcf364473f9a9fc87d228a2749824b61cbcc6ff44bbab52bcfaf9' +
'262cf1b175a90a113ebc75d62ee48869ddccf42a7ec5e390003cafa371aa314' +
'85bf43143f96cb57d82c39bcec40506f441a0c0aa35203bf1347bac4b154f40' +
'74e29accb1be1e76cce8dddfdccdc8614823671517fc51b65799fdfc173be0c' +
'99aee7c45c8e9c3dbd031299cebe3aff9a7342176b5edc9cdce4f14206b82ce' +
'ef933f06d8ed0bd0b7546aad9aad842e712af79dd101d8b37675bef6f1d6c5e' +
'b38a8649821d45b6c0f996a54f2f5bcbe23f57343cacbfbeb3ab9bcd58ac6f3' +
'b28c6fad194b173c8282ba5a74374409ff051fdeb898431dfd6ac35072fb8df' +
'783b33217c93dd1b3c10fe187373d64b496188d6d1b16a47fed35e3968aaa82' +
'3255dcbc7261c54', 'hex'),
'256': Buffer.from(
'29d5798cb5e3c86164853ae36a73193f4d331a39ee8c633f47d38054731aec3' +
'46751910e65a1b53a87c138a7d6dc053455deb71b6586569b40947cd4dbfb41' +
'2a202c80023280dd16ee38bd531c7a799dd7879780e9c141be5694bf8cc4780' +
'8ac64a6fe29f54b3806a6f4b26fea17046b061684bbe61147ac71ee4904b45a' +
'674d2533767081eec707de7aad1ee8b2e9ea90620eea704d443e3e9fe665622' +
'b02cc459c566880228007ad5a7821683b2dfb5d33f0e83c5ebd865a14b87a1d' +
'e155d526749f50456aa8ecc9458c62f02da085e16a2df5d4a0b0801b7299b69' +
'091d648c48ab7573df59638529ee032727d7aaca181ea463ff5881e880980dc' +
'e59ddec395bd46084728c35d1b07eaa4af66c99573f8b37d427ac21a3ddac6b' +
'5988cc730941f0ef1c5034680ef20560fd756f5be5f8d296f00e81c984357c5' +
'ff760dfb475416e786bcaf738a25c705eec70263cb4b3ee71596ef5ec9b9db3' +
'ad2e497834c94683c4a5206a831fbb603e8add2c91365a6075e0bc2d392e54b' +
'f10f32bb24af4ee362e0035fd15d7e70b21d126cf1e84fd22902eed0beab869' +
'3bcbfe57a20d1a67681df82d6c359435eda9bb90090ff84d5193b53f2394594' +
'6d853da31ed6fe36a903d94d427bc1ccc76d7b31badfe508e6a4abc491e10a6' +
'ff86fa4d836e1fd', 'hex')
};
const kBadPadding = {
'128': {
zeroPadChar: Buffer.from('ee1bf8a9da8aa456cf6624df06a64d0e', 'hex'),
bigPadChar: Buffer.from('5b437768fceeaf90114b0ca3d4342e33', 'hex'),
inconsistentPadChars:
Buffer.from('876570d0036ae21419db4f5e3ad4f2c0', 'hex')
},
'256': {
zeroPadChar: Buffer.from('01fd8dd61ec1fe448cc89d6ec859b181', 'hex'),
bigPadChar: Buffer.from('58076edd4a22616d6319bdde5e5a1b3c', 'hex'),
inconsistentPadChars:
Buffer.from('98363c943b88c1154d8caa43784a6a3e', 'hex')
}
};
const kKeyLengths = [128, 256];
const passing = [];
kKeyLengths.forEach((keyLength) => {
passing.push({
keyBuffer: kKeyBytes[keyLength],
algorithm: { name: 'AES-CBC', iv },
plaintext: kPlaintext,
result: kCipherText[keyLength]
});
});
const failing = [];
kKeyLengths.forEach((keyLength) => {
failing.push({
keyBuffer: kKeyBytes[keyLength],
algorithm: {
name: 'AES-CBC',
iv: iv.slice(0, 8)
},
plaintext: kPlaintext,
result: kCipherText[keyLength]
});
const longIv = new Uint8Array(24);
longIv.set(iv, 0);
longIv.set(iv.slice(0, 8), 16);
failing.push({
keyBuffer: kKeyBytes[keyLength],
algorithm: { name: 'AES-CBC', iv: longIv },
plaintext: kPlaintext,
result: kCipherText[keyLength]
});
});
// Scenarios that should fail decryption because of bad padding
const decryptionFailing = [];
kKeyLengths.forEach(function(keyLength) {
[
'zeroPadChar',
'bigPadChar',
'inconsistentPadChars'
].forEach((paddingProblem) => {
const badCiphertext =
new Uint8Array(kCipherText[keyLength].byteLength);
badCiphertext.set(
kCipherText[keyLength]
.slice(0, kCipherText[keyLength].byteLength - 16));
badCiphertext.set(kBadPadding[keyLength][paddingProblem]);
decryptionFailing.push({
keyBuffer: kKeyBytes[keyLength],
algorithm: { name: 'AES-CBC', iv },
plaintext: kPlaintext,
result: badCiphertext
});
});
});
return { passing, failing, decryptionFailing };
};

99
test/fixtures/crypto/aes_ctr.js vendored Normal file
View File

@ -0,0 +1,99 @@
'use strict';
module.exports = function() {
const kPlaintext =
Buffer.from('546869732073706563696669636174696f6e206465736372696265' +
'732061204a6176615363726970742041504920666f722070657266' +
'6f726d696e672062617369632063727970746f6772617068696320' +
'6f7065726174696f6e7320696e20776562206170706c6963617469' +
'6f6e732c20737563682061732068617368696e672c207369676e61' +
'747572652067656e65726174696f6e20616e642076657269666963' +
'6174696f6e2c20616e6420656e6372797074696f6e20616e642064' +
'656372797074696f6e2e204164646974696f6e616c6c792c206974' +
'2064657363726962657320616e2041504920666f72206170706c69' +
'636174696f6e7320746f2067656e657261746520616e642f6f7220' +
'6d616e61676520746865206b6579696e67206d6174657269616c20' +
'6e656365737361727920746f20706572666f726d20746865736520' +
'6f7065726174696f6e732e205573657320666f7220746869732041' +
'50492072616e67652066726f6d2075736572206f72207365727669' +
'63652061757468656e7469636174696f6e2c20646f63756d656e74' +
'206f7220636f6465207369676e696e672c20616e64207468652063' +
'6f6e666964656e7469616c69747920616e6420696e746567726974' +
'79206f6620636f6d6d756e69636174696f6e732e', 'hex');
const kKeyBytes = {
'128': Buffer.from('dec0d4fcbf3c4741c892dabd1cd4c04e', 'hex'),
'256': Buffer.from('67693823fb1d58073f91ece9cc3af910e5532616a4d27b1' +
'3eb7b74d8000bbf30', 'hex')
}
const counter = Buffer.from('55aaf89ba89413d54ea727a76c27a284', 'hex');
const kCiphertext = {
'128': Buffer.from(
'e91175fda4f5ea57c52b0d000bbe98af68c0a59058aeed8ab5b7063503a1ce4' +
'70d79dad174f90aaafaa5449d848dc8b2c557d1e7fa4b9a41a2fb1e9fea1414' +
'b593dab40c04f14b4f81400fe43c93990181b096a15561169aea177f100416e' +
'20b6810b00ee1b04fef67f3bede28baf4d41d397daf1511e9020d7766e9e604' +
'10de38e1432dbffa0f992dc1f0d4756544e8c765af7df706f90e009db9384c3' +
'3e44dea543c2a77bbd52022de41e7d71a498de7feb9760eb47e503366c88dcc' +
'2d1a387788de2d8f78e72c2bdd8815bc8a54e8d0eee275683ca5041290f031a' +
'd5a4454efa17cc4907718f3ef4b75fedbd13583254f441a15a8a3323b12f40b' +
'8fbebc816cf9b468d8d7a5a0fb548498c39a6ed84615f894929838aef8e3016' +
'60f76b632493f23709fedfd5e107f78267f331b60a38c146f9710484a4acdef' +
'f110b3b7745ff83aa8cb5de9e15b11e20a785572041f2852a1981156edcf07e' +
'46eb64144449cce74b9cc94163a6fda8ae19219721d60b757b5b5ec718dabd5' +
'0954b6e6a393f656f6346f40229d0c50e01c15701f2a4fe5d25a174edf9b90e' +
'e0c0ebf9e06b5fe00558638a1ea3781403b0c9206d9e814d6a79fb7a56060e1' +
'c7176af36c6a1ad635981a9bfd8007d8cf6d9f93f0e8e22b93a9a2ccd7090ab' +
'1df63cea3f040', 'hex'),
'256': Buffer.from(
'37529a432f50ba4e53385f8266ec3deccceceade7ae29395e9291076c95bb9a' +
'24f4792fcdd6ea5894b815edb5d5e4022fabe055a06b1a7e01979555b579838' +
'64bf23019cb1b37ffdadb057f728cfb2af0a33d146344cfba0accb4dbf613a7' +
'bee523ca6d6860e474a9c0f4d068d4c0acd94cc55cbf21e4285ca15116c9702' +
'0f2c33b4585008f8fe97c9e29c0627c5d47c48d94be88b9b16c7f2df740a8d2' +
'a07556305b82b919f7a87ca2ed19db27262c277c213f2a7eca25e5a6adbea43' +
'0ba2e1061198171054285aff9e0869c638dcd524cbf1f255da675acad6d7867' +
'9a9958b7a8f9bb21dd9c580ad196f9a0e4c6a6500d7bb21df74cd5934ce3c4d' +
'8d1f39d34a2adb58d224c48097887cde9d3be146a3ea3bade4c6864cf9e445b' +
'5c4c2b3ef4e2b8f5eea0ab1c0b9abe7a4fe5b2c0b1d94df6b12953d3273260e' +
'80bd094dec97a3177a9cec0b5042be1804040c9439403b8f72f7426fa756ad6' +
'266cf2c8659e740329dd0d24f9f85497662cad739f71d6174011c77f8f31fb4' +
'4226288dfb86817ef17116321c71bb9ed97db6e990f62058580f006683431f2' +
'29662f1d5e3cdaffe0335467ca72635688c939ec8b32d6465f651a635f73c0a' +
'4e7f0aadb0e81f5bcbfaec2671ac97fdc2fd32f24c941775c37a6810d4b171b' +
'c8aba90a86603', 'hex')
};
const kKeyLengths = [128, 256];
const passing = [];
kKeyLengths.forEach((keyLength) => {
passing.push({
keyBuffer: kKeyBytes[keyLength],
algorithm: {name: 'AES-CTR', counter, length: 64},
plaintext: kPlaintext,
result: kCiphertext[keyLength]
});
});
const failing = [];
kKeyLengths.forEach((keyLength) => {
failing.push({
keyBuffer: kKeyBytes[keyLength],
algorithm: {name: 'AES-CTR', counter, length: 0},
plaintext: kPlaintext,
result: kCiphertext[keyLength]
});
failing.push({
keyBuffer: kKeyBytes[keyLength],
algorithm: {name: 'AES-CTR', counter, length: 129},
plaintext: kPlaintext,
result: kCiphertext[keyLength]
});
});
return { passing, failing, decryptionFailing: [] };
};

134
test/fixtures/crypto/aes_gcm.js vendored Normal file
View File

@ -0,0 +1,134 @@
'use strict';
module.exports = function() {
const kPlaintext =
Buffer.from('546869732073706563696669636174696f6e206465736372696265' +
'732061204a6176615363726970742041504920666f722070657266' +
'6f726d696e672062617369632063727970746f6772617068696320' +
'6f7065726174696f6e7320696e20776562206170706c6963617469' +
'6f6e732c20737563682061732068617368696e672c207369676e61' +
'747572652067656e65726174696f6e20616e642076657269666963' +
'6174696f6e2c20616e6420656e6372797074696f6e20616e642064' +
'656372797074696f6e2e204164646974696f6e616c6c792c206974' +
'2064657363726962657320616e2041504920666f72206170706c69' +
'636174696f6e7320746f2067656e657261746520616e642f6f7220' +
'6d616e61676520746865206b6579696e67206d6174657269616c20' +
'6e656365737361727920746f20706572666f726d20746865736520' +
'6f7065726174696f6e732e205573657320666f7220746869732041' +
'50492072616e67652066726f6d2075736572206f72207365727669' +
'63652061757468656e7469636174696f6e2c20646f63756d656e74' +
'206f7220636f6465207369676e696e672c20616e64207468652063' +
'6f6e666964656e7469616c69747920616e6420696e746567726974' +
'79206f6620636f6d6d756e69636174696f6e732e', 'hex');
const kKeyBytes = {
'128': Buffer.from('dec0d4fcbf3c4741c892dabd1cd4c04e', 'hex'),
'256': Buffer.from('67693823fb1d58073f91ece9cc3af910e5532616a4d27b1' +
'3eb7b74d8000bbf30', 'hex')
}
const iv = Buffer.from('3a92732aa6ea39bf3986e0c73fa920002' +
'02175385ef8adeac2c87335eb928dd4', 'hex');
const additionalData = Buffer.from(
'5468657265206172652037206675727468657220656469746f72696' +
'16c206e6f74657320696e2074686520646f63756d656e742e', 'hex');
const tag = {
'128': Buffer.from('c2e2c6fdef1cc5f07bd8b097efc8b8b7', 'hex'),
'256': Buffer.from('bceff1309f15d500f12a554cc21c313c', 'hex')
};
const tag_with_empty_ad = {
'128': Buffer.from('de330b1724defaf81b621e519623dcc6', 'hex'),
'256': Buffer.from('f4ba56cb9a25bff8f6398b82e02fd9ee', 'hex')
};
// AES-GCM produces ciphertext and a tag.
const kCiphertext = {
'128': Buffer.from(
'b4f128b7693493eee0afafeca8f4f17909cae1ed38d8fdfeba666fcfe4be82b19ff6' +
'0635f971e4fe517efdbf642bfb936b5ba6e7c9f1b4d6702f7ba4ba86364116b5c952' +
'ec3b348bac2729597b3e66a75296fa5d60a98759f5ffa4c0a99f19108b914c049083' +
'94c5cc2e176ec1e47f78f21836f0b5a262f4f944867a7e97266c7444966d26c2159f' +
'8ccdb7236197ba789116eb16d2dfbb8fa2b75dc468336035eafab84ced9d25cbe257' +
'de4bf05fdade4051a54bc9d8be0d74d945422fa144f74afd9db5a27935205b7ce669' +
'e011bb323d4d674f4739a374ea951b69181f9f0380822a5e7dc88efb94c91195e854' +
'321112cbbae2a4e3ca4c4110a3e084341f658148ab9f2ab1fd6256c95f753e0ccd4e' +
'247ec47959b925a142b575ba477c846e781bf6a3120d5ac87f52d1f1aa49f78960f4' +
'fefb77479c1b6b35212d16009030200b74157df6d9ab9ee08eea8df2a8599a42e3a1' +
'b66001584e0c07ef1ece1f596f6b2a25f194e80108fb7592b70930275e3b46e61aa5' +
'619c8c8d1f3e0ace3730cf00c5cac56c85af5004109adfff04c4bcb2f01d0d7805e1' +
'ca0323e19e5c9849cd6b9de0f563c2ab9cf5f7b7a5283ec86e1d97ce64af5824f25a' +
'045249fa8cf5d9099923f2ce4ec579730f508065bff05b97f93e3ef412031187ded2' +
'5d957b', 'hex'),
'256': Buffer.from(
'0861eb7146208783d2d17ca0ffb6091d7dc11bf0812e0289a98e3d079136aacf9f6f' +
'275f573fa21b0612dbd774225a3972f4669143063398f7a5f27464dbb148b1116e43' +
'5ddb64d914cf599a2d25695343a28ceb8128b1caae3694379cc1e8f986a3c3337274' +
'4126496360f9e0451177babcb52b4e9c4c8ae23f05f8095e1a0102eb27ae4a2fb716' +
'282f2f0d64770c43b2b838a7ee8f0d2cd0b9976c0611347ab6d2cf2adb254a5e7e24' +
'f9252004da2cee4538db1f4dad2ebb672470d5fc2857a4f0a39f20817db26c2f1c1f' +
'242a73240e91c39cbf2ea3f9b51f5a491e4839df3f3c4f8c0e751f91de9c79ed2091' +
'8f600cfe2315153ba8ab9ad9003bcaaf67d6c0af1a122b36b0de4b16077afde0913d' +
'2ad049ed548dd1d5e42ef43b0944062358bd0a3e09551c2c521399a0b2f038a0f4c9' +
'ad4d3d14e31eb4a71069b9c15fcf2917864ec6b65d1859f7e74be9c289f272c2be82' +
'8aee5e89c1c27389becfa9539b0ed2a081c3a1eaddff7243620c5d2941b7f467f765' +
'52f67d577d4e15ba66cd142820c9ae0f34f0d9b4a26c06d3291287e8b812bca99dbe' +
'4ca64bb07f27fb16cb995031f17c89977bcc2b9fbeb1c41275a92e98fb2d19a41b91' +
'd6e4370f0283d850ffccaf643b910f6728212dffc8feac8a143a57b6c094db2958e6' +
'e546f9', 'hex')
};
const kKeyLengths = [128, 256];
const kTagLengths = [32, 64, 96, 104, 112, 120, 128];
const passing = [];
kKeyLengths.forEach((keyLength) => {
kTagLengths.forEach((tagLength) => {
const byteCount = tagLength / 8;
const result =
new Uint8Array(kCiphertext[keyLength].byteLength + byteCount);
result.set(kCiphertext[keyLength], 0);
result.set(tag[keyLength].slice(0, byteCount),
kCiphertext[keyLength].byteLength);
passing.push({
keyBuffer: kKeyBytes[keyLength],
algorithm: { name: 'AES-GCM', iv, additionalData, tagLength },
plaintext: kPlaintext,
result
});
const noadresult =
new Uint8Array(kCiphertext[keyLength].byteLength + byteCount);
noadresult.set(kCiphertext[keyLength], 0);
noadresult.set(tag_with_empty_ad[keyLength].slice(0, byteCount),
kCiphertext[keyLength].byteLength);
passing.push({
keyBuffer: kKeyBytes[keyLength],
algorithm: { name: 'AES-GCM', iv, tagLength },
plaintext: kPlaintext,
result: noadresult
});
});
});
const failing = [];
kKeyLengths.forEach((keyLength) => {
[24, 48, 72, 95, 129, 256].forEach((badTagLength) => {
failing.push({
keyBuffer: kKeyBytes[keyLength],
algorithm: {
name: 'AES-GCM',
iv,
additionalData,
tagLength: badTagLength
},
plaintext: kPlaintext,
result: kCiphertext[keyLength]
});
});
});
return { passing, failing, decryptionFailing: [] };
};

Some files were not shown because too many files have changed in this diff Show More