diff --git a/.ci/build.py b/.ci/build.py index 561e13e..ea81fa8 100755 --- a/.ci/build.py +++ b/.ci/build.py @@ -23,9 +23,6 @@ def compute_features(features, for_who): if 'vendored' in features: feat.append('vendored') - if 'git' in features or 'botan3' in features: - feat.append('botan3') - if for_who == 'lib' and 'no-std' not in features: feat.append('std') @@ -64,7 +61,7 @@ def main(args = None): if "SCCACHE_MAXSIZE" not in os.environ: os.environ["SCCACHE_MAXSIZE"] = "2G" - KNOWN_FEATURES = ['vendored', 'git', 'no-std', 'botan3'] + KNOWN_FEATURES = ['vendored', 'git', 'no-std'] features = [] if len(args) == 1 else args[1].split(',') @@ -89,7 +86,7 @@ def main(args = None): os.environ["DYLD_LIBRARY_PATH"] = "/usr/local/lib" homebrew_dir = "/opt/homebrew/lib" - if 'botan3' in features and os.access(homebrew_dir, os.R_OK): + if os.access(homebrew_dir, os.R_OK): os.environ["RUSTFLAGS"] = "-D warnings -L/opt/homebrew/lib" os.environ["RUSTDOCFLAGS"] = "-D warnings -L/opt/homebrew/lib" os.environ["DYLD_LIBRARY_PATH"] = homebrew_dir diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77a9846..9dbb7ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,7 @@ jobs: runs-on: ubuntu-24.04 steps: + - run: sudo apt-get -qq install libbotan-2-dev - uses: dtolnay/rust-toolchain@master with: toolchain: nightly @@ -84,11 +85,9 @@ jobs: matrix: include: - toolchain: stable - features: botan3 - toolchain: stable features: vendored - toolchain: nightly - features: botan3 steps: - run: brew install ccache botan diff --git a/README.md b/README.md index 3984ab6..cbaddbf 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,15 @@ The following features are supported: is still required. * `vendored`: Build a copy of the C++ library directly, without relying on a system installed version. -* `botan3`: Enable support for using APIs added in Botan 3. - This enables several new features, and more efficient operation. - This feature is implicitly enabled if you use `vendored`. * `static`: Enable static linking for a non-vendored, externally provided Botan dependency. * `pkg-config`: Enable finding a non-vendored, externally provided Botan with pkg-config. Can be used in combination with `static`. + +The crate detects at build time what features are exported from the C interface, +and adjusts itself accordingly. These feature sets can be checked using +`#[cfg(botan_ffi_YYYYMMDD)]` where YYYYMMDD can be any of + +* 20230403: Botan 3.0 +* 20240408: Botan 3.4 +* 20250506: Botan 3.8 diff --git a/botan-src/Cargo.toml b/botan-src/Cargo.toml index f1d14bf..c1e0e0f 100644 --- a/botan-src/Cargo.toml +++ b/botan-src/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "botan-src" -version = "0.30701.1" +version = "0.30701.2" authors = ["Rodolphe Breard ", "Jack Lloyd "] description = "Sources of Botan cryptography library" license = "MIT" diff --git a/botan-src/examples/build.rs b/botan-src/examples/build.rs index e6ff05c..d2ff323 100644 --- a/botan-src/examples/build.rs +++ b/botan-src/examples/build.rs @@ -1,5 +1,5 @@ fn main() { let (lib_dir, include_dir) = botan_src::build(); println!("Library directory: {lib_dir}"); - println!("Include directory: {include_dir}"); + println!("Include directory: {}", include_dir.display()); } diff --git a/botan-src/src/lib.rs b/botan-src/src/lib.rs index 515768f..dc8009f 100644 --- a/botan-src/src/lib.rs +++ b/botan-src/src/lib.rs @@ -5,7 +5,7 @@ use std::process::Command; const BUILD_ERROR_MSG: &str = "Unable to build botan."; const SRC_DIR_ERROR_MSG: &str = "Unable to find the source directory."; const SRC_DIR: &str = "botan"; -const INCLUDE_DIR: &str = "build/include/botan"; +const INCLUDE_DIR: &str = "build/include/public"; macro_rules! pathbuf_to_string { ($s: ident) => { @@ -115,14 +115,16 @@ fn make(build_dir: &str) { } } -pub fn build() -> (String, String) { +pub fn build() -> (String, std::path::PathBuf) { let src_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SRC_DIR); let build_dir = env::var_os("OUT_DIR").map_or(src_dir.to_owned(), PathBuf::from); let build_dir = build_dir.join("botan"); let include_dir = build_dir.join(INCLUDE_DIR); let build_dir = pathbuf_to_string!(build_dir); + let orig_dir = env::current_dir().expect(SRC_DIR_ERROR_MSG); env::set_current_dir(&src_dir).expect(SRC_DIR_ERROR_MSG); configure(&build_dir); make(&build_dir); - (build_dir, pathbuf_to_string!(include_dir)) + env::set_current_dir(&orig_dir).expect("Unable to restore cwd"); + (build_dir, include_dir) } diff --git a/botan-sys/Cargo.toml b/botan-sys/Cargo.toml index aadee6e..96f4660 100644 --- a/botan-sys/Cargo.toml +++ b/botan-sys/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "botan-sys" -version = "0.11.1" +version = "0.12.0" authors = ["Jack Lloyd "] -links = "botan-3" -build = "build.rs" +links = "botan" +build = "build/main.rs" description = "FFI wrapper for Botan cryptography library" license = "MIT" homepage = "https://botan.randombit.net/" @@ -16,11 +16,11 @@ rust-version = "1.64" [features] default = [] -vendored = ["botan-src", "botan3"] -botan3 = [] +vendored = ["botan-src"] static = [] pkg-config = ["dep:pkg-config"] [build-dependencies] -botan-src = { version = "0.30701.1", optional = true, path = "../botan-src" } +botan-src = { version = "0.30701.2", optional = true, path = "../botan-src" } pkg-config = { version = "0.3.30", optional = true } +cc = "1" diff --git a/botan-sys/README.md b/botan-sys/README.md index 3739dc1..aedbecd 100644 --- a/botan-sys/README.md +++ b/botan-sys/README.md @@ -1,7 +1,24 @@ # botan-sys This crate contains the FFI declarations for calling the C API included in the -[Botan](https://botan.randombit.net/) cryptography library. +[Botan](https://botan.randombit.net/) cryptography library as well as the rules +for linking to it. A high level Rust interface built on these declarations is included in the [botan](https://crates.io/crates/botan) crate. + +## Features + +* `vendored`: Build against the `botan-src` crate +* `static`: Statically link the library. This is always used if `vendored` is set +* `pkg-config`: Use `pkg-config` instead of probing to find the library + +## Environment Variables + +The following environment variables are used to guide features + +* `BOTAN_INCLUDE_DIR` the base path to where the relevant library includes are + found. For example if the headers are in `/opt/foo/botan-3/botan`, this + variable should be set to `/opt/foo`. If not set, tries a few common + locations. This variable is ignored if the `pkg-config` or `vendored` + features are used. diff --git a/botan-sys/build.rs b/botan-sys/build.rs deleted file mode 100644 index 6db8af8..0000000 --- a/botan-sys/build.rs +++ /dev/null @@ -1,80 +0,0 @@ -#[cfg(feature = "vendored")] -fn emit_dylibs() -> Vec<&'static str> { - // Windows doesn't need to dynamically link the C++ runtime - // but we do need to link to DLLs with needed functionality: - if cfg!(target_os = "windows") { - return vec!["user32", "crypt32"]; - } - - // On Linux we use libstdc++ - if cfg!(any(target_os = "linux")) { - return vec!["stdc++"]; - } - - // For all other platforms, link to libc++ from LLVM/Clang - vec!["c++"] -} - -fn botan_lib_major_version() -> i32 { - if cfg!(any(feature = "vendored", feature = "botan3")) { - 3 - } else { - 2 - } -} - -fn botan_library_name() -> String { - let major_version = botan_lib_major_version(); - - if cfg!(target_os = "windows") && major_version == 2 { - // For some unknown reason, Botan 2.x does not include - // the major version in the name of the Windows library - "botan".to_string() - } else { - format!("botan-{major_version}") - } -} - -fn main() { - #[cfg(feature = "vendored")] - { - let (lib_dir, _) = botan_src::build(); - println!("cargo:vendored=1"); - println!("cargo:rustc-link-search=native={}", &lib_dir); - println!("cargo:rustc-link-lib=static={}", botan_library_name()); - - for dylib in emit_dylibs() { - println!("cargo:rustc-flags=-l dylib={}", dylib); - } - } - #[cfg(not(feature = "vendored"))] - { - #[cfg(feature = "static")] - { - #[cfg(feature = "pkg-config")] - { - pkg_config::Config::new() - .statik(true) - .probe(&botan_library_name()) - .unwrap(); - } - #[cfg(not(feature = "pkg-config"))] - { - println!("cargo:rustc-link-lib=static={}", botan_library_name()); - } - } - #[cfg(not(feature = "static"))] - { - #[cfg(feature = "pkg-config")] - { - pkg_config::Config::new() - .probe(&botan_library_name()) - .unwrap(); - } - #[cfg(not(feature = "pkg-config"))] - { - println!("cargo:rustc-link-lib={}", botan_library_name()); - } - } - } -} diff --git a/botan-sys/build/main.rs b/botan-sys/build/main.rs new file mode 100644 index 0000000..60e3aed --- /dev/null +++ b/botan-sys/build/main.rs @@ -0,0 +1,252 @@ +use std::collections::HashMap; +use std::path::PathBuf; + +const KNOWN_FFI_VERSIONS: [(u32, u32); 7] = [ + (3, 20250506), // 3.8 + (3, 20240408), // 3.4 + (3, 20231009), // 3.2 + (3, 20230711), // 3.1 + (3, 20230403), // 3.0 + (2, 20210220), // 2.18 + (2, 20191214), // 2.13 +]; + +#[cfg(feature = "vendored")] +fn emit_dylibs() -> Vec<&'static str> { + // Windows doesn't need to dynamically link the C++ runtime + // but we do need to link to DLLs with needed functionality: + if cfg!(target_os = "windows") { + return vec!["user32", "crypt32"]; + } + + // On Linux we use libstdc++ + if cfg!(any(target_os = "linux")) { + return vec!["stdc++"]; + } + + // For all other platforms, link to libc++ from LLVM/Clang + vec!["c++"] +} + +fn sanity_check_ffi(major_version: u32, minor_version: u32, ffi_version: u32) -> u32 { + if ffi_version == 0 { + panic!("The libbotan found does not support the FFI feature"); + } + + if ffi_version < 20191214 { + panic!("This version of Botan is too old; at least 2.13.0 is required"); + } + + if major_version < 2 { + panic!("Major version 1 or lower not supported"); + } + + if major_version > 4 { + panic!("Major version unexpectedly high"); + } + + if major_version >= 3 && ffi_version > 20250506 { + // Some future version; assume FFI is additive + return 20250506; + } + + for (mv, fv) in &KNOWN_FFI_VERSIONS { + if ffi_version == *fv && major_version >= *mv { + return *fv; + } + } + + panic!( + "Unexpected version settings major={} minor={} ffi={}", + major_version, minor_version, ffi_version + ); +} + +#[allow(dead_code)] +fn env_var(key: &str) -> Option { + println!("cargo:rerun-if-env-changed={}", key); + std::env::var(key).ok() +} + +#[derive(Debug, Copy, Clone)] +struct DetectedVersionInfo { + major_version: u32, + #[allow(dead_code)] + minor_version: u32, + ffi_version: u32, +} + +impl DetectedVersionInfo { + fn library_link_name(&self) -> String { + if cfg!(target_os = "windows") && self.major_version == 2 { + // For some unknown reason, Botan 2.x does not include + // the major version in the name of the Windows library + "botan".to_string() + } else { + format!("botan-{}", self.major_version) + } + } + + fn from_map(map: HashMap) -> Self { + let major_version = *map.get("MAJOR_VERSION").expect("Missing MAJOR_VERSION"); + let minor_version = *map.get("MINOR_VERSION").expect("Missing MINOR_VERSION"); + let ffi_version = *map.get("FFI_VERSION").expect("Missing FFI_VERSION"); + let ffi_version = sanity_check_ffi(major_version, minor_version, ffi_version); + Self { + major_version, + minor_version, + ffi_version, + } + } + + fn from_header(include_dir: &PathBuf) -> Self { + println!("cargo:rerun-if-changed=build/version.c"); + let mut cc = cc::Build::new(); + cc.include(include_dir); + + match cc.file("build/version.c").try_expand() { + Ok(o) => { + let expanded = String::from_utf8(o).expect("Output is not valid UTF8"); + let mut map = HashMap::new(); + + for line in expanded.split('\n') { + if line.is_empty() || line.starts_with('#') { + continue; + } + let line = line.replace('\r', ""); + + let parts = line.split(' ').collect::>(); + + if parts.len() != 2 { + continue; + } + + if parts[0] == "MAJOR_VERSION" + || parts[0] == "MINOR_VERSION" + || parts[0] == "FFI_VERSION" + { + if let Ok(code) = parts[1].parse::() { + map.insert(parts[0].to_owned(), code); + } else { + panic!("Unexpected line '{}'", line); + } + } + } + + DetectedVersionInfo::from_map(map) + } + Err(e) => { + panic!("Failed to expand header {:?}", e); + } + } + } +} + +#[cfg(not(feature = "vendored"))] +fn find_botan_include_dir() -> std::path::PathBuf { + #[cfg(feature = "pkg-config")] + { + for major in [3, 2] { + let lib_name = format!("botan-{}", major); + + let statik = if cfg!(feature = "static") { + true + } else { + false + }; + + if let Ok(config) = pkg_config::Config::new().statik(statik).probe(&lib_name) { + return config.include_paths[0].clone(); + } + } + } + + #[cfg(not(feature = "pkg-config"))] + { + if let Some(dir) = env_var("BOTAN_INCLUDE_DIR") { + return dir.into(); + } + + fn possible_header_locations() -> Vec { + let dirs = [ + "/opt/homebrew/include", + "/usr/local/include", + "/usr/include", + "/opt/include", + ]; + let mut paths = vec![]; + + for dirname in dirs { + let path = PathBuf::from(dirname); + if path.exists() { + paths.push(path); + } + } + + paths + } + + for major_version in [3, 2] { + let dir = PathBuf::from(format!("botan-{}", major_version)); + for basedir in possible_header_locations() { + let inc_dir = basedir.join(dir.clone()); + if inc_dir.exists() { + return inc_dir; + } + } + } + + panic!("Unable to find the headers cooresponding with any supported version of Botan"); + } +} + +fn main() { + for (_, v) in &KNOWN_FFI_VERSIONS { + println!("cargo:rustc-check-cfg=cfg(botan_ffi_{})", v); + } + + // TODO refactor this to avoid duplication between the two branches + + #[cfg(feature = "vendored")] + { + let (lib_dir, inc_dir) = botan_src::build(); + + let version = DetectedVersionInfo::from_header(&inc_dir); + println!("cargo:vendored=1"); + println!("cargo:rustc-link-search=native={}", &lib_dir); + println!( + "cargo:rustc-link-lib=static={}", + version.library_link_name() + ); + + for dylib in emit_dylibs() { + println!("cargo:rustc-flags=-l dylib={}", dylib); + } + println!("cargo:ffi_version={}", version.ffi_version); + for (_, ffi) in &KNOWN_FFI_VERSIONS { + if *ffi <= version.ffi_version { + println!("cargo:rustc-cfg=botan_ffi_{}", ffi); + } + } + } + #[cfg(not(feature = "vendored"))] + { + let version = DetectedVersionInfo::from_header(&find_botan_include_dir()); + + if cfg!(feature = "static") { + println!( + "cargo:rustc-link-lib=static={}", + version.library_link_name() + ); + } else { + println!("cargo:rustc-link-lib={}", version.library_link_name()); + } + // TODO(MSRV) cargo::metadata after 1.77 + println!("cargo:ffi_version={}", version.ffi_version); + for (_, ffi) in &KNOWN_FFI_VERSIONS { + if *ffi <= version.ffi_version { + println!("cargo:rustc-cfg=botan_ffi_{}", ffi); + } + } + } +} diff --git a/botan-sys/build/version.c b/botan-sys/build/version.c new file mode 100644 index 0000000..8c278c2 --- /dev/null +++ b/botan-sys/build/version.c @@ -0,0 +1,10 @@ +#include + +MAJOR_VERSION BOTAN_VERSION_MAJOR +MINOR_VERSION BOTAN_VERSION_MINOR + +#if defined(BOTAN_HAS_FFI) +FFI_VERSION BOTAN_HAS_FFI +#else +FFI_VERSION 0 +#endif diff --git a/botan-sys/src/cipher.rs b/botan-sys/src/cipher.rs index a3fcb4c..eae69e8 100644 --- a/botan-sys/src/cipher.rs +++ b/botan-sys/src/cipher.rs @@ -11,7 +11,7 @@ extern "C" { pub fn botan_cipher_get_default_nonce_length(cipher: botan_cipher_t, nl: *mut usize) -> c_int; pub fn botan_cipher_get_update_granularity(cipher: botan_cipher_t, ug: *mut usize) -> c_int; - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] pub fn botan_cipher_get_ideal_update_granularity( cipher: botan_cipher_t, ug: *mut usize, diff --git a/botan-sys/src/errors.rs b/botan-sys/src/errors.rs index def9164..9cd20d8 100644 --- a/botan-sys/src/errors.rs +++ b/botan-sys/src/errors.rs @@ -30,7 +30,7 @@ extern "C" { pub fn botan_error_description(err: BOTAN_FFI_ERROR) -> *const c_char; - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] pub fn botan_error_last_exception_message() -> *const c_char; } diff --git a/botan-sys/src/keywrap.rs b/botan-sys/src/keywrap.rs index b095bd7..28719c5 100644 --- a/botan-sys/src/keywrap.rs +++ b/botan-sys/src/keywrap.rs @@ -1,7 +1,4 @@ -use crate::ffi_types::c_int; - -#[cfg(feature = "botan3")] -use crate::ffi_types::c_char; +use crate::ffi_types::{c_char, c_int}; extern "C" { @@ -23,7 +20,7 @@ extern "C" { unwrapped_key_len: *mut usize, ) -> c_int; - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] pub fn botan_nist_kw_enc( cipher_algo: *const c_char, padding: c_int, @@ -35,7 +32,7 @@ extern "C" { wrapped_key_len: *mut usize, ) -> c_int; - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] pub fn botan_nist_kw_dec( cipher_algo: *const c_char, padding: c_int, diff --git a/botan-sys/src/lib.rs b/botan-sys/src/lib.rs index 12b8041..5d64ee3 100644 --- a/botan-sys/src/lib.rs +++ b/botan-sys/src/lib.rs @@ -1,5 +1,6 @@ #![no_std] #![allow(non_camel_case_types)] +#![allow(unused_imports)] mod block; mod cipher; @@ -18,21 +19,19 @@ mod rng; mod utils; mod version; mod x509; - -#[cfg(feature = "botan3")] mod zfec; pub mod ffi_types { pub use core::ffi::{c_char, c_int, c_uint, c_void}; - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] pub type botan_view_ctx = *mut c_void; - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] pub type botan_view_bin_fn = extern "C" fn(view_ctx: botan_view_ctx, data: *const u8, len: usize) -> c_int; - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] pub type botan_view_str_fn = extern "C" fn(view_ctx: botan_view_ctx, data: *const c_char, len: usize) -> c_int; } @@ -54,6 +53,4 @@ pub use rng::*; pub use utils::*; pub use version::*; pub use x509::*; - -#[cfg(feature = "botan3")] pub use zfec::*; diff --git a/botan-sys/src/mac.rs b/botan-sys/src/mac.rs index 5934552..08dad77 100644 --- a/botan-sys/src/mac.rs +++ b/botan-sys/src/mac.rs @@ -11,7 +11,7 @@ extern "C" { pub fn botan_mac_set_key(mac: botan_mac_t, key: *const u8, key_len: usize) -> c_int; - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] pub fn botan_mac_set_nonce(mac: botan_mac_t, nonce: *const u8, nonce_len: usize) -> c_int; pub fn botan_mac_name(mac: botan_mac_t, name: *mut c_char, name_len: *mut usize) -> c_int; diff --git a/botan-sys/src/pk_ops.rs b/botan-sys/src/pk_ops.rs index c879f55..7079306 100644 --- a/botan-sys/src/pk_ops.rs +++ b/botan-sys/src/pk_ops.rs @@ -130,35 +130,38 @@ extern "C" { pkcs_id: *mut u8, pkcs_id_len: *mut usize, ) -> c_int; -} -#[cfg(feature = "botan3")] -extern "C" { + #[cfg(botan_ffi_20230403)] pub fn botan_pk_op_key_agreement_view_public( key: botan_privkey_t, view_ctx: botan_view_ctx, view_fn: botan_view_bin_fn, ) -> c_int; + #[cfg(botan_ffi_20230403)] pub fn botan_pk_op_kem_encrypt_create( op: *mut botan_pk_op_kem_encrypt_t, key: botan_pubkey_t, kdf: *const c_char, ) -> c_int; + #[cfg(botan_ffi_20230403)] pub fn botan_pk_op_kem_encrypt_destroy(op: botan_pk_op_kem_encrypt_t) -> c_int; + #[cfg(botan_ffi_20230403)] pub fn botan_pk_op_kem_encrypt_shared_key_length( op: botan_pk_op_kem_encrypt_t, desired_shared_key_length: usize, output_shared_key_length: *mut usize, ) -> c_int; + #[cfg(botan_ffi_20230403)] pub fn botan_pk_op_kem_encrypt_encapsulated_key_length( op: botan_pk_op_kem_encrypt_t, output_encapsulated_key_length: *mut usize, ) -> c_int; + #[cfg(botan_ffi_20230403)] pub fn botan_pk_op_kem_encrypt_create_shared_key( op: botan_pk_op_kem_encrypt_t, rng: botan_rng_t, @@ -171,20 +174,24 @@ extern "C" { encapsulated_key_len: *mut usize, ) -> c_int; + #[cfg(botan_ffi_20230403)] pub fn botan_pk_op_kem_decrypt_destroy(op: botan_pk_op_kem_decrypt_t) -> c_int; + #[cfg(botan_ffi_20230403)] pub fn botan_pk_op_kem_decrypt_create( op: *mut botan_pk_op_kem_decrypt_t, key: botan_privkey_t, kdf: *const c_char, ) -> c_int; + #[cfg(botan_ffi_20230403)] pub fn botan_pk_op_kem_decrypt_shared_key_length( op: botan_pk_op_kem_decrypt_t, desired_shared_key_length: usize, output_shared_key_length: *mut usize, ) -> c_int; + #[cfg(botan_ffi_20230403)] pub fn botan_pk_op_kem_decrypt_shared_key( op: botan_pk_op_kem_decrypt_t, salt: *const u8, diff --git a/botan-sys/src/pubkey.rs b/botan-sys/src/pubkey.rs index 2ba4bd3..d9826ba 100644 --- a/botan-sys/src/pubkey.rs +++ b/botan-sys/src/pubkey.rs @@ -32,12 +32,6 @@ extern "C" { rng: botan_rng_t, params: *const c_char, ) -> c_int; - pub fn botan_privkey_create_mceliece( - key: *mut botan_privkey_t, - rng: botan_rng_t, - n: usize, - t: usize, - ) -> c_int; pub fn botan_privkey_create_dh( key: *mut botan_privkey_t, rng: botan_rng_t, @@ -211,6 +205,18 @@ extern "C" { pub fn botan_privkey_x25519_get_privkey(key: botan_privkey_t, output: *mut u8) -> c_int; pub fn botan_pubkey_x25519_get_pubkey(key: botan_pubkey_t, pubkey: *mut u8) -> c_int; + #[cfg(botan_ffi_20240408)] + pub fn botan_privkey_load_x448(key: *mut botan_privkey_t, privkey: *const u8) -> c_int; + + #[cfg(botan_ffi_20240408)] + pub fn botan_pubkey_load_x448(key: *mut botan_pubkey_t, pubkey: *const u8) -> c_int; + + #[cfg(botan_ffi_20240408)] + pub fn botan_privkey_load_ed448(key: *mut botan_privkey_t, privkey: *const u8) -> c_int; + + #[cfg(botan_ffi_20240408)] + pub fn botan_pubkey_load_ed448(key: *mut botan_pubkey_t, pubkey: *const u8) -> c_int; + pub fn botan_privkey_load_ecdsa( key: *mut botan_privkey_t, scalar: botan_mp_t, @@ -262,40 +268,57 @@ extern "C" { hash_algo: *const c_char, key: botan_pubkey_t, ) -> c_int; -} -#[cfg(feature = "botan3")] -extern "C" { + #[cfg(botan_ffi_20230403)] + pub fn botan_pubkey_view_raw( + key: botan_pubkey_t, + view_ctx: botan_view_ctx, + view_fn: botan_view_bin_fn, + ) -> c_int; + + #[cfg(botan_ffi_20230403)] pub fn botan_pubkey_view_der( key: botan_pubkey_t, view_ctx: botan_view_ctx, view_fn: botan_view_bin_fn, ) -> c_int; + #[cfg(botan_ffi_20230403)] pub fn botan_pubkey_view_pem( key: botan_pubkey_t, view_ctx: botan_view_ctx, view_fn: botan_view_str_fn, ) -> c_int; + #[cfg(botan_ffi_20230403)] + pub fn botan_privkey_view_raw( + key: botan_privkey_t, + view_ctx: botan_view_ctx, + view_fn: botan_view_bin_fn, + ) -> c_int; + + #[cfg(botan_ffi_20230403)] pub fn botan_privkey_view_der( key: botan_privkey_t, view_ctx: botan_view_ctx, view_fn: botan_view_bin_fn, ) -> c_int; + #[cfg(botan_ffi_20230403)] pub fn botan_privkey_view_pem( key: botan_privkey_t, view_ctx: botan_view_ctx, view_fn: botan_view_str_fn, ) -> c_int; + #[cfg(botan_ffi_20230403)] pub fn botan_pubkey_view_ec_public_point( key: botan_pubkey_t, view_ctx: botan_view_ctx, view_fn: botan_view_bin_fn, ) -> c_int; + #[cfg(botan_ffi_20230403)] pub fn botan_privkey_view_encrypted_der( key: botan_privkey_t, rng: botan_rng_t, @@ -307,6 +330,7 @@ extern "C" { view_fn: botan_view_bin_fn, ) -> c_int; + #[cfg(botan_ffi_20230403)] pub fn botan_privkey_view_encrypted_pem( key: botan_privkey_t, rng: botan_rng_t, @@ -317,4 +341,93 @@ extern "C" { view_ctx: botan_view_ctx, view_fn: botan_view_str_fn, ) -> c_int; + + #[cfg(botan_ffi_20250506)] + pub fn botan_privkey_stateful_operation(key: botan_privkey_t, stateful: *mut c_int) -> c_int; + + #[cfg(botan_ffi_20250506)] + pub fn botan_privkey_remaining_operations( + key: botan_privkey_t, + remaining_ops: *mut u64, + ) -> c_int; + + #[cfg(botan_ffi_20250506)] + pub fn botan_pubkey_load_frodokem( + key: *mut botan_pubkey_t, + bytes: *const u8, + len: usize, + mode: *const c_char, + ) -> c_int; + + #[cfg(botan_ffi_20250506)] + pub fn botan_privkey_load_frodokem( + key: *mut botan_privkey_t, + bytes: *const u8, + len: usize, + mode: *const c_char, + ) -> c_int; + + #[cfg(botan_ffi_20250506)] + pub fn botan_pubkey_load_classic_mceliece( + key: *mut botan_pubkey_t, + bytes: *const u8, + len: usize, + mode: *const c_char, + ) -> c_int; + + #[cfg(botan_ffi_20250506)] + pub fn botan_privkey_load_classic_mceliece( + key: *mut botan_privkey_t, + bytes: *const u8, + len: usize, + mode: *const c_char, + ) -> c_int; + + #[cfg(botan_ffi_20250506)] + pub fn botan_pubkey_load_ml_kem( + key: *mut botan_pubkey_t, + bytes: *const u8, + len: usize, + mode: *const c_char, + ) -> c_int; + + #[cfg(botan_ffi_20250506)] + pub fn botan_privkey_load_ml_kem( + key: *mut botan_privkey_t, + bytes: *const u8, + len: usize, + mode: *const c_char, + ) -> c_int; + + #[cfg(botan_ffi_20250506)] + pub fn botan_pubkey_load_slh_dsa( + key: *mut botan_pubkey_t, + bytes: *const u8, + len: usize, + mode: *const c_char, + ) -> c_int; + + #[cfg(botan_ffi_20250506)] + pub fn botan_privkey_load_slh_dsa( + key: *mut botan_privkey_t, + bytes: *const u8, + len: usize, + mode: *const c_char, + ) -> c_int; + + #[cfg(botan_ffi_20250506)] + pub fn botan_pubkey_load_ml_dsa( + key: *mut botan_pubkey_t, + bytes: *const u8, + len: usize, + mode: *const c_char, + ) -> c_int; + + #[cfg(botan_ffi_20250506)] + pub fn botan_privkey_load_ml_dsa( + key: *mut botan_privkey_t, + bytes: *const u8, + len: usize, + mode: *const c_char, + ) -> c_int; } diff --git a/botan-sys/src/x509.rs b/botan-sys/src/x509.rs index 586c749..dff0328 100644 --- a/botan-sys/src/x509.rs +++ b/botan-sys/src/x509.rs @@ -126,14 +126,14 @@ extern "C" { pub fn botan_x509_cert_validation_status(code: c_int) -> *const c_char; - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] pub fn botan_x509_cert_view_public_key_bits( cert: botan_x509_cert_t, view_ctx: botan_view_ctx, view_fn: botan_view_bin_fn, ) -> c_int; - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] pub fn botan_x509_cert_view_as_string( cert: botan_x509_cert_t, view_ctx: botan_view_ctx, diff --git a/botan-sys/src/zfec.rs b/botan-sys/src/zfec.rs index 707fab7..ec723c2 100644 --- a/botan-sys/src/zfec.rs +++ b/botan-sys/src/zfec.rs @@ -1,6 +1,6 @@ extern "C" { - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] pub fn botan_zfec_encode( k: usize, n: usize, @@ -9,7 +9,7 @@ extern "C" { outputs: *mut *mut u8, ) -> crate::ffi_types::c_int; - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] pub fn botan_zfec_decode( k: usize, n: usize, diff --git a/botan-sys/tests/tests.rs b/botan-sys/tests/tests.rs index 6af48e7..0cab355 100644 --- a/botan-sys/tests/tests.rs +++ b/botan-sys/tests/tests.rs @@ -74,20 +74,6 @@ fn test_version() { assert!(botan_ffi_supports_api(api_version) == 0); assert!(botan_ffi_supports_api(api_version + 1) != 0); - - #[cfg(feature = "botan3")] - { - assert_eq!(botan_version_major(), 3); - } - - #[cfg(not(feature = "botan3"))] - { - if botan_version_major() == 2 { - assert!(botan_version_minor() > 8); - } else { - assert_eq!(botan_version_major(), 3); - } - } } } diff --git a/botan/Cargo.toml b/botan/Cargo.toml index c47f3b6..182ec81 100644 --- a/botan/Cargo.toml +++ b/botan/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "botan" -version = "0.11.1" +version = "0.12.0" authors = ["Jack Lloyd "] description = "Rust wrapper for Botan cryptography library" license = "MIT" @@ -14,7 +14,7 @@ edition = "2021" rust-version = "1.64" [dependencies] -botan-sys = { version = "0.11", path = "../botan-sys" } +botan-sys = { version = "0.12", path = "../botan-sys" } [dev-dependencies] wycheproof = { version = "0.6", default-features = false, features = ["aead", "cipher", "dsa", "ecdh", "ecdsa", "eddsa", "hkdf", "keywrap", "mac", "primality", "rsa_enc", "rsa_sig", "xdh"] } @@ -24,6 +24,5 @@ hex = "0.4" default = ["std"] std = [] vendored = ["botan-sys/vendored"] -botan3 = ["botan-sys/botan3"] static = ["botan-sys/static"] pkg-config = ["botan-sys/pkg-config"] \ No newline at end of file diff --git a/botan/build.rs b/botan/build.rs new file mode 100644 index 0000000..cbd333b --- /dev/null +++ b/botan/build.rs @@ -0,0 +1,24 @@ +use std::env; +use std::str::FromStr; + +fn main() { + let known_ffi_versions = [ + 20250506, 20240408, 20231009, 20230711, 20230403, 20210220, 20191214, + ]; + + for ffi in &known_ffi_versions { + println!("cargo:rustc-check-cfg=cfg(botan_ffi_{})", ffi); + } + + if let Ok(version) = env::var("DEP_BOTAN_FFI_VERSION") { + let version = u64::from_str(&version).unwrap(); + + for ffi in known_ffi_versions { + if version >= ffi { + println!("cargo:rustc-cfg=botan_ffi_{}", ffi); + } + } + } else { + panic!("Expected DEP_BOTAN_FFI_VERSION to be set"); + } +} diff --git a/botan/examples/version.rs b/botan/examples/version.rs new file mode 100644 index 0000000..d84c64c --- /dev/null +++ b/botan/examples/version.rs @@ -0,0 +1,4 @@ +fn main() { + let version = botan::Version::current(); + println!("{:?}", version); +} diff --git a/botan/src/cipher.rs b/botan/src/cipher.rs index d4b14c8..5e04c26 100644 --- a/botan/src/cipher.rs +++ b/botan/src/cipher.rs @@ -51,13 +51,13 @@ impl Cipher { let (min_keylen, max_keylen, mod_keylen) = botan_usize3!(botan_cipher_get_keyspec, obj)?; - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] let ideal_update_granularity = Some(botan_usize!( botan_cipher_get_ideal_update_granularity, obj )?); - #[cfg(not(feature = "botan3"))] + #[cfg(not(botan_ffi_20230403))] let ideal_update_granularity = None; Ok(Cipher { diff --git a/botan/src/keywrap.rs b/botan/src/keywrap.rs index 3ad4f08..a3572ad 100644 --- a/botan/src/keywrap.rs +++ b/botan/src/keywrap.rs @@ -1,7 +1,7 @@ use crate::utils::*; use botan_sys::*; -#[cfg(feature = "botan3")] +#[cfg(botan_ffi_20230403)] /// Wrap a key using NIST key wrap algorithm pub fn nist_kw_enc(cipher_algo: &str, padding: bool, kek: &[u8], key: &[u8]) -> Result> { let mut output = vec![0; key.len() + if padding { 32 } else { 8 }]; @@ -24,8 +24,8 @@ pub fn nist_kw_enc(cipher_algo: &str, padding: bool, kek: &[u8], key: &[u8]) -> Ok(output) } -#[cfg(feature = "botan3")] /// Unwrap a key using NIST key wrap algorithm +#[cfg(botan_ffi_20230403)] pub fn nist_kw_dec( cipher_algo: &str, padding: bool, diff --git a/botan/src/lib.rs b/botan/src/lib.rs index a9bd67e..0450d0e 100644 --- a/botan/src/lib.rs +++ b/botan/src/lib.rs @@ -1,5 +1,6 @@ #![warn(missing_docs)] #![deny(missing_docs)] +#![allow(unused_imports)] //! A wrapper for the Botan cryptography library @@ -108,17 +109,13 @@ mod mp; mod otp; mod pbkdf; mod pk_ops; + mod pubkey; mod rng; mod utils; mod version; mod x509_cert; mod x509_crl; - -#[cfg(feature = "botan3")] -mod pk_ops_kem; - -#[cfg(feature = "botan3")] mod zfec; pub use crate::mp::*; @@ -140,9 +137,10 @@ pub use pubkey::*; pub use version::*; pub use x509_cert::*; pub use x509_crl::*; +pub use zfec::*; -#[cfg(feature = "botan3")] -pub use pk_ops_kem::*; +#[cfg(botan_ffi_20230403)] +mod pk_ops_kem; -#[cfg(feature = "botan3")] -pub use zfec::*; +#[cfg(botan_ffi_20230403)] +pub use pk_ops_kem::*; diff --git a/botan/src/mac.rs b/botan/src/mac.rs index cbed5f8..93fb878 100644 --- a/botan/src/mac.rs +++ b/botan/src/mac.rs @@ -81,7 +81,7 @@ impl MsgAuthCode { botan_call!(botan_mac_set_key, self.obj, key.as_ptr(), key.len()) } - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] /// Set the nonce for the authentication code object /// /// Only a few MACs support this; currently only GMAC @@ -90,7 +90,7 @@ impl MsgAuthCode { /// ``` /// let mut gmac = botan::MsgAuthCode::new("GMAC(AES-128)").unwrap(); /// gmac.set_key(&vec![0; 16]).unwrap(); - /// gmac.set_nonce(&vec![0; 12]).unwrap(); + /// gmac.set_nonce(&vec![0; 12]); /// ``` pub fn set_nonce(&mut self, nonce: &[u8]) -> Result<()> { botan_call!(botan_mac_set_nonce, self.obj, nonce.as_ptr(), nonce.len()) diff --git a/botan/src/pubkey.rs b/botan/src/pubkey.rs index 7824baa..9909012 100644 --- a/botan/src/pubkey.rs +++ b/botan/src/pubkey.rs @@ -115,6 +115,20 @@ impl Privkey { Ok(Privkey { obj }) } + #[cfg(botan_ffi_20240408)] + /// Load an X448 private key + /// + /// # Examples + /// + /// ``` + /// let v = vec![0x42; 56]; + /// let key = botan::Privkey::load_x448(&v); + /// ``` + pub fn load_x448(key: &[u8]) -> Result { + let obj = botan_init!(botan_privkey_load_x448, key.as_ptr())?; + Ok(Privkey { 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())?; @@ -247,14 +261,14 @@ impl Privkey { /// DER encode the key (unencrypted) pub fn der_encode(&self) -> Result> { - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] { call_botan_ffi_viewing_vec_u8(&|ctx, cb| unsafe { botan_privkey_view_der(self.obj, ctx, cb) }) } - #[cfg(not(feature = "botan3"))] + #[cfg(not(botan_ffi_20230403))] { call_botan_ffi_returning_vec_u8(4096, &|out_buf, out_len| unsafe { botan_privkey_export(self.obj, out_buf, out_len, 0u32) @@ -264,14 +278,14 @@ impl Privkey { /// PEM encode the private key (unencrypted) pub fn pem_encode(&self) -> Result { - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] { call_botan_ffi_viewing_str_fn(&|ctx, cb| unsafe { botan_privkey_view_pem(self.obj, ctx, cb) }) } - #[cfg(not(feature = "botan3"))] + #[cfg(not(botan_ffi_20230403))] { call_botan_ffi_returning_string(4096, &|out_buf, out_len| unsafe { botan_privkey_export(self.obj, out_buf, out_len, 1u32) @@ -310,7 +324,7 @@ impl Privkey { let rng_handle = rng.handle(); - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] { call_botan_ffi_viewing_vec_u8(&|ctx, cb| unsafe { botan_privkey_view_encrypted_der( @@ -326,7 +340,7 @@ impl Privkey { }) } - #[cfg(not(feature = "botan3"))] + #[cfg(not(botan_ffi_20230403))] { call_botan_ffi_returning_vec_u8(4096, &|out_buf, out_len| unsafe { botan_privkey_export_encrypted_pbkdf_iter( @@ -374,7 +388,7 @@ impl Privkey { let pbkdf = make_cstr(pbkdf)?; let rng_handle = rng.handle(); - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] { call_botan_ffi_viewing_str_fn(&|ctx, cb| unsafe { botan_privkey_view_encrypted_pem( @@ -390,7 +404,7 @@ impl Privkey { }) } - #[cfg(not(feature = "botan3"))] + #[cfg(not(botan_ffi_20230403))] { call_botan_ffi_returning_string(4096, &|out_buf, out_len| unsafe { botan_privkey_export_encrypted_pbkdf_iter( @@ -408,18 +422,42 @@ impl Privkey { } } + #[cfg(botan_ffi_20250506)] + /// Check if the key in question is stateful (eg XMMS, LMS) + pub fn is_stateful(&self) -> Result { + let mut stateful = 0; + let rc = unsafe { botan_privkey_stateful_operation(self.obj, &mut stateful) }; + if rc == 0 { + if stateful == 0 { + Ok(false) + } else if stateful == 1 { + Ok(true) + } else { + Err(Error::with_message( + ErrorType::InternalError, + format!( + "Unexpected return {} from botan_privkey_stateful_operation", + stateful + ), + )) + } + } else { + Err(Error::from_rc(rc)) + } + } + /// Return the key agrement key, only valid for DH/ECDH pub fn key_agreement_key(&self) -> Result> { - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] { call_botan_ffi_viewing_vec_u8(&|ctx, cb| unsafe { botan_pk_op_key_agreement_view_public(self.obj, ctx, cb) }) } - #[cfg(not(feature = "botan3"))] + #[cfg(not(botan_ffi_20230403))] { - let ka_key_len = 512; // fixme + let ka_key_len = 512; call_botan_ffi_returning_vec_u8(ka_key_len, &|out_buf, out_len| unsafe { botan_pk_op_key_agreement_export_public(self.obj, out_buf, out_len) }) @@ -441,6 +479,17 @@ impl Privkey { Ok(r) } + #[cfg(botan_ffi_20250506)] + /// Get the raw bytes associated with this key + /// + /// This is not defined for certain schemes which do not have an obvious + /// encoding (eg RSA), so will return an error for some keys + pub fn raw_bytes(&self) -> Result> { + call_botan_ffi_viewing_vec_u8(&|ctx, cb| unsafe { + botan_privkey_view_raw(self.obj, ctx, cb) + }) + } + /// Get the public and private key associated with this key pub fn get_ed25519_key(&self) -> Result<(Vec, Vec)> { let mut out = vec![0; 64]; @@ -456,9 +505,17 @@ impl Privkey { /// Get the X25519 private key pub fn get_x25519_key(&self) -> Result> { - let mut out = vec![0; 32]; - botan_call!(botan_privkey_x25519_get_privkey, self.obj, out.as_mut_ptr())?; - Ok(out) + #[cfg(botan_ffi_20250506)] + { + self.raw_bytes() + } + + #[cfg(not(botan_ffi_20250506))] + { + let mut out = vec![0; 32]; + botan_call!(botan_privkey_x25519_get_privkey, self.obj, out.as_mut_ptr())?; + Ok(out) + } } /// Sign a message using the specified padding method @@ -588,6 +645,26 @@ impl Pubkey { Ok(Pubkey { obj }) } + #[cfg(botan_ffi_20250506)] + /// Load a ML-KEM public key from the raw byte encoding + /// + /// The exact type can be determined by the length and does not need to be specified + pub fn load_ml_kem(key: &[u8]) -> Result { + let params = make_cstr(match key.len() { + 800 => "ML-KEM-512", + 1184 => "ML-KEM-768", + 1568 => "ML-KEM-1024", + _ => return Err(Error::bad_parameter("Invalid ML-KEM key length")), + })?; + let obj = botan_init!( + botan_pubkey_load_ml_kem, + key.as_ptr(), + key.len(), + params.as_ptr() + )?; + Ok(Pubkey { obj }) + } + /// Return estimated bit strength of this key pub fn estimated_strength(&self) -> Result { botan_usize!(botan_pubkey_estimated_strength, self.obj) @@ -618,16 +695,16 @@ impl Pubkey { /// DER encode this public key pub fn der_encode(&self) -> Result> { - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] { call_botan_ffi_viewing_vec_u8(&|ctx, cb| unsafe { botan_pubkey_view_der(self.obj, ctx, cb) }) } - #[cfg(not(feature = "botan3"))] + #[cfg(not(botan_ffi_20230403))] { - let der_len = 4096; // fixme + let der_len = 4096; call_botan_ffi_returning_vec_u8(der_len, &|out_buf, out_len| unsafe { botan_pubkey_export(self.obj, out_buf, out_len, 0u32) }) @@ -636,23 +713,23 @@ impl Pubkey { /// PEM encode this public key pub fn pem_encode(&self) -> Result { - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] { call_botan_ffi_viewing_str_fn(&|ctx, cb| unsafe { botan_pubkey_view_pem(self.obj, ctx, cb) }) } - #[cfg(not(feature = "botan3"))] + #[cfg(not(botan_ffi_20230403))] { - let pem_len = 4096; // fixme + let pem_len = 4096; call_botan_ffi_returning_string(pem_len, &|out_buf, out_len| unsafe { botan_pubkey_export(self.obj, out_buf, out_len, 1u32) }) } } - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] /// Return the encoded elliptic curve point associated with this key /// /// Only valid for EC based keys @@ -679,20 +756,42 @@ impl Pubkey { Ok(r) } + #[cfg(botan_ffi_20250506)] + /// Return the raw byte encoding of this key + pub fn raw_bytes(&self) -> Result> { + call_botan_ffi_viewing_vec_u8(&|ctx, cb| unsafe { + botan_pubkey_view_raw(self.obj, ctx, cb) + }) + } + /// Return the 32-byte Ed25519 public key pub fn get_ed25519_key(&self) -> Result> { - let mut out = vec![0; 32]; - - botan_call!(botan_pubkey_ed25519_get_pubkey, self.obj, out.as_mut_ptr())?; + #[cfg(botan_ffi_20250506)] + { + self.raw_bytes() + } - Ok(out) + #[cfg(not(botan_ffi_20250506))] + { + let mut out = vec![0; 32]; + botan_call!(botan_pubkey_ed25519_get_pubkey, self.obj, out.as_mut_ptr())?; + Ok(out) + } } /// Get the X25519 public key pub fn get_x25519_key(&self) -> Result> { - let mut out = vec![0; 32]; - botan_call!(botan_pubkey_x25519_get_pubkey, self.obj, out.as_mut_ptr())?; - Ok(out) + #[cfg(botan_ffi_20250506)] + { + self.raw_bytes() + } + + #[cfg(not(botan_ffi_20250506))] + { + let mut out = vec![0; 32]; + botan_call!(botan_pubkey_x25519_get_pubkey, self.obj, out.as_mut_ptr())?; + Ok(out) + } } /// Encrypt a message using the specified padding method diff --git a/botan/src/utils.rs b/botan/src/utils.rs index 0c85920..ac6b368 100644 --- a/botan/src/utils.rs +++ b/botan/src/utils.rs @@ -52,7 +52,7 @@ pub(crate) fn call_botan_ffi_returning_vec_u8( Ok(output) } -#[cfg(feature = "botan3")] +#[cfg(botan_ffi_20230403)] pub(crate) mod view { use super::*; @@ -133,7 +133,7 @@ pub(crate) mod view { } } -#[cfg(feature = "botan3")] +#[cfg(botan_ffi_20230403)] pub(crate) use crate::view::*; fn cstr_slice_to_str(raw_cstr: &[u8]) -> Result { @@ -141,7 +141,7 @@ fn cstr_slice_to_str(raw_cstr: &[u8]) -> Result { Ok(cstr.to_str().map_err(Error::conversion_error)?.to_owned()) } -#[cfg(feature = "botan3")] +#[cfg(botan_ffi_20230403)] unsafe fn cstr_to_str(raw_cstr: *const c_char) -> Result { let cstr = CStr::from_ptr(raw_cstr); Ok(cstr.to_str().map_err(Error::conversion_error)?.to_owned()) @@ -179,7 +179,7 @@ impl Error { pub(crate) fn from_rc(rc: c_int) -> Self { let err_type = ErrorType::from(rc); - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] let message = { let cptr = unsafe { botan_sys::botan_error_last_exception_message() }; match unsafe { cstr_to_str(cptr) } { @@ -189,7 +189,7 @@ impl Error { } }; - #[cfg(not(feature = "botan3"))] + #[cfg(not(botan_ffi_20230403))] let message = None; Self { err_type, message } diff --git a/botan/src/x509_cert.rs b/botan/src/x509_cert.rs index 20a0b12..de434bc 100644 --- a/botan/src/x509_cert.rs +++ b/botan/src/x509_cert.rs @@ -214,7 +214,7 @@ impl Certificate { /// Return the byte representation of the public key pub fn public_key_bits(&self) -> Result> { - #[cfg(not(feature = "botan3"))] + #[cfg(not(botan_ffi_20230403))] { let pk_len = 4096; // fixme call_botan_ffi_returning_vec_u8(pk_len, &|out_buf, out_len| unsafe { @@ -222,7 +222,7 @@ impl Certificate { }) } - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] { call_botan_ffi_viewing_vec_u8(&|ctx, cb| unsafe { botan_x509_cert_view_public_key_bits(self.obj, ctx, cb) @@ -239,7 +239,7 @@ impl Certificate { /// Return a free-form string representation of this certificate pub fn to_string(&self) -> Result { - #[cfg(not(feature = "botan3"))] + #[cfg(not(botan_ffi_20230403))] { let as_str_len = 4096; call_botan_ffi_returning_string(as_str_len, &|out_buf, out_len| unsafe { @@ -247,7 +247,7 @@ impl Certificate { }) } - #[cfg(feature = "botan3")] + #[cfg(botan_ffi_20230403)] { call_botan_ffi_viewing_str_fn(&|ctx, cb| unsafe { botan_x509_cert_view_as_string(self.obj, ctx, cb) diff --git a/botan/src/zfec.rs b/botan/src/zfec.rs index 90510b5..58d2e68 100644 --- a/botan/src/zfec.rs +++ b/botan/src/zfec.rs @@ -1,6 +1,7 @@ use crate::utils::*; use botan_sys::*; +#[cfg(botan_ffi_20230403)] /// Forward Error Correction Encoding pub fn zfec_encode(k: usize, n: usize, input: &[u8]) -> Result>> { let share_size = input.len() / k; @@ -26,6 +27,7 @@ pub fn zfec_encode(k: usize, n: usize, input: &[u8]) -> Result>> { Ok(outputs) } +#[cfg(botan_ffi_20230403)] /// Forward Error Correction Decoding pub fn zfec_decode( k: usize, diff --git a/botan/tests/tests.rs b/botan/tests/tests.rs index 2f81d27..3ffb1dd 100644 --- a/botan/tests/tests.rs +++ b/botan/tests/tests.rs @@ -6,30 +6,6 @@ use std::str::FromStr; fn test_version() -> Result<(), botan::Error> { let version = botan::Version::current()?; - /* - If we are running against a released version we know it must be at - least 2.8 since we require APIs added after the 2.7 release. - */ - - #[cfg(feature = "botan3")] - { - assert_eq!(version.major, 3); - } - - #[cfg(feature = "vendored")] - { - assert_eq!(version.major, 3); - } - - #[cfg(not(feature = "botan3"))] - { - assert!(version.major == 2 || version.major == 3); - - if version.major == 2 { - assert!(version.minor >= 8); - } - } - assert!(version.release_date == 0 || version.release_date >= 20181001); assert!(version.ffi_api >= 20180713); @@ -38,10 +14,36 @@ fn test_version() -> Result<(), botan::Error> { assert!(botan::Version::supports_version(20180713)); assert!(!botan::Version::supports_version(20180712)); - assert!(version.at_least(2, 8)); + assert!(version.at_least(2, 13)); assert!(version.at_least(2, 4)); assert!(version.at_least(1, 100)); + println!("{:?}", version); + + if cfg!(botan_ffi_20250506) { + assert!(version.at_least(3, 8)); + } + + if cfg!(botan_ffi_20240408) { + assert!(version.at_least(3, 4)); + } + + if cfg!(botan_ffi_20231009) { + assert!(version.at_least(3, 2)); + } + + if cfg!(botan_ffi_20230711) { + assert!(version.at_least(3, 1)); + } + + if cfg!(botan_ffi_20230403) { + assert!(version.at_least(3, 0)); + } + + if cfg!(botan_ffi_20191214) { + assert!(version.at_least(2, 13)); + } + Ok(()) } @@ -901,7 +903,7 @@ fn test_rfc3394_aes_key_wrap() -> Result<(), botan::Error> { Ok(()) } -#[cfg(feature = "botan3")] +#[cfg(botan_ffi_20230403)] #[test] fn test_aes_key_wrap() -> Result<(), botan::Error> { let kek = @@ -915,10 +917,10 @@ fn test_aes_key_wrap() -> Result<(), botan::Error> { botan::hex_encode(&wrapped)?, "28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21" ); - let unwrapped = botan::nist_kw_dec("AES-256", false, &kek, &wrapped)?; assert_eq!(unwrapped, key); + Ok(()) } @@ -1161,12 +1163,13 @@ fn test_dsa() -> Result<(), botan::Error> { Ok(()) } -#[cfg(feature = "botan3")] +#[cfg(botan_ffi_20230403)] #[test] fn test_zfec() -> Result<(), botan::Error> { let k = 2; let n = 3; let input_bytes = b"abcdefghijklmnop"; + let output_shares = botan::zfec_encode(k, n, input_bytes)?; assert_eq!(output_shares.len(), n); @@ -1186,7 +1189,7 @@ fn test_zfec() -> Result<(), botan::Error> { Ok(()) } -#[cfg(feature = "botan3")] +#[cfg(botan_ffi_20230403)] #[test] fn test_kyber() -> Result<(), botan::Error> { let mut rng = botan::RandomNumberGenerator::new()?; @@ -1210,3 +1213,34 @@ fn test_kyber() -> Result<(), botan::Error> { Ok(()) } + +#[cfg(botan_ffi_20250506)] +#[test] +fn test_ml_kem() -> Result<(), botan::Error> { + let mut rng = botan::RandomNumberGenerator::new()?; + + for kl in [512, 768, 1024] { + let params = format!("ML-KEM-{}", kl); + let sk = botan::Privkey::create("ML-KEM", ¶ms, &mut rng)?; + let pk = sk.pubkey()?; + + let pk_bytes = pk.raw_bytes()?; + let pk2 = botan::Pubkey::load_ml_kem(&pk_bytes)?; + + let salt = rng.read(12)?; + let shared_key_len = 32; + let kdf = "KDF2(SHA-256)"; + + let kem_e = botan::KeyEncapsulation::new(&pk2, kdf)?; + let (shared_key, encap_key) = kem_e.create_shared_key(&mut rng, &salt, shared_key_len)?; + + assert_eq!(shared_key.len(), shared_key_len); + + let kem_d = botan::KeyDecapsulation::new(&sk, kdf)?; + let shared_key_d = kem_d.decrypt_shared_key(&encap_key, &salt, shared_key_len)?; + + assert_eq!(shared_key, shared_key_d); + } + + Ok(()) +} diff --git a/botan/tests/wycheproof.rs b/botan/tests/wycheproof.rs index 845b8f4..68b7d91 100644 --- a/botan/tests/wycheproof.rs +++ b/botan/tests/wycheproof.rs @@ -82,7 +82,7 @@ fn wycheproof_keywrap_tests() -> Result<(), botan::Error> { Ok(()) } -#[cfg(feature = "botan3")] +#[cfg(botan_ffi_20230403)] #[test] fn wycheproof_nist_kw_tests() -> Result<(), botan::Error> { use wycheproof::keywrap::*; @@ -482,7 +482,7 @@ fn wycheproof_mac_test( Ok(()) } -#[cfg(feature = "botan3")] +#[cfg(botan_ffi_20230403)] #[test] fn wycheproof_mac_with_nonce_tests() -> Result<(), botan::Error> { use wycheproof::mac_with_nonce::*; @@ -512,6 +512,7 @@ fn wycheproof_mac_with_nonce_tests() -> Result<(), botan::Error> { Ok(()) } + #[test] fn wycheproof_primality_tests() -> Result<(), botan::Error> { use wycheproof::{primality::*, TestResult}; @@ -909,9 +910,10 @@ fn wycheproof_eddsa_verify_tests() -> Result<(), botan::Error> { use wycheproof::eddsa::*; let is_botan2 = botan::Version::current()?.major == 2; + let supports_ed448 = cfg!(botan_ffi_20240408); for test_name in TestName::all() { - if test_name == TestName::Ed448 { + if test_name == TestName::Ed448 && !supports_ed448 { continue; } @@ -1003,8 +1005,10 @@ fn wycheproof_xdh_tests() -> Result<(), botan::Error> { let version = botan::Version::current()?; let will_accept_zeros = !version.at_least(3, 6); + let supports_x448 = botan::Version::supports_version(20240408); + for test_name in TestName::all() { - if test_name == TestName::X448 { + if test_name == TestName::X448 && !supports_x448 { continue; } @@ -1012,7 +1016,11 @@ fn wycheproof_xdh_tests() -> Result<(), botan::Error> { for test_group in &test_set.test_groups { for test in &test_group.tests { - let priv_key = botan::Privkey::load_x25519(&test.private_key)?; + let priv_key = match test_name { + #[cfg(botan_ffi_20240408)] + TestName::X448 => botan::Privkey::load_x448(&test.private_key), + _ => botan::Privkey::load_x25519(&test.private_key), + }?; let mut ka = botan::KeyAgreement::new(&priv_key, "Raw")?;