mirror of
https://github.com/nodejs/node.git
synced 2025-05-09 15:46:27 +00:00
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:
parent
ba77dc8597
commit
dae283d96f
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -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
44
benchmark/crypto/hkdf.js
Normal 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);
|
||||
}
|
71
benchmark/crypto/keygen.js
Normal file
71
benchmark/crypto/keygen.js
Normal 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);
|
||||
}
|
58
benchmark/crypto/webcrypto-digest.js
Normal file
58
benchmark/crypto/webcrypto-digest.js
Normal 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
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
1672
doc/api/webcrypto.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
342
lib/internal/crypto/aes.js
Normal 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,
|
||||
};
|
@ -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() {
|
||||
|
@ -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))
|
||||
|
@ -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
265
lib/internal/crypto/dsa.js
Normal 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
304
lib/internal/crypto/ec.js
Normal 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,
|
||||
};
|
@ -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,
|
||||
};
|
||||
|
90
lib/internal/crypto/hashnames.js
Normal file
90
lib/internal/crypto/hashnames.js
Normal 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
183
lib/internal/crypto/hkdf.js
Normal 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,
|
||||
};
|
@ -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,
|
||||
};
|
||||
|
@ -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
211
lib/internal/crypto/mac.js
Normal 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,
|
||||
};
|
@ -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,
|
||||
};
|
||||
|
@ -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
371
lib/internal/crypto/rsa.js
Normal 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,
|
||||
};
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
|
760
lib/internal/crypto/webcrypto.js
Normal file
760
lib/internal/crypto/webcrypto.js
Normal 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;
|
@ -123,6 +123,7 @@ primordials.SafePromise = makeSafe(
|
||||
'BigInt64Array',
|
||||
'BigUint64Array',
|
||||
'Boolean',
|
||||
'DataView',
|
||||
'Date',
|
||||
'Error',
|
||||
'EvalError',
|
||||
|
54
node.gyp
54
node.gyp
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
34
src/base64.h
34
src/base64.h
@ -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
|
||||
|
||||
|
||||
|
@ -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
349
src/crypto/README.md
Normal 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
597
src/crypto/crypto_aes.cc
Normal 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
89
src/crypto/crypto_aes.h
Normal 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_
|
@ -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>
|
||||
|
||||
|
@ -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
848
src/crypto/crypto_cipher.cc
Normal 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
278
src/crypto/crypto_cipher.h
Normal 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, ¶ms)
|
||||
.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_
|
@ -29,7 +29,6 @@
|
||||
|
||||
namespace node {
|
||||
namespace crypto {
|
||||
|
||||
inline ClientHelloParser::ClientHelloParser()
|
||||
: state_(kEnded),
|
||||
onhello_cb_(nullptr),
|
||||
|
@ -24,7 +24,6 @@
|
||||
|
||||
namespace node {
|
||||
namespace crypto {
|
||||
|
||||
void ClientHelloParser::Parse(const uint8_t* data, size_t avail) {
|
||||
switch (state_) {
|
||||
case kWaiting:
|
||||
|
@ -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.
|
||||
|
@ -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 |
|
||||
|
@ -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
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
125
src/crypto/crypto_context.h
Normal 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
677
src/crypto/crypto_dh.cc
Normal 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
157
src/crypto/crypto_dh.h
Normal 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
272
src/crypto/crypto_dsa.cc
Normal 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
87
src/crypto/crypto_dsa.h
Normal 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
814
src/crypto/crypto_ecdh.cc
Normal 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
170
src/crypto/crypto_ecdh.h
Normal 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
314
src/crypto/crypto_hash.cc
Normal 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
92
src/crypto/crypto_hash.h
Normal 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
147
src/crypto/crypto_hkdf.cc
Normal 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
66
src/crypto/crypto_hkdf.h
Normal 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
275
src/crypto/crypto_hmac.cc
Normal 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
94
src/crypto/crypto_hmac.h
Normal 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
107
src/crypto/crypto_keygen.cc
Normal 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
292
src/crypto/crypto_keygen.h
Normal 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, ¶ms)
|
||||
.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
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
401
src/crypto/crypto_keys.h
Normal 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, ¶ms).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
149
src/crypto/crypto_pbkdf2.cc
Normal 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
|
76
src/crypto/crypto_pbkdf2.h
Normal file
76
src/crypto/crypto_pbkdf2.h
Normal 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_
|
73
src/crypto/crypto_random.cc
Normal file
73
src/crypto/crypto_random.cc
Normal 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
|
57
src/crypto/crypto_random.h
Normal file
57
src/crypto/crypto_random.h
Normal 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
550
src/crypto/crypto_rsa.cc
Normal 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
140
src/crypto/crypto_rsa.h
Normal 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
153
src/crypto/crypto_scrypt.cc
Normal 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
|
||||
|
87
src/crypto/crypto_scrypt.h
Normal file
87
src/crypto/crypto_scrypt.h
Normal 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
881
src/crypto/crypto_sig.cc
Normal 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
165
src/crypto/crypto_sig.h
Normal 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
128
src/crypto/crypto_spkac.cc
Normal 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
20
src/crypto/crypto_spkac.h
Normal 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
860
src/crypto/crypto_ssl.cc
Normal 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
146
src/crypto/crypto_ssl.h
Normal 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_
|
55
src/crypto/crypto_timing.cc
Normal file
55
src/crypto/crypto_timing.cc
Normal 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
|
20
src/crypto/crypto_timing.h
Normal file
20
src/crypto/crypto_timing.h
Normal 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
600
src/crypto/crypto_util.cc
Normal 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
674
src/crypto/crypto_util.h
Normal 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 ¶ms_; }
|
||||
|
||||
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, ¶ms)
|
||||
.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_
|
21
src/env.h
21
src/env.h
@ -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") \
|
||||
|
@ -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"
|
||||
|
13
src/node.h
13
src/node.h
@ -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,
|
||||
|
7011
src/node_crypto.cc
7011
src/node_crypto.cc
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
||||
|
@ -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") \
|
||||
|
@ -93,6 +93,7 @@ void NativeModuleLoader::InitializeModuleCategories() {
|
||||
|
||||
#if !HAVE_OPENSSL
|
||||
"crypto",
|
||||
"crypto/promises",
|
||||
"https",
|
||||
"http2",
|
||||
"tls",
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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
144
test/fixtures/crypto/aes_cbc.js
vendored
Normal 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
99
test/fixtures/crypto/aes_ctr.js
vendored
Normal 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
134
test/fixtures/crypto/aes_gcm.js
vendored
Normal 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
Loading…
Reference in New Issue
Block a user