From 5f3f08949449d2bd7f608454d2afd7296f516895 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Tue, 26 Nov 2024 18:11:15 +0100 Subject: [PATCH] LibCrypto: Implement `ECPrivateKey` and `ECPublicKey` Added basic EC private and public key definitions as well as ASN.1 encoding and decoding. A lot of refactoring can be made around the ASN.1 processing (here and in other parts of the codebase) by utilizing what is available in `LibCrypto::Certificate` as macros, but I think it's outside the scope of implementing ECDH support for WebCryptoAPI. --- Libraries/LibCrypto/CMakeLists.txt | 1 + Libraries/LibCrypto/PK/EC.cpp | 124 +++++++++++++++++++++++++++++ Libraries/LibCrypto/PK/EC.h | 87 ++++++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 Libraries/LibCrypto/PK/EC.cpp create mode 100644 Libraries/LibCrypto/PK/EC.h diff --git a/Libraries/LibCrypto/CMakeLists.txt b/Libraries/LibCrypto/CMakeLists.txt index cfade641e6b..ef9909971a3 100644 --- a/Libraries/LibCrypto/CMakeLists.txt +++ b/Libraries/LibCrypto/CMakeLists.txt @@ -34,6 +34,7 @@ set(SOURCES Hash/SHA2.cpp NumberTheory/ModularFunctions.cpp PK/RSA.cpp + PK/EC.cpp ) serenity_lib(LibCrypto crypto) diff --git a/Libraries/LibCrypto/PK/EC.cpp b/Libraries/LibCrypto/PK/EC.cpp new file mode 100644 index 00000000000..5118952f33a --- /dev/null +++ b/Libraries/LibCrypto/PK/EC.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2024, Altomani Gianluca + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Crypto::PK { + +template<> +ErrorOr ECPrivateKey::export_as_der() const +{ + ASN1::Encoder encoder; + TRY(encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr { + TRY(encoder.write(1u)); // version + + auto d_bytes = TRY(ByteBuffer::create_uninitialized(m_d.byte_length())); + auto d_size = m_d.export_data(d_bytes.span()); + TRY(encoder.write(d_bytes.span().slice(0, d_size))); + + if (m_parameters.has_value()) { + TRY(encoder.write_constructed(ASN1::Class::Context, static_cast(0), [&]() -> ErrorOr { + TRY(encoder.write>(*m_parameters, {}, ASN1::Kind::ObjectIdentifier)); + return {}; + })); + } + + if (m_public_key.has_value()) { + TRY(encoder.write_constructed(ASN1::Class::Context, static_cast(1), [&]() -> ErrorOr { + auto public_key_bytes = TRY(m_public_key->to_uncompressed()); + TRY(encoder.write(ASN1::BitStringView(public_key_bytes, 0))); + + return {}; + })); + } + + return {}; + })); + + return encoder.finish(); +} + +// https://www.rfc-editor.org/rfc/rfc5915#section-3 +ErrorOr EC::parse_ec_key(ReadonlyBytes der) +{ + // ECPrivateKey ::= SEQUENCE { + // version INTEGER { ecPrivkeyVer1(1) }(ecPrivkeyVer1), + // privateKey OCTET STRING, + // parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, + // publicKey [1] BIT STRING OPTIONAL + // } + KeyPairType keypair; + + ASN1::Decoder decoder(der); + + auto tag = TRY(decoder.peek()); + if (tag.kind != ASN1::Kind::Sequence) { + auto message = TRY(String::formatted("EC key parse failed: Expected a Sequence but got {}", ASN1::kind_name(tag.kind))); + return Error::from_string_view(message.bytes_as_string_view()); + } + + TRY(decoder.enter()); + + auto version = TRY(decoder.read()); + if (version != 1) { + auto message = TRY(String::formatted("EC key parse failed: Invalid version {}", version)); + return Error::from_string_view(message.bytes_as_string_view()); + } + + auto private_key = TRY(decoder.read()); + + Optional> parameters; + if (!decoder.eof()) { + auto tag = TRY(decoder.peek()); + if (static_cast(tag.kind) == 0) { + TRY(decoder.rewrite_tag(ASN1::Kind::Sequence)); + TRY(decoder.enter()); + + parameters = TRY(Crypto::Certificate::parse_ec_parameters(decoder, {})); + + TRY(decoder.leave()); + } + } + + Optional> public_key; + if (!decoder.eof()) { + auto tag = TRY(decoder.peek()); + if (static_cast(tag.kind) == 1) { + TRY(decoder.rewrite_tag(ASN1::Kind::Sequence)); + TRY(decoder.enter()); + + auto public_key_bits = TRY(decoder.read()); + auto public_key_bytes = TRY(public_key_bits.raw_bytes()); + if (public_key_bytes.size() != 1 + private_key.length() * 2) { + return Error::from_string_literal("EC key parse failed: Invalid public key length"); + } + + if (public_key_bytes[0] != 0x04) { + return Error::from_string_literal("EC key parse failed: Unsupported public key format"); + } + + public_key = ::Crypto::PK::ECPublicKey<> { + UnsignedBigInteger::import_data(public_key_bytes.slice(1, private_key.length())), + UnsignedBigInteger::import_data(public_key_bytes.slice(1 + private_key.length(), private_key.length())), + }; + + TRY(decoder.leave()); + } + } + + keypair.private_key = ECPrivateKey { + UnsignedBigInteger::import_data(private_key), + parameters, + public_key, + }; + + return keypair; +} + +} diff --git a/Libraries/LibCrypto/PK/EC.h b/Libraries/LibCrypto/PK/EC.h new file mode 100644 index 00000000000..d4492805610 --- /dev/null +++ b/Libraries/LibCrypto/PK/EC.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024, Altomani Gianluca + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Crypto::PK { + +template +class ECPublicKey { +public: + ECPublicKey(Integer x, Integer y) + : m_x(move(x)) + , m_y(move(y)) + { + } + + ECPublicKey() + : m_x(0) + , m_y(0) + { + } + + Integer const& x() const { return m_x; } + Integer const& y() const { return m_y; } + + ErrorOr to_uncompressed() const + { + auto bytes = TRY(ByteBuffer::create_uninitialized(1 + m_x.byte_length() + m_y.byte_length())); + bytes[0] = 0x04; // uncompressed + auto x_size = m_x.export_data(bytes.span().slice(1)); + auto y_size = m_y.export_data(bytes.span().slice(1 + x_size)); + return bytes.slice(0, 1 + x_size + y_size); + } + +private: + Integer m_x; + Integer m_y; +}; + +// https://www.rfc-editor.org/rfc/rfc5915#section-3 +template +class ECPrivateKey { +public: + ECPrivateKey(Integer d, Optional> parameters, Optional> public_key) + : m_d(move(d)) + , m_parameters(parameters) + , m_public_key(public_key) + { + } + + ECPrivateKey() = default; + + Integer const& d() const { return m_d; } + Optional const&> parameters() const { return m_parameters; } + Optional const&> public_key() const { return m_public_key; } + + ErrorOr export_as_der() const; + +private: + Integer m_d; + Optional> m_parameters; + Optional> m_public_key; +}; + +template +struct ECKeyPair { + PubKey public_key; + PrivKey private_key; +}; + +using IntegerType = UnsignedBigInteger; +class EC : public PKSystem, ECPublicKey> { +public: + using KeyPairType = ECKeyPair; + + static ErrorOr parse_ec_key(ReadonlyBytes der); +}; + +}