Skip to content

Commit

Permalink
Merge pull request #4373 from Rohde-Schwarz/ffi/frodo_key_loading
Browse files Browse the repository at this point in the history
FFI: Loading of raw FrodoKEM keys & FIX: "insufficient buffer handling" in FFI's decapsulate
  • Loading branch information
reneme authored Oct 15, 2024
2 parents 0cbc2fe + cb9b92b commit 4996790
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 2 deletions.
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 operation: FrodoKEM
*/

BOTAN_FFI_EXPORT(3, 6)
int botan_privkey_load_frodokem(botan_privkey_t* key, const uint8_t privkey[], size_t key_len, const char* frodo_mode);

BOTAN_FFI_EXPORT(3, 6)
int botan_pubkey_load_frodokem(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len, const char* frodo_mode);

/*
* Algorithm specific key operations: ECDSA and ECDH
*/
Expand Down
2 changes: 1 addition & 1 deletion src/lib/ffi/ffi_pk_op.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ int botan_pk_op_kem_decrypt_shared_key(botan_pk_op_kem_decrypt_t op,
const auto shared_key =
kem.decrypt(encapsulated_key, encapsulated_key_len, desired_shared_key_len, salt, salt_len);

write_vec_output(shared_key_out, shared_key_len, shared_key);
return write_vec_output(shared_key_out, shared_key_len, shared_key);
});
}

Expand Down
48 changes: 48 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_FRODOKEM)
#include <botan/frodokem.h>
#endif

namespace {

#if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO)
Expand Down Expand Up @@ -1027,6 +1031,50 @@ int botan_pubkey_view_kyber_raw_key(botan_pubkey_t key, botan_view_ctx ctx, bota
#endif
}

/*
* Algorithm specific key operations: FrodoKEM
*/

int botan_privkey_load_frodokem(botan_privkey_t* key, const uint8_t privkey[], size_t key_len, const char* frodo_mode) {
#if defined(BOTAN_HAS_FRODOKEM)
if(key == nullptr || privkey == nullptr || frodo_mode == nullptr) {
return BOTAN_FFI_ERROR_NULL_POINTER;
}

*key = nullptr;

return ffi_guard_thunk(__func__, [=]() -> int {
const auto mode = Botan::FrodoKEMMode(frodo_mode);
auto frodo_key = std::make_unique<Botan::FrodoKEM_PrivateKey>(std::span{privkey, key_len}, mode);
*key = new botan_privkey_struct(std::move(frodo_key));
return BOTAN_FFI_SUCCESS;
});
#else
BOTAN_UNUSED(key, privkey, key_len, frodo_mode);
return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_load_frodokem(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len, const char* frodo_mode) {
#if defined(BOTAN_HAS_FRODOKEM)
if(key == nullptr || pubkey == nullptr || frodo_mode == nullptr) {
return BOTAN_FFI_ERROR_NULL_POINTER;
}

*key = nullptr;

return ffi_guard_thunk(__func__, [=]() -> int {
const auto mode = Botan::FrodoKEMMode(frodo_mode);
auto frodo_key = std::make_unique<Botan::FrodoKEM_PublicKey>(std::span{pubkey, key_len}, mode);
*key = new botan_pubkey_struct(std::move(frodo_key));
return BOTAN_FFI_SUCCESS;
});
#else
BOTAN_UNUSED(key, pubkey, key_len, frodo_mode);
return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_pubkey_view_ec_public_point(const botan_pubkey_t key, botan_view_ctx ctx, botan_view_bin_fn view) {
#if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO)
return BOTAN_FFI_VISIT(key, [=](const auto& k) -> int {
Expand Down
4 changes: 3 additions & 1 deletion src/lib/pubkey/frodokem/frodokem_common/frodo_constants.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
namespace Botan {

FrodoKEMConstants::FrodoKEMConstants(FrodoKEMMode mode) : m_mode(mode), m_len_a(128), m_n_bar(8) {
BOTAN_ASSERT(m_mode.is_available(), "Mode is not available.");
if(!mode.is_available()) {
throw Not_Implemented("FrodoKEM mode " + mode.to_string() + " is not available");
}

if(mode.is_ephemeral()) {
m_len_salt = 0;
Expand Down
14 changes: 14 additions & 0 deletions src/python/botan3.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,8 @@ def ffi_api(fn, args, allowed_errors=None):
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])
ffi_api(dll.botan_pubkey_view_kyber_raw_key, [c_void_p, c_void_p, VIEW_BIN_CALLBACK])
ffi_api(dll.botan_privkey_load_frodokem, [c_void_p, c_void_p, c_int, c_char_p])
ffi_api(dll.botan_pubkey_load_frodokem, [c_void_p, c_void_p, c_int, c_char_p])
ffi_api(dll.botan_privkey_load_ecdsa, [c_void_p, c_void_p, c_char_p])
ffi_api(dll.botan_pubkey_load_ecdsa, [c_void_p, c_void_p, c_void_p, c_char_p])
ffi_api(dll.botan_pubkey_load_ecdh, [c_void_p, c_void_p, c_void_p, c_char_p])
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_frodokem(cls, frodo_mode, key):
obj = c_void_p(0)
_DLL.botan_pubkey_load_frodokem(byref(obj), key, len(key), _ctype_str(frodo_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_frodokem(cls, frodo_mode, key):
obj = c_void_p(0)
_DLL.botan_privkey_load_frodokem(byref(obj), key, len(key), _ctype_str(frodo_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_frodokem_raw_keys(self):
frodo_mode = "FrodoKEM-640-SHAKE"
sk = botan.PrivateKey.create("FrodoKEM", frodo_mode, botan.RandomNumberGenerator("user"))
pk = sk.get_public_key()

sk_bits = sk.to_raw()
pk_bits = pk.to_raw()

sk_read = botan.PrivateKey.load_frodokem(frodo_mode, sk_bits)
pk_read = botan.PublicKey.load_frodokem(frodo_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
175 changes: 175 additions & 0 deletions src/tests/test_ffi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3323,6 +3323,151 @@ class ViewBytesSink final {
std::vector<uint8_t> m_buf;
};

/**
* Base class for roundtrip tests of FFI bindings for Key Encapsulation Mechanisms.
*/
class FFI_KEM_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;

public:
void ffi_test(Test::Result& result, botan_rng_t rng) override {
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());

// KEM encryption (using the loaded public key)
botan_pk_op_kem_encrypt_t kem_enc;
TEST_FFI_OK(botan_pk_op_kem_encrypt_create, (&kem_enc, pub_loaded, "Raw"));

// explicitly query output lengths
size_t shared_key_length = 0;
size_t ciphertext_length = 0;
TEST_FFI_OK(botan_pk_op_kem_encrypt_shared_key_length, (kem_enc, 0, &shared_key_length));
TEST_FFI_OK(botan_pk_op_kem_encrypt_encapsulated_key_length, (kem_enc, &ciphertext_length));

// check that insufficient buffer space is handled correctly
size_t shared_key_length_out = 0;
size_t ciphertext_length_out = 0;
TEST_FFI_RC(BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE,
botan_pk_op_kem_encrypt_create_shared_key,
(kem_enc,
rng,
nullptr /* no salt */,
0,
0 /* default key length */,
nullptr,
&shared_key_length_out,
nullptr,
&ciphertext_length_out));

// TODO: should this report both lengths for usage convenience?
result.confirm("at least one buffer length is reported",
shared_key_length_out == shared_key_length || ciphertext_length_out == ciphertext_length);

// allocate buffers (with additional space) and perform the actual encryption
shared_key_length_out = shared_key_length * 2;
ciphertext_length_out = ciphertext_length * 2;
Botan::secure_vector<uint8_t> shared_key(shared_key_length_out);
std::vector<uint8_t> ciphertext(ciphertext_length_out);
TEST_FFI_OK(botan_pk_op_kem_encrypt_create_shared_key,
(kem_enc,
rng,
nullptr /* no salt */,
0,
0 /* default key length */,
shared_key.data(),
&shared_key_length_out,
ciphertext.data(),
&ciphertext_length_out));
result.test_eq("shared key length", shared_key_length, shared_key_length_out);
result.test_eq("ciphertext length", ciphertext_length, ciphertext_length_out);
shared_key.resize(shared_key_length_out);
ciphertext.resize(ciphertext_length_out);
TEST_FFI_OK(botan_pk_op_kem_encrypt_destroy, (kem_enc));

// KEM decryption (using the generated private key)
botan_pk_op_kem_decrypt_t kem_dec;
TEST_FFI_OK(botan_pk_op_kem_decrypt_create, (&kem_dec, priv, "Raw"));
size_t shared_key_length2 = 0;
TEST_FFI_OK(botan_pk_op_kem_decrypt_shared_key_length, (kem_dec, shared_key_length, &shared_key_length2));
result.test_eq("shared key lengths are consistent", shared_key_length, shared_key_length2);

// check that insufficient buffer space is handled correctly
shared_key_length_out = 0;
TEST_FFI_RC(BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE,
botan_pk_op_kem_decrypt_shared_key,
(kem_dec,
nullptr /* no salt */,
0,
ciphertext.data(),
ciphertext.size(),
0 /* default length */,
nullptr,
&shared_key_length_out));
result.test_eq("reported buffer length requirement", shared_key_length, shared_key_length_out);

// allocate buffer (double the size) and perform the actual decryption
shared_key_length_out = shared_key_length * 2;
Botan::secure_vector<uint8_t> shared_key2(shared_key_length_out);
TEST_FFI_OK(botan_pk_op_kem_decrypt_shared_key,
(kem_dec,
nullptr /* no salt */,
0,
ciphertext.data(),
ciphertext.size(),
0 /* default length */,
shared_key2.data(),
&shared_key_length_out));
result.test_eq("shared key output length", shared_key_length, shared_key_length_out);
shared_key2.resize(shared_key_length_out);
TEST_FFI_OK(botan_pk_op_kem_decrypt_destroy, (kem_dec));

// final check and clean up
result.test_eq("shared keys match", shared_key, shared_key2);

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 +3603,35 @@ class FFI_Kyber1024_Test final : public FFI_Test {
}
};

class FFI_FrodoKEM_Test final : public FFI_KEM_Roundtrip_Test {
public:
std::string name() const override { return "FFI FrodoKEM"; }

protected:
const char* algo() const override { return "FrodoKEM"; }

privkey_loader_fn_t private_key_load_function() const override { return botan_privkey_load_frodokem; }

pubkey_loader_fn_t public_key_load_function() const override { return botan_pubkey_load_frodokem; }

std::vector<const char*> modes() const override {
return std::vector{
"FrodoKEM-640-SHAKE",
"FrodoKEM-976-SHAKE",
"FrodoKEM-1344-SHAKE",
"eFrodoKEM-640-SHAKE",
"eFrodoKEM-976-SHAKE",
"eFrodoKEM-1344-SHAKE",
"FrodoKEM-640-AES",
"FrodoKEM-976-AES",
"FrodoKEM-1344-AES",
"eFrodoKEM-640-AES",
"eFrodoKEM-976-AES",
"eFrodoKEM-1344-AES",
};
}
};

class FFI_ElGamal_Test final : public FFI_Test {
public:
std::string name() const override { return "FFI ElGamal"; }
Expand Down Expand Up @@ -3700,6 +3874,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_frodokem", FFI_FrodoKEM_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 4996790

Please sign in to comment.