LibCrypto: Support the parsing of certs and keys for ml-kem keys

This commit is contained in:
Tete17 2026-01-04 18:41:48 +01:00 committed by Shannon Booth
parent df0796bdf2
commit dac1952e47
4 changed files with 143 additions and 0 deletions

View file

@ -355,6 +355,36 @@ ErrorOr<PrivateKey> parse_private_key_info(ASN1::Decoder& decoder, Vector<String
EXIT_SCOPE();
return private_key;
}
if (private_key.algorithm.identifier.span() == ASN1::ml_kem_512_oid.span()) {
auto maybe_key = Crypto::PK::MLKEM::parse_mlkem_key(PK::MLKEMSize::MLKEM512, value.bytes(), current_scope);
if (maybe_key.is_error()) {
ERROR_WITH_SCOPE(maybe_key.release_error());
}
private_key.mlkem = move(maybe_key.release_value().private_key);
EXIT_SCOPE();
return private_key;
}
if (private_key.algorithm.identifier.span() == ASN1::ml_kem_768_oid.span()) {
auto maybe_key = Crypto::PK::MLKEM::parse_mlkem_key(PK::MLKEMSize::MLKEM768, value.bytes(), current_scope);
if (maybe_key.is_error()) {
ERROR_WITH_SCOPE(maybe_key.release_error());
}
private_key.mlkem = move(maybe_key.release_value().private_key);
EXIT_SCOPE();
return private_key;
}
if (private_key.algorithm.identifier.span() == ASN1::ml_kem_1024_oid.span()) {
auto maybe_key = Crypto::PK::MLKEM::parse_mlkem_key(PK::MLKEMSize::MLKEM1024, value.bytes(), current_scope);
if (maybe_key.is_error()) {
ERROR_WITH_SCOPE(maybe_key.release_error());
}
private_key.mlkem = move(maybe_key.release_value().private_key);
EXIT_SCOPE();
return private_key;
}
// https://datatracker.ietf.org/doc/html/rfc8410#section-9
// For all of the OIDs, the parameters MUST be absent.

View file

@ -9,6 +9,7 @@
#include <LibCrypto/ASN1/DER.h>
#include <LibCrypto/PK/EC.h>
#include <LibCrypto/PK/MLDSA.h>
#include <LibCrypto/PK/MLKEM.h>
#include <LibCrypto/PK/RSA.h>
namespace Crypto::Certificate {
@ -46,6 +47,7 @@ public:
PK::RSAPrivateKey rsa;
PK::ECPrivateKey ec;
PK::MLDSAPrivateKey mldsa;
PK::MLKEMPrivateKey mlkem;
AlgorithmIdentifier algorithm;
ByteBuffer raw_key;

View file

@ -6,6 +6,8 @@
#include <LibCrypto/PK/MLKEM.h>
#include <LibCrypto/ASN1/DER.h>
#include <LibCrypto/Curves/SECPxxxr1.h>
#include <LibCrypto/OpenSSL.h>
#include <openssl/core_names.h>
@ -28,6 +30,110 @@ static char const* mlkem_size_to_openssl_name(MLKEMSize size)
}
}
static ErrorOr<ByteBuffer> read_mlkem_seed(ASN1::Decoder& decoder, Vector<StringView>& current_scope)
{
// seed ::= OCTET STRING (SIZE (64))
READ_OBJECT(OctetString, StringView, seed_bits);
auto const seed = seed_bits.bytes();
if (seed.size() != 64) {
ERROR_WITH_SCOPE("Invalid seed length");
}
POP_SCOPE();
return ByteBuffer::copy(seed);
}
static ErrorOr<ByteBuffer> read_mlkem_private_key(MLKEMSize size, ASN1::Decoder& decoder, Vector<StringView>& current_scope)
{
// expandedKey ::= OCTET STRING (SIZE (1632 | 2400 | 3168))
ENTER_TYPED_SCOPE(OctetString, "expandedKey");
READ_OBJECT(OctetString, StringView, expanded_key_bits);
auto const expanded_key = expanded_key_bits.bytes();
switch (size) {
case MLKEMSize::MLKEM512:
if (expanded_key.size() != 1632) {
ERROR_WITH_SCOPE("Invalid expandedKey size");
}
break;
case MLKEMSize::MLKEM768:
if (expanded_key.size() != 2400) {
ERROR_WITH_SCOPE("Invalid expandedKey size");
}
break;
case MLKEMSize::MLKEM1024:
if (expanded_key.size() != 3168) {
ERROR_WITH_SCOPE("Invalid expandedKey size");
}
break;
default:
VERIFY_NOT_REACHED();
}
POP_SCOPE();
return ByteBuffer::copy(expanded_key);
}
ErrorOr<ByteBuffer> MLKEMPrivateKey::export_as_der() const
{
ASN1::Encoder encoder;
TRY(encoder.write<ReadonlyBytes>(m_seed, ASN1::Class::Context, static_cast<ASN1::Kind>(0)));
return encoder.finish();
}
// https://datatracker.ietf.org/doc/html/draft-ietf-lamps-kyber-certificates-11#autoid-7
ErrorOr<MLKEM::KeyPairType> MLKEM::parse_mlkem_key(MLKEMSize size, ReadonlyBytes der, Vector<StringView> current_scope)
{
ASN1::Decoder decoder(der);
// ML-KEM-PrivateKey ::= CHOICE {
// seed [0] IMPLICIT OCTET STRING (SIZE (64)),
// expandedKey OCTET STRING (SIZE (1632 | 2400 | 3168)),
// both SEQUENCE {
// seed OCTET STRING (SIZE (64)),
// expandedKey OCTET STRING (SIZE (1632 | 2400 | 3168))
// }
// }
if (decoder.eof()) {
return Error::from_string_literal("Input key is empty");
}
auto const tag = TRY(decoder.peek());
if (static_cast<u8>(tag.kind) == 0) {
REWRITE_TAG(OctetString);
return generate_key_pair(size, TRY(read_mlkem_seed(decoder, current_scope)));
}
if (tag.kind == ASN1::Kind::OctetString) {
return KeyPairType {
{},
{ {}, {}, TRY(read_mlkem_private_key(size, decoder, current_scope)) }
};
}
if (tag.kind == ASN1::Kind::Sequence) {
ENTER_TYPED_SCOPE(Sequence, "both");
ENTER_TYPED_SCOPE(OctetString, "seed");
auto key_pair = TRY(generate_key_pair(size, TRY(read_mlkem_seed(decoder, current_scope))));
POP_SCOPE()
ENTER_TYPED_SCOPE(OctetString, "expandedKey");
if (auto const expanded_key = TRY(read_mlkem_private_key(size, decoder, current_scope));
key_pair.private_key.private_key() != expanded_key) {
ERROR_WITH_SCOPE("Invalid expanded_key");
}
POP_SCOPE();
POP_SCOPE();
return key_pair;
}
return Error::from_string_literal("Invalid key format");
}
ErrorOr<MLKEMEncapsulation> MLKEM::encapsulate(MLKEMSize size, MLKEMPublicKey const& key)
{
auto public_key = TRY(OpenSSL_PKEY::wrap(EVP_PKEY_new_raw_public_key_ex(nullptr, mlkem_size_to_openssl_name(size), nullptr, key.public_key().data(), key.public_key().size())));

View file

@ -44,6 +44,9 @@ public:
MLKEMPrivateKey() = default;
ByteBuffer const& seed() const { return m_seed; }
ByteBuffer const& private_key() const { return m_private_key; }
ErrorOr<ByteBuffer> export_as_der() const;
private:
ByteBuffer m_seed;
@ -69,6 +72,8 @@ class MLKEM {
public:
using KeyPairType = MLKEMKeyPair<PublicKeyType, PrivateKeyType>;
static ErrorOr<KeyPairType> parse_mlkem_key(MLKEMSize, ReadonlyBytes der, Vector<StringView> current_scope);
static ErrorOr<MLKEMEncapsulation> encapsulate(MLKEMSize size, MLKEMPublicKey const& key);
static ErrorOr<KeyPairType> generate_key_pair(MLKEMSize size, ByteBuffer seed = {});
};