diff --git a/doc/api_ref/ffi.rst b/doc/api_ref/ffi.rst index 5d0e97b1ff6..cf512d8f923 100644 --- a/doc/api_ref/ffi.rst +++ b/doc/api_ref/ffi.rst @@ -249,6 +249,7 @@ supported it. ============== =================== FFI Version Supported Starting ============== =================== +20250829 3.10.0 20250506 3.8.0 20240408 3.4.0 20231009 3.2.0 diff --git a/src/lib/ffi/ffi.cpp b/src/lib/ffi/ffi.cpp index 7185ca8ff8a..59ed3579372 100644 --- a/src/lib/ffi/ffi.cpp +++ b/src/lib/ffi/ffi.cpp @@ -219,6 +219,11 @@ uint32_t botan_ffi_api_version() { } int botan_ffi_supports_api(uint32_t api_version) { + // This is the API introduced in 3.10 + if(api_version == 20250829) { + return BOTAN_FFI_SUCCESS; + } + // This is the API introduced in 3.8 if(api_version == 20250506) { return BOTAN_FFI_SUCCESS; diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index 3bb64aad1f6..ff3792961bb 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -69,7 +69,7 @@ API follows a few simple rules: * that declaration is not visible here since this header is intentionally * free-standing, depending only on a few C standard library headers. */ -#define BOTAN_FFI_API_VERSION 20250506 +#define BOTAN_FFI_API_VERSION 20250829 /** * BOTAN_FFI_EXPORT indicates public FFI functions. @@ -1862,15 +1862,24 @@ int botan_privkey_load_ecdsa(botan_privkey_t* key, botan_mp_t scalar, const char BOTAN_FFI_EXPORT(2, 2) int botan_pubkey_load_ecdsa(botan_pubkey_t* key, botan_mp_t public_x, botan_mp_t public_y, const char* curve_name); +BOTAN_FFI_EXPORT(3, 10) +int botan_pubkey_load_ecdsa_sec1(botan_pubkey_t* key, const uint8_t sec1[], size_t sec1_len, const char* curve_name); + BOTAN_FFI_EXPORT(2, 2) int botan_pubkey_load_ecdh(botan_pubkey_t* key, botan_mp_t public_x, botan_mp_t public_y, const char* curve_name); +BOTAN_FFI_EXPORT(3, 10) +int botan_pubkey_load_ecdh_sec1(botan_pubkey_t* key, const uint8_t sec1[], size_t sec1_len, const char* curve_name); + BOTAN_FFI_EXPORT(2, 2) int botan_privkey_load_ecdh(botan_privkey_t* key, botan_mp_t scalar, const char* curve_name); BOTAN_FFI_EXPORT(2, 2) int botan_pubkey_load_sm2(botan_pubkey_t* key, botan_mp_t public_x, botan_mp_t public_y, const char* curve_name); +BOTAN_FFI_EXPORT(3, 10) +int botan_pubkey_load_sm2_sec1(botan_pubkey_t* key, const uint8_t sec1[], size_t sec1_len, const char* curve_name); + BOTAN_FFI_EXPORT(2, 2) int botan_privkey_load_sm2(botan_privkey_t* key, botan_mp_t scalar, const char* curve_name); diff --git a/src/lib/ffi/ffi_pkey_algs.cpp b/src/lib/ffi/ffi_pkey_algs.cpp index 77d85533ec5..a122dcb9e05 100644 --- a/src/lib/ffi/ffi_pkey_algs.cpp +++ b/src/lib/ffi/ffi_pkey_algs.cpp @@ -139,6 +139,24 @@ int pubkey_load_ec(std::unique_ptr& key, } } +template +int pubkey_load_ec_sec1(std::unique_ptr& key, + std::span sec1, + std::string_view curve_name) { + if(!Botan::EC_Group::supports_named_group(curve_name)) { + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; + } + + const auto group = Botan::EC_Group::from_name(curve_name); + + if(auto pt = Botan::EC_AffinePoint::deserialize(group, sec1)) { + key.reset(new ECPublicKey_t(group, pt.value())); + return BOTAN_FFI_SUCCESS; + } else { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } +} + #endif Botan::BigInt pubkey_get_field(const Botan::Public_Key& key, std::string_view field) { @@ -452,6 +470,29 @@ int botan_pubkey_load_ecdsa(botan_pubkey_t* key, #endif } +int botan_pubkey_load_ecdsa_sec1(botan_pubkey_t* key, const uint8_t sec1[], size_t sec1_len, const char* curve_name) { +#if defined(BOTAN_HAS_ECDSA) + if(key == nullptr || sec1 == nullptr || curve_name == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + *key = nullptr; + + return ffi_guard_thunk(__func__, [=]() -> int { + std::unique_ptr p_key; + + int rc = pubkey_load_ec_sec1(p_key, {sec1, sec1_len}, curve_name); + if(rc == BOTAN_FFI_SUCCESS) { + ffi_new_object(key, std::move(p_key)); + } + + return rc; + }); +#else + BOTAN_UNUSED(key, sec1, sec1_len, curve_name); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_privkey_load_ecdsa(botan_privkey_t* key, const botan_mp_t scalar, const char* curve_name) { #if defined(BOTAN_HAS_ECDSA) if(key == nullptr || curve_name == nullptr) { @@ -619,6 +660,29 @@ int botan_pubkey_load_ecdh(botan_pubkey_t* key, #endif } +int botan_pubkey_load_ecdh_sec1(botan_pubkey_t* key, const uint8_t sec1[], size_t sec1_len, const char* curve_name) { +#if defined(BOTAN_HAS_ECDH) + if(key == nullptr || sec1 == nullptr || curve_name == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + *key = nullptr; + + return ffi_guard_thunk(__func__, [=]() -> int { + std::unique_ptr p_key; + + int rc = pubkey_load_ec_sec1(p_key, {sec1, sec1_len}, curve_name); + if(rc == BOTAN_FFI_SUCCESS) { + ffi_new_object(key, std::move(p_key)); + } + + return rc; + }); +#else + BOTAN_UNUSED(key, sec1, sec1_len, curve_name); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_privkey_load_ecdh(botan_privkey_t* key, const botan_mp_t scalar, const char* curve_name) { #if defined(BOTAN_HAS_ECDH) if(key == nullptr || curve_name == nullptr) { @@ -698,6 +762,29 @@ int botan_pubkey_load_sm2(botan_pubkey_t* key, #endif } +int botan_pubkey_load_sm2_sec1(botan_pubkey_t* key, const uint8_t sec1[], size_t sec1_len, const char* curve_name) { +#if defined(BOTAN_HAS_SM2) + if(key == nullptr || sec1 == nullptr || curve_name == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + *key = nullptr; + + return ffi_guard_thunk(__func__, [=]() -> int { + std::unique_ptr p_key; + + int rc = pubkey_load_ec_sec1(p_key, {sec1, sec1_len}, curve_name); + if(rc == BOTAN_FFI_SUCCESS) { + ffi_new_object(key, std::move(p_key)); + } + + return rc; + }); +#else + BOTAN_UNUSED(key, sec1, sec1_len, curve_name); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_privkey_load_sm2(botan_privkey_t* key, const botan_mp_t scalar, const char* curve_name) { #if defined(BOTAN_HAS_SM2) if(key == nullptr || curve_name == nullptr) { diff --git a/src/lib/ffi/info.txt b/src/lib/ffi/info.txt index c5fe00dc9b6..a10f8727187 100644 --- a/src/lib/ffi/info.txt +++ b/src/lib/ffi/info.txt @@ -1,5 +1,5 @@ -FFI -> 20250506 +FFI -> 20250829 diff --git a/src/python/botan3.py b/src/python/botan3.py index 46bf286f969..fa71882abcb 100755 --- a/src/python/botan3.py +++ b/src/python/botan3.py @@ -26,8 +26,10 @@ from datetime import datetime from collections.abc import Iterable -# This Python module requires the FFI API version introduced in Botan 3.8.0 -BOTAN_FFI_VERSION = 20250506 +# This Python module requires the FFI API version introduced in Botan 3.10.0 +# +# 3.10.0 - introduced botan_pubkey_load_ec*_sec1() +BOTAN_FFI_VERSION = 20250829 # # Base exception for all exceptions raised from this module @@ -411,9 +413,12 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_pubkey_load_classic_mceliece, [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_ecdsa_sec1, [c_void_p, c_void_p, c_size_t, c_char_p]) ffi_api(dll.botan_pubkey_load_ecdh, [c_void_p, c_void_p, c_void_p, c_char_p]) ffi_api(dll.botan_privkey_load_ecdh, [c_void_p, c_void_p, c_char_p]) + ffi_api(dll.botan_pubkey_load_ecdh_sec1, [c_void_p, c_void_p, c_size_t, c_char_p]) ffi_api(dll.botan_pubkey_load_sm2, [c_void_p, c_void_p, c_void_p, c_char_p]) + ffi_api(dll.botan_pubkey_load_sm2_sec1, [c_void_p, c_void_p, c_size_t, c_char_p]) ffi_api(dll.botan_privkey_load_sm2, [c_void_p, c_void_p, c_char_p]) ffi_api(dll.botan_pubkey_load_sm2_enc, [c_void_p, c_void_p, c_void_p, c_char_p]) ffi_api(dll.botan_privkey_load_sm2_enc, [c_void_p, c_void_p, c_char_p]) @@ -1257,6 +1262,12 @@ def load_ecdsa(cls, curve, pub_x, pub_y): _DLL.botan_pubkey_load_ecdsa(byref(pub.handle_()), pub_x.handle_(), pub_y.handle_(), _ctype_str(curve)) return pub + @classmethod + def load_ecdsa_sec1(cls, curve, sec1_encoding): + pub = PublicKey() + _DLL.botan_pubkey_load_ecdsa_sec1(byref(pub.handle_()), _ctype_bits(sec1_encoding), len(sec1_encoding), _ctype_str(curve)) + return pub + @classmethod def load_ecdh(cls, curve, pub_x, pub_y): pub = PublicKey() @@ -1265,6 +1276,12 @@ def load_ecdh(cls, curve, pub_x, pub_y): _DLL.botan_pubkey_load_ecdh(byref(pub.handle_()), pub_x.handle_(), pub_y.handle_(), _ctype_str(curve)) return pub + @classmethod + def load_ecdh_sec1(cls, curve, sec1_encoding): + pub = PublicKey() + _DLL.botan_pubkey_load_ecdh_sec1(byref(pub.handle_()), _ctype_bits(sec1_encoding), len(sec1_encoding), _ctype_str(curve)) + return pub + @classmethod def load_sm2(cls, curve, pub_x, pub_y): pub = PublicKey() @@ -1273,6 +1290,12 @@ def load_sm2(cls, curve, pub_x, pub_y): _DLL.botan_pubkey_load_sm2(byref(pub.handle_()), pub_x.handle_(), pub_y.handle_(), _ctype_str(curve)) return pub + @classmethod + def load_sm2_sec1(cls, curve, sec1_encoding): + pub = PublicKey() + _DLL.botan_pubkey_load_sm2_sec1(byref(pub.handle_()), _ctype_bits(sec1_encoding), len(sec1_encoding), _ctype_str(curve)) + return pub + @classmethod def load_kyber(cls, key): pub = PublicKey() diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py index 0d147c17156..52ebdd96e7d 100644 --- a/src/scripts/test_python.py +++ b/src/scripts/test_python.py @@ -509,6 +509,13 @@ def verify_positive_and_negative(verifier, sig): verify = botan.PKVerify(pk, param_str) verify_positive_and_negative(verify, sig) + @staticmethod + def _ecc_sec1_convert_to_compressed(uncompressed_sec1): + assert uncompressed_sec1[0] == 0x04 + is_odd = uncompressed_sec1[-1] & 0x01 != 0 + x = uncompressed_sec1[1:1 + (len(uncompressed_sec1) - 1) // 2] + return (b"\x03" if is_odd else b"\x02") + x + def test_rsa(self): rng = botan.RandomNumberGenerator() rsapriv = botan.PrivateKey.create('RSA', '1024', rng) @@ -609,6 +616,15 @@ def test_ecdsa(self): priv2 = botan.PrivateKey.load_ecdsa(group, priv.get_field('x')) self._pksign_roundtrips(priv2, pub, hash_fn) + # Load public key from SEC.1 encoding + uncompressed_sec1 = pub.to_raw() + pub3 = botan.PublicKey.load_ecdsa_sec1(group, uncompressed_sec1) + self._pksign_roundtrips(priv, pub3, hash_fn) + + compressed_sec1 = BotanPythonTests._ecc_sec1_convert_to_compressed(uncompressed_sec1) + pub4 = botan.PublicKey.load_ecdsa_sec1(group, compressed_sec1) + self._pksign_roundtrips(priv, pub4, hash_fn) + def test_sm2(self): rng = botan.RandomNumberGenerator() @@ -636,6 +652,15 @@ def test_sm2(self): priv2 = botan.PrivateKey.load_sm2(group, priv.get_field('x')) self._pksign_roundtrips(priv2, pub, hash_fn) + # Load public key from SEC.1 encoding + uncompressed_sec1 = pub.to_raw() + pub3 = botan.PublicKey.load_sm2_sec1(group, uncompressed_sec1) + self._pksign_roundtrips(priv, pub3, hash_fn) + + compressed_sec1 = BotanPythonTests._ecc_sec1_convert_to_compressed(uncompressed_sec1) + pub4 = botan.PublicKey.load_sm2_sec1(group, compressed_sec1) + self._pksign_roundtrips(priv, pub4, hash_fn) + def test_ecdh(self): a_rng = botan.RandomNumberGenerator('user') b_rng = botan.RandomNumberGenerator('user') @@ -681,6 +706,17 @@ def test_ecdh(self): a_raw = hex_encode(a_priv.to_raw()) self.assertEqual(int(a_raw, base=16), a_priv_x) + uncompressed_sec1 = a_priv.get_public_key().to_raw() + compressed_sec1 = BotanPythonTests._ecc_sec1_convert_to_compressed(uncompressed_sec1) + new_a_pub1 = botan.PublicKey.load_ecdh_sec1(grp, uncompressed_sec1) + new_a_pub2 = botan.PublicKey.load_ecdh_sec1(grp, compressed_sec1) + self.assertEqual(new_a_pub1.to_raw(), new_a_pub2.to_raw()) + self.assertEqual(new_a_pub1.to_raw(), a_priv.get_public_key().to_raw()) + b_op2 = botan.PKKeyAgreement(b_priv, kdf) + b_key2 = b_op2.agree(compressed_sec1, 32, salt) + self.assertEqual(b_key2, b_key) + + def test_rfc7748_kex(self): rng = botan.RandomNumberGenerator() diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index 02a13e4bf51..a19d8204e87 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -61,6 +61,72 @@ namespace { // NOLINTEND(*-macro-usage) +/** + * Helper class for testing "view"-style API functions that take a callback + * that gets passed a variable-length buffer of bytes. + * + * Example: + * botan_privkey_t priv; + * ViewBytesSink sink; + * botan_privkey_view_raw(priv, sink.delegate(), sink.callback()); + * std::cout << hex_encode(sink.get()) << std::endl; + */ +class ViewBytesSink final { + public: + void* delegate() { return this; } + + botan_view_bin_fn callback() { return &write_fn; } + + std::span get() const { return m_buf; } + + const uint8_t* data() const { return m_buf.data(); } + + size_t size() const { return m_buf.size(); } + + private: + static int write_fn(void* ctx, const uint8_t buf[], size_t len) { + if(ctx == nullptr || buf == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + auto* sink = static_cast(ctx); + sink->m_buf.assign(buf, buf + len); + + return BOTAN_FFI_SUCCESS; + } + + private: + std::vector m_buf; +}; + +/** + * See ViewBytesSink for how to use this. Works for `botan_view_str_fn` instead. +*/ +class ViewStringSink final { + public: + void* delegate() { return this; } + + botan_view_str_fn callback() { return &write_fn; } + + std::string_view get() { return m_str; } + + private: + static int write_fn(void* ctx, const char* str, size_t len) { + if(ctx == nullptr || str == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + auto* sink = static_cast(ctx); + // discard the null terminator + sink->m_str = std::string(str, len - 1); + + return BOTAN_FFI_SUCCESS; + } + + private: + std::string m_str; +}; + // NOLINTBEGIN(*-init-variables) class FFI_Test : public Test { @@ -2767,6 +2833,7 @@ class FFI_ECDSA_Test final : public FFI_Test { botan_mp_t private_scalar; botan_mp_t public_x; botan_mp_t public_y; + ViewBytesSink sec1; botan_mp_init(&private_scalar); botan_mp_init(&public_x); botan_mp_init(&public_y); @@ -2777,13 +2844,17 @@ class FFI_ECDSA_Test final : public FFI_Test { TEST_FFI_OK(botan_privkey_get_field, (private_scalar, priv, "x")); TEST_FFI_OK(botan_pubkey_get_field, (public_x, pub, "public_x")); TEST_FFI_OK(botan_pubkey_get_field, (public_y, pub, "public_y")); + TEST_FFI_OK(botan_pubkey_view_raw, (pub, sec1.delegate(), sec1.callback())); botan_privkey_t loaded_privkey; - botan_pubkey_t loaded_pubkey; + botan_pubkey_t loaded_pubkey1; + botan_pubkey_t loaded_pubkey2; TEST_FFI_OK(botan_privkey_load_ecdsa, (&loaded_privkey, private_scalar, kCurve)); - TEST_FFI_OK(botan_pubkey_load_ecdsa, (&loaded_pubkey, public_x, public_y, kCurve)); + TEST_FFI_OK(botan_pubkey_load_ecdsa, (&loaded_pubkey1, public_x, public_y, kCurve)); + TEST_FFI_OK(botan_pubkey_load_ecdsa_sec1, (&loaded_pubkey2, sec1.data(), sec1.size(), kCurve)); TEST_FFI_OK(botan_privkey_check_key, (loaded_privkey, rng, 0)); - TEST_FFI_OK(botan_pubkey_check_key, (loaded_pubkey, rng, 0)); + TEST_FFI_OK(botan_pubkey_check_key, (loaded_pubkey1, rng, 0)); + TEST_FFI_OK(botan_pubkey_check_key, (loaded_pubkey2, rng, 0)); std::array namebuf{}; size_t name_len = namebuf.size(); @@ -2852,7 +2923,8 @@ class FFI_ECDSA_Test final : public FFI_Test { TEST_FFI_OK(botan_pubkey_destroy, (pub)); TEST_FFI_OK(botan_privkey_destroy, (priv)); TEST_FFI_OK(botan_privkey_destroy, (loaded_privkey)); - TEST_FFI_OK(botan_pubkey_destroy, (loaded_pubkey)); + TEST_FFI_OK(botan_pubkey_destroy, (loaded_pubkey1)); + TEST_FFI_OK(botan_pubkey_destroy, (loaded_pubkey2)); } }; @@ -2866,7 +2938,8 @@ class FFI_SM2_Sig_Test final : public FFI_Test { botan_privkey_t priv; botan_pubkey_t pub; botan_privkey_t loaded_privkey; - botan_pubkey_t loaded_pubkey; + botan_pubkey_t loaded_pubkey1; + botan_pubkey_t loaded_pubkey2; if(!TEST_FFI_INIT(botan_privkey_create, (&priv, "SM2_Sig", kCurve, rng))) { return; @@ -2883,6 +2956,7 @@ class FFI_SM2_Sig_Test final : public FFI_Test { botan_mp_t private_scalar; botan_mp_t public_x; botan_mp_t public_y; + ViewBytesSink sec1; botan_mp_init(&private_scalar); botan_mp_init(&public_x); botan_mp_init(&public_y); @@ -2890,10 +2964,13 @@ class FFI_SM2_Sig_Test final : public FFI_Test { TEST_FFI_OK(botan_privkey_get_field, (private_scalar, priv, "x")); TEST_FFI_OK(botan_pubkey_get_field, (public_x, pub, "public_x")); TEST_FFI_OK(botan_pubkey_get_field, (public_y, pub, "public_y")); + TEST_FFI_OK(botan_pubkey_view_raw, (pub, sec1.delegate(), sec1.callback())); REQUIRE_FFI_OK(botan_privkey_load_sm2, (&loaded_privkey, private_scalar, kCurve)); - REQUIRE_FFI_OK(botan_pubkey_load_sm2, (&loaded_pubkey, public_x, public_y, kCurve)); + REQUIRE_FFI_OK(botan_pubkey_load_sm2, (&loaded_pubkey1, public_x, public_y, kCurve)); + REQUIRE_FFI_OK(botan_pubkey_load_sm2_sec1, (&loaded_pubkey2, sec1.data(), sec1.size(), kCurve)); TEST_FFI_OK(botan_privkey_check_key, (loaded_privkey, rng, 0)); - TEST_FFI_OK(botan_pubkey_check_key, (loaded_pubkey, rng, 0)); + TEST_FFI_OK(botan_pubkey_check_key, (loaded_pubkey1, rng, 0)); + TEST_FFI_OK(botan_pubkey_check_key, (loaded_pubkey2, rng, 0)); std::array namebuf{}; size_t name_len = namebuf.size(); @@ -2955,7 +3032,8 @@ class FFI_SM2_Sig_Test final : public FFI_Test { TEST_FFI_OK(botan_pubkey_destroy, (pub)); TEST_FFI_OK(botan_privkey_destroy, (priv)); TEST_FFI_OK(botan_privkey_destroy, (loaded_privkey)); - TEST_FFI_OK(botan_pubkey_destroy, (loaded_pubkey)); + TEST_FFI_OK(botan_pubkey_destroy, (loaded_pubkey1)); + TEST_FFI_OK(botan_pubkey_destroy, (loaded_pubkey2)); } }; @@ -2968,7 +3046,8 @@ class FFI_SM2_Enc_Test final : public FFI_Test { botan_privkey_t priv; botan_pubkey_t pub; botan_privkey_t loaded_privkey; - botan_pubkey_t loaded_pubkey; + botan_pubkey_t loaded_pubkey1; + botan_pubkey_t loaded_pubkey2; if(!TEST_FFI_INIT(botan_privkey_create, (&priv, "SM2_Enc", kCurve, rng))) { return; @@ -2985,6 +3064,7 @@ class FFI_SM2_Enc_Test final : public FFI_Test { botan_mp_t private_scalar; botan_mp_t public_x; botan_mp_t public_y; + ViewBytesSink sec1; botan_mp_init(&private_scalar); botan_mp_init(&public_x); botan_mp_init(&public_y); @@ -2992,10 +3072,13 @@ class FFI_SM2_Enc_Test final : public FFI_Test { TEST_FFI_OK(botan_privkey_get_field, (private_scalar, priv, "x")); TEST_FFI_OK(botan_pubkey_get_field, (public_x, pub, "public_x")); TEST_FFI_OK(botan_pubkey_get_field, (public_y, pub, "public_y")); + TEST_FFI_OK(botan_pubkey_view_raw, (pub, sec1.delegate(), sec1.callback())); REQUIRE_FFI_OK(botan_privkey_load_sm2_enc, (&loaded_privkey, private_scalar, kCurve)); - REQUIRE_FFI_OK(botan_pubkey_load_sm2_enc, (&loaded_pubkey, public_x, public_y, kCurve)); + REQUIRE_FFI_OK(botan_pubkey_load_sm2_enc, (&loaded_pubkey1, public_x, public_y, kCurve)); + REQUIRE_FFI_OK(botan_pubkey_load_sm2_sec1, (&loaded_pubkey2, sec1.data(), sec1.size(), kCurve)); TEST_FFI_OK(botan_privkey_check_key, (loaded_privkey, rng, 0)); - TEST_FFI_OK(botan_pubkey_check_key, (loaded_pubkey, rng, 0)); + TEST_FFI_OK(botan_pubkey_check_key, (loaded_pubkey1, rng, 0)); + TEST_FFI_OK(botan_pubkey_check_key, (loaded_pubkey2, rng, 0)); std::array namebuf{}; size_t name_len = namebuf.size(); @@ -3009,7 +3092,7 @@ class FFI_SM2_Enc_Test final : public FFI_Test { TEST_FFI_OK(botan_rng_get, (rng, message.data(), message.size())); botan_pk_op_encrypt_t enc; - if(TEST_FFI_OK(botan_pk_op_encrypt_create, (&enc, loaded_pubkey, "", 0))) { + if(TEST_FFI_OK(botan_pk_op_encrypt_create, (&enc, loaded_pubkey1, "", 0))) { size_t ctext_len; TEST_FFI_OK(botan_pk_op_encrypt_output_length, (enc, message.size(), &ctext_len)); @@ -3036,7 +3119,8 @@ class FFI_SM2_Enc_Test final : public FFI_Test { TEST_FFI_OK(botan_pubkey_destroy, (pub)); TEST_FFI_OK(botan_privkey_destroy, (priv)); TEST_FFI_OK(botan_privkey_destroy, (loaded_privkey)); - TEST_FFI_OK(botan_pubkey_destroy, (loaded_pubkey)); + TEST_FFI_OK(botan_pubkey_destroy, (loaded_pubkey1)); + TEST_FFI_OK(botan_pubkey_destroy, (loaded_pubkey2)); } }; @@ -3063,6 +3147,7 @@ class FFI_ECDH_Test final : public FFI_Test { botan_mp_t private_scalar; botan_mp_t public_x; botan_mp_t public_y; + ViewBytesSink sec1; botan_mp_init(&private_scalar); botan_mp_init(&public_x); botan_mp_init(&public_y); @@ -3070,15 +3155,20 @@ class FFI_ECDH_Test final : public FFI_Test { TEST_FFI_OK(botan_privkey_get_field, (private_scalar, priv1, "x")); TEST_FFI_OK(botan_pubkey_get_field, (public_x, pub1, "public_x")); TEST_FFI_OK(botan_pubkey_get_field, (public_y, pub1, "public_y")); + TEST_FFI_OK(botan_pubkey_view_raw, (pub1, sec1.delegate(), sec1.callback())); botan_privkey_t loaded_privkey1; botan_pubkey_t loaded_pubkey1; + botan_pubkey_t loaded_pubkey2; REQUIRE_FFI_OK(botan_privkey_load_ecdh, (&loaded_privkey1, private_scalar, "secp256r1")); REQUIRE_FFI_OK(botan_pubkey_load_ecdh, (&loaded_pubkey1, public_x, public_y, "secp256r1")); + REQUIRE_FFI_OK(botan_pubkey_load_ecdh_sec1, (&loaded_pubkey2, sec1.data(), sec1.size(), "secp256r1")); TEST_FFI_OK(botan_privkey_check_key, (loaded_privkey1, rng, 0)); TEST_FFI_OK(botan_pubkey_check_key, (loaded_pubkey1, rng, 0)); + TEST_FFI_OK(botan_pubkey_check_key, (loaded_pubkey2, rng, 0)); ffi_test_pubkey_export(result, loaded_pubkey1, priv1, rng); + ffi_test_pubkey_export(result, loaded_pubkey2, priv1, rng); ffi_test_pubkey_export(result, pub2, priv2, rng); #if defined(BOTAN_HAS_KDF2) && defined(BOTAN_HAS_SHA_256) @@ -3136,6 +3226,7 @@ class FFI_ECDH_Test final : public FFI_Test { TEST_FFI_OK(botan_pubkey_destroy, (pub2)); TEST_FFI_OK(botan_privkey_destroy, (loaded_privkey1)); TEST_FFI_OK(botan_pubkey_destroy, (loaded_pubkey1)); + TEST_FFI_OK(botan_pubkey_destroy, (loaded_pubkey2)); } }; @@ -3408,68 +3499,6 @@ class FFI_X448_Test final : public FFI_Test { } }; -/** - * Helper class for testing "view"-style API functions that take a callback - * that gets passed a variable-length buffer of bytes. - * - * Example: - * botan_privkey_t priv; - * ViewBytesSink sink; - * botan_privkey_view_raw(priv, sink.delegate(), sink.callback()); - * std::cout << hex_encode(sink.get()) << std::endl; - */ -class ViewBytesSink final { - public: - void* delegate() { return this; } - - botan_view_bin_fn callback() { return &write_fn; } - - const std::vector& get() { return m_buf; } - - private: - static int write_fn(void* ctx, const uint8_t buf[], size_t len) { - if(ctx == nullptr || buf == nullptr) { - return BOTAN_FFI_ERROR_NULL_POINTER; - } - - auto* sink = static_cast(ctx); - sink->m_buf.assign(buf, buf + len); - - return 0; - } - - private: - std::vector m_buf; -}; - -/** - * See ViewBytesSink for how to use this. Works for `botan_view_str_fn` instead. -*/ -class ViewStringSink final { - public: - void* delegate() { return this; } - - botan_view_str_fn callback() { return &write_fn; } - - std::string_view get() { return m_str; } - - private: - static int write_fn(void* ctx, const char* str, size_t len) { - if(ctx == nullptr || str == nullptr) { - return BOTAN_FFI_ERROR_NULL_POINTER; - } - - auto* sink = static_cast(ctx); - // discard the null terminator - sink->m_str = std::string(str, len - 1); - - return 0; - } - - private: - std::string m_str; -}; - /** * Base class for roundtrip tests of FFI bindings for Key Encapsulation Mechanisms. */