Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/scripts/test_hsm_utimaco.sh
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ UTIMACO_LIB_DIR="$(dirname "$UTIMACO_PKCS11_LIB")"

# Utimaco integration test (KMS)

env -u LD_PRELOAD -u OPENSSL_CONF -u OPENSSL_MODULES \
env -u LD_PRELOAD \
PATH="$PATH" \
LD_LIBRARY_PATH="${UTIMACO_LIB_DIR}:${NIX_OPENSSL_OUT:+$NIX_OPENSSL_OUT/lib:}${LD_LIBRARY_PATH:-}" \
HSM_MODEL="utimaco" \
Expand All @@ -80,7 +80,7 @@ env -u LD_PRELOAD -u OPENSSL_CONF -u OPENSSL_MODULES \

# Utimaco loader test (pure Nix, scoped runtime)

env -u LD_PRELOAD -u OPENSSL_CONF -u OPENSSL_MODULES \
env -u LD_PRELOAD \
PATH="$PATH" \
LD_LIBRARY_PATH="${UTIMACO_LIB_DIR}:${NIX_OPENSSL_OUT:+$NIX_OPENSSL_OUT/lib:}${LD_LIBRARY_PATH:-}" \
HSM_MODEL="utimaco" \
Expand All @@ -98,7 +98,7 @@ env -u LD_PRELOAD -u OPENSSL_CONF -u OPENSSL_MODULES \
# Optionally run Google CSE CLI tests if environment is provided
if [ -n "${TEST_GOOGLE_OAUTH_CLIENT_ID:-}" ] && [ -n "${TEST_GOOGLE_OAUTH_CLIENT_SECRET:-}" ] && [ -n "${TEST_GOOGLE_OAUTH_REFRESH_TOKEN:-}" ]; then
# shellcheck disable=SC2086
env -u LD_PRELOAD -u OPENSSL_CONF -u OPENSSL_MODULES "PATH=$PATH" \
env -u LD_PRELOAD "PATH=$PATH" \
LD_LIBRARY_PATH="${UTIMACO_LIB_DIR}:${NIX_OPENSSL_OUT:+$NIX_OPENSSL_OUT/lib:}${LD_LIBRARY_PATH:-}" \
HSM_MODEL="utimaco" \
HSM_USER_PASSWORD="$HSM_USER_PASSWORD" \
Expand All @@ -114,7 +114,7 @@ if [ -n "${TEST_GOOGLE_OAUTH_CLIENT_ID:-}" ] && [ -n "${TEST_GOOGLE_OAUTH_CLIENT
-- --nocapture kmip_2_1_xml_pkcs11_m_1_21 --ignored

# shellcheck disable=SC2086
env -u LD_PRELOAD -u OPENSSL_CONF -u OPENSSL_MODULES "PATH=$PATH" \
env -u LD_PRELOAD "PATH=$PATH" \
LD_LIBRARY_PATH="${UTIMACO_LIB_DIR}:${NIX_OPENSSL_OUT:+$NIX_OPENSSL_OUT/lib:}${LD_LIBRARY_PATH:-}" \
HSM_MODEL="utimaco" \
HSM_USER_PASSWORD="$HSM_USER_PASSWORD" \
Expand Down
9 changes: 6 additions & 3 deletions .github/scripts/test_pykmip.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ FEATURES_FLAG=(--features non-fips)
: "${COSMIAN_KMS_CONF:=$REPO_ROOT/scripts/kms.toml}"
export COSMIAN_KMS_CONF

# Ensure Python's ssl module can initialize: avoid custom OpenSSL config used by Rust OpenSSL.
# Do NOT clear LD_LIBRARY_PATH; keep Nix-provided runtime consistent to avoid GLIBC mismatches.
unset OPENSSL_CONF OPENSSL_MODULES || true
# Note: OPENSSL_CONF and OPENSSL_MODULES are intentionally kept set here so the KMS
# server process can find the OpenSSL providers (e.g. legacy.dylib) in the Nix store.
# The compiled-in MODULESDIR is /usr/local/cosmian/lib/ossl-modules (production path),
# which does not exist in the nix-shell dev environment.
# All Python invocations below already use `env -u OPENSSL_CONF -u OPENSSL_MODULES`
# to isolate Python's ssl module from the Rust/KMS OpenSSL configuration.

# Ensure Python is available (nix.sh sets WITH_PYTHON=1 which adds python311 + virtualenv)
require_cmd python3 "Python 3 is required. Re-run via 'bash .github/scripts/nix.sh test pykmip' so nix-shell can provide it."
Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,26 @@ All notable changes to this project will be documented in this file.
### 🐛 Bug Fixes

- Add MLKEM algorithms to the predefined DEFAULT KMIP policy
- Fix non-FIPS `openssl.cnf` provider configuration: the FIPS provider was incorrectly
activated in non-FIPS builds via `nix/openssl.nix`, blocking default-provider algorithms
(ChaCha20, secp256k1) and causing 6 crypto test failures. `nix/openssl.nix` now generates
distinct provider configurations per build variant: FIPS builds use `fips+base`, non-FIPS
builds use `default+legacy+base`.
- Fix `KResultHelper` import in `main.rs` being feature-gated to `non-fips` only, causing a
missing `.context()` method on `init_openssl_providers()` result in FIPS builds.

### ⚙️ Build

- Refactor OpenSSL provider management into a dedicated `openssl_providers` module in
`crate/server/src/`, consolidating `safe_openssl_version_info()`, `init_openssl_providers()`
(production), and `init_openssl_providers_for_tests()` (test environments) into a single place.
- Improve determinism of `nix/openssl.nix` OpenSSL builds:
- Patch `ENGINESDIR`/`MODULESDIR` in the generated Makefile to fixed
`/usr/local/cosmian/lib/...` paths, preventing Nix store path embedding in compiled
`libcrypto` strings.
- Scrub Nix store paths from `crypto/buildinf.h` after `make depend`.
- Set `SOURCE_DATE_EPOCH=1` and `ZERO_AR_DATE=1` in build and install phases.
- Normalize all output file timestamps with `find $out -exec touch --date=@1 {} +`.

### ⚙️ Build

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion crate/cli/src/tests/kms/certificates/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use openssl::{
x509::{X509, store::X509StoreBuilder},
};
use tempfile::TempDir;
use test_kms_server::start_default_test_kms_server;
use test_kms_server::{init_openssl_providers_for_tests, start_default_test_kms_server};
use uuid::Uuid;

use crate::{
Expand All @@ -39,6 +39,8 @@ use crate::{
#[tokio::test]
async fn test_import_export_p12_25519() -> KmsCliResult<()> {
log_init(option_env!("RUST_LOG"));
init_openssl_providers_for_tests();

// load the PKCS#12 file
let p12_bytes =
include_bytes!("../../../../../../test_data/certificates/another_p12/ed25519.p12");
Expand Down Expand Up @@ -221,6 +223,8 @@ async fn test_import_export_p12_25519() -> KmsCliResult<()> {

#[tokio::test]
async fn test_import_p12_rsa() {
init_openssl_providers_for_tests();

let tmp_dir = TempDir::new().unwrap();
let tmp_path = tmp_dir.path();
// load the PKCS#12 file
Expand Down Expand Up @@ -415,6 +419,8 @@ async fn test_self_signed_export_loop() -> KmsCliResult<()> {

#[tokio::test]
async fn test_export_root_and_intermediate_pkcs12() -> KmsCliResult<()> {
init_openssl_providers_for_tests();

// Create a test server
let ctx = start_default_test_kms_server().await;

Expand Down Expand Up @@ -475,6 +481,8 @@ async fn test_export_root_and_intermediate_pkcs12() -> KmsCliResult<()> {

#[tokio::test]
async fn test_export_import_legacy_p12() -> KmsCliResult<()> {
init_openssl_providers_for_tests();

// Create a test server
let ctx = start_default_test_kms_server().await;

Expand Down
1 change: 1 addition & 0 deletions crate/server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ num-bigint-dig = { workspace = true, features = [
"zeroize",
] }
openssl = { workspace = true }
openssl-sys = "0.9"
opentelemetry = { workspace = true }
opentelemetry-otlp = { workspace = true }
opentelemetry_sdk = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion crate/server/src/config/command_line/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::path::PathBuf;
use clap::Args;
use serde::{Deserialize, Serialize};

#[expect(clippy::struct_excessive_bools)]
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Default, Args, Deserialize, Serialize, Clone)]
#[serde(default)]
pub struct LoggingConfig {
Expand Down
6 changes: 4 additions & 2 deletions crate/server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ pub mod core;
pub mod cron;
pub mod error;
pub mod middlewares;
pub mod openssl_providers;
pub mod result;
pub mod routes;
pub mod socket_server;
pub mod start_kms_server;
pub mod tls_config;

#[expect(
clippy::panic,
clippy::unwrap_used,
Expand All @@ -22,5 +25,4 @@ pub mod start_kms_server;
dead_code
)]
#[cfg(test)]
mod tests;
pub mod tls_config;
pub mod tests;
46 changes: 16 additions & 30 deletions crate/server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ use std::sync::Arc;

use cosmian_kms_server::{
config::{ClapConfig, ServerParams},
result::KResult,
openssl_providers::safe_openssl_version_info,
result::{KResult, KResultHelper},
};
#[cfg(feature = "non-fips")]
use cosmian_kms_server_database::reexport::cosmian_kmip::KmipResultHelper;
use cosmian_logger::{TelemetryConfig, TracingConfig, info, tracing_init};
use dotenvy::dotenv;
use openssl::provider::Provider;
use tracing::span;

/// Get the default `RUST_LOG` configuration if not set
Expand Down Expand Up @@ -85,36 +83,24 @@ async fn run() -> KResult<()> {
let span = span!(tracing::Level::TRACE, "kms");
let _guard = span.enter();

info!(
"OpenSSL version: {}, in {}, number: {:x}",
openssl::version::version(),
openssl::version::dir(),
openssl::version::number()
);
let (ossl_version, ossl_dir, ossl_number) = safe_openssl_version_info();
if ossl_number == 0 {
tracing::error!(
"OpenSSL does not appear to be available (version number is 0). \
Please verify that OpenSSL is correctly installed and accessible."
);
return Err(cosmian_kms_server::error::KmsError::ServerError(
"OpenSSL is not available – cannot start the KMS server".to_owned(),
));
}
info!("OpenSSL version: {ossl_version}, in {ossl_dir}, number: {ossl_number:x}");

// For an explanation of OpenSSL providers,
// https://docs.openssl.org/3.1/man7/crypto/#openssl-providers

// In FIPS mode, we only load the FIPS provider
#[cfg(not(feature = "non-fips"))]
{
info!("Load FIPS provider");
Provider::load(None, "fips")?;
}

// Not in FIPS mode and version > 3.0: load the default provider and the legacy provider
// so that we can use the legacy algorithms.
// particularly those used for old PKCS#12 formats
#[cfg(feature = "non-fips")]
if openssl::version::number() >= 0x3000_0000 {
info!("Load legacy provider");
Provider::try_load(None, "legacy", true)
.context("unable to load the openssl legacy provider")?;
} else {
// In version < 3.0, we only load the default provider
info!("Load default provider");
Provider::load(None, "default")?;
}
// Load the appropriate OpenSSL provider based on FIPS mode and OpenSSL version
cosmian_kms_server::openssl_providers::init_openssl_providers()
.context("unable to load the required OpenSSL provider")?;

// Instantiate a config object using the env variables and the args of the binary
info!("Command line / file config: {clap_config:#?}");
Expand Down
157 changes: 157 additions & 0 deletions crate/server/src/openssl_providers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
use std::ffi::CStr;

#[cfg(feature = "non-fips")]
use cosmian_logger::info;

/// Safely retrieve OpenSSL version information without risking a segmentation
/// fault. In some edge-case environments (missing OpenSSL shared library,
/// incomplete installation, restricted container, …) the underlying
/// `OpenSSL_version` C function may return a NULL pointer or call through an
/// uninitialised function-pointer table, both of which cause a SIGSEGV.
///
/// This helper calls the raw `openssl-sys` FFI directly so it can check for a
/// NULL return *before* converting to a Rust `&str`.
///
/// # Returns
///
/// Returns a tuple of `(version_string, dir_string, version_number)` where:
/// - `version_string` is the OpenSSL version text (e.g., "OpenSSL 3.6.0")
/// - `dir_string` is the OpenSSL installation directory
/// - `version_number` is the numeric version (e.g., 0x30600000 for version 3.6.0)
///
/// If OpenSSL is not available, returns `("<unavailable>", "<unavailable>", 0)`.
#[allow(unsafe_code)]
#[must_use]
pub fn safe_openssl_version_info() -> (String, String, u64) {
// SAFETY: `OpenSSL_version_num` returns a plain integer and never
// dereferences any pointer, so it is safe to call even when OpenSSL is not
// fully initialised. We call it first as a cheap liveness check.
// Note: Returns `c_ulong` which is u32 on Windows and u64 on Unix.
// We need `as u64` for cross-platform compatibility (widening is safe).
#[allow(clippy::as_conversions)]
let num = unsafe { openssl_sys::OpenSSL_version_num() } as u64;
if num == 0 {
return ("<unavailable>".to_owned(), "<unavailable>".to_owned(), 0);
}

// SAFETY: `OpenSSL_version` returns a `*const c_char` pointing to a static
// string. We check for NULL before creating a `CStr`.
let version = unsafe {
let ptr = openssl_sys::OpenSSL_version(openssl_sys::OPENSSL_VERSION);
if ptr.is_null() {
"<unavailable>".to_owned()
} else {
CStr::from_ptr(ptr)
.to_str()
.unwrap_or("<invalid utf-8>")
.to_owned()
}
};

let dir = unsafe {
let ptr = openssl_sys::OpenSSL_version(openssl_sys::OPENSSL_DIR);
if ptr.is_null() {
"<unavailable>".to_owned()
} else {
CStr::from_ptr(ptr)
.to_str()
.unwrap_or("<invalid utf-8>")
.to_owned()
}
};

(version, dir, num)
}

/// Initialize OpenSSL providers for test environments.
///
/// In non-FIPS mode with OpenSSL >= 3.0: loads the legacy provider for old PKCS#12 formats.
/// In non-FIPS mode with OpenSSL < 3.0: loads the default provider.
/// In FIPS mode: no-op (FIPS provider is loaded via openssl.cnf).
///
/// Note: The default provider is already active via openssl.cnf configuration.
/// This function only adds the legacy provider on top of it.
#[cfg(feature = "non-fips")]
#[allow(
unsafe_code,
clippy::as_conversions,
clippy::missing_panics_doc,
clippy::expect_used
)]
pub fn init_openssl_providers_for_tests() {
use std::sync::OnceLock;

use openssl::provider::Provider;

// Keep provider alive for program lifetime — it must not be dropped
static PROVIDER: OnceLock<Provider> = OnceLock::new();

PROVIDER.get_or_init(|| {
let ossl_number = unsafe { openssl_sys::OpenSSL_version_num() as u64 };
if ossl_number >= 0x3000_0000 {
// OpenSSL 3.x: load the legacy provider for old PKCS#12 formats
Provider::try_load(None, "legacy", true).expect("Failed to load legacy provider")
} else {
// OpenSSL < 3.0: load the default provider
Provider::load(None, "default").expect("Failed to load default provider")
}
});
}

/// No-op for FIPS builds
#[cfg(not(feature = "non-fips"))]
pub const fn init_openssl_providers_for_tests() {
// No-op in FIPS mode
}

/// Initialize OpenSSL providers for production KMS server.
///
/// For FIPS mode: loads the FIPS provider.
/// For non-FIPS mode with OpenSSL >= 3.0: loads the legacy provider for old PKCS#12 formats.
/// For non-FIPS mode with OpenSSL < 3.0: loads the default provider.
///
/// Note: In non-FIPS mode, the default provider is already active via openssl.cnf.
/// This function only adds the legacy provider on top of it.
///
/// This function uses `OnceLock` to ensure providers are loaded only once and kept alive
/// for the lifetime of the program.
///
/// # Errors
///
/// Returns an error if the provider fails to load.
#[allow(unsafe_code, clippy::as_conversions)]
pub fn init_openssl_providers() -> Result<(), openssl::error::ErrorStack> {
use std::sync::OnceLock;

use openssl::provider::Provider;

#[cfg(not(feature = "non-fips"))]
{
static PROVIDER: OnceLock<Provider> = OnceLock::new();
if PROVIDER.get().is_none() {
let provider = Provider::load(None, "fips")?;
drop(PROVIDER.set(provider));
}
Ok(())
}

#[cfg(feature = "non-fips")]
{
static PROVIDER: OnceLock<Provider> = OnceLock::new();

if PROVIDER.get().is_none() {
let ossl_number = unsafe { openssl_sys::OpenSSL_version_num() as u64 };
let provider = if ossl_number >= 0x3000_0000 {
// OpenSSL 3.x: load the legacy provider for old PKCS#12 formats
info!("Load legacy provider");
Provider::try_load(None, "legacy", true)?
} else {
// OpenSSL < 3.0: load the default provider
info!("Load default provider");
Provider::load(None, "default")?
};
drop(PROVIDER.set(provider));
}
Ok(())
}
}
Loading
Loading