Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c3db09b
Deep equality compares KeyObjects and CryptoKeys
claude Oct 31, 2026
b1d1884
Ed25519: port node's check for small-order points
claude Oct 31, 2026
ff52e32
tls: port OpenSSL's NAME_CONSTRAINTS_check_CN
claude Oct 31, 2026
1a8e432
tls: build the issuerCertificate chain with X509_check_issued
claude Oct 31, 2026
c2895e6
JWK: an Ed25519 or X25519 private key needs the x that belongs to d
claude Oct 31, 2026
c3144cc
Leave BoringSSL's error queue empty, and fill opensslErrorStack
claude Oct 31, 2026
3338f54
Test Bun.deepEquals and the expect() matchers on KeyObjects and Crypt…
claude Oct 31, 2026
3898bf0
[autofix.ci] apply automated fixes
autofix-ci[bot] Oct 31, 2026
6f7c87e
ncrypto: take hasSmallOrderEdDsaPoint as upstream has it
claude Oct 31, 2026
5b42b5c
The cipher code reads the error queue directly
claude Oct 31, 2026
163b3d7
Put the SAFETY comment directly above the unsafe block
claude Oct 31, 2026
ef5574b
createPrivateKey: keep the decoded "d" alive while "x" is decoded
claude Oct 31, 2026
928d269
tls: check the common name of every verified chain
claude Oct 31, 2026
5c5fd1c
subtle.importKey: decode an OKP JWK's "d" like its "x"
claude Oct 31, 2026
92e6526
Tests: each error-queue case has to fail, and one more deep-equality …
claude Oct 31, 2026
7f1dde6
subtle.verify: OperationError when the Ed25519 key cannot be wrapped
claude Oct 31, 2026
20d9b04
tls: leave the error queue as the common name check found it
claude Oct 31, 2026
9286b0e
Test: always use an OKP key that the two base64 alphabets spell diffe…
claude Oct 31, 2026
3823902
Merge branch 'main' into claude/node-crypto-fixes-2
claude Oct 31, 2026
c72433a
tls: match common names against name constraints with OpenSSL's own r…
claude Oct 31, 2026
7a45279
crypto: import OKP JWKs the way Node's ImportJWKRawKey does
claude Oct 31, 2026
ee34a4c
assert.partialDeepStrictEqual compares CryptoKeys
claude Oct 31, 2026
0db1b45
crypto: tighten the BoringSSL error queue handling
claude Oct 31, 2026
4a6a5ca
Test: more key comparison and OKP JWK cases
claude Oct 31, 2026
8e812dd
Merge remote-tracking branch 'origin/main' into claude/node-crypto-fi…
claude Oct 31, 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
94 changes: 92 additions & 2 deletions packages/bun-usockets/src/crypto/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1169,11 +1169,101 @@ enum {
US_IDENTITY_UNCHECKED = 2,
};

/* A blocked public key (blocked_spkis.cpp) is reported the way X509_verify_cert reports its own errors: to the SSL's
* verify callback, which says whether to go on. Without one, as in quic.c and node:quic, this returns 0 as it would. */
/* The syntax check of OpenSSL's cn2dnsid: whether a common name reads as a host name of two or more labels.
* https://github.com/nodejs/node/blob/v26.12.1/deps/openssl/openssl/crypto/x509/v3_ncons.c#L341 */
static int us_looks_like_dns_name(const unsigned char *in, int len) {
int is_dns_name = 0;
for (int i = 0; i < len; i++) {
unsigned char c = in[i];
if (OPENSSL_isalnum(c) || c == '_') continue;
if (i == 0 || i == len - 1) return 0;
if (c == '-') continue;
if (c != '.' || in[i + 1] == '.' || in[i - 1] == '-' || in[i + 1] == '-') return 0;
is_dns_name = 1;
}
return is_dns_name;
}

/* OpenSSL's nc_dns. */
static int us_dns_name_is_within(const unsigned char *name, int len, const ASN1_IA5STRING *base) {
if (base->length == 0) return 1;
if (len < base->length) return 0;
name += len - base->length;
if (len > base->length && base->data[0] != '.' && name[-1] != '.') return 0;
return OPENSSL_strncasecmp((const char *)name, (const char *)base->data, base->length) == 0;
}

/* OpenSSL's nc_match for a DNS name. BoringSSL's strips a trailing dot and refuses an explicit minimum of 0. */
static int us_name_constraints_check_dns_name(const NAME_CONSTRAINTS *nc, const unsigned char *name, int len) {
enum { UNCONSTRAINED, NOT_PERMITTED, PERMITTED } match = UNCONSTRAINED;
for (size_t i = 0; i < sk_GENERAL_SUBTREE_num(nc->permittedSubtrees); i++) {
const GENERAL_SUBTREE *sub = sk_GENERAL_SUBTREE_value(nc->permittedSubtrees, i);
if (sub->base->type != GEN_DNS) continue;
if (sub->maximum || ASN1_INTEGER_get(sub->minimum) != 0) return X509_V_ERR_SUBTREE_MINMAX;
if (match != PERMITTED) match = us_dns_name_is_within(name, len, sub->base->d.dNSName) ? PERMITTED : NOT_PERMITTED;
}
if (match == NOT_PERMITTED) return X509_V_ERR_PERMITTED_VIOLATION;
for (size_t i = 0; i < sk_GENERAL_SUBTREE_num(nc->excludedSubtrees); i++) {
const GENERAL_SUBTREE *sub = sk_GENERAL_SUBTREE_value(nc->excludedSubtrees, i);
if (sub->base->type != GEN_DNS) continue;
if (sub->maximum || ASN1_INTEGER_get(sub->minimum) != 0) return X509_V_ERR_SUBTREE_MINMAX;
if (us_dns_name_is_within(name, len, sub->base->d.dNSName)) return X509_V_ERR_EXCLUDED_VIOLATION;
}
return X509_V_OK;
}

/* OpenSSL's NAME_CONSTRAINTS_check_CN. BoringSSL looks at common names only when there is no subjectAltName at all. */
static int us_name_constraints_check_cn(X509 *leaf, const NAME_CONSTRAINTS *nc) {
const X509_NAME *subject = X509_get_subject_name(leaf);
int error = X509_V_OK;
for (int i = -1; error == X509_V_OK && (i = X509_NAME_get_index_by_NID(subject, NID_commonName, i)) >= 0;) {
unsigned char *common_name = NULL;
int len = ASN1_STRING_to_UTF8(&common_name, X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject, i)));
/* OpenSSL's check_name_constraints gives up here, which its X509_verify_cert reports as unspecified. */
if (len < 0) return X509_V_ERR_UNSPECIFIED;
while (len > 0 && common_name[len - 1] == '\0') len--;
if (len > 0 && memchr(common_name, 0, len)) {
error = X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
} else if (us_looks_like_dns_name(common_name, len)) {
error = us_name_constraints_check_dns_name(nc, common_name, len);
}
OPENSSL_free(common_name);
}
return error;
}

/* OpenSSL's has_san_id(cert, GEN_DNS). */
static int us_x509_has_dns_name(X509 *cert) {
GENERAL_NAMES *names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
int has_dns_name = 0;
for (size_t i = 0; i < sk_GENERAL_NAME_num(names) && !has_dns_name; i++) {
has_dns_name = sk_GENERAL_NAME_value(names, i)->type == GEN_DNS;
}
GENERAL_NAMES_free(names);
return has_dns_name;
}

/* When the leaf's subjectAltName lists no DNS name, its common names are checked against the chain's constraints on DNS
* names, as in OpenSSL's check_name_constraints. The nearest failing CA decides the error, as with node's callback. */
static int us_x509_store_ctx_common_name_error(X509_STORE_CTX *ctx) {
STACK_OF(X509) *chain = X509_STORE_CTX_get0_chain(ctx);
X509 *leaf = sk_X509_value(chain, 0);
int error = X509_V_OK;
for (size_t i = 1; i < sk_X509_num(chain) && error == X509_V_OK; i++) {
NAME_CONSTRAINTS *nc = X509_get_ext_d2i(sk_X509_value(chain, i), NID_name_constraints, NULL, NULL);
if (nc && !us_x509_has_dns_name(leaf)) error = us_name_constraints_check_cn(leaf, nc);
NAME_CONSTRAINTS_free(nc);
}
return error;
}

/* What X509_verify_cert does not look for, a blocked public key (blocked_spkis.cpp) and
* us_x509_store_ctx_common_name_error, is reported the way it reports its own errors: to the SSL's verify callback,
* which says whether to go on. Without one, as in quic.c and node:quic, this returns 0 as it would. */
static int us_x509_verify_chain(X509_STORE_CTX *ctx) {
int ok = X509_verify_cert(ctx);
int error = us_x509_store_ctx_blocked_key_error(ctx);
if (error == X509_V_OK && ok) error = us_x509_store_ctx_common_name_error(ctx);
if (error == X509_V_OK) return ok;
X509_STORE_CTX_set_error(ctx, error);
SSL *ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
Expand Down
9 changes: 9 additions & 0 deletions src/js/internal-for-testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ export const addBlockedSPKIForTesting: (hash: ArrayBufferView) => void = $newCpp
);
export const clearBlockedSPKIsForTesting: () => void = $newCppFunction("NodeTLS.cpp", "clearBlockedSPKIsForTesting", 0);

// This thread's BoringSSL error queue. An API that returns with entries on it makes the next unrelated failure that
// reads the queue report them. `put` adds BN_R_BITS_TOO_SMALL; `take` empties it and returns its entries, oldest first.
export const putBoringSSLErrorForTesting: () => void = $newCppFunction("NodeTLS.cpp", "putBoringSSLErrorForTesting", 0);
export const takeBoringSSLErrorQueueForTesting: () => string[] = $newCppFunction(
"NodeTLS.cpp",
"takeBoringSSLErrorQueueForTesting",
0,
);

// Runtime-dispatched SIMD xxHash3 kernel (src/jsc/bindings/xxhash3.cpp), driven
// directly so tests can exercise the Highway path independent of Bun.hash.
export const xxHash3ForTesting: (view: ArrayBufferView, seed?: number | bigint) => bigint = $newCppFunction(
Expand Down
1 change: 1 addition & 0 deletions src/jsc/JSGlobalObject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ impl JSGlobalObject {

pub fn throw_invalid_scrypt_params(&self) -> JsError {
let err = bun_boringssl::c::ERR_peek_last_error();
bun_boringssl::c::ERR_clear_error();
if err != 0 {
let mut buf = [0u8; 256];
// SAFETY: FFI — `buf` is a 256-byte stack buffer; `len` matches its capacity;
Expand Down
25 changes: 24 additions & 1 deletion src/jsc/bindings/NodeTLS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,9 @@ JSC_DEFINE_HOST_FUNCTION(parseCACertificates, (JSC::JSGlobalObject * globalObjec
}
if (parseError) {
// Reuse the shared ERR_OSSL_<LIB>_<REASON> decoration path
// (library/function/reason/code + opensslErrorStack), same as node:crypto.
// (library/function/reason/code), same as node:crypto. Node's
// LoadCertsFromBIO pops its errors, so there is no opensslErrorStack.
ERR_clear_error();
throwCryptoError(globalObject, scope, parseError);
return {};
}
Expand Down Expand Up @@ -362,4 +364,25 @@ JSC_DEFINE_HOST_FUNCTION(clearBlockedSPKIsForTesting, (JSC::JSGlobalObject * glo
return JSValue::encode(jsUndefined());
}

JSC_DEFINE_HOST_FUNCTION(putBoringSSLErrorForTesting, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
OPENSSL_PUT_ERROR(BN, BN_R_BITS_TOO_SMALL);
return JSValue::encode(jsUndefined());
}

JSC_DEFINE_HOST_FUNCTION(takeBoringSSLErrorQueueForTesting, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
auto& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSArray* errors = constructEmptyArray(globalObject, nullptr);
RETURN_IF_EXCEPTION(scope, {});
while (uint32_t error = ERR_get_error()) {
char message[256];
ERR_error_string_n(error, message, sizeof(message));
errors->putDirectIndex(globalObject, errors->length(), jsString(vm, String::fromUTF8(message)));
RETURN_IF_EXCEPTION(scope, {});
}
return JSValue::encode(errors);
}

} // namespace Bun
2 changes: 2 additions & 0 deletions src/jsc/bindings/NodeTLS.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ JSC_DECLARE_HOST_FUNCTION(parseCACertificates);
JSC_DECLARE_HOST_FUNCTION(selectServerName);
JSC_DECLARE_HOST_FUNCTION(addBlockedSPKIForTesting);
JSC_DECLARE_HOST_FUNCTION(clearBlockedSPKIsForTesting);
JSC_DECLARE_HOST_FUNCTION(putBoringSSLErrorForTesting);
JSC_DECLARE_HOST_FUNCTION(takeBoringSSLErrorQueueForTesting);

}
22 changes: 22 additions & 0 deletions src/jsc/bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@
#include "JavaScriptCore/AOTJSONHooks.h"
#include "DOMURL.h"
#include "JSDOMURL.h"
#include "JSCryptoKey.h"
#include "JSKeyObject.h"

#include <string_view>
#include <bun-uws/src/App.h>
Expand Down Expand Up @@ -1407,6 +1409,23 @@ static std::optional<bool> temporalObjectsDequal(JSC::JSObject* o1, JSC::JSObjec
return std::nullopt;
}

// A KeyObject or CryptoKey keeps its key in an internal slot and has no own properties
// either. Own properties are still compared afterwards, like node does.
static bool keyObjectsDequal(JSC::JSObject* o1, JSC::JSObject* o2)
{
auto* key1 = dynamicDowncast<Bun::JSKeyObject>(o1);
auto* key2 = dynamicDowncast<Bun::JSKeyObject>(o2);
if (key1 || key2)
return key1 && key2 && key1->handle().equals(key2->handle()).value_or(false);

auto* cryptoKey1 = dynamicDowncast<JSCryptoKey>(o1);
auto* cryptoKey2 = dynamicDowncast<JSCryptoKey>(o2);
if (cryptoKey1 || cryptoKey2)
return cryptoKey1 && cryptoKey2 && Bun::KeyObject::cryptoKeysEqual(cryptoKey1->wrapped(), cryptoKey2->wrapped());

return true;
}

struct DeepEqualsMode {
bool isStrict;
bool enableAsymmetricMatchers;
Expand Down Expand Up @@ -2177,6 +2196,9 @@ std::optional<bool> specialObjectsDequal(JSC::JSGlobalObject* globalObject, Mark
if (temporalEqual.has_value())
return temporalEqual;

if (!keyObjectsDequal(obj1, obj2))
return false;

const bool isSymbol1 = obj1->inherits<SymbolObject>();
const bool isBigInt1 = obj1->inherits<BigIntObject>();
if (isSymbol1 || isBigInt1) {
Expand Down
146 changes: 144 additions & 2 deletions src/jsc/bindings/ncrypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,22 @@ static constexpr int kX509NameFlagsRFC2253WithinUtf8JSON = XN_FLAG_RFC2253 & ~AS

// ============================================================================

// Not upstream: ERR_clear_error() resets all 16 slots even when the queue is empty, 46ns against 8ns for a peek.
static inline void clearErrorQueue()
{
if (ERR_peek_error() != 0) ERR_clear_error();
}

ClearErrorOnReturn::ClearErrorOnReturn(CryptoErrorList* errors)
: errors_(errors)
{
ERR_clear_error();
clearErrorQueue();
}

ClearErrorOnReturn::~ClearErrorOnReturn()
{
if (errors_ != nullptr) errors_->capture();
ERR_clear_error();
clearErrorQueue();
}

MarkPopErrorOnReturn::MarkPopErrorOnReturn(CryptoErrorList* errors)
Expand Down Expand Up @@ -1871,6 +1877,142 @@ int EVPKeyPointer::base_id() const
return base_id(get());
}

namespace {
constexpr size_t kEd25519PointSize = 32;
constexpr size_t kEd448PointSize = 57;

// Ed25519 has cofactor 8, so the first eight entries are the full
// canonical small-order subgroup: identity, one point of order 2,
// two points of order 4, and four points of order 8.
constexpr unsigned char kEd25519SmallOrderPoints[][kEd25519PointSize] = {
// Identity.
{ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
// Order 2.
{ 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f },
// Order 4.
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
// Order 8.
{ 0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b,
0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39,
0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a },
{ 0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b,
0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39,
0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa },
{ 0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4,
0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6,
0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05 },
{ 0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4,
0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6,
0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x85 },
// Non-canonical encodings of the same small-order points.
{ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 },
{ 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
{ 0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f },
{ 0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
{ 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
{ 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f },
};

// Ed448 has cofactor 4, so these four entries are the full canonical
// small-order subgroup: identity, one point of order 2, and two points
// of order 4.
constexpr unsigned char kEd448SmallOrderPoints[][kEd448PointSize] = {
// Identity.
{ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
// Order 2.
{ 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 },
// Order 4.
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 },
};

template<size_t PointSize, size_t Count>
bool ContainsPoint(const unsigned char* candidate,
const unsigned char (&points)[Count][PointSize])
{
for (const auto& point : points) {
if (memcmp(candidate, point, PointSize) == 0) return true;
}
return false;
}

bool IsSmallOrderEdDsaPoint(const EVPKeyPointer& key,
const unsigned char* candidate,
size_t size)
{
if (key.id() == EVP_PKEY_ED25519) {
return size == kEd25519PointSize && ContainsPoint(candidate, kEd25519SmallOrderPoints);
}
if (key.id() == EVP_PKEY_ED448) {
return size == kEd448PointSize && ContainsPoint(candidate, kEd448SmallOrderPoints);
}
return false;
}

} // namespace

bool EVPKeyPointer::hasSmallOrderEdDsaPoint(
const Buffer<const unsigned char>& signature) const
{
const size_t point_size = id() == EVP_PKEY_ED25519 ? kEd25519PointSize
: id() == EVP_PKEY_ED448 ? kEd448PointSize
: 0;
if (point_size == 0) return false;

if (signature.len != point_size * 2) return false;

if (IsSmallOrderEdDsaPoint(*this, signature.data, point_size)) {
return true;
}

unsigned char raw_public_key[kEd448PointSize];
size_t raw_public_key_size = point_size;
if (EVP_PKEY_get_raw_public_key(
get(), raw_public_key, &raw_public_key_size)
!= 1) {
return false;
}

return IsSmallOrderEdDsaPoint(*this, raw_public_key, raw_public_key_size);
}

size_t EVPKeyPointer::size() const
{
if (get() == nullptr) return 0;
Expand Down
2 changes: 2 additions & 0 deletions src/jsc/bindings/ncrypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,8 @@ class EVPKeyPointer final {

int id() const;
int base_id() const;
bool hasSmallOrderEdDsaPoint(
const Buffer<const unsigned char>& signature) const;
size_t size() const;

size_t rawPublicKeySize() const;
Expand Down
Loading
Loading