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
15 changes: 15 additions & 0 deletions doc/api_ref/pubkey.rst
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,21 @@ Currently two flavors of Kyber are implemented in separate Botan modules:
* ``kyber_90s``, that uses AES/SHA-2 instead of Keccak-based primitives.
This mode is deprecated and will be removed in a future release.

ML-KEM-composite (draft-ietf-lamps-pq-composite-kem-16)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

ML-KEM-composite algorithms are accessible in Botan via the generic algorithm string
"ML-KEM-composite" and the respective OIDs registered
under the names MLKEM768-RSA2048-SHA3-256, MLKEM768-RSA3072-SHA3-256,
MLKEM768-RSA4096-SHA3-256, MLKEM768-X25519-SHA3-256,
MLKEM768-ECDH-P256-SHA3-256, MLKEM768-ECDH-P384-SHA3-256,
MLKEM768-ECDH-brainpoolP256r1-SHA3-256, MLKEM1024-RSA3072-SHA3-256,
MLKEM1024-ECDH-P384-SHA3-256, MLKEM1024-ECDH-brainpoolP384r1-SHA3-256,
MLKEM1024-X448-SHA3-256, MLKEM1024-ECDH-P521-SHA3-256.

There is no default parameter set defined, thus a parameter string has to be
provided in any case.

Ed25519 and Ed448
~~~~~~~~~~~~~~~~~

Expand Down
3 changes: 2 additions & 1 deletion readme.rst
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ Public Key Cryptography
* DH, ECDH, X25519 and X448 key agreement
* Elliptic curve signature schemes ECDSA, Ed25519, Ed448, ECGDSA, ECKCDSA, SM2
* Post-quantum signature schemes ML-DSA (Dilithium), SLH-DSA (SPHINCS+), HSS/LMS, XMSS
* Post-quantum key encapsulation schemes ML-KEM (Kyber), FrodoKEM, Classic McEliece
* Post-quantum key encapsulation schemes ML-KEM (Kyber), ML-KEM-composite
according to draft-ietf-lamps-pq-composite-kem-16, FrodoKEM, Classic McEliece

Ciphers, hashes, MACs, and checksums
----------------------------------------
Expand Down
13 changes: 13 additions & 0 deletions src/build-data/oids.txt
Original file line number Diff line number Diff line change
Expand Up @@ -493,3 +493,16 @@
1.3.6.1.4.1.25258.4.1 = numsp256d1
1.3.6.1.4.1.25258.4.2 = numsp384d1
1.3.6.1.4.1.25258.4.3 = numsp512d1

1.3.6.1.5.5.7.6.55 = MLKEM768-RSA2048-SHA3-256
1.3.6.1.5.5.7.6.56 = MLKEM768-RSA3072-SHA3-256
1.3.6.1.5.5.7.6.57 = MLKEM768-RSA4096-SHA3-256
1.3.6.1.5.5.7.6.58 = MLKEM768-X25519-SHA3-256
1.3.6.1.5.5.7.6.59 = MLKEM768-ECDH-P256-SHA3-256
1.3.6.1.5.5.7.6.60 = MLKEM768-ECDH-P384-SHA3-256
1.3.6.1.5.5.7.6.61 = MLKEM768-ECDH-brainpoolP256r1-SHA3-256
1.3.6.1.5.5.7.6.62 = MLKEM1024-RSA3072-SHA3-256
1.3.6.1.5.5.7.6.63 = MLKEM1024-ECDH-P384-SHA3-256
1.3.6.1.5.5.7.6.64 = MLKEM1024-ECDH-brainpoolP384r1-SHA3-256
1.3.6.1.5.5.7.6.65 = MLKEM1024-X448-SHA3-256
1.3.6.1.5.5.7.6.66 = MLKEM1024-ECDH-P521-SHA3-256
1 change: 1 addition & 0 deletions src/build-data/policy/bsi.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ rsa
ecies
dh
ecdh
mlkem-composite

# Allowed KDF for ECIES (see 2.3.4)
kdf1_iso18033
Expand Down
33 changes: 32 additions & 1 deletion src/cli/perf_pk_kem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@
#include "perf.h"

#include <ostream>
#include <vector>

#if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO)
#include <botan/pk_algs.h>
#include <botan/pubkey.h>
#include <botan/rng.h>
#endif

#if defined(BOTAN_HAS_MLKEM_COMPOSITE)
#include <botan/mlkem_comp_parameters.h>
#endif

namespace Botan_CLI {

namespace {
Expand Down Expand Up @@ -45,7 +50,12 @@ class PerfTest_PK_KEM : public PerfTest {
const auto msec = config.runtime();
auto& rng = config.rng();

const std::string kdf = "KDF2(SHA-256)"; // arbitrary choice
std::string kdf = "KDF2(SHA-256)"; // arbitrary choice
#if BOTAN_HAS_MLKEM_COMPOSITE
if(algo == Botan::MLKEM_Composite_Param::generic_algo_name) {
kdf = "";
}
#endif

auto keygen_timer = config.make_timer(nm, 1, "keygen");

Expand Down Expand Up @@ -130,6 +140,27 @@ BOTAN_REGISTER_PERF_TEST("ML-KEM", PerfTest_ML_KEM);

#endif

#if defined(BOTAN_HAS_MLKEM_COMPOSITE)

class PerfTest_MLKEM_Composite final : public PerfTest_PK_KEM {
public:
std::string algo() const override { return std::string(Botan::MLKEM_Composite_Param::generic_algo_name); }

std::vector<std::string> keygen_params(const PerfConfig& /*config*/) const override {
const auto all_params = Botan::MLKEM_Composite_Param::all_supported_param_sets();
std::vector<std::string> result;
result.reserve(all_params.size());
for(const auto& param : all_params) {
result.push_back(param.id_str());
}
return result;
}
};

BOTAN_REGISTER_PERF_TEST("MLKEM-Composite", PerfTest_MLKEM_Composite);

#endif

#if defined(BOTAN_HAS_FRODOKEM)

class PerfTest_FrodoKEM final : public PerfTest_PK_KEM {
Expand Down
1 change: 1 addition & 0 deletions src/configs/typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ extend-exclude = [
"src/build-data/cc/msvc.txt",
"src/build-data/cc/clangcl.txt",
"doc/authors.txt",
"/src/tests/data/pubkey/mlkem_composite.vec",
]

[default]
Expand Down
50 changes: 49 additions & 1 deletion src/lib/asn1/static_oids.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* This file was automatically generated by ./src/scripts/dev_tools/gen_oids.py on 2026-05-13
* This file was automatically generated by src/scripts/dev_tools/gen_oids.py on 2026-06-16
* All manual changes will be lost. Edit the script instead.
*
* Botan is released under the Simplified BSD License (see license.txt)
Expand Down Expand Up @@ -583,6 +583,30 @@ std::optional<std::string_view> OID_Map::lookup_static_oid(const OID& oid) {
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 3, 8}, "PKIX.TimeStamping");
case 0x94929:
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 3, 9}, "PKIX.OCSPSigning");
case 0x94B9A:
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 6, 55}, "MLKEM768-RSA2048-SHA3-256");
case 0x94B9B:
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 6, 56}, "MLKEM768-RSA3072-SHA3-256");
case 0x94B9C:
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 6, 57}, "MLKEM768-RSA4096-SHA3-256");
case 0x94B9D:
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 6, 58}, "MLKEM768-X25519-SHA3-256");
case 0x94B9E:
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 6, 59}, "MLKEM768-ECDH-P256-SHA3-256");
case 0x94B9F:
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 6, 60}, "MLKEM768-ECDH-P384-SHA3-256");
case 0x94BA0:
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 6, 61}, "MLKEM768-ECDH-brainpoolP256r1-SHA3-256");
case 0x94BA1:
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 6, 62}, "MLKEM1024-RSA3072-SHA3-256");
case 0x94BA2:
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 6, 63}, "MLKEM1024-ECDH-P384-SHA3-256");
case 0x94BA3:
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 6, 64}, "MLKEM1024-ECDH-brainpoolP384r1-SHA3-256");
case 0x94BA4:
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 6, 65}, "MLKEM1024-X448-SHA3-256");
case 0x94BA5:
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 6, 66}, "MLKEM1024-ECDH-P521-SHA3-256");
case 0x94CEA:
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 8, 5}, "PKIX.XMPPAddr");
case 0x94CEE:
Expand Down Expand Up @@ -793,6 +817,8 @@ std::optional<OID> OID_Map::lookup_static_oid_name(std::string_view req) {
return if_match(req, "Camellia-256/GCM", {0, 3, 4401, 5, 3, 1, 9, 46});
case 0x1033D:
return if_match(req, "DSA/SHA-3(384)", {2, 16, 840, 1, 101, 3, 4, 3, 7});
case 0x104EA:
return if_match(req, "MLKEM768-RSA4096-SHA3-256", {1, 3, 6, 1, 5, 5, 7, 6, 57});
case 0x1139D:
return if_match(req, "secp192k1", {1, 3, 132, 0, 31});
case 0x113D6:
Expand Down Expand Up @@ -927,6 +953,8 @@ std::optional<OID> OID_Map::lookup_static_oid_name(std::string_view req) {
return if_match(req, "SLH-DSA-SHAKE-192f", {2, 16, 840, 1, 101, 3, 4, 3, 29});
case 0x3BC97:
return if_match(req, "SLH-DSA-SHAKE-192s", {2, 16, 840, 1, 101, 3, 4, 3, 28});
case 0x3BD00:
return if_match(req, "MLKEM1024-RSA3072-SHA3-256", {1, 3, 6, 1, 5, 5, 7, 6, 62});
case 0x3D127:
return if_match(req, "DSA", {1, 2, 840, 10040, 4, 1});
case 0x3E249:
Expand Down Expand Up @@ -971,6 +999,8 @@ std::optional<OID> OID_Map::lookup_static_oid_name(std::string_view req) {
return if_match(req, "Dilithium-4x4-AES-r3", {1, 3, 6, 1, 4, 1, 25258, 1, 10, 1});
case 0x48861:
return if_match(req, "ChaCha20Poly1305", {1, 2, 840, 113549, 1, 9, 16, 3, 18});
case 0x49045:
return if_match(req, "MLKEM1024-ECDH-brainpoolP384r1-SHA3-256", {1, 3, 6, 1, 5, 5, 7, 6, 64});
case 0x4A292:
return if_match(req, "frp256v1", {1, 2, 250, 1, 223, 101, 256, 1});
case 0x4A9EE:
Expand Down Expand Up @@ -1003,6 +1033,8 @@ std::optional<OID> OID_Map::lookup_static_oid_name(std::string_view req) {
return if_match(req, "PKCS9.ContentType", {1, 2, 840, 113549, 1, 9, 3});
case 0x50B26:
return if_match(req, "FrodoKEM-640-AES", {1, 3, 6, 1, 4, 1, 25258, 1, 15, 1});
case 0x50D65:
return if_match(req, "MLKEM768-ECDH-brainpoolP256r1-SHA3-256", {1, 3, 6, 1, 5, 5, 7, 6, 61});
case 0x50D78:
return if_match(req, "x962_p192v2", {1, 2, 840, 10045, 3, 1, 2});
case 0x50D79:
Expand All @@ -1019,6 +1051,8 @@ std::optional<OID> OID_Map::lookup_static_oid_name(std::string_view req) {
return if_match(req, "Serpent/CBC", {1, 3, 6, 1, 4, 1, 25258, 3, 1});
case 0x5576D:
return if_match(req, "SphincsPlus-sha2-128f-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 2, 2});
case 0x55EE5:
return if_match(req, "MLKEM1024-X448-SHA3-256", {1, 3, 6, 1, 5, 5, 7, 6, 65});
case 0x55EF6:
return if_match(req, "AES-192/OCB", {1, 3, 6, 1, 4, 1, 25258, 3, 2, 2});
case 0x55FFA:
Expand Down Expand Up @@ -1083,6 +1117,8 @@ std::optional<OID> OID_Map::lookup_static_oid_name(std::string_view req) {
return if_match(req, "AES-192/CBC", {2, 16, 840, 1, 101, 3, 4, 1, 22});
case 0x61F7E:
return if_match(req, "AES-192/CCM", {2, 16, 840, 1, 101, 3, 4, 1, 27});
case 0x62CDB:
return if_match(req, "MLKEM768-X25519-SHA3-256", {1, 3, 6, 1, 5, 5, 7, 6, 58});
case 0x64947:
return if_match(req, "OpenPGP.Ed25519", {1, 3, 6, 1, 4, 1, 11591, 15, 1});
case 0x652E7:
Expand All @@ -1095,6 +1131,8 @@ std::optional<OID> OID_Map::lookup_static_oid_name(std::string_view req) {
return if_match(req, "HMAC(SHA-384)", {1, 2, 840, 113549, 2, 10});
case 0x67D86:
return if_match(req, "ECGDSA/SHA-384", {1, 3, 36, 3, 3, 2, 5, 4, 5});
case 0x67FCB:
return if_match(req, "MLKEM768-RSA2048-SHA3-256", {1, 3, 6, 1, 5, 5, 7, 6, 55});
case 0x68A0B:
return if_match(req, "Camellia-128/OCB", {1, 3, 6, 1, 4, 1, 25258, 3, 2, 6});
case 0x68E33:
Expand All @@ -1103,6 +1141,8 @@ std::optional<OID> OID_Map::lookup_static_oid_name(std::string_view req) {
return if_match(req, "X509v3.SubjectAlternativeName", {2, 5, 29, 17});
case 0x692F8:
return if_match(req, "SM4/CBC", {1, 2, 156, 10197, 1, 104, 2});
case 0x693E5:
return if_match(req, "MLKEM1024-ECDH-P384-SHA3-256", {1, 3, 6, 1, 5, 5, 7, 6, 63});
case 0x695E1:
return if_match(req, "Dilithium-4x4-r3", {1, 3, 6, 1, 4, 1, 25258, 1, 9, 1});
case 0x696DC:
Expand Down Expand Up @@ -1243,16 +1283,22 @@ std::optional<OID> OID_Map::lookup_static_oid_name(std::string_view req) {
return if_match(req, "ECKCDSA", {1, 0, 14888, 3, 0, 5});
case 0x8E0C1:
return if_match(req, "X509v3.CertificatePolicies", {2, 5, 29, 32});
case 0x8E1BF:
return if_match(req, "MLKEM1024-ECDH-P521-SHA3-256", {1, 3, 6, 1, 5, 5, 7, 6, 66});
case 0x8E39A:
return if_match(req, "HSS-LMS-Private-Key", {1, 3, 6, 1, 4, 1, 25258, 1, 13});
case 0x8EC51:
return if_match(req, "Kyber-768-r3", {1, 3, 6, 1, 4, 1, 25258, 1, 7, 2});
case 0x8F94A:
return if_match(req, "Dilithium-6x5-r3", {1, 3, 6, 1, 4, 1, 25258, 1, 9, 2});
case 0x8FB4C:
return if_match(req, "MLKEM768-ECDH-P256-SHA3-256", {1, 3, 6, 1, 5, 5, 7, 6, 59});
case 0x8FC20:
return if_match(req, "AES-128/SIV", {1, 3, 6, 1, 4, 1, 25258, 3, 4, 1});
case 0x8FDE0:
return if_match(req, "SHA-3(256)", {2, 16, 840, 1, 101, 3, 4, 2, 8});
case 0x909D3:
return if_match(req, "MLKEM768-ECDH-P384-SHA3-256", {1, 3, 6, 1, 5, 5, 7, 6, 60});
case 0x919E3:
return if_match(req, "Serpent/GCM", {1, 3, 6, 1, 4, 1, 25258, 3, 101});
case 0x91C1A:
Expand Down Expand Up @@ -1381,6 +1427,8 @@ std::optional<OID> OID_Map::lookup_static_oid_name(std::string_view req) {
return if_match(req, "RSA/PKCS1v15(SHA-3(512))", {2, 16, 840, 1, 101, 3, 4, 3, 16});
case 0xB6427:
return if_match(req, "Camellia-192/GCM", {0, 3, 4401, 5, 3, 1, 9, 26});
case 0xB70A8:
return if_match(req, "MLKEM768-RSA3072-SHA3-256", {1, 3, 6, 1, 5, 5, 7, 6, 56});
case 0xB7102:
return if_match(req, "brainpool224r1", {1, 3, 36, 3, 3, 2, 8, 1, 1, 5});
case 0xB710D:
Expand Down
16 changes: 16 additions & 0 deletions src/lib/ffi/ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -2111,6 +2111,22 @@ int botan_privkey_load_ml_kem(botan_privkey_t* key, const uint8_t privkey[], siz
BOTAN_FFI_EXPORT(3, 6)
int botan_pubkey_load_ml_kem(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len, const char* mlkem_mode);

/*
* Algorithm specific key operations: MLKEM-Composite
*/

BOTAN_FFI_EXPORT(3, 12)
int botan_privkey_load_mlkem_composite(botan_privkey_t* key,
const uint8_t privkey[],
size_t key_len,
const char* mlkem_composite_algo);

BOTAN_FFI_EXPORT(3, 12)
int botan_pubkey_load_mlkem_composite(botan_pubkey_t* key,
const uint8_t pubkey[],
size_t key_len,
const char* mlkem_composite_algo);

/*
* Algorithm specific key operations: SLH-DSA
*/
Expand Down
61 changes: 61 additions & 0 deletions src/lib/ffi/ffi_pkey_algs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@
#include <botan/ml_kem.h>
#endif

#if defined(BOTAN_HAS_MLKEM_COMPOSITE)
#include <botan/mlkem_comp.h>
#endif

#if defined(BOTAN_HAS_FRODOKEM)
#include <botan/frodokem.h>
#endif
Expand Down Expand Up @@ -1348,6 +1352,63 @@ int botan_pubkey_load_ml_kem(botan_pubkey_t* key, const uint8_t pubkey[], size_t
#endif
}

/*
* Algorithm specific key operations: MLKEM-Composite
*/

int botan_privkey_load_mlkem_composite(botan_privkey_t* key,
const uint8_t privkey[],
size_t key_len,
const char* mlkem_composite_algo) {
#if defined(BOTAN_HAS_MLKEM_COMPOSITE)
if(key == nullptr || privkey == nullptr || mlkem_composite_algo == nullptr) {
return BOTAN_FFI_ERROR_NULL_POINTER;
}
Comment on lines +1364 to +1366

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: There's a helper that could be used here:

Suggested change
if(key == nullptr || privkey == nullptr || mlkem_composite_algo == nullptr) {
return BOTAN_FFI_ERROR_NULL_POINTER;
}
if(Botan::any_null_pointers(key, privkey, mlkem_composite_algo)) {
return BOTAN_FFI_ERROR_NULL_POINTER;
}

*key = nullptr;

return ffi_guard_thunk(__func__, [=]() -> int {
auto param = Botan::MLKEM_Composite_Param::from_id_str(mlkem_composite_algo);
if(!param.has_value()) {
return BOTAN_FFI_ERROR_BAD_PARAMETER;
}

auto mlkem_composite_key =
std::make_unique<Botan::MLKEM_Composite_PrivateKey>(param.value().id(), std::span{privkey, key_len});
return ffi_new_object(key, std::move(mlkem_composite_key));
});
#else
BOTAN_UNUSED(key, key_len, privkey, mlkem_composite_algo);
return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_mlkem_composite(botan_pubkey_t* key,
const uint8_t pubkey[],
size_t key_len,
const char* mlkem_composite_algo) {
#if defined(BOTAN_HAS_MLKEM_COMPOSITE)
if(key == nullptr || pubkey == nullptr || mlkem_composite_algo == nullptr) {
return BOTAN_FFI_ERROR_NULL_POINTER;
Comment on lines +1390 to +1391

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above:

Suggested change
if(key == nullptr || pubkey == nullptr || mlkem_composite_algo == nullptr) {
return BOTAN_FFI_ERROR_NULL_POINTER;
if(Botan::any_null_pointers(key, pubkey, mlkem_composite_algo)) {
return BOTAN_FFI_ERROR_NULL_POINTER;

}

*key = nullptr;

return ffi_guard_thunk(__func__, [=]() -> int {
auto param = Botan::MLKEM_Composite_Param::from_id_str(mlkem_composite_algo);
if(!param.has_value()) {
return BOTAN_FFI_ERROR_BAD_PARAMETER;
}

auto mlkem_composite_key =
std::make_unique<Botan::MLKEM_Composite_PublicKey>(param.value().id(), std::span{pubkey, key_len});
return ffi_new_object(key, std::move(mlkem_composite_key));
});
#else
BOTAN_UNUSED(key, key_len, pubkey, mlkem_composite_algo);
return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

/*
* Algorithm specific key operations: ML-DSA
*/
Expand Down
Loading
Loading