mirror of
https://github.com/oven-sh/bun
synced 2026-02-11 19:38:58 +00:00
Add a few passing tests for node:crypto (#17987)
This commit is contained in:
@@ -1010,8 +1010,11 @@ JSC::EncodedJSValue CRYPTO_INVALID_CURVE(JSC::ThrowScope& throwScope, JSC::JSGlo
|
||||
|
||||
JSC::EncodedJSValue CRYPTO_JWK_UNSUPPORTED_CURVE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& curve)
|
||||
{
|
||||
auto message = makeString("Unsupported JWK EC curve: "_s, curve);
|
||||
throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_JWK_UNSUPPORTED_CURVE, message));
|
||||
WTF::StringBuilder builder;
|
||||
builder.append("Unsupported JWK EC curve: "_s);
|
||||
builder.append(curve);
|
||||
builder.append('.');
|
||||
throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_JWK_UNSUPPORTED_CURVE, builder.toString()));
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -1072,6 +1075,13 @@ JSC::EncodedJSValue CRYPTO_HASH_UPDATE_FAILED(JSC::ThrowScope& throwScope, JSC::
|
||||
return {};
|
||||
}
|
||||
|
||||
JSC::EncodedJSValue CRYPTO_TIMING_SAFE_EQUAL_LENGTH(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject)
|
||||
{
|
||||
auto message = "Input buffers must have the same byte length"_s;
|
||||
scope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH, message));
|
||||
return {};
|
||||
}
|
||||
|
||||
JSC::EncodedJSValue MISSING_PASSPHRASE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral message)
|
||||
{
|
||||
throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_MISSING_PASSPHRASE, message));
|
||||
|
||||
@@ -98,6 +98,7 @@ JSC::EncodedJSValue CRYPTO_INVALID_DIGEST(JSC::ThrowScope& throwScope, JSC::JSGl
|
||||
JSC::EncodedJSValue CRYPTO_HASH_FINALIZED(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject);
|
||||
JSC::EncodedJSValue CRYPTO_HASH_UPDATE_FAILED(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject);
|
||||
JSC::EncodedJSValue MISSING_PASSPHRASE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral message);
|
||||
JSC::EncodedJSValue CRYPTO_TIMING_SAFE_EQUAL_LENGTH(JSC::ThrowScope&, JSC::JSGlobalObject*);
|
||||
|
||||
// URL
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ const errors: ErrorCodeMapping = [
|
||||
["ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS", Error],
|
||||
["ERR_CRYPTO_HASH_FINALIZED", Error],
|
||||
["ERR_CRYPTO_HASH_UPDATE_FAILED", Error],
|
||||
["ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH", RangeError],
|
||||
["ERR_MISSING_PASSPHRASE", TypeError],
|
||||
["ERR_DLOPEN_FAILED", Error],
|
||||
["ERR_ENCODING_INVALID_ENCODED_DATA", TypeError],
|
||||
|
||||
@@ -102,13 +102,34 @@ fn pbkdf2Sync(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JS
|
||||
return out_arraybuffer;
|
||||
}
|
||||
|
||||
pub fn createNodeCryptoBindingZig(global: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
const crypto = JSC.JSValue.createEmptyObject(global, 4);
|
||||
pub fn timingSafeEqual(global: *JSGlobalObject, callFrame: *JSC.CallFrame) JSError!JSValue {
|
||||
const l_value, const r_value = callFrame.argumentsAsArray(2);
|
||||
|
||||
crypto.put(global, bun.String.init("pbkdf2"), JSC.JSFunction.create(global, "pbkdf2", pbkdf2, 5, .{}));
|
||||
crypto.put(global, bun.String.init("pbkdf2Sync"), JSC.JSFunction.create(global, "pbkdf2Sync", pbkdf2Sync, 5, .{}));
|
||||
crypto.put(global, bun.String.init("randomInt"), JSC.JSFunction.create(global, "randomInt", randomInt, 2, .{}));
|
||||
const l_buf = l_value.asArrayBuffer(global) orelse {
|
||||
return global.ERR_INVALID_ARG_TYPE("The \"buf1\" argument must be an instance of ArrayBuffer, Buffer, TypedArray, or DataView.", .{}).throw();
|
||||
};
|
||||
const l = l_buf.byteSlice();
|
||||
|
||||
const r_buf = r_value.asArrayBuffer(global) orelse {
|
||||
return global.ERR_INVALID_ARG_TYPE("The \"buf2\" argument must be an instance of ArrayBuffer, Buffer, TypedArray, or DataView.", .{}).throw();
|
||||
};
|
||||
const r = r_buf.byteSlice();
|
||||
|
||||
if (l.len != r.len) {
|
||||
return global.ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH("Input buffers must have the same byte length", .{}).throw();
|
||||
}
|
||||
|
||||
return JSC.jsBoolean(BoringSSL.CRYPTO_memcmp(l.ptr, r.ptr, l.len) == 0);
|
||||
}
|
||||
|
||||
pub fn createNodeCryptoBindingZig(global: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
const crypto = JSC.JSValue.createEmptyObject(global, 5);
|
||||
|
||||
crypto.put(global, String.init("pbkdf2"), JSC.JSFunction.create(global, "pbkdf2", pbkdf2, 5, .{}));
|
||||
crypto.put(global, String.init("pbkdf2Sync"), JSC.JSFunction.create(global, "pbkdf2Sync", pbkdf2Sync, 5, .{}));
|
||||
crypto.put(global, String.init("randomInt"), JSC.JSFunction.create(global, "randomInt", randomInt, 2, .{}));
|
||||
crypto.put(global, String.init("randomUUID"), JSC.JSFunction.create(global, "randomUUID", randomUUID, 1, .{}));
|
||||
crypto.put(global, String.init("timingSafeEqual"), JSC.JSFunction.create(global, "timingSafeEqual", timingSafeEqual, 2, .{}));
|
||||
|
||||
return crypto;
|
||||
}
|
||||
|
||||
@@ -539,28 +539,8 @@ pub const Crypto = struct {
|
||||
return globalThis.ERR_CRYPTO_INVALID_SCRYPT_PARAMS(message, fmt).throw();
|
||||
}
|
||||
|
||||
pub fn timingSafeEqual(_: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const arguments = callframe.arguments_old(2).slice();
|
||||
|
||||
if (arguments.len < 2) {
|
||||
return globalThis.throwInvalidArguments("Expected 2 typed arrays but got nothing", .{});
|
||||
}
|
||||
|
||||
const array_buffer_a = arguments[0].asArrayBuffer(globalThis) orelse {
|
||||
return globalThis.throwInvalidArguments("Expected typed array but got {s}", .{@tagName(arguments[0].jsType())});
|
||||
};
|
||||
const a = array_buffer_a.byteSlice();
|
||||
|
||||
const array_buffer_b = arguments[1].asArrayBuffer(globalThis) orelse {
|
||||
return globalThis.throwInvalidArguments("Expected typed array but got {s}", .{@tagName(arguments[1].jsType())});
|
||||
};
|
||||
const b = array_buffer_b.byteSlice();
|
||||
|
||||
const len = a.len;
|
||||
if (b.len != len) {
|
||||
return globalThis.throw("Input buffers must have the same byte length", .{});
|
||||
}
|
||||
return JSC.jsBoolean(len == 0 or bun.BoringSSL.c.CRYPTO_memcmp(a.ptr, b.ptr, len) == 0);
|
||||
pub fn timingSafeEqual(_: *@This(), global: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
return JSC.Node.Crypto.timingSafeEqual(global, callframe);
|
||||
}
|
||||
|
||||
pub fn timingSafeEqualWithoutTypeChecks(
|
||||
@@ -574,10 +554,10 @@ pub const Crypto = struct {
|
||||
|
||||
const len = a.len;
|
||||
if (b.len != len) {
|
||||
return globalThis.throw("Input buffers must have the same byte length", .{});
|
||||
return globalThis.ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH("Input buffers must have the same byte length", .{}).throw();
|
||||
}
|
||||
|
||||
return JSC.jsBoolean(len == 0 or bun.BoringSSL.c.CRYPTO_memcmp(a.ptr, b.ptr, len) == 0);
|
||||
return JSC.jsBoolean(bun.BoringSSL.c.CRYPTO_memcmp(a.ptr, b.ptr, len) == 0);
|
||||
}
|
||||
|
||||
pub fn getRandomValues(
|
||||
|
||||
@@ -47,9 +47,10 @@ const { POINT_CONVERSION_COMPRESSED, POINT_CONVERSION_HYBRID, POINT_CONVERSION_U
|
||||
|
||||
const {
|
||||
randomInt: _randomInt,
|
||||
pbkdf2: _pbkdf2,
|
||||
pbkdf2Sync: _pbkdf2Sync,
|
||||
timingSafeEqual: _timingSafeEqual,
|
||||
randomUUID: _randomUUID,
|
||||
pbkdf2: pbkdf2_,
|
||||
pbkdf2Sync: pbkdf2Sync_,
|
||||
} = $zig("node_crypto_binding.zig", "createNodeCryptoBindingZig");
|
||||
|
||||
const { validateObject, validateString, validateInt32 } = require("internal/validators");
|
||||
@@ -493,7 +494,7 @@ function pbkdf2(password, salt, iterations, keylen, digest, callback) {
|
||||
digest = undefined;
|
||||
}
|
||||
|
||||
const promise = pbkdf2_(password, salt, iterations, keylen, digest, callback);
|
||||
const promise = _pbkdf2(password, salt, iterations, keylen, digest, callback);
|
||||
if (callback) {
|
||||
promise.then(
|
||||
result => callback(null, result),
|
||||
@@ -506,7 +507,7 @@ function pbkdf2(password, salt, iterations, keylen, digest, callback) {
|
||||
}
|
||||
|
||||
function pbkdf2Sync(password, salt, iterations, keylen, digest) {
|
||||
return pbkdf2Sync_(password, salt, iterations, keylen, digest);
|
||||
return _pbkdf2Sync(password, salt, iterations, keylen, digest);
|
||||
}
|
||||
|
||||
// node_modules/des.js/lib/des/utils.js
|
||||
@@ -4380,8 +4381,9 @@ var require_browser7 = __commonJS({
|
||||
exports.DiffieHellmanGroup = exports.createDiffieHellmanGroup = exports.getDiffieHellman = getDiffieHellman;
|
||||
exports.createDiffieHellman = exports.DiffieHellman = createDiffieHellman;
|
||||
|
||||
// TODO: move entire function out of js in diffie-hellman pr
|
||||
exports.diffieHellman = function diffieHellman(options) {
|
||||
validateObject(options);
|
||||
validateObject(options, "options");
|
||||
|
||||
const { privateKey, publicKey } = options;
|
||||
|
||||
@@ -10194,17 +10196,6 @@ var crypto_exports = require_crypto_browserify2();
|
||||
|
||||
var getRandomValues = array => crypto.getRandomValues(array),
|
||||
randomUUID = () => crypto.randomUUID(),
|
||||
timingSafeEqual =
|
||||
"timingSafeEqual" in crypto
|
||||
? (a, b) => {
|
||||
let { byteLength: byteLengthA } = a,
|
||||
{ byteLength: byteLengthB } = b;
|
||||
if (typeof byteLengthA != "number" || typeof byteLengthB != "number")
|
||||
throw new TypeError("Input must be an array buffer view");
|
||||
if (byteLengthA !== byteLengthB) throw new RangeError("Input buffers must have the same length");
|
||||
return crypto.timingSafeEqual(a, b);
|
||||
}
|
||||
: void 0,
|
||||
scryptSync =
|
||||
"scryptSync" in crypto
|
||||
? (password, salt, keylen, options) => {
|
||||
@@ -10229,16 +10220,14 @@ var getRandomValues = array => crypto.getRandomValues(array),
|
||||
}
|
||||
}
|
||||
: void 0;
|
||||
timingSafeEqual &&
|
||||
(Object.defineProperty(timingSafeEqual, "name", {
|
||||
value: "::bunternal::",
|
||||
}),
|
||||
scrypt &&
|
||||
Object.defineProperty(scrypt, "name", {
|
||||
value: "::bunternal::",
|
||||
}),
|
||||
Object.defineProperty(scryptSync, "name", {
|
||||
value: "::bunternal::",
|
||||
}));
|
||||
scryptSync &&
|
||||
Object.defineProperty(scryptSync, "name", {
|
||||
value: "::bunternal::",
|
||||
});
|
||||
|
||||
class KeyObject {
|
||||
// we use $bunNativePtr so that util.types.isKeyObject can detect it
|
||||
@@ -10576,7 +10565,7 @@ crypto_exports.getCurves = getCurves;
|
||||
crypto_exports.getCipherInfo = getCipherInfo;
|
||||
crypto_exports.scrypt = scrypt;
|
||||
crypto_exports.scryptSync = scryptSync;
|
||||
crypto_exports.timingSafeEqual = timingSafeEqual;
|
||||
crypto_exports.timingSafeEqual = _timingSafeEqual;
|
||||
crypto_exports.webcrypto = webcrypto;
|
||||
crypto_exports.subtle = _subtle;
|
||||
crypto_exports.X509Certificate = X509Certificate;
|
||||
|
||||
@@ -69,6 +69,7 @@ pub const Node = struct {
|
||||
pub const Util = struct {
|
||||
pub const parseArgs = @import("./bun.js/node/util/parse_args.zig").parseArgs;
|
||||
};
|
||||
pub const Crypto = @import("./bun.js/node/node_crypto_binding.zig");
|
||||
};
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const {
|
||||
generateKeyPair,
|
||||
} = require('crypto');
|
||||
const {
|
||||
assertApproximateSize,
|
||||
testEncryptDecrypt,
|
||||
testSignVerify,
|
||||
} = require('../common/crypto');
|
||||
|
||||
// Test async RSA key generation with an encrypted private key, but encoded as DER.
|
||||
{
|
||||
generateKeyPair('rsa', {
|
||||
publicExponent: 0x10001,
|
||||
modulusLength: 512,
|
||||
publicKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'der'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8',
|
||||
format: 'der',
|
||||
cipher: 'aes-256-cbc',
|
||||
passphrase: 'secret'
|
||||
}
|
||||
}, common.mustSucceed((publicKeyDER, privateKeyDER) => {
|
||||
assert(Buffer.isBuffer(publicKeyDER));
|
||||
assertApproximateSize(publicKeyDER, 74);
|
||||
|
||||
assert(Buffer.isBuffer(privateKeyDER));
|
||||
|
||||
// Since the private key is encrypted, signing shouldn't work anymore.
|
||||
const publicKey = {
|
||||
key: publicKeyDER,
|
||||
type: 'pkcs1',
|
||||
format: 'der',
|
||||
};
|
||||
assert.throws(() => {
|
||||
testSignVerify(publicKey, {
|
||||
key: privateKeyDER,
|
||||
format: 'der',
|
||||
type: 'pkcs8'
|
||||
});
|
||||
}, {
|
||||
name: 'TypeError',
|
||||
code: 'ERR_MISSING_PASSPHRASE',
|
||||
message: 'Passphrase required for encrypted key'
|
||||
});
|
||||
|
||||
// Signing should work with the correct password.
|
||||
|
||||
const privateKey = {
|
||||
key: privateKeyDER,
|
||||
format: 'der',
|
||||
type: 'pkcs8',
|
||||
passphrase: 'secret'
|
||||
};
|
||||
testEncryptDecrypt(publicKey, privateKey);
|
||||
testSignVerify(publicKey, privateKey);
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const {
|
||||
generateKeyPair,
|
||||
} = require('crypto');
|
||||
const {
|
||||
testSignVerify,
|
||||
spkiExp,
|
||||
pkcs8EncExp,
|
||||
} = require('../common/crypto');
|
||||
|
||||
const { hasOpenSSL3 } = require('../common/crypto');
|
||||
|
||||
// Test async elliptic curve key generation, e.g. for ECDSA, with an encrypted
|
||||
// private key with paramEncoding explicit.
|
||||
{
|
||||
generateKeyPair('ec', {
|
||||
namedCurve: 'P-256',
|
||||
paramEncoding: 'explicit',
|
||||
publicKeyEncoding: {
|
||||
type: 'spki',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8',
|
||||
format: 'pem',
|
||||
cipher: 'aes-128-cbc',
|
||||
passphrase: 'top secret'
|
||||
}
|
||||
}, common.mustSucceed((publicKey, privateKey) => {
|
||||
assert.strictEqual(typeof publicKey, 'string');
|
||||
assert.match(publicKey, spkiExp);
|
||||
assert.strictEqual(typeof privateKey, 'string');
|
||||
assert.match(privateKey, pkcs8EncExp);
|
||||
|
||||
// Since the private key is encrypted, signing shouldn't work anymore.
|
||||
assert.throws(() => testSignVerify(publicKey, privateKey),
|
||||
hasOpenSSL3 ? {
|
||||
message: 'error:07880109:common libcrypto ' +
|
||||
'routines::interrupted or cancelled'
|
||||
} : {
|
||||
name: 'TypeError',
|
||||
code: 'ERR_MISSING_PASSPHRASE',
|
||||
message: 'Passphrase required for encrypted key'
|
||||
});
|
||||
|
||||
testSignVerify(publicKey, {
|
||||
key: privateKey,
|
||||
passphrase: 'top secret'
|
||||
});
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const {
|
||||
generateKeyPair,
|
||||
} = require('crypto');
|
||||
const {
|
||||
testSignVerify,
|
||||
spkiExp,
|
||||
sec1EncExp,
|
||||
hasOpenSSL3,
|
||||
} = require('../common/crypto');
|
||||
|
||||
{
|
||||
// Test async explicit elliptic curve key generation with an encrypted
|
||||
// private key.
|
||||
generateKeyPair('ec', {
|
||||
namedCurve: 'prime256v1',
|
||||
paramEncoding: 'explicit',
|
||||
publicKeyEncoding: {
|
||||
type: 'spki',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'sec1',
|
||||
format: 'pem',
|
||||
cipher: 'aes-128-cbc',
|
||||
passphrase: 'secret'
|
||||
}
|
||||
}, common.mustSucceed((publicKey, privateKey) => {
|
||||
assert.strictEqual(typeof publicKey, 'string');
|
||||
assert.match(publicKey, spkiExp);
|
||||
assert.strictEqual(typeof privateKey, 'string');
|
||||
assert.match(privateKey, sec1EncExp('AES-128-CBC'));
|
||||
|
||||
// Since the private key is encrypted, signing shouldn't work anymore.
|
||||
assert.throws(() => testSignVerify(publicKey, privateKey),
|
||||
hasOpenSSL3 ? {
|
||||
message: 'error:07880109:common libcrypto ' +
|
||||
'routines::interrupted or cancelled'
|
||||
} : {
|
||||
name: 'TypeError',
|
||||
code: 'ERR_MISSING_PASSPHRASE',
|
||||
message: 'Passphrase required for encrypted key'
|
||||
});
|
||||
|
||||
testSignVerify(publicKey, { key: privateKey, passphrase: 'secret' });
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const {
|
||||
generateKeyPair,
|
||||
} = require('crypto');
|
||||
const {
|
||||
testSignVerify,
|
||||
spkiExp,
|
||||
pkcs8EncExp,
|
||||
hasOpenSSL3,
|
||||
} = require('../common/crypto');
|
||||
|
||||
// Test async elliptic curve key generation, e.g. for ECDSA, with an encrypted
|
||||
// private key.
|
||||
{
|
||||
generateKeyPair('ec', {
|
||||
namedCurve: 'P-256',
|
||||
paramEncoding: 'named',
|
||||
publicKeyEncoding: {
|
||||
type: 'spki',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8',
|
||||
format: 'pem',
|
||||
cipher: 'aes-128-cbc',
|
||||
passphrase: 'top secret'
|
||||
}
|
||||
}, common.mustSucceed((publicKey, privateKey) => {
|
||||
assert.strictEqual(typeof publicKey, 'string');
|
||||
assert.match(publicKey, spkiExp);
|
||||
assert.strictEqual(typeof privateKey, 'string');
|
||||
assert.match(privateKey, pkcs8EncExp);
|
||||
|
||||
// Since the private key is encrypted, signing shouldn't work anymore.
|
||||
assert.throws(() => testSignVerify(publicKey, privateKey),
|
||||
hasOpenSSL3 ? {
|
||||
message: 'error:07880109:common libcrypto ' +
|
||||
'routines::interrupted or cancelled'
|
||||
} : {
|
||||
name: 'TypeError',
|
||||
code: 'ERR_MISSING_PASSPHRASE',
|
||||
message: 'Passphrase required for encrypted key'
|
||||
});
|
||||
|
||||
testSignVerify(publicKey, {
|
||||
key: privateKey,
|
||||
passphrase: 'top secret'
|
||||
});
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const {
|
||||
generateKeyPair,
|
||||
} = require('crypto');
|
||||
const {
|
||||
testSignVerify,
|
||||
spkiExp,
|
||||
sec1EncExp,
|
||||
hasOpenSSL3,
|
||||
} = require('../common/crypto');
|
||||
|
||||
{
|
||||
// Test async named elliptic curve key generation with an encrypted
|
||||
// private key.
|
||||
generateKeyPair('ec', {
|
||||
namedCurve: 'prime256v1',
|
||||
paramEncoding: 'named',
|
||||
publicKeyEncoding: {
|
||||
type: 'spki',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'sec1',
|
||||
format: 'pem',
|
||||
cipher: 'aes-128-cbc',
|
||||
passphrase: 'secret'
|
||||
}
|
||||
}, common.mustSucceed((publicKey, privateKey) => {
|
||||
assert.strictEqual(typeof publicKey, 'string');
|
||||
assert.match(publicKey, spkiExp);
|
||||
assert.strictEqual(typeof privateKey, 'string');
|
||||
assert.match(privateKey, sec1EncExp('AES-128-CBC'));
|
||||
|
||||
// Since the private key is encrypted, signing shouldn't work anymore.
|
||||
assert.throws(() => testSignVerify(publicKey, privateKey),
|
||||
hasOpenSSL3 ? {
|
||||
message: 'error:07880109:common libcrypto ' +
|
||||
'routines::interrupted or cancelled'
|
||||
} : {
|
||||
name: 'TypeError',
|
||||
code: 'ERR_MISSING_PASSPHRASE',
|
||||
message: 'Passphrase required for encrypted key'
|
||||
});
|
||||
|
||||
testSignVerify(publicKey, { key: privateKey, passphrase: 'secret' });
|
||||
}));
|
||||
}
|
||||
62
test/js/node/test/parallel/test-crypto-keygen-async-rsa.js
Normal file
62
test/js/node/test/parallel/test-crypto-keygen-async-rsa.js
Normal file
@@ -0,0 +1,62 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const {
|
||||
generateKeyPair,
|
||||
} = require('crypto');
|
||||
const {
|
||||
assertApproximateSize,
|
||||
testEncryptDecrypt,
|
||||
testSignVerify,
|
||||
pkcs1EncExp,
|
||||
hasOpenSSL3,
|
||||
} = require('../common/crypto');
|
||||
|
||||
// Test async RSA key generation with an encrypted private key.
|
||||
{
|
||||
generateKeyPair('rsa', {
|
||||
publicExponent: 0x10001,
|
||||
modulusLength: 512,
|
||||
publicKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'der'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem',
|
||||
cipher: 'aes-256-cbc',
|
||||
passphrase: 'secret'
|
||||
}
|
||||
}, common.mustSucceed((publicKeyDER, privateKey) => {
|
||||
assert(Buffer.isBuffer(publicKeyDER));
|
||||
assertApproximateSize(publicKeyDER, 74);
|
||||
|
||||
assert.strictEqual(typeof privateKey, 'string');
|
||||
assert.match(privateKey, pkcs1EncExp('AES-256-CBC'));
|
||||
|
||||
// Since the private key is encrypted, signing shouldn't work anymore.
|
||||
const publicKey = {
|
||||
key: publicKeyDER,
|
||||
type: 'pkcs1',
|
||||
format: 'der',
|
||||
};
|
||||
const expectedError = hasOpenSSL3 ? {
|
||||
name: 'Error',
|
||||
message: 'error:07880109:common libcrypto routines::interrupted or ' +
|
||||
'cancelled'
|
||||
} : {
|
||||
name: 'TypeError',
|
||||
code: 'ERR_MISSING_PASSPHRASE',
|
||||
message: 'Passphrase required for encrypted key'
|
||||
};
|
||||
assert.throws(() => testSignVerify(publicKey, privateKey), expectedError);
|
||||
|
||||
const key = { key: privateKey, passphrase: 'secret' };
|
||||
testEncryptDecrypt(publicKey, key);
|
||||
testSignVerify(publicKey, key);
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const {
|
||||
createPrivateKey,
|
||||
generateKeyPair,
|
||||
} = require('crypto');
|
||||
const {
|
||||
testSignVerify,
|
||||
hasOpenSSL3,
|
||||
} = require('../common/crypto');
|
||||
|
||||
// Passing an empty passphrase string should not cause OpenSSL's default
|
||||
// passphrase prompt in the terminal.
|
||||
// See https://github.com/nodejs/node/issues/35898.
|
||||
for (const type of ['pkcs1', 'pkcs8']) {
|
||||
generateKeyPair('rsa', {
|
||||
modulusLength: 1024,
|
||||
privateKeyEncoding: {
|
||||
type,
|
||||
format: 'pem',
|
||||
cipher: 'aes-256-cbc',
|
||||
passphrase: ''
|
||||
}
|
||||
}, common.mustSucceed((publicKey, privateKey) => {
|
||||
assert.strictEqual(publicKey.type, 'public');
|
||||
|
||||
for (const passphrase of ['', Buffer.alloc(0)]) {
|
||||
const privateKeyObject = createPrivateKey({
|
||||
passphrase,
|
||||
key: privateKey
|
||||
});
|
||||
assert.strictEqual(privateKeyObject.asymmetricKeyType, 'rsa');
|
||||
}
|
||||
|
||||
// Encrypting with an empty passphrase is not the same as not encrypting
|
||||
// the key, and not specifying a passphrase should fail when decoding it.
|
||||
assert.throws(() => {
|
||||
return testSignVerify(publicKey, privateKey);
|
||||
}, hasOpenSSL3 ? {
|
||||
name: 'Error',
|
||||
code: 'ERR_OSSL_CRYPTO_INTERRUPTED_OR_CANCELLED',
|
||||
message: 'error:07880109:common libcrypto routines::interrupted or cancelled'
|
||||
} : {
|
||||
name: 'TypeError',
|
||||
code: 'ERR_MISSING_PASSPHRASE',
|
||||
message: 'Passphrase required for encrypted key'
|
||||
});
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const {
|
||||
generateKeyPairSync,
|
||||
} = require('crypto');
|
||||
|
||||
{
|
||||
assert.throws(() => generateKeyPairSync('ec', {
|
||||
namedCurve: 'secp224r1',
|
||||
publicKeyEncoding: {
|
||||
format: 'jwk'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
format: 'jwk'
|
||||
}
|
||||
}), {
|
||||
name: 'Error',
|
||||
code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE',
|
||||
message: 'Unsupported JWK EC curve: secp224r1.'
|
||||
});
|
||||
}
|
||||
119
test/js/node/test/sequential/test-crypto-timing-safe-equal.js
Normal file
119
test/js/node/test/sequential/test-crypto-timing-safe-equal.js
Normal file
@@ -0,0 +1,119 @@
|
||||
// Flags: --expose-internals --no-warnings --allow-natives-syntax
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const crypto = require('crypto');
|
||||
|
||||
// 'should consider equal strings to be equal'
|
||||
assert.strictEqual(
|
||||
crypto.timingSafeEqual(Buffer.from('foo'), Buffer.from('foo')),
|
||||
true
|
||||
);
|
||||
|
||||
// 'should consider unequal strings to be unequal'
|
||||
assert.strictEqual(
|
||||
crypto.timingSafeEqual(Buffer.from('foo'), Buffer.from('bar')),
|
||||
false
|
||||
);
|
||||
|
||||
{
|
||||
// Test TypedArrays with different lengths but equal byteLengths.
|
||||
const buf = crypto.randomBytes(16).buffer;
|
||||
const a1 = new Uint8Array(buf);
|
||||
const a2 = new Uint16Array(buf);
|
||||
const a3 = new Uint32Array(buf);
|
||||
|
||||
for (const left of [a1, a2, a3]) {
|
||||
for (const right of [a1, a2, a3]) {
|
||||
assert.strictEqual(crypto.timingSafeEqual(left, right), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// When the inputs are floating-point numbers, timingSafeEqual neither has
|
||||
// equality nor SameValue semantics. It just compares the underlying bytes,
|
||||
// ignoring the TypedArray type completely.
|
||||
|
||||
const cmp = (fn) => (a, b) => a.every((x, i) => fn(x, b[i]));
|
||||
const eq = cmp((a, b) => a === b);
|
||||
const is = cmp(Object.is);
|
||||
|
||||
function test(a, b, { equal, sameValue, timingSafeEqual }) {
|
||||
assert.strictEqual(eq(a, b), equal);
|
||||
assert.strictEqual(is(a, b), sameValue);
|
||||
assert.strictEqual(crypto.timingSafeEqual(a, b), timingSafeEqual);
|
||||
}
|
||||
|
||||
test(new Float32Array([NaN]), new Float32Array([NaN]), {
|
||||
equal: false,
|
||||
sameValue: true,
|
||||
timingSafeEqual: true
|
||||
});
|
||||
|
||||
test(new Float64Array([0]), new Float64Array([-0]), {
|
||||
equal: true,
|
||||
sameValue: false,
|
||||
timingSafeEqual: false
|
||||
});
|
||||
|
||||
const x = new BigInt64Array([0x7ff0000000000001n, 0xfff0000000000001n]);
|
||||
test(new Float64Array(x.buffer), new Float64Array([NaN, NaN]), {
|
||||
equal: false,
|
||||
sameValue: true,
|
||||
timingSafeEqual: false
|
||||
});
|
||||
}
|
||||
|
||||
assert.throws(
|
||||
() => crypto.timingSafeEqual(Buffer.from([1, 2, 3]), Buffer.from([1, 2])),
|
||||
{
|
||||
code: 'ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH',
|
||||
name: 'RangeError',
|
||||
message: 'Input buffers must have the same byte length'
|
||||
}
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
() => crypto.timingSafeEqual('not a buffer', Buffer.from([1, 2])),
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
}
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
() => crypto.timingSafeEqual(Buffer.from([1, 2]), 'not a buffer'),
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
}
|
||||
);
|
||||
|
||||
if (typeof Bun === 'undefined') {
|
||||
// V8 Fast API
|
||||
const foo = Buffer.from('foo');
|
||||
const bar = Buffer.from('bar');
|
||||
const longer = Buffer.from('longer');
|
||||
function testFastPath(buf1, buf2) {
|
||||
return crypto.timingSafeEqual(buf1, buf2);
|
||||
}
|
||||
eval('%PrepareFunctionForOptimization(testFastPath)');
|
||||
assert.strictEqual(testFastPath(foo, bar), false);
|
||||
eval('%OptimizeFunctionOnNextCall(testFastPath)');
|
||||
assert.strictEqual(testFastPath(foo, bar), false);
|
||||
assert.strictEqual(testFastPath(foo, foo), true);
|
||||
assert.throws(() => testFastPath(foo, longer), {
|
||||
code: 'ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH',
|
||||
});
|
||||
|
||||
if (common.isDebug) {
|
||||
const { internalBinding } = require('internal/test/binding');
|
||||
const { getV8FastApiCallCount } = internalBinding('debug');
|
||||
assert.strictEqual(getV8FastApiCallCount('crypto.timingSafeEqual.ok'), 2);
|
||||
assert.strictEqual(getV8FastApiCallCount('crypto.timingSafeEqual.error'), 1);
|
||||
}
|
||||
}
|
||||
@@ -163,7 +163,7 @@ it("crypto.timingSafeEqual", () => {
|
||||
crypto.timingSafeEqual(uuid, uuid.slice(0, uuid.length - 2));
|
||||
expect.unreachable();
|
||||
} catch (e) {
|
||||
expect(e.message).toBe("Input buffers must have the same length");
|
||||
expect(e.message).toBe("Input buffers must have the same byte length");
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user