From 7b5f5f58895380a734f5cbe8195b473babcadd11 Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Tue, 3 Mar 2026 15:58:54 -0500 Subject: [PATCH] Properly serialize public key operations when built against Botan < 3.11 All prior versions of the library had bugs affecting certain algorithms (namely XMSS, LMS, FrodoKEM, ML-KEM, and ML-DSA) which would cause incorrect computation or memory errors if multiple threads attempt to perform operations at the same time on the same key object. This lock requires std since otherwise we do not have a mutex available. So now building this crate no_std requires Botan 3.11.0 or later be used, since that's the only situation we can safely skip the lock. --- .ci/build.py | 2 +- .github/workflows/ci.yml | 6 +-- botan/src/lib.rs | 5 ++- botan/src/pubkey.rs | 86 ++++++++++++++++++++++++++-------------- botan/src/x509_crl.rs | 8 ++-- 5 files changed, 68 insertions(+), 39 deletions(-) diff --git a/.ci/build.py b/.ci/build.py index f024a84..7169ab5 100755 --- a/.ci/build.py +++ b/.ci/build.py @@ -71,7 +71,7 @@ def main(args = None): print("ERROR: Unknown toolchain %s" % (toolchain)) return 1 - features = [] if len(args) < 3 else args[2].split(',') + features = [] if len(args) < 3 else args[2].split('+') for feat in features: if feat not in KNOWN_FEATURES: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 503de84..df540c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,17 +45,17 @@ jobs: include: - toolchain: stable - toolchain: stable - features: no-std + features: no-std+git - toolchain: stable features: git - toolchain: stable features: vendored - toolchain: 1.64.0 # MSRV no-std - features: no-std + features: no-std+git - toolchain: 1.64.0 # MSRV - toolchain: nightly - toolchain: nightly - features: no-std + features: no-std+git steps: - run: sudo apt-get -qq install ccache libbotan-2-dev diff --git a/botan/src/lib.rs b/botan/src/lib.rs index ff0e13b..fc2a8e4 100644 --- a/botan/src/lib.rs +++ b/botan/src/lib.rs @@ -10,6 +10,9 @@ #[macro_use] extern crate alloc; +#[cfg(all(not(botan_ffi_20260303), not(feature = "std")))] +compile_error!("Building this crate no_std requires Botan 3.11.0 or higher"); + extern crate botan_sys; macro_rules! botan_call { @@ -50,7 +53,7 @@ macro_rules! botan_init { #[allow(unused)] macro_rules! botan_init_at { ($fn:path, $($before:expr),* ; $($after:expr),*) => {{ - let mut obj = std::ptr::null_mut(); + let mut obj = ptr::null_mut(); let rc = unsafe { $fn($($before,)* &mut obj $(, $after)*) }; diff --git a/botan/src/pubkey.rs b/botan/src/pubkey.rs index baf44c8..9dbfe48 100644 --- a/botan/src/pubkey.rs +++ b/botan/src/pubkey.rs @@ -9,6 +9,8 @@ use crate::rng::RandomNumberGenerator; /// A public key object pub struct Pubkey { obj: botan_pubkey_t, + #[cfg(not(botan_ffi_20260303))] + op_lock: std::sync::Mutex<()>, } unsafe impl Sync for Pubkey {} @@ -20,6 +22,8 @@ botan_impl_drop!(Pubkey, botan_pubkey_destroy); /// A private key object pub struct Privkey { obj: botan_privkey_t, + #[cfg(not(botan_ffi_20260303))] + op_lock: std::sync::Mutex<()>, } unsafe impl Sync for Privkey {} @@ -28,6 +32,14 @@ unsafe impl Send for Privkey {} botan_impl_drop!(Privkey, botan_privkey_destroy); impl Privkey { + fn from_obj(obj: botan_privkey_t) -> Self { + Self { + obj, + #[cfg(not(botan_ffi_20260303))] + op_lock: std::sync::Mutex::new(()), + } + } + pub(crate) fn handle(&self) -> botan_privkey_t { self.obj } @@ -42,7 +54,7 @@ impl Privkey { rng.handle() )?; - Ok(Privkey { obj }) + Ok(Privkey::from_obj(obj)) } /// Create a new ElGamal private key with a random group @@ -53,7 +65,7 @@ impl Privkey { ) -> Result { let obj = botan_init!(botan_privkey_create_elgamal, rng.handle(), p_bits, q_bits)?; - Ok(Self { obj }) + Ok(Privkey::from_obj(obj)) } /// Create a new DSA private key with a random group @@ -64,7 +76,7 @@ impl Privkey { ) -> Result { let obj = botan_init!(botan_privkey_create_dsa, rng.handle(), p_bits, q_bits)?; - Ok(Self { obj }) + Ok(Privkey::from_obj(obj)) } /// Load an RSA private key (p,q,e) @@ -80,7 +92,7 @@ impl Privkey { /// ``` pub fn load_rsa(p: &MPI, q: &MPI, e: &MPI) -> Result { let obj = botan_init!(botan_privkey_load_rsa, p.handle(), q.handle(), e.handle())?; - Ok(Privkey { obj }) + Ok(Privkey::from_obj(obj)) } /// Load an Ed25519 private key @@ -96,7 +108,7 @@ impl Privkey { return Err(Error::bad_parameter("Invalid input length")); } let obj = botan_init!(botan_privkey_load_ed25519, key.as_ptr())?; - Ok(Privkey { obj }) + Ok(Privkey::from_obj(obj)) } /// Load an X25519 private key @@ -112,7 +124,7 @@ impl Privkey { return Err(Error::bad_parameter("Invalid input length")); } let obj = botan_init!(botan_privkey_load_x25519, key.as_ptr())?; - Ok(Privkey { obj }) + Ok(Privkey::from_obj(obj)) } /// Load an X448 private key @@ -128,20 +140,20 @@ impl Privkey { pub fn load_x448(key: &[u8]) -> Result { crate::ffi_version_guard!("load_x448", botan_ffi_20240408, [key], { let obj = botan_init!(botan_privkey_load_x448, key.as_ptr())?; - Ok(Privkey { obj }) + Ok(Privkey::from_obj(obj)) }) } /// Load a PKCS#1 encoded RSA private key pub fn load_rsa_pkcs1(pkcs1: &[u8]) -> Result { let obj = botan_init!(botan_privkey_load_rsa_pkcs1, pkcs1.as_ptr(), pkcs1.len())?; - Ok(Privkey { obj }) + Ok(Privkey::from_obj(obj)) } /// Load an DH private key (p,g,x) pub fn load_dh(p: &MPI, g: &MPI, x: &MPI) -> Result { let obj = botan_init!(botan_privkey_load_dh, p.handle(), g.handle(), x.handle())?; - Ok(Privkey { obj }) + Ok(Privkey::from_obj(obj)) } /// Load an DSA private key (p,q,g,x) @@ -153,7 +165,7 @@ impl Privkey { g.handle(), x.handle() )?; - Ok(Privkey { obj }) + Ok(Privkey::from_obj(obj)) } /// Load an ElGamal private key (p,g,x) @@ -164,21 +176,21 @@ impl Privkey { g.handle(), x.handle() )?; - Ok(Privkey { obj }) + Ok(Privkey::from_obj(obj)) } /// Load an ECDSA private key with specified curve and secret scalar pub fn load_ecdsa(s: &MPI, curve_name: &str) -> Result { let curve_name = make_cstr(curve_name)?; let obj = botan_init!(botan_privkey_load_ecdsa, s.handle(), curve_name.as_ptr())?; - Ok(Privkey { obj }) + Ok(Privkey::from_obj(obj)) } /// Load an ECDH private key with specified curve and secret scalar pub fn load_ecdh(s: &MPI, curve_name: &str) -> Result { let curve_name = make_cstr(curve_name)?; let obj = botan_init!(botan_privkey_load_ecdh, s.handle(), curve_name.as_ptr())?; - Ok(Privkey { obj }) + Ok(Privkey::from_obj(obj)) } /// Load DER bytes as an unencrypted PKCS#8 private key @@ -190,7 +202,7 @@ impl Privkey { der.len(), ptr::null() )?; - Ok(Privkey { obj }) + Ok(Privkey::from_obj(obj)) } /// Load PEM string as an unencrypted PKCS#8 private key @@ -204,7 +216,7 @@ impl Privkey { ptr::null() )?; - Ok(Privkey { obj }) + Ok(Privkey::from_obj(obj)) } /// Load DER bytes as an encrypted PKCS#8 private key @@ -217,7 +229,7 @@ impl Privkey { der.len(), passphrase.as_ptr() )?; - Ok(Privkey { obj }) + Ok(Privkey::from_obj(obj)) } /// Load PEM string as an encrypted PKCS#8 private key @@ -232,7 +244,7 @@ impl Privkey { passphrase.as_ptr() )?; - Ok(Privkey { obj }) + Ok(Privkey::from_obj(obj)) } /// Check if the key seems to be valid @@ -252,7 +264,7 @@ impl Privkey { /// Return the public key associated with this private key pub fn pubkey(&self) -> Result { let obj = botan_init!(botan_privkey_export_pubkey, self.obj)?; - Ok(Pubkey { obj }) + Ok(Pubkey::from_handle(obj)) } /// Return the name of the algorithm @@ -522,6 +534,8 @@ impl Privkey { padding: &str, rng: &mut RandomNumberGenerator, ) -> Result> { + #[cfg(not(botan_ffi_20260303))] + let _lock = self.op_lock.lock().expect("lock poisoned"); let mut signer = Signer::new(self, padding)?; signer.update(message)?; signer.finish(rng) @@ -529,6 +543,8 @@ impl Privkey { /// Decrypt a message that was encrypted using the specified padding method pub fn decrypt(&self, ctext: &[u8], padding: &str) -> Result> { + #[cfg(not(botan_ffi_20260303))] + let _lock = self.op_lock.lock().expect("lock poisoned"); let mut decryptor = Decryptor::new(self, padding)?; decryptor.decrypt(ctext) } @@ -541,6 +557,8 @@ impl Privkey { salt: &[u8], kdf: &str, ) -> Result> { + #[cfg(not(botan_ffi_20260303))] + let _lock = self.op_lock.lock().expect("lock poisoned"); let mut op = KeyAgreement::new(self, kdf)?; op.agree(output_len, other_key, salt) } @@ -548,7 +566,11 @@ impl Privkey { impl Pubkey { pub(crate) fn from_handle(obj: botan_pubkey_t) -> Pubkey { - Pubkey { obj } + Pubkey { + obj, + #[cfg(not(botan_ffi_20260303))] + op_lock: std::sync::Mutex::new(()), + } } pub(crate) fn handle(&self) -> botan_pubkey_t { @@ -558,7 +580,7 @@ impl Pubkey { /// Load a DER encoded public key pub fn load_der(der: &[u8]) -> Result { let obj = botan_init!(botan_pubkey_load, der.as_ptr(), der.len())?; - Ok(Pubkey { obj }) + Ok(Pubkey::from_handle(obj)) } /// Load a PEM encoded public key @@ -568,13 +590,13 @@ impl Pubkey { make_cstr(pem)?.as_ptr() as *const u8, pem.len() )?; - Ok(Pubkey { obj }) + Ok(Pubkey::from_handle(obj)) } /// Load an RSA public key (n,e) pub fn load_rsa(n: &MPI, e: &MPI) -> Result { let obj = botan_init!(botan_pubkey_load_rsa, n.handle(), e.handle())?; - Ok(Pubkey { obj }) + Ok(Pubkey::from_handle(obj)) } /// Load a PKCS#1 encoded RSA public key @@ -587,7 +609,7 @@ impl Pubkey { /// Load an DH public key (p,g,y) pub fn load_dh(p: &MPI, g: &MPI, y: &MPI) -> Result { let obj = botan_init!(botan_pubkey_load_dh, p.handle(), g.handle(), y.handle())?; - Ok(Pubkey { obj }) + Ok(Pubkey::from_handle(obj)) } /// Load an DSA public key (p,q,g,y) @@ -599,7 +621,7 @@ impl Pubkey { g.handle(), y.handle() )?; - Ok(Pubkey { obj }) + Ok(Pubkey::from_handle(obj)) } /// Load an ElGamal public key (p,g,y) @@ -610,7 +632,7 @@ impl Pubkey { g.handle(), y.handle() )?; - Ok(Pubkey { obj }) + Ok(Pubkey::from_handle(obj)) } /// Load an ECDSA public key (x,y) for the specified curve @@ -622,7 +644,7 @@ impl Pubkey { pub_y.handle(), curve_name.as_ptr() )?; - Ok(Pubkey { obj }) + Ok(Pubkey::from_handle(obj)) } /// Load an ECDH public key (x,y) for the specified curve @@ -634,19 +656,19 @@ impl Pubkey { pub_y.handle(), curve_name.as_ptr() )?; - Ok(Pubkey { obj }) + Ok(Pubkey::from_handle(obj)) } /// Load an Ed25519 public key pub fn load_ed25519(key: &[u8]) -> Result { let obj = botan_init!(botan_pubkey_load_ed25519, key.as_ptr())?; - Ok(Pubkey { obj }) + Ok(Pubkey::from_handle(obj)) } /// Load an X25519 key pub fn load_x25519(key: &[u8]) -> Result { let obj = botan_init!(botan_pubkey_load_x25519, key.as_ptr())?; - Ok(Pubkey { obj }) + Ok(Pubkey::from_handle(obj)) } /// Load a ML-KEM public key from the raw byte encoding @@ -669,7 +691,7 @@ impl Pubkey { key.len(), params.as_ptr() )?; - Ok(Pubkey { obj }) + Ok(Pubkey::from_handle(obj)) }) } @@ -815,12 +837,16 @@ impl Pubkey { padding: &str, rng: &mut RandomNumberGenerator, ) -> Result> { + #[cfg(not(botan_ffi_20260303))] + let _lock = self.op_lock.lock().expect("lock poisoned"); let mut op = Encryptor::new(self, padding)?; op.encrypt(message, rng) } /// Verify a message that was signed using the specified padding method pub fn verify(&self, message: &[u8], signature: &[u8], padding: &str) -> Result { + #[cfg(not(botan_ffi_20260303))] + let _lock = self.op_lock.lock().expect("lock poisoned"); let mut op = Verifier::new(self, padding)?; op.update(message)?; op.finish(signature) diff --git a/botan/src/x509_crl.rs b/botan/src/x509_crl.rs index f48d6bc..bbc46ad 100644 --- a/botan/src/x509_crl.rs +++ b/botan/src/x509_crl.rs @@ -55,10 +55,10 @@ impl CRL { next_update, hash_fn .as_ref() - .map_or(std::ptr::null(), |hash_fn| hash_fn.as_ptr()), + .map_or(ptr::null(), |hash_fn| hash_fn.as_ptr()), padding .as_ref() - .map_or(std::ptr::null(), |padding| padding.as_ptr()) + .map_or(ptr::null(), |padding| padding.as_ptr()) )?; Ok(Self { obj }) @@ -98,10 +98,10 @@ impl CRL { new_entries_h.len(), hash_fn .as_ref() - .map_or(std::ptr::null(), |hash_fn| hash_fn.as_ptr()), + .map_or(ptr::null(), |hash_fn| hash_fn.as_ptr()), padding .as_ref() - .map_or(std::ptr::null(), |padding| padding.as_ptr()) + .map_or(ptr::null(), |padding| padding.as_ptr()) )?; Ok(Self { obj })