Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
144 changes: 143 additions & 1 deletion src/crypto/crypto_sig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,147 @@ bool ApplyRSAOptions(const EVPKeyPointer& pkey,
return true;
}

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(int id,
const unsigned char* candidate,
size_t size) {
switch (id) {
case EVP_PKEY_ED25519:
return size == kEd25519PointSize &&
ContainsPoint(candidate, kEd25519SmallOrderPoints);
case EVP_PKEY_ED448:
return size == kEd448PointSize &&
ContainsPoint(candidate, kEd448SmallOrderPoints);
default:
return false;
}
}

bool HasSmallOrderEdDsaPoint(const EVPKeyPointer& key,
const ByteSource& signature) {
const int id = key.id();
size_t point_size;

switch (id) {
case EVP_PKEY_ED25519:
point_size = kEd25519PointSize;
break;
case EVP_PKEY_ED448:
point_size = kEd448PointSize;
break;
default:
return false;
}

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

if (IsSmallOrderEdDsaPoint(id, signature.data<unsigned char>(), 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(
key.get(), raw_public_key, &raw_public_key_size) != 1) {
return false;
}

return IsSmallOrderEdDsaPoint(id, raw_public_key, raw_public_key_size);
}

std::unique_ptr<BackingStore> Node_SignFinal(Environment* env,
EVPMDCtxPointer&& mdctx,
const EVPKeyPointer& pkey,
Expand Down Expand Up @@ -754,7 +895,8 @@ bool SignTraits::DeriveBits(Environment* env,
case SignConfiguration::Mode::Verify: {
auto buf = DataPointer::Alloc(1);
static_cast<char*>(buf.get())[0] = 0;
if (context.verify(params.data, params.signature)) {
if (context.verify(params.data, params.signature) &&
!HasSmallOrderEdDsaPoint(key, params.signature)) {
static_cast<char*>(buf.get())[0] = 1;
}
*out = ByteSource::Allocated(buf.release());
Expand Down
41 changes: 41 additions & 0 deletions test/parallel/test-webcrypto-sign-verify-eddsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,44 @@ const vectors = require('../fixtures/crypto/eddsa')();

const supportsContext = hasOpenSSL(3, 2);

const smallOrderVerifyVectors = [
{
name: 'Ed25519',
publicKey: Buffer.from(
'c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa',
'hex'),
signature: Buffer.from(
'c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a' +
'0000000000000000000000000000000000000000000000000000000000000000',
'hex'),
data: Buffer.from(
'8c93255d71dcab10e8f379c26200f3c7bd5f09d9bc3068d3ef4edeb4853022b6',
'hex'),
},
];

if (!process.features.openssl_is_boringssl) {
smallOrderVerifyVectors.push({
name: 'Ed448',
publicKey: Buffer.concat([Buffer.from([1]), Buffer.alloc(56)]),
signature: Buffer.concat([Buffer.from([1]), Buffer.alloc(113)]),
data: Buffer.from([1, 2, 3]),
});
}

async function testSmallOrderVerify({ name, publicKey, signature, data }) {
const key = await subtle.importKey(
'raw',
publicKey,
{ name },
false,
['verify']);

assert.strictEqual(
await subtle.verify({ name }, key, signature, data),
false);
}

async function testVerify({ name,
context,
publicKeyBuffer,
Expand Down Expand Up @@ -260,6 +298,9 @@ async function testSign({ name,
variations.push(testVerify(vector));
variations.push(testSign(vector));
});
smallOrderVerifyVectors.forEach((vector) => {
variations.push(testSmallOrderVerify(vector));
});

await Promise.all(variations);
})().then(common.mustCall());
Expand Down
19 changes: 0 additions & 19 deletions test/wpt/status/WebCryptoAPI.cjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
'use strict';

const os = require('node:os');

const { hasOpenSSL } = require('../../common/crypto.js');

const s390x = os.arch() === 's390x';

const conditionalFileSkips = {};
const conditionalSubtestSkips = {};

Expand Down Expand Up @@ -115,19 +111,4 @@ module.exports = {
'historical.any.js': {
'skip': 'Not relevant in Node.js context',
},
'sign_verify/eddsa_small_order_points.https.any.js': {
'fail': {
'note': 'see https://github.com/nodejs/node/issues/54572',
'expected': [
'Ed25519 Verification checks with small-order key of order - Test 1',
'Ed25519 Verification checks with small-order key of order - Test 2',
'Ed25519 Verification checks with small-order key of order - Test 12',
'Ed25519 Verification checks with small-order key of order - Test 13',
...(s390x ? [] : [
'Ed25519 Verification checks with small-order key of order - Test 0',
'Ed25519 Verification checks with small-order key of order - Test 11',
]),
],
},
},
};
Loading