diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 0369dc8d94b6..f6342e002186 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -5883,6 +5883,7 @@ dependencies = [ "itertools 0.14.0", "libc", "llm-multimodal", + "openssl", "prost", "prost-types", "rmp-serde", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 455e660bcfee..3f6fac02562b 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -55,6 +55,8 @@ native-tls-vendored = { package = "native-tls", version = "0.2.18", features = [ ndarray = { version = "0.16.1", features = ["serde"] } openai-harmony = "0.0.8" openai-protocol = "1.6.0" +# For linking the system OpenSSL (ie. on systems with FIPS enabled) +openssl = "0.10" parking_lot = "0.12.5" paste = "1.0.15" prometheus-client = "0.24.0" @@ -65,7 +67,8 @@ pyo3 = "0.28.3" pythonize = "0.28.0" rand = "0.9.2" reasoning-parser = "1.2.2" -reqwest = { version = "0.12.8", default-features = false, features = ["rustls-tls"] } +# NOTE: TLS backend selected per-build by vllm-chat features (rustls / openssl) +reqwest = { version = "0.12.8", default-features = false } riptoken = { version = "0.3.0", default-features = false } rmp-serde = "1.3.1" rmpv = { version = "1.3.1", features = ["with-serde"] } @@ -112,13 +115,14 @@ tracing-subscriber = { version = "0.3.20", features = ["env-filter", "fmt"] } trait-set = "0.3.0" uuid = { version = "1.22.0", features = ["v4"] } validator = { version = "0.20.0", features = ["derive"] } -vllm-chat = { path = "src/chat" } +# NOTE: default-features = false so the backend is chosen explicitly +vllm-chat = { path = "src/chat", default-features = false } vllm-engine-core-client = { path = "src/engine-core-client" } vllm-llm = { path = "src/llm" } vllm-managed-engine = { path = "src/managed-engine" } vllm-metrics = { path = "src/metrics" } vllm-reasoning-parser = { path = "src/reasoning-parser" } -vllm-server = { path = "src/server" } +vllm-server = { path = "src/server", default-features = false } vllm-text = { path = "src/text" } vllm-tokenizer = { path = "src/tokenizer" } vllm-tool-parser = { path = "src/tool-parser" } diff --git a/rust/src/chat/Cargo.toml b/rust/src/chat/Cargo.toml index 0523b9defe93..f5a133ae8df0 100644 --- a/rust/src/chat/Cargo.toml +++ b/rust/src/chat/Cargo.toml @@ -4,6 +4,11 @@ version.workspace = true edition.workspace = true license.workspace = true +[features] +default = ["rustls"] +rustls = ["reqwest/rustls-tls"] +openssl = ["reqwest/native-tls"] + [dependencies] anyhow.workspace = true asynk-strim-attr.workspace = true diff --git a/rust/src/cmd/Cargo.toml b/rust/src/cmd/Cargo.toml index b0caa65b4e8c..17d5a8b8f275 100644 --- a/rust/src/cmd/Cargo.toml +++ b/rust/src/cmd/Cargo.toml @@ -9,7 +9,9 @@ name = "vllm-rs" path = "src/main.rs" [features] -default = [] +default = ["rustls"] +rustls = ["vllm-server/rustls"] +openssl = ["vllm-server/openssl"] native-tls-vendored = ["dep:native-tls-vendored"] [dependencies] diff --git a/rust/src/server/Cargo.toml b/rust/src/server/Cargo.toml index cb62f3376bc6..d5a2b775d4dc 100644 --- a/rust/src/server/Cargo.toml +++ b/rust/src/server/Cargo.toml @@ -4,6 +4,13 @@ version.workspace = true edition.workspace = true license.workspace = true +[features] +default = ["rustls"] +# Features are additive so Cargo's `--all-features` mode remains valid; use +# `--no-default-features --features openssl` for OpenSSL-only builds. +rustls = ["dep:sha2", "dep:subtle", "vllm-chat/rustls"] +openssl = ["dep:openssl", "vllm-chat/openssl"] + [dependencies] anyhow.workspace = true asynk-strim-attr.workspace = true @@ -14,15 +21,16 @@ http-body.workspace = true itertools.workspace = true libc.workspace = true llm-multimodal.workspace = true +openssl = { workspace = true, optional = true } prost.workspace = true prost-types.workspace = true rmpv.workspace = true serde.workspace = true serde_json.workspace = true serde_with.workspace = true -sha2.workspace = true +sha2 = { workspace = true, optional = true } socket2.workspace = true -subtle.workspace = true +subtle = { workspace = true, optional = true } thiserror-ext.workspace = true tokio.workspace = true tokio-stream.workspace = true diff --git a/rust/src/server/src/middleware/auth.rs b/rust/src/server/src/middleware/auth.rs index 696e30615a87..afacd973b695 100644 --- a/rust/src/server/src/middleware/auth.rs +++ b/rust/src/server/src/middleware/auth.rs @@ -62,17 +62,34 @@ fn verify_token(authorization: Option<&HeaderValue>, api_key_hashes: &[ApiKeyHas token_match } +#[cfg(not(feature = "openssl"))] fn constant_time_eq(left: &ApiKeyHash, right: &ApiKeyHash) -> bool { use subtle::ConstantTimeEq; bool::from(left.ct_eq(right)) } +#[cfg(feature = "openssl")] +fn constant_time_eq(left: &ApiKeyHash, right: &ApiKeyHash) -> bool { + openssl::memcmp::eq(left, right) +} + #[cfg(test)] mod tests { use super::constant_time_eq; use crate::state::hash_api_key; + // SHA-256("abc"); pins both backends to the same NIST vector. + #[test] + fn hash_api_key_matches_nist_sha256_vector() { + const SHA256_ABC: [u8; 32] = [ + 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, + 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, + 0xf2, 0x00, 0x15, 0xad, + ]; + assert_eq!(hash_api_key("abc"), SHA256_ABC); + } + #[test] fn constant_time_eq_checks_sha256_digests() { assert!(constant_time_eq( diff --git a/rust/src/server/src/state.rs b/rust/src/server/src/state.rs index 0be074dff88c..cdf7566586fc 100644 --- a/rust/src/server/src/state.rs +++ b/rust/src/server/src/state.rs @@ -2,7 +2,6 @@ use std::sync::Arc; use std::sync::atomic::{AtomicU64, Ordering}; use serde_json::Value; -use sha2::{Digest, Sha256}; use tokio::time::{Duration, Instant, sleep_until}; use tracing::warn; use vllm_chat::ChatLlm; @@ -17,10 +16,18 @@ const SHUTDOWN_REFCOUNT_POLL_INTERVAL: Duration = Duration::from_millis(100); pub(crate) type ApiKeyHash = [u8; 32]; +#[cfg(not(feature = "openssl"))] pub(crate) fn hash_api_key(api_key: &str) -> ApiKeyHash { + use sha2::{Digest, Sha256}; + Sha256::digest(api_key.as_bytes()).into() } +#[cfg(feature = "openssl")] +pub(crate) fn hash_api_key(api_key: &str) -> ApiKeyHash { + openssl::sha::sha256(api_key.as_bytes()) +} + /// Shared router state for the minimal single-model OpenAI server. pub struct AppState { /// All public model IDs served by this frontend. The first entry is the