Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
25d9782
crypto.generatePrime: find primes with `add`
claude Nov 1, 2026
2c92f4d
process.send(): a CryptoKey that is not extractable is a DataCloneError
claude Nov 1, 2026
3af68a7
BoringSSL: a safe prime of 2 bits is still refused with `add`
claude Nov 1, 2026
06a3d4c
RSA PKCS#1 v1.5 signatures with seven more digests
claude Nov 1, 2026
6e3addb
node:crypto: buffers, encodings and Diffie-Hellman errors like Node's
claude Nov 1, 2026
f1bf1e1
node:crypto: read and write keys like Node
claude Nov 1, 2026
4baa757
node:crypto: Sign, Verify and diffieHellman() like Node
claude Nov 1, 2026
980674f
Verify: a key that cannot verify is still an error
claude Nov 1, 2026
b26838b
test: start the prime search right after the worker's message
claude Nov 1, 2026
b31f71a
node:crypto: one list of digest names, and hash() like Node's
claude Nov 1, 2026
6770b12
node:crypto: the shape of a few results, like Node's
claude Nov 1, 2026
0b780cc
webcrypto: parse Ed25519 and X25519 keys as DER, and three smaller fixes
claude Nov 1, 2026
062774a
node:crypto: fixes from review
claude Nov 1, 2026
26a176d
node:crypto: declare getHashes and oneShotDigest for --aot, and more …
claude Nov 1, 2026
db591ff
crypto: keep the inputs of crypto.hash(), and more from review
claude Nov 1, 2026
00f2b67
crypto.hash(): keep writing the digest into a view
claude Nov 3, 2026
75577bb
Update BoringSSL: the search for a prime with `add` keeps to OpenSSL's
claude Nov 3, 2026
5621978
Merge remote-tracking branch 'origin/main' into claude/node-crypto-re…
claude Nov 3, 2026
7d75623
Leave crypto.generatePrime() with `add` as it was
claude Nov 3, 2026
a5cb7e3
Pin BoringSSL to the commit that merged the PKCS#1 v1.5 rows
claude Nov 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .claude/commands/upgrade-boringssl.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ Then `bun run ci:watch` and fix anything that turns up.
- **`EVP_PBE_validate_scrypt_params`** — `crypto/evp/scrypt.cc`, `include/openssl/evp.h`
- **Electron `SSL_want` / `EVP_CIPHER_do_all_sorted`** — `ssl/ssl_lib.cc` (return `rwstate` directly), `ssl/ssl_test.cc` (drops the corresponding test block), `decrepit/evp/evp_do_all.cc`, `crypto/cipher/get_cipher.cc`, `include/openssl/cipher.h`
- **MLDSA stack-frame pragma** — `crypto/fipsmodule/mldsa/mldsa.cc.inc`
- **`BN_generate_prime_ex` with `add`** — `crypto/fipsmodule/bn/prime.cc.inc` (one `probable_prime_dh` for plain and safe primes, after OpenSSL's; upstream's never returns for most `add`), `crypto/fipsmodule/bn/bn_test.cc`, `include/openssl/bn.h`
- **PKCS#1 v1.5 signature prefixes for the digests above** — `crypto/fipsmodule/rsa/rsa.cc.inc` (`kPKCS1SigPrefixes` rows for SHA-512/224, SHA-512/256, SHA3-\* and RIPEMD-160), `crypto/rsa/rsa_test.cc`

If upstream upstreams any of these (check `git grep` on `upstream/main` before re-applying), drop the fork's copy.

Expand Down
2 changes: 1 addition & 1 deletion scripts/build/deps/boringssl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { quote } from "../shell.ts";
import type { Dependency, DirectBuild } from "../source.ts";
import { LIBC_ALLOCATION_SYMBOLS, depSourceDir } from "../source.ts";

const BORINGSSL_COMMIT = "41bf9b59c2ebf277a7aa427e1ecad5cc80dd4d4f";
const BORINGSSL_COMMIT = "b34f2602ca2b29990239e6a8454770d4ef29cfa8";

export const boringssl: Dependency = {
name: "boringssl",
Expand Down
6 changes: 0 additions & 6 deletions src/codegen/builtins/host/runtime-dependencies.builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -601,12 +601,6 @@ const nativeCalls = [
onBehalfOf: environmentObjects,
},
{ file: inBindings("ProcessBindingUV.cpp"), caller: "jsErrname", onBehalfOf: ["process.binding"] },
{
file: inBindings("node/crypto/CryptoGenRsaKeyPair.cpp"),
caller: "RsaKeyPairJobCtx::fromJS",
times: 2,
onBehalfOf: nodeCryptoBinding,
},
{
file: inBindings("node/crypto/JSKeyObjectConstructor.cpp"),
caller: "jsKeyObjectConstructor_from",
Expand Down
11 changes: 6 additions & 5 deletions src/js/internal/util/deprecate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ const { validateString } = require("internal/validators");

const codesWarned = new Set<string>();

function getDeprecationWarningEmitter(
function getDeprecationWarningEmitter<Arg = void>(
code: string | undefined,
msg: string,
deprecated: (...args: unknown[]) => unknown,
shouldEmitWarning = () => true,
deprecated: ((...args: unknown[]) => unknown) | undefined,
shouldEmitWarning: (arg: Arg) => boolean = () => true,
) {
let warned = false;
return function () {
if (!warned && shouldEmitWarning()) {
return function (arg: Arg) {
if (!warned && shouldEmitWarning(arg)) {
warned = true;
if (code !== undefined) {
if (!codesWarned.has(code)) {
Expand Down Expand Up @@ -68,4 +68,5 @@ function deprecate(fn: (this: unknown, ...args: unknown[]) => unknown, msg: stri

export default {
deprecate,
getDeprecationWarningEmitter,
};
81 changes: 75 additions & 6 deletions src/js/node/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ const StringDecoder = require("node:string_decoder").StringDecoder;
const LazyTransform = require("internal/streams/lazy_transform");
const { defineCustomPromisifyArgs } = require("internal/promisify");
const Writable = require("internal/streams/writable");
const { CryptoHasher } = Bun;

const {
getCurves,
getHashes,
oneShotDigest,
certVerifySpkac,
certExportPublicKey,
certExportChallenge,
Expand Down Expand Up @@ -76,7 +77,6 @@ const {
getFips,
setFips,
setEngine,
getHashes,
scrypt,
scryptSync,
argon2: _argon2,
Expand Down Expand Up @@ -121,7 +121,7 @@ const {
validateString,
validateUint32,
} = require("internal/validators");
const { deprecate } = require("internal/util/deprecate");
const { deprecate, getDeprecationWarningEmitter } = require("internal/util/deprecate");

const kHandle = Symbol("kHandle");

Expand Down Expand Up @@ -209,7 +209,9 @@ const crypto = globalThis.crypto;

var crypto_exports: any = {};

crypto_exports.getRandomValues = value => crypto.getRandomValues(value);
crypto_exports.getRandomValues = function getRandomValues(value) {
return crypto.getRandomValues(value);
};
crypto_exports.constants = $processBindingConstants.crypto;

crypto_exports.KeyObject = KeyObject;
Expand All @@ -227,8 +229,65 @@ crypto_exports.createPrivateKey = createPrivateKey;
var webcrypto = crypto;
var _subtle = webcrypto.subtle;

crypto_exports.hash = function hash(algorithm, input, outputEncoding: import("bun").DigestEncoding = "hex") {
return CryptoHasher.hash(algorithm, input, outputEncoding);
const maybeEmitDeprecationWarning = getDeprecationWarningEmitter(
"DEP0198",
"Creating SHAKE128/256 digests without an explicit options.outputLength is deprecated.",
undefined,
(algorithm: string) => {
if (algorithm.length !== 8 && algorithm.length !== 9) return false;
const normalized = algorithm.toLowerCase().replace("-", "");
return normalized === "shake128" || normalized === "shake256";
Comment thread
dylan-conway marked this conversation as resolved.
Resolved
},
);

// https://github.com/nodejs/node/blob/v26.12.1/lib/internal/crypto/hash.js#L323-L391
crypto_exports.hash = function hash(algorithm, input, options) {
validateString(algorithm, "algorithm");
if (typeof input !== "string" && !ArrayBuffer.isView(input)) {
Comment thread
dylan-conway marked this conversation as resolved.
Resolved
throw $ERR_INVALID_ARG_TYPE("input", ["Buffer", "TypedArray", "DataView", "string"], input);
Comment thread
dylan-conway marked this conversation as resolved.
Resolved
}
if (options === undefined) {
maybeEmitDeprecationWarning(algorithm);
return oneShotDigest(algorithm, input, "hex");
}
let outputEncoding;
let outputLength;
let hasDigestOptions = false;

if (typeof options === "string") {
outputEncoding = options;
} else {
validateObject(options, "options");
outputLength = options.outputLength;
outputEncoding = options.outputEncoding;
hasDigestOptions = options.functionName !== undefined || options.customization !== undefined;
}

outputEncoding ??= "hex";

let normalized = outputEncoding;
// Fast case: if it's 'hex', we don't need to validate it further.
if (normalized !== "hex") {
validateString(outputEncoding, "outputEncoding");
normalized = normalizeEncoding(outputEncoding);
// If the encoding is invalid, normalizeEncoding() returns undefined.
if (normalized === undefined) {
// normalizeEncoding() doesn't handle 'buffer'.
if (outputEncoding.toLowerCase() === "buffer") {
normalized = "buffer";
Comment thread
dylan-conway marked this conversation as resolved.
Resolved
} else {
throw $ERR_INVALID_ARG_VALUE("outputEncoding", outputEncoding);
}
}
}

if (outputLength !== undefined) {
validateUint32(outputLength, "outputLength");
} else {
maybeEmitDeprecationWarning(algorithm);
}

return oneShotDigest(algorithm, input, normalized, outputLength, hasDigestOptions);
};

crypto_exports.pbkdf2 = pbkdf2;
Expand Down Expand Up @@ -320,6 +379,16 @@ function Hash(this: $Node.Hash, algorithm: string | $Node.NativeHash, options?:
const handle = new _Hash(algorithm, options);
this[kHandle] = handle;

if (typeof algorithm === "string") {
// They are for cSHAKE, which BoringSSL does not have.
if (options != null && (options.functionName !== undefined || options.customization !== undefined)) {
throw new Error("Digest method not supported");
}
if (typeof options !== "object" || options === null || options.outputLength === undefined) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
maybeEmitDeprecationWarning(algorithm);
}
}

LazyTransform.$apply(this, [options]);
}
$toClass(Hash, "Hash", LazyTransform);
Expand Down
24 changes: 3 additions & 21 deletions src/jsc/bindings/JSX509Certificate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,20 +136,11 @@ static JSValue createX509Certificate(JSC::VM& vm, JSGlobalObject* globalObject,
RETURN_IF_EXCEPTION(scope, {});
data = view->bytes();
} else if (auto* typedArray = dynamicDowncast<JSArrayBufferView>(arg)) {
if (typedArray->isDetached()) [[unlikely]] {
Bun::throwError(globalObject, scope, ErrorCode::ERR_INVALID_ARG_TYPE, "TypedArray is detached"_s);
return {};
}
data = typedArray->span();
} else if (auto* buffer = dynamicDowncast<JSArrayBuffer>(arg)) {
auto* impl = buffer->impl();
if (!impl) [[unlikely]] {
Bun::throwError(globalObject, scope, ErrorCode::ERR_INVALID_ARG_TYPE, "Buffer is detached"_s);
return {};
}
data = impl->span();
data = buffer->impl()->span();
} else {
Bun::throwError(globalObject, scope, ErrorCode::ERR_INVALID_ARG_TYPE, "X509Certificate constructor argument must be a Buffer, TypedArray, or string"_s);
Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "buffer"_s, "string or an instance of Buffer, TypedArray, or DataView"_s, arg);
return {};
}

Expand All @@ -170,16 +161,7 @@ JSC_DEFINE_HOST_FUNCTION(x509CertificateConstructorConstruct, (JSGlobalObject *
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);

if (!callFrame->argumentCount()) {
Bun::throwError(globalObject, scope, ErrorCode::ERR_MISSING_ARGS, "X509Certificate constructor requires at least one argument"_s);
return {};
}

JSValue arg = callFrame->uncheckedArgument(0);
if (!arg.isCell()) {
Bun::throwError(globalObject, scope, ErrorCode::ERR_INVALID_ARG_TYPE, "X509Certificate constructor argument must be a Buffer, TypedArray, or string"_s);
return {};
}
JSValue arg = callFrame->argument(0);

auto* zigGlobalObject = defaultGlobalObject(globalObject);
Structure* structure = zigGlobalObject->m_JSX509CertificateClassStructure.get(zigGlobalObject);
Expand Down
Loading
Loading