From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 469A71FF14F for ; Wed, 17 Jun 2026 11:00:11 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 05F271A6F6; Wed, 17 Jun 2026 10:59:57 +0200 (CEST) From: Dominik Csapak To: pve-devel@lists.proxmox.com, pbs-devel@lists.proxmox.com Subject: [PATCH proxmox v3 2/6] http: tls: use legacy behavior when PROXMOX_NEW_TLS_CHECK is not set Date: Wed, 17 Jun 2026 10:59:14 +0200 Message-ID: <20260617085949.1528300-3-d.csapak@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260617085949.1528300-1-d.csapak@proxmox.com> References: <20260617085949.1528300-1-d.csapak@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.049 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [tls.rs] Message-ID-Hash: CW4KHXTJFPPDTPESJKKFM4ZADPJE6TRS X-Message-ID-Hash: CW4KHXTJFPPDTPESJKKFM4ZADPJE6TRS X-MailFrom: d.csapak@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: if that environment variable is not set to "1", give the openssl result priority, and potentially ignore a given fingerprint that is not matching. If that's the case, print a warning. Signed-off-by: Dominik Csapak --- proxmox-http/Cargo.toml | 2 ++ proxmox-http/src/tls.rs | 31 +++++++++++++++++++++++++------ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/proxmox-http/Cargo.toml b/proxmox-http/Cargo.toml index e4bff930..aadb6a42 100644 --- a/proxmox-http/Cargo.toml +++ b/proxmox-http/Cargo.toml @@ -20,6 +20,7 @@ http-body = { workspace = true, optional = true } http-body-util = { workspace = true, optional = true } hyper = { workspace = true, optional = true } hyper-util = { workspace = true, optional = true, features = ["http2"] } +log = { workspace = true, optional = true } native-tls = { workspace = true, optional = true } openssl = { version = "0.10", optional = true } serde_json = { workspace = true, optional = true } @@ -107,6 +108,7 @@ websocket = [ "body", ] tls = [ + "dep:log", "dep:openssl", "dep:thiserror", ] diff --git a/proxmox-http/src/tls.rs b/proxmox-http/src/tls.rs index 7365230e..635b0e7f 100644 --- a/proxmox-http/src/tls.rs +++ b/proxmox-http/src/tls.rs @@ -27,19 +27,31 @@ pub enum SslVerifyError { /// Intended as an openssl verification callback. /// -/// The following things are checked: +/// If the 'PROXMOX_NEW_TLS_CHECK' environment variable is set to "1", +/// the following things are checked: /// /// * If no fingerprint is given, return the openssl verification result -/// * If a fingerprint is given, do: -/// * Ignore all non-leaf certificates/ +/// * If a fingerprint is given, ignore all non-leaf certificates +/// +/// Otherwise, we trust the openssl result if the whole chain was trusted pub fn openssl_verify_callback( openssl_valid: bool, ctx: &mut X509StoreContextRef, expected_fp: Option<&str>, ) -> Result<(), SslVerifyError> { let trust_openssl = ctx.error() != X509VerifyResult::APPLICATION_VERIFICATION; - if expected_fp.is_none() && openssl_valid && trust_openssl { - return Ok(()); + + let new_check = matches!(std::env::var("PROXMOX_NEW_TLS_CHECK").as_deref(), Ok("1")); + + if openssl_valid && trust_openssl { + if new_check && expected_fp.is_none() { + return Ok(()); + } + + // legacy mode: skip all valid certs except the leaf, so we can warn if fingerprint does not match + if !new_check && ctx.error_depth() > 0 { + return Ok(()); + } } let cert = match ctx.current_cert() { @@ -50,7 +62,7 @@ pub fn openssl_verify_callback( }; if ctx.error_depth() > 0 { - // openssl was not valid, but we want to continue, so save that we don't trust openssl + // if openssl is not valid, and we want to continue, save that we don't trust openssl ctx.set_error(X509VerifyResult::APPLICATION_VERIFICATION); return Ok(()); } @@ -65,6 +77,13 @@ pub fn openssl_verify_callback( ctx.set_error(X509VerifyResult::OK); Ok(()) } else { + if !new_check && openssl_valid && trust_openssl { + log::warn!( + "Certificate chain valid, but fingerprint does not match, ignoring fingerprint! To prioritize the fingerprint, set `PROXMOX_NEW_TLS_CHECK=1` in your environment." + ); + return Ok(()); + } + Err(SslVerifyError::FingerprintMismatch { fingerprint, expected: expected_fp.to_string(), -- 2.47.3