From 46e9412b19ffcfc8a2825ff92ced4c20ff67e87c Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Mon, 24 Feb 2025 03:19:29 -0500 Subject: [PATCH] Use compile time detection of what features are available Previously features were gated on the botan3 feature (which is removed here). This didn't work well in practice as it made it difficult to add support for features added in Botan3 after the 3.0 release. Add declarations in botan-sys for functions added to ffi.h since 3.0.0, gated on the detected library version. Also add interfaces for some of the new functionality to the high level Rust crate. --- .ci/build.py | 7 +- .github/workflows/ci.yml | 3 +- README.md | 11 +- botan-src/Cargo.toml | 2 +- botan-src/examples/build.rs | 2 +- botan-src/src/lib.rs | 8 +- botan-sys/Cargo.toml | 12 +- botan-sys/README.md | 19 ++- botan-sys/build.rs | 80 ------------ botan-sys/build/main.rs | 252 ++++++++++++++++++++++++++++++++++++ botan-sys/build/version.c | 10 ++ botan-sys/src/cipher.rs | 2 +- botan-sys/src/errors.rs | 2 +- botan-sys/src/keywrap.rs | 9 +- botan-sys/src/lib.rs | 11 +- botan-sys/src/mac.rs | 2 +- botan-sys/src/pk_ops.rs | 13 +- botan-sys/src/pubkey.rs | 131 +++++++++++++++++-- botan-sys/src/x509.rs | 4 +- botan-sys/src/zfec.rs | 4 +- botan-sys/tests/tests.rs | 14 -- botan/Cargo.toml | 5 +- botan/build.rs | 24 ++++ botan/examples/version.rs | 4 + botan/src/cipher.rs | 4 +- botan/src/keywrap.rs | 4 +- botan/src/lib.rs | 16 +-- botan/src/mac.rs | 4 +- botan/src/pubkey.rs | 155 ++++++++++++++++++---- botan/src/utils.rs | 10 +- botan/src/x509_cert.rs | 8 +- botan/src/zfec.rs | 2 + botan/tests/tests.rs | 92 ++++++++----- botan/tests/wycheproof.rs | 18 ++- 34 files changed, 707 insertions(+), 237 deletions(-) delete mode 100644 botan-sys/build.rs create mode 100644 botan-sys/build/main.rs create mode 100644 botan-sys/build/version.c create mode 100644 botan/build.rs create mode 100644 botan/examples/version.rs 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")?;