use bindgen::callbacks::{IntKind, ParseCallbacks};
use std::path::{Path, PathBuf};
#[derive(Debug)]
struct OsslCallbacks;
impl ParseCallbacks for OsslCallbacks {
fn int_macro(&self, name: &str, value: i64) -> Option<IntKind> {
if name == "OPENSSL_VERSION_NUMBER" {
assert!(
value >= 0x3000_0070,
"native-ossl requires OpenSSL >= 3.0.7; \
found OPENSSL_VERSION_NUMBER = {value:#010x}"
);
println!("cargo::rustc-cfg=ossl307");
println!("cargo::metadata=OSSL307=1");
if value >= 0x3010_0000 {
println!("cargo::rustc-cfg=ossl310");
println!("cargo::metadata=OSSL310=1");
}
if value >= 0x3020_0000 {
println!("cargo::rustc-cfg=ossl320");
println!("cargo::metadata=OSSL320=1");
}
if value >= 0x3050_0000 {
println!("cargo::rustc-cfg=ossl350");
println!("cargo::metadata=OSSL350=1");
}
if value >= 0x4000_0000 {
println!("cargo::rustc-cfg=ossl_v400");
println!("cargo::metadata=OSSL_V400=1");
}
}
None
}
fn str_macro(&self, name: &str, _value: &[u8]) {
match name {
"OSSL_PKEY_PARAM_SLH_DSA_SEED" => {
println!("cargo::rustc-cfg=ossl_slhdsa");
println!("cargo::metadata=OSSL_SLHDSA=1");
}
"OSSL_PKEY_PARAM_ML_DSA_SEED" => {
println!("cargo::rustc-cfg=ossl_mldsa");
println!("cargo::metadata=OSSL_MLDSA=1");
}
"OSSL_PKEY_PARAM_ML_KEM_SEED" => {
println!("cargo::rustc-cfg=ossl_mlkem");
println!("cargo::metadata=OSSL_MLKEM=1");
}
_ => {}
}
}
fn func_macro(&self, name: &str, _value: &[&[u8]]) {
if name == "OSSL_PARAM_clear_free" {
println!("cargo::rustc-cfg=param_clear_free");
println!("cargo::metadata=PARAM_CLEAR_FREE=1");
}
}
}
fn allow_error_param_syms(mut b: bindgen::Builder) -> bindgen::Builder {
b = b.allowlist_item("OPENSSL_VERSION_NUMBER");
for sym in [
"ERR_get_error_all",
"ERR_peek_error",
"ERR_clear_error",
"ERR_error_string",
"ERR_reason_error_string",
"ERR_lib_error_string",
"ERR_set_mark",
"ERR_pop_to_mark",
"OSSL_ERR_STATE_new",
"OSSL_ERR_STATE_save",
"OSSL_ERR_STATE_restore",
"OSSL_ERR_STATE_free",
] {
b = b.allowlist_function(sym);
}
for sym in ["ERR_GET_LIB", "ERR_GET_REASON", "ERR_GET_RFLAGS"] {
b = b.allowlist_item(sym);
}
b = b.allowlist_type("ERR_STATE");
for sym in [
"OSSL_PARAM_BLD_new",
"OSSL_PARAM_BLD_free",
"OSSL_PARAM_BLD_push_int",
"OSSL_PARAM_BLD_push_uint",
"OSSL_PARAM_BLD_push_size_t",
"OSSL_PARAM_BLD_push_utf8_string",
"OSSL_PARAM_BLD_push_octet_string",
"OSSL_PARAM_BLD_push_octet_ptr",
"OSSL_PARAM_BLD_push_uint64",
"OSSL_PARAM_BLD_to_param",
"OSSL_PARAM_free",
"OSSL_PARAM_get_int",
"OSSL_PARAM_get_uint",
"OSSL_PARAM_get_size_t",
"OSSL_PARAM_get_octet_string_ptr",
"OSSL_PARAM_get_utf8_string_ptr",
"OSSL_PARAM_set_int",
"OSSL_PARAM_set_uint",
"OSSL_PARAM_set_size_t",
"OSSL_PARAM_get_int64",
"OSSL_PARAM_get_uint64",
"OSSL_PARAM_get_long",
"OSSL_PARAM_get_BN",
"OSSL_PARAM_BLD_push_BN",
"OSSL_PARAM_modified",
"OSSL_PARAM_locate",
"OPENSSL_cleanse",
"CRYPTO_memcmp",
"PEM_write_bio_PKCS8PrivateKey",
"i2d_PKCS8PrivateKeyInfo_bio",
"PKCS12_key_gen_utf8",
] {
b = b.allowlist_item(sym);
}
b.allowlist_type("OSSL_PARAM")
.allowlist_type("OSSL_PARAM_BLD")
}
fn allow_bio_libctx_syms(mut b: bindgen::Builder) -> bindgen::Builder {
for sym in [
"BIO_new",
"BIO_free",
"BIO_free_all",
"BIO_up_ref",
"BIO_s_mem",
"BIO_new_mem_buf",
"BIO_ctrl",
"BIO_read",
"BIO_read_ex",
"BIO_write",
"BIO_write_ex",
"BIO_pending",
"BIO_wpending",
"BIO_push",
"BIO_pop",
"BIO_next",
"BIO_find_type",
"BIO_CTRL_INFO",
"BIO_new_bio_pair",
] {
b = b.allowlist_item(sym);
}
b = b
.allowlist_type("BIO")
.allowlist_type("BIO_METHOD")
.allowlist_type("BUF_MEM");
for sym in [
"OSSL_LIB_CTX_new",
"OSSL_LIB_CTX_free",
"OSSL_PROVIDER_load",
"OSSL_PROVIDER_unload",
"OSSL_PROVIDER_available",
] {
b = b.allowlist_item(sym);
}
b.allowlist_type("OSSL_LIB_CTX")
.allowlist_type("OSSL_PROVIDER")
}
fn allow_digest_cipher_syms(mut b: bindgen::Builder) -> bindgen::Builder {
for sym in [
"EVP_MD_fetch",
"EVP_MD_free",
"EVP_MD_up_ref",
"EVP_MD_get_size",
"EVP_MD_get_block_size",
"EVP_MD_get_type",
"EVP_MD_CTX_new",
"EVP_MD_CTX_free",
"EVP_MD_CTX_copy_ex",
"EVP_DigestInit_ex2",
"EVP_DigestUpdate",
"EVP_DigestFinal_ex",
"EVP_DigestFinalXOF",
"EVP_Digest",
"EVP_MD_CTX_get0_md",
"EVP_MD_CTX_serialize",
"EVP_MD_CTX_deserialize",
] {
b = b.allowlist_item(sym);
}
b = b.allowlist_type("EVP_MD").allowlist_type("EVP_MD_CTX");
for sym in [
"EVP_CIPHER_fetch",
"EVP_CIPHER_free",
"EVP_CIPHER_up_ref",
"EVP_CIPHER_get_key_length",
"EVP_CIPHER_get_iv_length",
"EVP_CIPHER_get_block_size",
"EVP_CIPHER_get_flags",
"EVP_CIPHER_CTX_new",
"EVP_CIPHER_CTX_free",
"EVP_CIPHER_CTX_get_block_size",
"EVP_EncryptInit_ex2",
"EVP_EncryptUpdate",
"EVP_EncryptFinal_ex",
"EVP_DecryptInit_ex2",
"EVP_DecryptUpdate",
"EVP_DecryptFinal_ex",
"EVP_CIPHER_CTX_set_params",
"EVP_CIPHER_CTX_get_params",
"EVP_CIPHER_CTX_ctrl",
"EVP_CTRL_GCM_GET_TAG",
"EVP_CTRL_GCM_SET_TAG",
"EVP_CTRL_CCM_GET_TAG",
"EVP_CTRL_CCM_SET_TAG",
] {
b = b.allowlist_item(sym);
}
b.allowlist_type("EVP_CIPHER")
.allowlist_type("EVP_CIPHER_CTX")
}
fn allow_mac_rand_syms(mut b: bindgen::Builder) -> bindgen::Builder {
for sym in [
"EVP_MAC_fetch",
"EVP_MAC_free",
"EVP_MAC_up_ref",
"EVP_MAC_CTX_new",
"EVP_MAC_CTX_free",
"EVP_MAC_CTX_dup",
"EVP_MAC_init",
"EVP_MAC_update",
"EVP_MAC_final",
"EVP_MAC_finalXOF",
"EVP_MAC_CTX_get_mac_size",
"EVP_MAC_CTX_get_block_size",
"EVP_MAC_CTX_get0_mac",
"EVP_MAC_get0_name",
] {
b = b.allowlist_item(sym);
}
b = b.allowlist_type("EVP_MAC").allowlist_type("EVP_MAC_CTX");
for sym in [
"EVP_RAND_fetch",
"EVP_RAND_free",
"EVP_RAND_CTX_new",
"EVP_RAND_CTX_free",
"EVP_RAND_CTX_up_ref",
"EVP_RAND_generate",
"EVP_RAND_instantiate",
"EVP_RAND_set_params",
"EVP_RAND_get_params",
"EVP_RAND_get_strength",
"EVP_RAND_get_state",
"RAND_bytes",
"RAND_priv_bytes",
"RAND_get0_primary",
"RAND_get0_public",
"RAND_get0_private",
] {
b = b.allowlist_item(sym);
}
b.allowlist_type("EVP_RAND").allowlist_type("EVP_RAND_CTX")
}
fn allow_pkey_syms(mut b: bindgen::Builder) -> bindgen::Builder {
for sym in [
"EVP_PKEY_free",
"EVP_PKEY_up_ref",
"EVP_PKEY_get_bits",
"EVP_PKEY_get_security_bits",
"EVP_PKEY_is_a",
"EVP_PKEY_get_params",
"EVP_PKEY_eq",
"d2i_PrivateKey_bio",
"d2i_PUBKEY_bio",
"d2i_AutoPrivateKey_ex",
"d2i_PUBKEY_ex",
"i2d_PrivateKey",
"i2d_PUBKEY",
"PEM_read_bio_PrivateKey",
"PEM_read_bio_PrivateKey_ex",
"PEM_write_bio_PrivateKey",
"PEM_read_bio_PUBKEY",
"PEM_read_bio_PUBKEY_ex",
"PEM_write_bio_PUBKEY",
] {
b = b.allowlist_item(sym);
}
b = b.allowlist_type("EVP_PKEY");
for sym in [
"EVP_PKEY_CTX_free",
"EVP_PKEY_CTX_new_from_name",
"EVP_PKEY_CTX_new_from_pkey",
"EVP_PKEY_CTX_set_params",
"EVP_PKEY_CTX_get_params",
"EVP_PKEY_keygen_init",
"EVP_PKEY_keygen",
"EVP_PKEY_paramgen_init",
"EVP_PKEY_paramgen",
"EVP_PKEY_derive_init",
"EVP_PKEY_derive_set_peer",
"EVP_PKEY_derive",
"EVP_PKEY_encrypt_init",
"EVP_PKEY_encrypt",
"EVP_PKEY_decrypt_init",
"EVP_PKEY_decrypt",
"EVP_PKEY_encapsulate_init",
"EVP_PKEY_encapsulate",
"EVP_PKEY_decapsulate_init",
"EVP_PKEY_decapsulate",
"EVP_DigestSignInit_ex",
"EVP_DigestSignUpdate",
"EVP_DigestSignFinal",
"EVP_DigestSign",
"EVP_DigestVerifyInit_ex",
"EVP_DigestVerifyUpdate",
"EVP_DigestVerifyFinal",
"EVP_DigestVerify",
"EVP_PKEY_fromdata_init",
"EVP_PKEY_fromdata",
"EVP_PKEY_todata",
"EVP_PKEY_sign_init",
"EVP_PKEY_verify_init",
"EVP_PKEY_sign",
"EVP_PKEY_verify",
"EVP_PKEY_sign_message_init",
"EVP_PKEY_verify_message_init",
"EVP_PKEY_sign_message_update",
"EVP_PKEY_verify_message_update",
"EVP_PKEY_sign_message_final",
"EVP_PKEY_verify_message_final",
"EVP_PKEY_CTX_set_signature",
] {
b = b.allowlist_item(sym);
}
b.allowlist_type("EVP_PKEY_CTX")
}
fn allow_kdf_syms(mut b: bindgen::Builder) -> bindgen::Builder {
for sym in [
"EVP_KDF_fetch",
"EVP_KDF_free",
"EVP_KDF_CTX_new",
"EVP_KDF_CTX_free",
"EVP_KDF_derive",
"EVP_KDF_CTX_set_params",
"EVP_KDF_HKDF_MODE_EXTRACT_AND_EXPAND",
"EVP_KDF_HKDF_MODE_EXTRACT_ONLY",
"EVP_KDF_HKDF_MODE_EXPAND_ONLY",
] {
b = b.allowlist_item(sym);
}
b.allowlist_type("EVP_KDF").allowlist_type("EVP_KDF_CTX")
}
fn allow_bignum_syms(mut b: bindgen::Builder) -> bindgen::Builder {
for sym in [
"BN_bin2bn",
"BN_bn2bin",
"BN_bn2nativepad",
"BN_num_bits",
"BN_free",
] {
b = b.allowlist_function(sym);
}
b.allowlist_type("BIGNUM")
}
fn allow_signature_syms(mut b: bindgen::Builder) -> bindgen::Builder {
for sym in [
"EVP_SIGNATURE_fetch",
"EVP_SIGNATURE_free",
"EVP_SIGNATURE_up_ref",
] {
b = b.allowlist_item(sym);
}
b.allowlist_type("EVP_SIGNATURE")
}
fn allow_x509_syms(mut b: bindgen::Builder) -> bindgen::Builder {
for sym in [
"X509_new",
"X509_new_ex",
"X509_free",
"X509_up_ref",
"d2i_X509",
"i2d_X509",
"PEM_read_bio_X509",
"PEM_write_bio_X509",
"X509_get_subject_name",
"X509_get_issuer_name",
"X509_get0_pubkey",
"X509_get_pubkey",
"X509_get0_serialNumber",
"X509_get_serialNumber",
"X509_get0_notBefore",
"X509_get0_notAfter",
"X509_verify",
"X509_self_signed",
"X509_get_ext_count",
"X509_get_ext",
"X509_get_ext_by_NID",
"X509_get_ext_d2i",
"X509_NAME_entry_count",
"X509_NAME_get_entry",
"X509_NAME_oneline",
"X509_NAME_ENTRY_get_object",
"X509_NAME_ENTRY_get_data",
"X509_cmp_time",
"ASN1_TIME_to_tm",
"ASN1_INTEGER_get_int64",
"X509_set_version",
"X509_set_serialNumber",
"X509_set_subject_name",
"X509_set_issuer_name",
"X509_set_pubkey",
"X509_sign",
"X509_NAME_new",
"X509_NAME_add_entry_by_txt",
"OBJ_nid2sn",
"OBJ_obj2nid",
"OBJ_txt2nid",
"OBJ_sn2nid",
"X509_EXTENSION_get_object",
"X509_EXTENSION_get_critical",
"X509_EXTENSION_get_data",
"X509_NAME_print_ex",
"X509_NAME_free",
"ASN1_STRING_get0_data",
"ASN1_STRING_length",
"ASN1_TIME_print",
"X509_gmtime_adj",
"X509_getm_notBefore",
"X509_getm_notAfter",
"ASN1_INTEGER_new",
"ASN1_INTEGER_free",
"ASN1_INTEGER_set_int64",
] {
b = b.allowlist_item(sym);
}
b.allowlist_type("X509")
.allowlist_type("X509_NAME")
.allowlist_type("X509_NAME_ENTRY")
.allowlist_type("ASN1_TIME")
.allowlist_type("ASN1_INTEGER")
.allowlist_type("X509_EXTENSION")
.allowlist_type("GENERAL_NAME")
.allowlist_type("GENERAL_NAMES")
.allowlist_type("ASN1_STRING")
.allowlist_type("ASN1_OCTET_STRING")
}
fn allow_ssl_syms(mut b: bindgen::Builder) -> bindgen::Builder {
for sym in [
"SSL_CTX_new",
"SSL_CTX_free",
"SSL_CTX_up_ref",
"SSL_CTX_ctrl",
"SSL_ctrl",
"TLS_method",
"TLS_client_method",
"TLS_server_method",
"SSL_CTX_set_cipher_list",
"SSL_CTX_set_ciphersuites",
"SSL_CTX_use_certificate",
"SSL_CTX_use_PrivateKey",
"SSL_CTX_check_private_key",
"SSL_CTX_load_verify_locations",
"SSL_CTX_set_default_verify_paths",
"SSL_CTX_set_verify",
"SSL_CTX_set_session_cache_mode",
"SSL_new",
"SSL_free",
"SSL_set_bio",
"SSL_set_connect_state",
"SSL_set_accept_state",
"SSL_connect",
"SSL_accept",
"SSL_do_handshake",
"SSL_read_ex",
"SSL_write_ex",
"SSL_shutdown",
"SSL_get_error",
"SSL_get0_peer_certificate",
"SSL_get_peer_cert_chain",
"SSL_get_session",
"SSL_get1_session",
"SSL_set_session",
"SSL_SESSION_up_ref",
"SSL_SESSION_free",
] {
b = b.allowlist_item(sym);
}
b.allowlist_type("SSL_CTX")
.allowlist_type("SSL")
.allowlist_type("SSL_SESSION")
.allowlist_type("SSL_METHOD")
}
fn allow_x509_advanced_syms(mut b: bindgen::Builder) -> bindgen::Builder {
for sym in [
"OPENSSL_sk_num",
"OPENSSL_sk_value",
"OPENSSL_sk_pop_free",
"OPENSSL_sk_free",
"OPENSSL_sk_new_null",
"OPENSSL_sk_push",
] {
b = b.allowlist_function(sym);
}
b = b.allowlist_type("OPENSSL_STACK");
for sym in [
"X509_STORE_new",
"X509_STORE_free",
"X509_STORE_up_ref",
"X509_STORE_add_cert",
"X509_STORE_add_crl",
"X509_STORE_set_flags",
"X509_STORE_CTX_new",
"X509_STORE_CTX_free",
"X509_STORE_CTX_init",
"X509_STORE_CTX_get_error",
"X509_STORE_CTX_get0_chain",
"X509_verify_cert",
] {
b = b.allowlist_item(sym);
}
b = b
.allowlist_type("X509_STORE")
.allowlist_type("X509_STORE_CTX");
for sym in [
"X509_CRL_new",
"X509_CRL_new_ex",
"X509_CRL_free",
"X509_CRL_up_ref",
"PEM_read_bio_X509_CRL",
"PEM_write_bio_X509_CRL",
"d2i_X509_CRL_bio",
"i2d_X509_CRL_bio",
"X509_CRL_get_issuer",
"X509_CRL_get0_lastUpdate",
"X509_CRL_get0_nextUpdate",
"X509_CRL_verify",
] {
b = b.allowlist_item(sym);
}
b = b.allowlist_type("X509_CRL");
for sym in [
"PKCS12_new",
"PKCS12_free",
"d2i_PKCS12_bio",
"i2d_PKCS12_bio",
"PKCS12_parse",
"PKCS12_create_ex",
] {
b = b.allowlist_item(sym);
}
b = b.allowlist_type("PKCS12");
for sym in [
"OCSP_REQUEST_new",
"OCSP_REQUEST_free",
"d2i_OCSP_REQUEST",
"i2d_OCSP_REQUEST",
"OCSP_RESPONSE_new",
"OCSP_RESPONSE_free",
"d2i_OCSP_RESPONSE",
"i2d_OCSP_RESPONSE",
"OCSP_response_status",
"OCSP_response_get1_basic",
"OCSP_BASICRESP_new",
"OCSP_BASICRESP_free",
"OCSP_basic_verify",
"OCSP_CERTID_new",
"OCSP_CERTID_free",
"OCSP_CERTID_dup",
"OCSP_cert_to_id",
"OCSP_request_add0_id",
"OCSP_resp_count",
"OCSP_resp_get0",
"OCSP_resp_find",
"OCSP_resp_find_status",
"OCSP_single_get0_status",
"OCSP_check_validity",
] {
b = b.allowlist_item(sym);
}
b.allowlist_type("OCSP_REQUEST")
.allowlist_type("OCSP_RESPONSE")
.allowlist_type("OCSP_BASICRESP")
.allowlist_type("OCSP_CERTID")
.allowlist_type("OCSP_SINGLERESP")
}
struct OsslPaths {
include_paths: Vec<std::path::PathBuf>,
}
fn discover_openssl() -> OsslPaths {
println!("cargo:rerun-if-env-changed=NATIVE_OSSL_OPENSSL_SOURCES");
println!("cargo:rerun-if-env-changed=KRYOPTIC_OPENSSL_SOURCES");
if let Ok(sources) = std::env::var("NATIVE_OSSL_OPENSSL_SOURCES") {
let base = std::path::PathBuf::from(&sources)
.canonicalize()
.unwrap_or_else(|e| {
panic!("NATIVE_OSSL_OPENSSL_SOURCES={sources}: cannot canonicalize: {e}")
});
let include = base.join("include");
assert!(
include.exists(),
"NATIVE_OSSL_OPENSSL_SOURCES: no include/ directory at {}",
include.display()
);
println!("cargo:rustc-link-search=native={}", base.display());
println!("cargo:rustc-link-lib=static=crypto");
println!("cargo:rerun-if-changed={}/libcrypto.a", base.display());
OsslPaths {
include_paths: vec![include],
}
} else if let Ok(sources) = std::env::var("KRYOPTIC_OPENSSL_SOURCES") {
let base = std::path::PathBuf::from(&sources)
.canonicalize()
.unwrap_or_else(|e| {
panic!("KRYOPTIC_OPENSSL_SOURCES={sources}: cannot canonicalize: {e}")
});
let include = base.join("include");
assert!(
include.exists(),
"KRYOPTIC_OPENSSL_SOURCES: no include/ directory at {}",
include.display()
);
println!("cargo:rustc-link-search=native={}", base.display());
println!("cargo:rustc-link-lib=static=crypto");
println!("cargo:rerun-if-changed={}/libcrypto.a", base.display());
OsslPaths {
include_paths: vec![include],
}
} else {
let lib = pkg_config::Config::new()
.atleast_version("3.0.7")
.probe("openssl")
.expect("openssl >= 3.0.7 not found via pkg-config");
OsslPaths {
include_paths: lib.include_paths,
}
}
}
fn compute_keydata_offset(paths: &OsslPaths, source_include: &Path, out_dir: &Path) {
let _host = std::env::var("HOST").unwrap_or_default();
let target = std::env::var("TARGET").unwrap_or_default();
let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
let target_pointer_width = std::env::var("CARGO_CFG_TARGET_POINTER_WIDTH").unwrap_or_default();
assert!(
target_pointer_width != "32",
"fips-provider is not yet ported to 32-bit target {target}.\n\
The evp_pkey_st::keydata offset is architecture-dependent and\n\
has not been verified on 32-bit platforms."
);
let probe_src = out_dir.join("keydata_probe.c");
std::fs::write(
&probe_src,
b"#include <stdio.h>\n\
#include <stddef.h>\n\
#define OSSL_INTERNAL_REFCOUNT_H\n\
typedef struct { int val; } CRYPTO_REF_COUNT;\n\
#include <openssl/evp.h>\n\
#include <crypto/evp.h>\n\
int main(void) {\n\
printf(\"%zu\\n\", offsetof(struct evp_pkey_st, keydata));\n\
return 0;\n\
}\n",
)
.expect("failed to write keydata_probe.c");
let probe_bin = out_dir.join("keydata_probe");
let cc_var = format!("CC_{}", target.replace('-', "_"));
let cc = std::env::var(&cc_var)
.or_else(|_| std::env::var("CC"))
.unwrap_or_else(|_| "cc".to_string());
let mut cmd = std::process::Command::new(&cc);
for p in &paths.include_paths {
cmd.arg(format!("-I{}", p.display()));
}
cmd.arg(format!("-I{}", source_include.display()));
cmd.arg(&probe_src).arg("-o").arg(&probe_bin);
let status = cmd
.status()
.unwrap_or_else(|e| panic!("failed to run C compiler '{cc}': {e}"));
assert!(
status.success(),
"keydata_probe compilation failed (compiler: {cc})\n\
Ensure {cc} can access the OpenSSL headers."
);
let offset: usize = std::process::Command::new(&probe_bin)
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.and_then(|s| s.trim().parse().ok())
.unwrap_or_else(|| {
match target_arch.as_str() {
"x86_64" | "aarch64" | "riscv64" | "s390x" | "powerpc64" | "mips64" => 88,
_ => panic!(
"Could not determine evp_pkey_st::keydata offset for target \
'{target_arch}'. The fips-provider feature requires either a \
native build (probe binary can run) or a known-architecture \
target. Add the offset to the table in native-ossl-sys/build.rs \
or disable fips-provider for this target."
),
}
});
let offset_rs = out_dir.join("keydata_offset.rs");
std::fs::write(
&offset_rs,
format!(
"/// Byte offset of the `keydata` field in `evp_pkey_st`,\n\
/// computed by the C compiler for this target ABI.\n\
/// Used by [`Pkey::keydata`](crate::pkey::Pkey::keydata).\n\
pub const EVP_PKEY_KEYDATA_OFFSET: usize = {offset};\n"
),
)
.expect("failed to write keydata_offset.rs");
}
fn build_fips_bindings(paths: &OsslPaths, out_dir: &Path) {
let source_dir = std::env::var("OPENSSL_SOURCE_DIR").unwrap_or_else(|_| {
panic!(
"OPENSSL_SOURCE_DIR must be set when building with the fips-provider feature.\n\
Point it at the root of the OpenSSL source tree, e.g.:\n\
OPENSSL_SOURCE_DIR=/path/to/openssl cargo build --features fips-provider"
)
});
println!("cargo:rerun-if-env-changed=OPENSSL_SOURCE_DIR");
let source_path = std::path::PathBuf::from(&source_dir);
let source_include = source_path.join("include");
let evp_local_dir = source_path.join("crypto").join("evp");
let prov_common_include = source_path.join("providers").join("common").join("include");
let fips_wrapper = out_dir.join("fips_wrapper.h");
write_if_changed(
&fips_wrapper,
b"/* Non-public OpenSSL headers for fips-provider feature */\n\
/* Intentionally NOT defining FIPS_MODULE: native-ossl wraps the */\n\
/* system libcrypto, so EVP_PKEY structs use the full (non-FIPS) layout. */\n\
\n\
/*\n\
* Pre-empt <internal/refcount.h> to strip _Atomic from CRYPTO_REF_COUNT.\n\
*\n\
* WHY: evp_signature_st and evp_pkey_st embed CRYPTO_REF_COUNT by value.\n\
* On C11-atomics platforms, CRYPTO_REF_COUNT is `struct { _Atomic int val; }`.\n\
* The CURRENT reason these structs are still opaque in the generated bindings\n\
* is NOT _Atomic -- it is that OSSL_FUNC_*_fn typedefs in core_dispatch.h\n\
* use the K&R function-type form (`typedef T (fn_t)(args)`) rather than the\n\
* pointer-to-function form (`typedef T (*fn_t)(args)`), and bindgen 0.71.1\n\
* cannot translate K&R function-type typedefs to Rust; any struct with fields\n\
* of those types therefore becomes opaque.\n\
*\n\
* FUTURE-PROOFING: If a future bindgen release fixes the K&R typedef issue,\n\
* _Atomic in CRYPTO_REF_COUNT would become the *next* blocker. We pre-empt\n\
* refcount.h now so that fix becomes the only remaining step:\n\
* - Claim refcount.h's guard (OSSL_INTERNAL_REFCOUNT_H) to skip the header.\n\
* - Define CRYPTO_REF_COUNT with plain `int val` (no _Atomic).\n\
* - On platforms where ATOMIC_INT_LOCK_FREE > 0 (verified below with\n\
* _Static_assert), `_Atomic int` and `int` are layout-identical, so\n\
* the generated struct layouts remain byte-accurate on all such targets.\n\
*\n\
* This wrapper is ONLY consumed by bindgen, never compiled into real code,\n\
* so the stripped atomic qualifier has no runtime consequence.\n\
*/\n\
/* Assert the layout assumption before cloaking refcount.h. */\n\
#include <stdatomic.h>\n\
#if defined(ATOMIC_INT_LOCK_FREE) && ATOMIC_INT_LOCK_FREE > 0\n\
_Static_assert(sizeof(_Atomic int) == sizeof(int),\n\
\"fips_wrapper: _Atomic int and int differ in size on this target\");\n\
_Static_assert(_Alignof(_Atomic int) == _Alignof(int),\n\
\"fips_wrapper: _Atomic int and int differ in alignment on this target\");\n\
#endif\n\
\n\
#define OSSL_INTERNAL_REFCOUNT_H\n\
typedef struct { int val; } CRYPTO_REF_COUNT;\n\
\n\
#include <openssl/evp.h>\n\
#include <crypto/evp.h>\n\
#include <openssl/fips_names.h>\n\
#include <internal/provider.h>\n\
#include <evp_local.h>\n\
#include <prov/providercommon.h>\n\
#include <prov/provider_ctx.h>\n\
#include <openssl/self_test.h>\n\
\n\
int OSSL_provider_init_int(\n\
const OSSL_CORE_HANDLE *handle,\n\
const OSSL_DISPATCH *in,\n\
const OSSL_DISPATCH **out,\n\
void **provctx);\n",
);
let fips_bindings = bindgen::Builder::default()
.header(fips_wrapper.to_str().unwrap())
.layout_tests(false)
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.clang_args(
paths
.include_paths
.iter()
.map(|p| format!("-I{}", p.display())),
)
.clang_arg(format!("-I{}", source_include.display()))
.clang_arg(format!("-I{}", evp_local_dir.display()))
.clang_arg(format!("-I{}", prov_common_include.display()))
.allowlist_type("evp_signature_st")
.allowlist_type("evp_pkey_st")
.allowlist_type("prov_ctx_st")
.allowlist_function("ossl_prov_is_running")
.allowlist_function("ossl_set_error_state")
.allowlist_function("ossl_prov_ctx_new")
.allowlist_function("ossl_prov_ctx_free")
.allowlist_function("ossl_prov_ctx_set0_handle")
.allowlist_function("ossl_prov_ctx_set0_libctx")
.allowlist_function("ossl_prov_ctx_get0_libctx")
.allowlist_function("ossl_prov_ctx_get0_handle")
.allowlist_var("OSSL_SELF_TEST_TYPE_PCT")
.allowlist_function("OSSL_provider_init_int")
.generate()
.expect("bindgen failed to generate fips bindings");
fips_bindings
.write_to_file(out_dir.join("fips_bindings.rs"))
.expect("failed to write fips_bindings.rs");
compute_keydata_offset(paths, &source_include, out_dir);
}
fn write_if_changed(path: &Path, content: &[u8]) {
if std::fs::read(path).ok().as_deref() != Some(content) {
std::fs::write(path, content)
.unwrap_or_else(|e| panic!("failed to write {}: {e}", path.display()));
}
}
fn main() {
let paths = discover_openssl();
for path in &paths.include_paths {
println!("cargo:include={}", path.display());
}
println!("cargo:rerun-if-changed=build.rs");
println!("cargo::rustc-check-cfg=cfg(ossl307,ossl310,ossl320,ossl350,ossl_v400,ossl_slhdsa,ossl_mldsa,ossl_mlkem,param_clear_free)");
let out_dir = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR not set"));
if std::env::var("CARGO_FEATURE_FIPS_PROVIDER").is_ok() {
build_fips_bindings(&paths, &out_dir);
}
let wrapper_header = out_dir.join("wrapper.h");
write_if_changed(
&wrapper_header,
b"#include <openssl/evp.h>\n\
#include <openssl/bio.h>\n\
#include <openssl/x509.h>\n\
#include <openssl/x509_vfy.h>\n\
#include <openssl/ssl.h>\n\
#include <openssl/rand.h>\n\
#include <openssl/kdf.h>\n\
#include <openssl/err.h>\n\
#include <openssl/param_build.h>\n\
#include <openssl/core_names.h>\n\
#include <openssl/crypto.h>\n\
#include <openssl/provider.h>\n\
#include <openssl/pkcs12.h>\n\
#include <openssl/ocsp.h>\n\
#include <openssl/stack.h>\n",
);
let mut builder = bindgen::Builder::default()
.header(wrapper_header.to_str().unwrap())
.parse_callbacks(Box::new(OsslCallbacks))
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.layout_tests(false)
.clang_args(
paths
.include_paths
.iter()
.map(|p| format!("-I{}", p.display())),
);
builder = allow_error_param_syms(builder);
builder = allow_bio_libctx_syms(builder);
builder = allow_digest_cipher_syms(builder);
builder = allow_mac_rand_syms(builder);
builder = allow_pkey_syms(builder);
builder = allow_bignum_syms(builder);
builder = allow_signature_syms(builder);
builder = allow_kdf_syms(builder);
builder = allow_x509_syms(builder);
builder = allow_x509_advanced_syms(builder);
builder = allow_ssl_syms(builder);
let bindings = builder
.generate()
.expect("bindgen failed to generate bindings");
bindings
.write_to_file(out_dir.join("ossl_bindings.rs"))
.expect("failed to write ossl_bindings.rs");
}