Skip to content

Commit 4417ca0

Browse files
committed
LibCrypto+LibWeb: Implement ECDSA.sign
Gained ~20 tests, failing only on P-521.
1 parent 0d0f061 commit 4417ca0

File tree

3 files changed

+395
-283
lines changed

3 files changed

+395
-283
lines changed

Libraries/LibCrypto/Curves/SECPxxxr1.h

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,69 @@ class SECPxxxr1 : public EllipticCurve {
315315
SECPxxxr1Signature { r_bigint, s_bigint });
316316
}
317317

318+
ErrorOr<SECPxxxr1Signature> sign_scalar(ReadonlyBytes hash, UnsignedBigInteger private_key)
319+
{
320+
auto d = unsigned_big_integer_to_storage_type(private_key);
321+
322+
auto k_int = TRY(generate_private_key_scalar());
323+
auto k = unsigned_big_integer_to_storage_type(k_int);
324+
auto k_mo = to_montgomery_order(k);
325+
326+
auto kG = TRY(generate_public_key_internal(k));
327+
auto r = kG.x;
328+
329+
if (r.is_zero_constant_time()) {
330+
// Retry with a new k
331+
return sign_scalar(hash, private_key);
332+
}
333+
334+
// Compute z from the hash
335+
StorageType z = 0u;
336+
for (size_t i = 0; i < KEY_BYTE_SIZE && i < hash.size(); i++) {
337+
z <<= 8;
338+
z |= hash[i];
339+
}
340+
341+
// s = k^-1 * (z + r * d) mod n
342+
auto r_mo = to_montgomery_order(r);
343+
auto z_mo = to_montgomery_order(z);
344+
auto d_mo = to_montgomery_order(d);
345+
346+
// r * d mod n
347+
auto rd_mo = modular_multiply_order(r_mo, d_mo);
348+
349+
// z + (r * d) mod n
350+
auto z_plus_rd_mo = modular_add_order(z_mo, rd_mo);
351+
352+
// k^-1 mod n
353+
auto k_inv_mo = modular_inverse_order(k_mo);
354+
355+
// s = k^-1 * (z + r * d) mod n
356+
auto s_mo = modular_multiply_order(z_plus_rd_mo, k_inv_mo);
357+
auto s = from_montgomery_order(s_mo);
358+
359+
if (s.is_zero_constant_time()) {
360+
// Retry with a new k
361+
return sign_scalar(hash, private_key);
362+
}
363+
364+
return SECPxxxr1Signature { storage_type_to_unsigned_big_integer(r), storage_type_to_unsigned_big_integer(s) };
365+
}
366+
367+
ErrorOr<ByteBuffer> sign(ReadonlyBytes hash, ReadonlyBytes private_key_bytes)
368+
{
369+
auto signature = TRY(sign_scalar(hash, UnsignedBigInteger::import_data(private_key_bytes.data(), private_key_bytes.size())));
370+
371+
Crypto::ASN1::Encoder asn1_encoder;
372+
TRY(asn1_encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr<void> {
373+
TRY(asn1_encoder.write(signature.r));
374+
TRY(asn1_encoder.write(signature.s));
375+
return {};
376+
}));
377+
378+
return asn1_encoder.finish();
379+
}
380+
318381
private:
319382
StorageType unsigned_big_integer_to_storage_type(UnsignedBigInteger big)
320383
{

Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2359,35 +2359,93 @@ WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> ECDSA::sign(AlgorithmParams const&
23592359
auto& vm = realm.vm();
23602360
auto const& normalized_algorithm = static_cast<EcdsaParams const&>(params);
23612361

2362-
(void)vm;
2363-
(void)message;
2364-
23652362
// 1. If the [[type]] internal slot of key is not "private", then throw an InvalidAccessError.
23662363
if (key->type() != Bindings::KeyType::Private)
23672364
return WebIDL::InvalidAccessError::create(realm, "Key is not a private key"_string);
23682365

23692366
// 2. Let hashAlgorithm be the hash member of normalizedAlgorithm.
2370-
[[maybe_unused]] auto const& hash_algorithm = normalized_algorithm.hash;
2367+
auto const& hash_algorithm = TRY(normalized_algorithm.hash.name(vm));
2368+
2369+
// 3. Let M be the result of performing the digest operation specified by hashAlgorithm using message.
2370+
::Crypto::Hash::HashKind hash_kind;
2371+
if (hash_algorithm == "SHA-1") {
2372+
hash_kind = ::Crypto::Hash::HashKind::SHA1;
2373+
} else if (hash_algorithm == "SHA-256") {
2374+
hash_kind = ::Crypto::Hash::HashKind::SHA256;
2375+
} else if (hash_algorithm == "SHA-384") {
2376+
hash_kind = ::Crypto::Hash::HashKind::SHA384;
2377+
} else if (hash_algorithm == "SHA-512") {
2378+
hash_kind = ::Crypto::Hash::HashKind::SHA512;
2379+
} else {
2380+
return WebIDL::NotSupportedError::create(m_realm, MUST(String::formatted("Invalid hash function '{}'", hash_algorithm)));
2381+
}
2382+
::Crypto::Hash::Manager hash { hash_kind };
2383+
hash.update(message);
2384+
auto digest = hash.digest();
2385+
2386+
auto M = TRY_OR_THROW_OOM(vm, ByteBuffer::copy(digest.immutable_data(), hash.digest_size()));
2387+
2388+
// 4. Let d be the ECDSA private key associated with key.
2389+
auto d = key->handle().get<::Crypto::PK::ECPrivateKey<>>();
23712390

2372-
// NOTE: We dont have sign() on the SECPxxxr1 curves, so we can't implement this yet
2373-
// FIXME: 3. Let M be the result of performing the digest operation specified by hashAlgorithm using message.
2374-
// FIXME: 4. Let d be the ECDSA private key associated with key.
23752391
// FIXME: 5. Let params be the EC domain parameters associated with key.
2376-
// FIXME: 6. If the namedCurve attribute of the [[algorithm]] internal slot of key is "P-256", "P-384" or "P-521":
23772392

2378-
// FIXME: 1. Perform the ECDSA signing process, as specified in [RFC6090], Section 5.4, with M as the message, using params as the EC domain parameters, and with d as the private key.
2379-
// FIXME: 2. Let r and s be the pair of integers resulting from performing the ECDSA signing process.
2380-
// FIXME: 3. Let result be an empty byte sequence.
2381-
// FIXME: 4. Let n be the smallest integer such that n * 8 is greater than the logarithm to base 2 of the order of the base point of the elliptic curve identified by params.
2382-
// FIXME: 5. Convert r to an octet string of length n and append this sequence of bytes to result.
2383-
// FIXME: 6. Convert s to an octet string of length n and append this sequence of bytes to result.
2393+
auto const& internal_algorithm = static_cast<EcKeyAlgorithm const&>(*key->algorithm());
2394+
auto const& named_curve = internal_algorithm.named_curve();
2395+
2396+
ByteBuffer result;
23842397

2385-
// FIXME: Otherwise, the namedCurve attribute of the [[algorithm]] internal slot of key is a value specified in an applicable specification:
2386-
// FIXME: Perform the ECDSA signature steps specified in that specification, passing in M, params and d and resulting in result.
2398+
// 6. If the namedCurve attribute of the [[algorithm]] internal slot of key is "P-256", "P-384" or "P-521":
2399+
if (named_curve.is_one_of("P-256"sv, "P-384"sv, "P-521"sv)) {
2400+
size_t coord_size;
2401+
Variant<Empty, ::Crypto::Curves::SECP256r1, ::Crypto::Curves::SECP384r1> curve;
2402+
if (named_curve == "P-256") {
2403+
coord_size = 32;
2404+
curve = ::Crypto::Curves::SECP256r1 {};
2405+
} else if (named_curve == "P-384") {
2406+
coord_size = 48;
2407+
curve = ::Crypto::Curves::SECP384r1 {};
2408+
} else if (named_curve == "P-521") {
2409+
// FIXME: Support P-521
2410+
coord_size = 66;
2411+
return WebIDL::NotSupportedError::create(m_realm, "'P-521' is not supported yet"_string);
2412+
} else {
2413+
VERIFY_NOT_REACHED();
2414+
}
2415+
2416+
// 1. Perform the ECDSA signing process, as specified in [RFC6090], Section 5.4, with M as the message,
2417+
// using params as the EC domain parameters, and with d as the private key.
2418+
// 2. Let r and s be the pair of integers resulting from performing the ECDSA signing process.
2419+
auto maybe_signature = curve.visit(
2420+
[](Empty const&) -> ErrorOr<::Crypto::Curves::SECPxxxr1Signature> { return Error::from_string_literal("Failed to create valid crypto instance"); },
2421+
[&](auto instance) { return instance.sign_scalar(M, d.d()); });
2422+
2423+
if (maybe_signature.is_error()) {
2424+
auto error_message = MUST(String::from_utf8(maybe_signature.error().string_literal()));
2425+
return WebIDL::OperationError::create(m_realm, error_message);
2426+
}
2427+
2428+
auto signature = maybe_signature.release_value();
2429+
2430+
// 3. Let result be an empty byte sequence.
2431+
result = TRY_OR_THROW_OOM(vm, ByteBuffer::create_zeroed(coord_size * 2));
2432+
2433+
// 4. Let n be the smallest integer such that n * 8 is greater than the logarithm to base 2 of the order of the base point of the elliptic curve identified by params.
2434+
// 5. Convert r to an octet string of length n and append this sequence of bytes to result.
2435+
VERIFY(signature.r.byte_length() <= coord_size);
2436+
(void)signature.r.export_data(result.span());
2437+
2438+
// 6. Convert s to an octet string of length n and append this sequence of bytes to result.
2439+
VERIFY(signature.s.byte_length() <= coord_size);
2440+
(void)signature.s.export_data(result.span().slice(coord_size));
2441+
} else {
2442+
// FIXME: Otherwise, the namedCurve attribute of the [[algorithm]] internal slot of key is a value specified in an applicable specification:
2443+
// FIXME: Perform the ECDSA signature steps specified in that specification, passing in M, params and d and resulting in result.
2444+
}
23872445

23882446
// NOTE: The spec jumps to 9 here for some reason
2389-
// FIXME: 9. Return the result of creating an ArrayBuffer containing result.
2390-
return WebIDL::NotSupportedError::create(realm, "ECDSA signing is not supported yet"_string);
2447+
// 9. Return the result of creating an ArrayBuffer containing result.
2448+
return JS::ArrayBuffer::create(m_realm, result);
23912449
}
23922450

23932451
// https://w3c.github.io/webcrypto/#ecdsa-operations
@@ -2420,11 +2478,7 @@ WebIDL::ExceptionOr<JS::Value> ECDSA::verify(AlgorithmParams const& params, GC::
24202478
hash.update(message);
24212479
auto digest = hash.digest();
24222480

2423-
auto result_buffer = ByteBuffer::copy(digest.immutable_data(), hash.digest_size());
2424-
if (result_buffer.is_error())
2425-
return WebIDL::OperationError::create(m_realm, "Failed to create result buffer"_string);
2426-
2427-
auto M = result_buffer.release_value();
2481+
auto M = TRY_OR_THROW_OOM(realm.vm(), ByteBuffer::copy(digest.immutable_data(), hash.digest_size()));
24282482

24292483
// 4. Let Q be the ECDSA public key associated with key.
24302484
auto Q = key->handle().get<::Crypto::PK::ECPublicKey<>>();

0 commit comments

Comments
 (0)