Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FFI: Loading of raw FrodoKEM keys & FIX: "insufficient buffer handling" in FFI's decapsulate #4373

Merged
merged 5 commits into from
Oct 15, 2024
Merged
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
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
Loading