From bfb91307b41493142ecdcfdb908e35cad2726c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Mon, 14 Oct 2024 18:29:27 +0200 Subject: [PATCH] Add SLH-DSA specific bindings to FFI/Python --- doc/api_ref/python.rst | 9 ++++++ src/lib/ffi/ffi.h | 10 +++++++ src/lib/ffi/ffi_pkey_algs.cpp | 56 +++++++++++++++++++++++++++++++++++ src/python/botan3.py | 14 +++++++++ src/scripts/test_python.py | 14 +++++++++ src/tests/test_ffi.cpp | 43 ++++++++++++++++++++++++++- 6 files changed, 145 insertions(+), 1 deletion(-) diff --git a/doc/api_ref/python.rst b/doc/api_ref/python.rst index d84eaa10e0..cfa7ba5be4 100644 --- a/doc/api_ref/python.rst +++ b/doc/api_ref/python.rst @@ -309,6 +309,11 @@ Public Key Load an ML-DSA public key giving the mode as a string (like "ML-DSA-4x4") and the raw encoding of the public key. + .. py:classmethod:: load_slh_dsa(mode, raw_encoding) + + Load an SLH-DSA public key giving the mode as a string (like + "SLH-DSA-SHAKE-128f") and the raw encoding of the public key. + .. py:method:: check_key(rng_obj, strong=True): Test the key for consistency. If ``strong`` is ``True`` then @@ -408,6 +413,10 @@ Private Key Return a private ML-DSA key + .. py:classmethod:: load_slh_dsa(mode, raw_encoding) + + Return a private SLH-DSA key + .. py:method:: get_public_key() Return a public_key object diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index fdf85b53a8..c9341501bc 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -1566,6 +1566,16 @@ 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: SLH-DSA +*/ + +BOTAN_FFI_EXPORT(3, 6) +int botan_privkey_load_slh_dsa(botan_privkey_t* key, const uint8_t privkey[], size_t key_len, const char* slhdsa_mode); + +BOTAN_FFI_EXPORT(3, 6) +int botan_pubkey_load_slh_dsa(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len, const char* slhdsa_mode); + /* * Algorithm specific key operations: ECDSA and ECDH */ diff --git a/src/lib/ffi/ffi_pkey_algs.cpp b/src/lib/ffi/ffi_pkey_algs.cpp index c7c90eb7c8..316cd05beb 100644 --- a/src/lib/ffi/ffi_pkey_algs.cpp +++ b/src/lib/ffi/ffi_pkey_algs.cpp @@ -84,6 +84,10 @@ #include #endif +#if defined(BOTAN_HAS_SLH_DSA_WITH_SHA2) || defined(BOTAN_HAS_SLH_DSA_WITH_SHAKE) + #include +#endif + namespace { #if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO) @@ -1148,6 +1152,58 @@ int botan_pubkey_load_ml_dsa(botan_pubkey_t* key, const uint8_t pubkey[], size_t #endif } +/* +* Algorithm specific key operations: SLH-DSA +*/ + +int botan_privkey_load_slh_dsa(botan_privkey_t* key, const uint8_t privkey[], size_t key_len, const char* slhdsa_mode) { +#if defined(BOTAN_HAS_SLH_DSA_WITH_SHA2) || defined(BOTAN_HAS_SLH_DSA_WITH_SHAKE) + if(key == nullptr || privkey == nullptr || slhdsa_mode == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + *key = nullptr; + + return ffi_guard_thunk(__func__, [=]() -> int { + auto mode = Botan::Sphincs_Parameters::create(slhdsa_mode); + if(!mode.is_slh_dsa()) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + auto slhdsa_key = std::make_unique(std::span{privkey, key_len}, mode); + *key = new botan_privkey_struct(std::move(slhdsa_key)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(key, key_len, privkey, slhdsa_mode); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_pubkey_load_slh_dsa(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len, const char* slhdsa_mode) { +#if defined(BOTAN_HAS_SLH_DSA_WITH_SHA2) || defined(BOTAN_HAS_SLH_DSA_WITH_SHAKE) + if(key == nullptr || pubkey == nullptr || slhdsa_mode == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + *key = nullptr; + + return ffi_guard_thunk(__func__, [=]() -> int { + auto mode = Botan::Sphincs_Parameters::create(slhdsa_mode); + if(!mode.is_slh_dsa()) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + auto mldsa_key = std::make_unique(std::span{pubkey, key_len}, mode); + *key = new botan_pubkey_struct(std::move(mldsa_key)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(key, key_len, pubkey, slhdsa_mode); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + /* * Algorithm specific key operations: FrodoKEM */ diff --git a/src/python/botan3.py b/src/python/botan3.py index dab19feaf0..2ea33e46ed 100755 --- a/src/python/botan3.py +++ b/src/python/botan3.py @@ -361,6 +361,8 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_pubkey_x448_get_pubkey, [c_void_p, c_char_p]) ffi_api(dll.botan_privkey_load_ml_dsa, [c_void_p, c_void_p, c_int, c_char_p]) ffi_api(dll.botan_pubkey_load_ml_dsa, [c_void_p, c_void_p, c_int, c_char_p]) + ffi_api(dll.botan_privkey_load_slh_dsa, [c_void_p, c_void_p, c_int, c_char_p]) + ffi_api(dll.botan_pubkey_load_slh_dsa, [c_void_p, c_void_p, c_int, c_char_p]) ffi_api(dll.botan_privkey_load_kyber, [c_void_p, c_char_p, c_int]) ffi_api(dll.botan_pubkey_load_kyber, [c_void_p, c_char_p, c_int]) ffi_api(dll.botan_privkey_view_kyber_raw_key, [c_void_p, c_void_p, VIEW_BIN_CALLBACK]) @@ -1250,6 +1252,12 @@ def load_ml_dsa(cls, mldsa_mode, key): _DLL.botan_pubkey_load_ml_dsa(byref(obj), key, len(key), _ctype_str(mldsa_mode)) return PublicKey(obj) + @classmethod + def load_slh_dsa(cls, slhdsa_mode, key): + obj = c_void_p(0) + _DLL.botan_pubkey_load_slh_dsa(byref(obj), key, len(key), _ctype_str(slhdsa_mode)) + return PublicKey(obj) + @classmethod def load_frodokem(cls, frodo_mode, key): obj = c_void_p(0) @@ -1427,6 +1435,12 @@ def load_ml_dsa(cls, mldsa_mode, key): _DLL.botan_privkey_load_ml_dsa(byref(obj), key, len(key), _ctype_str(mldsa_mode)) return PrivateKey(obj) + @classmethod + def load_slh_dsa(cls, slh_dsa, key): + obj = c_void_p(0) + _DLL.botan_privkey_load_slh_dsa(byref(obj), key, len(key), _ctype_str(slh_dsa)) + return PrivateKey(obj) + @classmethod def load_frodokem(cls, frodo_mode, key): obj = c_void_p(0) diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py index 5956f1aa44..33262f1055 100644 --- a/src/scripts/test_python.py +++ b/src/scripts/test_python.py @@ -965,6 +965,20 @@ def test_ml_dsa_raw_keys(self): self.assertEqual(sk_read.to_raw(), sk_bits) self.assertEqual(pk_read.to_raw(), pk_bits) + def test_slh_dsa_raw_keys(self): + slhdsa_mode = "SLH-DSA-SHAKE-128f" + sk = botan.PrivateKey.create("SLH-DSA", slhdsa_mode, botan.RandomNumberGenerator("user")) + pk = sk.get_public_key() + + sk_bits = sk.to_raw() + pk_bits = pk.to_raw() + + sk_read = botan.PrivateKey.load_slh_dsa(slhdsa_mode, sk_bits) + pk_read = botan.PublicKey.load_slh_dsa(slhdsa_mode, pk_bits) + + self.assertEqual(sk_read.to_raw(), sk_bits) + self.assertEqual(pk_read.to_raw(), pk_bits) + def test_frodokem_raw_keys(self): frodo_mode = "FrodoKEM-640-SHAKE" sk = botan.PrivateKey.create("FrodoKEM", frodo_mode, botan.RandomNumberGenerator("user")) diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index 8e35e6d789..647e6ad10b 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -17,6 +17,7 @@ #include #include #include + #include #include #endif @@ -3778,6 +3779,45 @@ class FFI_ML_DSA_Test final : public FFI_Signature_Roundtrip_Test { const char* hash_algo_or_padding() const override { return ""; } }; +class FFI_SLH_DSA_Test final : public FFI_Signature_Roundtrip_Test { + public: + std::string name() const override { return "FFI SLH-DSA"; } + + private: + const char* algo() const override { return "SLH-DSA"; } + + privkey_loader_fn_t private_key_load_function() const override { return botan_privkey_load_slh_dsa; } + + pubkey_loader_fn_t public_key_load_function() const override { return botan_pubkey_load_slh_dsa; } + + std::vector modes() const override { + auto modes = std::vector{ + "SLH-DSA-SHA2-128f", + "SLH-DSA-SHAKE-128f", + "SLH-DSA-SHA2-192f", + "SLH-DSA-SHAKE-192f", + "SLH-DSA-SHA2-256f", + "SLH-DSA-SHAKE-256f", + }; + + if(Test::run_long_tests()) { + modes = Botan::concat(modes, + std::vector{ + "SLH-DSA-SHA2-128s", + "SLH-DSA-SHA2-192s", + "SLH-DSA-SHA2-256s", + "SLH-DSA-SHAKE-128s", + "SLH-DSA-SHAKE-192s", + "SLH-DSA-SHAKE-256s", + }); + } + + return modes; + } + + const char* hash_algo_or_padding() const override { return ""; } +}; + class FFI_ElGamal_Test final : public FFI_Test { public: std::string name() const override { return "FFI ElGamal"; } @@ -4021,8 +4061,9 @@ BOTAN_REGISTER_TEST("ffi", "ffi_kyber512", FFI_Kyber512_Test); BOTAN_REGISTER_TEST("ffi", "ffi_kyber768", FFI_Kyber768_Test); BOTAN_REGISTER_TEST("ffi", "ffi_kyber1024", FFI_Kyber1024_Test); BOTAN_REGISTER_TEST("ffi", "ffi_ml_kem", FFI_ML_KEM_Test); -BOTAN_REGISTER_TEST("ffi", "ffi_frodokem", FFI_FrodoKEM_Test); BOTAN_REGISTER_TEST("ffi", "ffi_ml_dsa", FFI_ML_DSA_Test); +BOTAN_REGISTER_TEST("ffi", "ffi_slh_dsa", FFI_SLH_DSA_Test); +BOTAN_REGISTER_TEST("ffi", "ffi_frodokem", FFI_FrodoKEM_Test); BOTAN_REGISTER_TEST("ffi", "ffi_elgamal", FFI_ElGamal_Test); BOTAN_REGISTER_TEST("ffi", "ffi_dh", FFI_DH_Test);