Skip to content

Commit

Permalink
Add SLH-DSA specific bindings to FFI/Python
Browse files Browse the repository at this point in the history
  • Loading branch information
reneme committed Oct 14, 2024
1 parent d4ad4e5 commit f5ffe99
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 0 deletions.
8 changes: 8 additions & 0 deletions doc/api_ref/python.rst
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@ Public Key
"sm2p256v1") and the public point as a pair of integers giving
the affine coordinates.
.. 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
Expand Down Expand Up @@ -390,6 +394,10 @@ Private Key
Return a private SM2 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
Expand Down
10 changes: 10 additions & 0 deletions src/lib/ffi/ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -1534,6 +1534,16 @@ int botan_privkey_view_kyber_raw_key(botan_privkey_t key, botan_view_ctx ctx, bo
BOTAN_FFI_EXPORT(3, 1)
int botan_pubkey_view_kyber_raw_key(botan_pubkey_t key, botan_view_ctx ctx, botan_view_bin_fn view);

/*
* 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
*/
Expand Down
55 changes: 55 additions & 0 deletions src/lib/ffi/ffi_pkey_algs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@
#include <botan/kyber.h>
#endif

#if defined(BOTAN_HAS_SLH_DSA_WITH_SHA2) || defined(BOTAN_HAS_SLH_DSA_WITH_SHAKE)
#include <botan/sphincsplus.h>
#endif

namespace {

#if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO)
Expand Down Expand Up @@ -923,6 +927,57 @@ int botan_pubkey_x448_get_pubkey(botan_pubkey_t key, uint8_t output[56]) {
#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<Botan::SphincsPlus_PrivateKey>(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)
*key = nullptr;

if(pubkey == nullptr || slhdsa_mode == nullptr) {
return BOTAN_FFI_ERROR_NULL_POINTER;
}

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<Botan::SphincsPlus_PublicKey>(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: Kyber
*/
Expand Down
14 changes: 14 additions & 0 deletions src/python/botan3.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,8 @@ def ffi_api(fn, args, allowed_errors=None):
ffi_api(dll.botan_pubkey_load_x448, [c_void_p, c_char_p])
ffi_api(dll.botan_privkey_x448_get_privkey, [c_void_p, c_char_p])
ffi_api(dll.botan_pubkey_x448_get_pubkey, [c_void_p, 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])
Expand Down Expand Up @@ -1232,6 +1234,12 @@ def load_kyber(cls, key):
_DLL.botan_pubkey_load_kyber(byref(obj), key, len(key))
return PublicKey(obj)

@classmethod
def load_slh_dsa(cls, mode, key):
obj = c_void_p(0)
_DLL.botan_pubkey_load_slh_dsa(byref(obj), key, len(key), _ctype_str(mode))
return PublicKey(obj)

def __del__(self):
_DLL.botan_pubkey_destroy(self.__obj)

Expand Down Expand Up @@ -1390,6 +1398,12 @@ def load_kyber(cls, key):
_DLL.botan_privkey_load_kyber(byref(obj), key, len(key))
return PrivateKey(obj)

@classmethod
def load_slh_dsa(cls, mode, key):
obj = c_void_p(0)
_DLL.botan_privkey_load_slh_dsa(byref(obj), key, len(key), _ctype_str(mode))
return PrivateKey(obj)

def __del__(self):
_DLL.botan_privkey_destroy(self.__obj)

Expand Down
14 changes: 14 additions & 0 deletions src/scripts/test_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,20 @@ def test_kyber_raw_keys(self):
pubkey_read = a_pub.view_kyber_raw_key()
self.assertEqual(pubkey_read, a_pub_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)


class BotanPythonZfecTests(unittest.TestCase):
"""
Expand Down
151 changes: 151 additions & 0 deletions src/tests/test_ffi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <botan/hex.h>
#include <botan/internal/fmt.h>
#include <botan/internal/loadstor.h>
#include <botan/internal/stl_util.h>
#include <set>
#endif

Expand Down Expand Up @@ -3323,6 +3324,116 @@ class ViewBytesSink final {
std::vector<uint8_t> m_buf;
};

/**
* Base class for roundtrip tests of FFI bindings for Signature Mechanisms.
*/
class FFI_Signature_Roundtrip_Test : public FFI_Test {
protected:
using privkey_loader_fn_t = int (*)(botan_privkey_t*, const uint8_t[], size_t, const char*);
using pubkey_loader_fn_t = int (*)(botan_pubkey_t*, const uint8_t[], size_t, const char*);

protected:
virtual const char* algo() const = 0;
virtual privkey_loader_fn_t private_key_load_function() const = 0;
virtual pubkey_loader_fn_t public_key_load_function() const = 0;
virtual std::vector<const char*> modes() const = 0;
virtual const char* hash_algo_or_padding() const = 0;

public:
void ffi_test(Test::Result& result, botan_rng_t rng) override {
const std::vector<uint8_t> message1 = {'H', 'e', 'l', 'l', 'o', ' '};
const std::vector<uint8_t> message2 = {'W', 'o', 'r', 'l', 'd', '!'};

for(auto mode : modes()) {
// generate a key pair
botan_privkey_t priv;
botan_pubkey_t pub;
if(!TEST_FFI_INIT(botan_privkey_create, (&priv, algo(), mode, rng))) {
continue;
}
TEST_FFI_OK(botan_privkey_export_pubkey, (&pub, priv));

// raw-encode the key pair
ViewBytesSink priv_bytes;
ViewBytesSink pub_bytes;
TEST_FFI_OK(botan_privkey_view_raw, (priv, priv_bytes.delegate(), priv_bytes.callback()));
TEST_FFI_OK(botan_pubkey_view_raw, (pub, pub_bytes.delegate(), pub_bytes.callback()));

// decode the key pair from raw encoding
botan_privkey_t priv_loaded;
botan_pubkey_t pub_loaded;
TEST_FFI_OK(private_key_load_function(),
(&priv_loaded, priv_bytes.get().data(), priv_bytes.get().size(), mode));
TEST_FFI_OK(public_key_load_function(),
(&pub_loaded, pub_bytes.get().data(), pub_bytes.get().size(), mode));

// re-encode and compare to the first round
ViewBytesSink priv_bytes2;
ViewBytesSink pub_bytes2;
TEST_FFI_OK(botan_privkey_view_raw, (priv_loaded, priv_bytes2.delegate(), priv_bytes2.callback()));
TEST_FFI_OK(botan_pubkey_view_raw, (pub_loaded, pub_bytes2.delegate(), pub_bytes2.callback()));
result.test_eq("private key encoding", priv_bytes.get(), priv_bytes2.get());
result.test_eq("public key encoding", pub_bytes.get(), pub_bytes2.get());

// Signature Creation (using the loaded private key)
botan_pk_op_sign_t signer;
TEST_FFI_OK(botan_pk_op_sign_create, (&signer, priv_loaded, hash_algo_or_padding(), 0));

// explicitly query the signature output length
size_t sig_output_length = 0;
TEST_FFI_OK(botan_pk_op_sign_output_length, (signer, &sig_output_length));

// pass a message to the signer
TEST_FFI_OK(botan_pk_op_sign_update, (signer, message1.data(), message1.size()));
TEST_FFI_OK(botan_pk_op_sign_update, (signer, message2.data(), message2.size()));

// check that insufficient buffer space is handled correctly
size_t sig_output_length_out = 0;
TEST_FFI_RC(BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE,
botan_pk_op_sign_finish,
(signer, rng, nullptr, &sig_output_length_out));
result.test_eq("reported sig lengths are equal", sig_output_length, sig_output_length_out);

// Recreate signer and try again
TEST_FFI_OK(botan_pk_op_sign_destroy, (signer));
TEST_FFI_OK(botan_pk_op_sign_create, (&signer, priv_loaded, hash_algo_or_padding(), 0));
TEST_FFI_OK(botan_pk_op_sign_update, (signer, message1.data(), message1.size()));
TEST_FFI_OK(botan_pk_op_sign_update, (signer, message2.data(), message2.size()));

// allocate buffers (with additional space) and perform the actual signing
sig_output_length_out = sig_output_length * 2;
Botan::secure_vector<uint8_t> signature(sig_output_length_out);
TEST_FFI_OK(botan_pk_op_sign_finish, (signer, rng, signature.data(), &sig_output_length_out));
result.test_eq("signature length", sig_output_length, sig_output_length_out);
signature.resize(sig_output_length_out);
TEST_FFI_OK(botan_pk_op_sign_destroy, (signer));

// Signature verification (using the generated public key)
botan_pk_op_verify_t verifier;
TEST_FFI_OK(botan_pk_op_verify_create, (&verifier, pub, hash_algo_or_padding(), 0));
TEST_FFI_OK(botan_pk_op_verify_update, (verifier, message1.data(), message1.size()));
TEST_FFI_OK(botan_pk_op_verify_update, (verifier, message2.data(), message2.size()));

// Verify signature
TEST_FFI_OK(botan_pk_op_verify_finish, (verifier, signature.data(), signature.size()));
TEST_FFI_OK(botan_pk_op_verify_destroy, (verifier));

// Verify signature with wrong message (only first half)
TEST_FFI_OK(botan_pk_op_verify_create, (&verifier, pub, hash_algo_or_padding(), 0));
TEST_FFI_OK(botan_pk_op_verify_update, (verifier, message1.data(), message1.size()));
TEST_FFI_RC(
BOTAN_FFI_INVALID_VERIFIER, botan_pk_op_verify_finish, (verifier, signature.data(), signature.size()));
TEST_FFI_OK(botan_pk_op_verify_destroy, (verifier));

// Cleanup
TEST_FFI_OK(botan_pubkey_destroy, (pub));
TEST_FFI_OK(botan_pubkey_destroy, (pub_loaded));
TEST_FFI_OK(botan_privkey_destroy, (priv));
TEST_FFI_OK(botan_privkey_destroy, (priv_loaded));
}
}
};

class FFI_Kyber512_Test final : public FFI_Test {
public:
std::string name() const override { return "FFI Kyber512"; }
Expand Down Expand Up @@ -3458,6 +3569,45 @@ class FFI_Kyber1024_Test final : public FFI_Test {
}
};

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<const char*> 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"; }
Expand Down Expand Up @@ -3700,6 +3850,7 @@ BOTAN_REGISTER_TEST("ffi", "ffi_x448", FFI_X448_Test);
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_slh_dsa", FFI_SLH_DSA_Test);
BOTAN_REGISTER_TEST("ffi", "ffi_elgamal", FFI_ElGamal_Test);
BOTAN_REGISTER_TEST("ffi", "ffi_dh", FFI_DH_Test);

Expand Down

0 comments on commit f5ffe99

Please sign in to comment.