From 69299b8965c7af2aca4d5fe34d945cf0c6df3fd8 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Fri, 31 Jul 2026 07:41:26 +0000 Subject: [PATCH] boringssl: restore is_safe_alt_name helper for NameBytes #36252 removed the x509::is_safe_alt_name helper (its only caller was rewritten), then #36165 landed a new call to X509::is_safe_alt_name in the NameBytes Display impl, written against the pre-#36252 tree. main stopped compiling at ff512eaa5f with E0433. Restore the helper as a local fn next to NameBytes. The sole caller passed utf8=false, so the utf8 branch is dropped; the body is otherwise a direct port of Node's IsSafeAltName (src/jsc/bindings/ncrypto.cpp). Covered by the existing Bun.connect altname-escaping tests and new unit tests in this crate. --- src/boringssl/lib.rs | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/boringssl/lib.rs b/src/boringssl/lib.rs index a84157a33aca..684fd1d8109f 100644 --- a/src/boringssl/lib.rs +++ b/src/boringssl/lib.rs @@ -575,13 +575,22 @@ pub unsafe extern "C" fn Bun__X509__checkHost( 0 } +/// Node's `IsSafeAltName` for IA5/Latin-1 entries: true when `name` contains +/// only printable ASCII with no `"`, `\\`, `,`, or `'`, so it can be rendered +/// in a subjectAltName list without JSON-style quoting. +#[inline] +fn is_safe_alt_name(name: &[u8]) -> bool { + name.iter() + .all(|&c| matches!(c, b' '..=b'~') && !matches!(c, b'"' | b'\\' | b',' | b'\'')) +} + /// Certificate name bytes (IA5 / Latin-1) for error messages. struct NameBytes<'a>(&'a [u8]); impl core::fmt::Display for NameBytes<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { use core::fmt::Write; - if X509::is_safe_alt_name(self.0, false) { + if is_safe_alt_name(self.0) { for &byte in self.0 { f.write_char(char::from(byte))?; } @@ -730,3 +739,34 @@ pub fn check_server_identity(ssl_ptr: &mut boring::SSL, hostname: &[u8]) -> bool .peer_leaf_certificate() .is_some_and(|x509| check_x509_server_identity(x509, hostname)) } + +#[cfg(test)] +mod tests { + use super::{NameBytes, is_safe_alt_name}; + + #[test] + fn safe_alt_name_matches_node() { + assert!(is_safe_alt_name(b"good.example.com")); + assert!(is_safe_alt_name(b"http://example.com/a%2Cb")); + assert!(is_safe_alt_name(b" ~")); + for &c in b"\"\\,'" { + assert!(!is_safe_alt_name(&[c])); + } + assert!(!is_safe_alt_name(b"ab\x1f")); + assert!(!is_safe_alt_name(b"ab\x7f")); + assert!(!is_safe_alt_name(b"ex\xe4mple.com")); + } + + #[test] + fn name_bytes_quoting() { + let fmt = |s: &[u8]| NameBytes(s).to_string(); + assert_eq!(fmt(b"good.example.com"), "good.example.com"); + assert_eq!( + fmt(b"good.example.com, DNS:evil.example.com"), + r#""good.example.com\u002c DNS:evil.example.com""# + ); + assert_eq!(fmt(b"\"quoted\""), r#""\"quoted\"""#); + assert_eq!(fmt(b"ex\xe4mple.com"), r#""ex\u00e4mple.com""#); + assert_eq!(fmt(b"a\\b"), r#""a\\b""#); + } +}