Skip to content
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
1 change: 1 addition & 0 deletions doc/api_ref/ffi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/lib/ffi/ffi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Comment thread
reneme marked this conversation as resolved.
return BOTAN_FFI_SUCCESS;
}

// This is the API introduced in 3.8
if(api_version == 20250506) {
return BOTAN_FFI_SUCCESS;
Expand Down
11 changes: 10 additions & 1 deletion src/lib/ffi/ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);

Expand Down
87 changes: 87 additions & 0 deletions src/lib/ffi/ffi_pkey_algs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,24 @@ int pubkey_load_ec(std::unique_ptr<ECPublicKey_t>& key,
}
}

template <class ECPublicKey_t>
int pubkey_load_ec_sec1(std::unique_ptr<ECPublicKey_t>& key,
std::span<const uint8_t> 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) {
Expand Down Expand Up @@ -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<Botan::ECDSA_PublicKey> 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) {
Expand Down Expand Up @@ -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<Botan::ECDH_PublicKey> 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) {
Expand Down Expand Up @@ -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<Botan::SM2_PublicKey> 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) {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/ffi/info.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<defines>
FFI -> 20250506
FFI -> 20250829
</defines>

<module_info>
Expand Down
27 changes: 25 additions & 2 deletions src/python/botan3.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand Down
36 changes: 36 additions & 0 deletions src/scripts/test_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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()

Expand Down
Loading
Loading