#![warn(unused_must_use)]
use core::ffi::{c_int, c_void};
use core::ptr;
use std::cell::Cell;
pub use bun_boringssl_sys as boring;
use bun_cares_sys as c_ares;
use bun_core::strings;
pub mod x509 {
#[inline]
pub fn is_safe_alt_name(name: &[u8], utf8: bool) -> bool {
for &c in name {
match c {
b'"' | b'\\' | b',' | b'\'' => return false,
_ => {
if utf8 {
if c < b' ' || c == 0x7f {
return false;
}
} else {
if c < b' ' || c > b'~' {
return false;
}
}
}
}
}
true
}
}
use x509 as X509;
pub use boring as c;
pub fn load() {
bun_core::run_once! {{
boring::CRYPTO_library_init();
let rc = boring::SSL_library_init();
debug_assert!(rc > 0);
boring::SSL_load_error_strings();
boring::ERR_load_BIO_strings();
boring::OpenSSL_add_all_algorithms();
if !cfg!(test) {
core::hint::black_box(OPENSSL_memory_alloc as *const ());
core::hint::black_box(OPENSSL_memory_get_size as *const ());
core::hint::black_box(OPENSSL_memory_free as *const ());
}
}}
}
#[allow(non_camel_case_types)]
type ssl_verify_result_t = c_int;
#[allow(non_upper_case_globals)]
const ssl_verify_ok: ssl_verify_result_t = 0;
pub const SSL_DEFAULT_CIPHER_LIST: &core::ffi::CStr = c"ALL";
use boring::{
CRYPTO_BUFFER_POOL, CRYPTO_BUFFER_POOL_new, SSL_CTX_set_cipher_list, SSL_CTX_set0_buffer_pool,
};
type SslCustomVerifyCb =
Option<unsafe extern "C" fn(ssl: *mut boring::SSL, out_alert: *mut u8) -> ssl_verify_result_t>;
unsafe extern "C" {
fn SSL_CTX_set_custom_verify(
ctx: *mut boring::SSL_CTX,
mode: c_int,
callback: SslCustomVerifyCb,
);
}
unsafe extern "C" fn noop_custom_verify(
_ssl: *mut boring::SSL,
_out_alert: *mut u8,
) -> ssl_verify_result_t {
ssl_verify_ok
}
struct CtxStore(ptr::NonNull<boring::SSL_CTX>);
unsafe impl Send for CtxStore {}
unsafe impl Sync for CtxStore {}
static CTX_STORE: std::sync::OnceLock<CtxStore> = std::sync::OnceLock::new();
std::thread_local! {
static AUTO_CRYPTO_BUFFER_POOL: Cell<*mut CRYPTO_BUFFER_POOL> =
const { Cell::new(ptr::null_mut()) };
}
pub unsafe fn ssl_ctx_setup(ctx: *mut boring::SSL_CTX) {
AUTO_CRYPTO_BUFFER_POOL.with(|pool| {
unsafe {
if pool.get().is_null() {
pool.set(CRYPTO_BUFFER_POOL_new());
}
SSL_CTX_set0_buffer_pool(ctx, pool.get());
let _ = SSL_CTX_set_cipher_list(ctx, SSL_DEFAULT_CIPHER_LIST.as_ptr());
}
});
}
pub fn init_client() -> *mut boring::SSL {
unsafe {
if let Some(stored) = CTX_STORE.get() {
let _ = boring::SSL_CTX_up_ref(stored.0.as_ptr());
}
let ctx = CTX_STORE
.get_or_init(|| {
let ctx = boring::SSL_CTX_new(boring::TLS_with_buffers_method());
SSL_CTX_set_custom_verify(ctx, 0, Some(noop_custom_verify));
ssl_ctx_setup(ctx);
CtxStore(ptr::NonNull::new(ctx).expect("SSL_CTX_new"))
})
.0
.as_ptr();
let ssl = boring::SSL_new(ctx);
boring::SSL_set_connect_state(ssl);
ssl
}
}
#[unsafe(no_mangle)]
pub extern "C" fn OPENSSL_memory_alloc(size: usize) -> *mut c_void {
bun_alloc::default_alloc::malloc(size)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn OPENSSL_memory_free(ptr: *mut c_void) {
unsafe {
let len = bun_alloc::default_alloc::usable_size(ptr);
ptr::write_bytes(ptr.cast::<u8>(), 0, len);
bun_alloc::default_alloc::free(ptr);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn OPENSSL_memory_get_size(ptr: *const c_void) -> usize {
unsafe { bun_alloc::default_alloc::usable_size(ptr) }
}
pub use bun_sys::posix::INET6_ADDRSTRLEN;
use bun_sys::posix::AF::{INET as AF_INET, INET6 as AF_INET6};
pub fn canonicalize_ip<'a>(
addr_str: &[u8],
out_ip: &'a mut [u8; INET6_ADDRSTRLEN + 1],
) -> Option<&'a [u8]> {
if addr_str.len() >= INET6_ADDRSTRLEN {
return None;
}
let mut ip_std_text = [0u8; INET6_ADDRSTRLEN + 1];
out_ip[..addr_str.len()].copy_from_slice(addr_str);
out_ip[addr_str.len()] = 0;
let mut af: c_int = AF_INET;
unsafe {
if c_ares::ares_inet_pton(af, out_ip.as_ptr().cast(), ip_std_text.as_mut_ptr().cast()) <= 0
{
af = AF_INET6;
if c_ares::ares_inet_pton(af, out_ip.as_ptr().cast(), ip_std_text.as_mut_ptr().cast())
<= 0
{
return None;
}
}
}
unsafe { c_ares::ntop(af, ip_std_text.as_ptr().cast(), &mut out_ip[..]) }
}
pub fn ip2_string<'a>(
ip: &boring::ASN1_OCTET_STRING,
out_ip: &'a mut [u8; INET6_ADDRSTRLEN + 1],
) -> Option<&'a [u8]> {
let af: c_int = match ip.length {
4 => AF_INET,
16 => AF_INET6,
_ => return None,
};
unsafe { c_ares::ntop(af, ip.data.cast(), &mut out_ip[..]) }
}
fn match_dns_name(pattern: &[u8], hostname: &[u8]) -> bool {
if pattern.is_empty() {
return false;
}
if !X509::is_safe_alt_name(pattern, false) {
return false;
}
if pattern[0] == b'*' {
if pattern.len() >= 2 && pattern[1] == b'.' {
let suffix = &pattern[2..];
if strings::index_of_char(suffix, b'.').is_some() {
if hostname.len() > suffix.len() + 1 {
let dot_index = hostname.len() - suffix.len() - 1;
if hostname[dot_index] == b'.'
&& strings::index_of_char(&hostname[..dot_index], b'.').is_none()
{
let host_suffix = &hostname[dot_index + 1..];
if strings::eql_case_insensitive_ascii(suffix, host_suffix, true) {
return true;
}
}
}
}
}
}
strings::eql_case_insensitive_ascii(pattern, hostname, true)
}
fn domain_to_ascii_host(host: &[u8]) -> Option<Vec<u8>> {
let domain = core::str::from_utf8(host).ok()?;
idna::domain_to_ascii(domain).ok().map(String::into_bytes)
}
fn unfqdn(name: &[u8]) -> &[u8] {
name.strip_suffix(b".").unwrap_or(name)
}
pub fn check_x509_server_identity(x509: &mut boring::X509, hostname: &[u8]) -> bool {
let host_is_ip = strings::is_ip_address(unfqdn(hostname));
let ascii_hostname;
let hostname = if strings::first_non_ascii(hostname).is_some() {
match domain_to_ascii_host(hostname) {
Some(ascii) => {
ascii_hostname = ascii;
&ascii_hostname[..]
}
None => return false,
}
} else {
hostname
};
let hostname = unfqdn(hostname);
let mut has_identifier_san = false;
unsafe {
let x509: *mut boring::X509 = x509;
let index = boring::X509_get_ext_by_NID(x509, boring::NID_subject_alt_name, -1);
if index >= 0 {
if let Some(ext) = boring::X509_get_ext(x509, index).as_mut() {
let method = boring::X509V3_EXT_get(ext);
if method != boring::X509V3_EXT_get_nid(boring::NID_subject_alt_name) {
return false;
}
let mut canonical_ip_buf = [0u8; INET6_ADDRSTRLEN + 1];
let mut cert_ip_buf = [0u8; INET6_ADDRSTRLEN + 1];
let host_ip: Option<&[u8]> = if host_is_ip {
Some(canonicalize_ip(hostname, &mut canonical_ip_buf).unwrap_or(hostname))
} else {
None
};
let names_ = boring::X509V3_EXT_d2i(ext);
if !names_.is_null() {
let names = names_.cast::<boring::struct_stack_st_GENERAL_NAME>();
let _guard = scopeguard::guard(names, |n| {
boring::sk_GENERAL_NAME_pop_free(n, boring::sk_GENERAL_NAME_free)
});
for i in 0..boring::sk_GENERAL_NAME_num(names) {
let r#gen = boring::sk_GENERAL_NAME_value(names, i);
if let Some(name) = r#gen.as_ref() {
match name.name_type {
boring::GEN_URI => {
has_identifier_san = true;
}
boring::GEN_DNS => {
has_identifier_san = true;
if !host_is_ip {
let dns_name = &*name.d.dNSName;
let dns_name_slice = core::slice::from_raw_parts(
dns_name.data,
usize::try_from(dns_name.length).expect("int cast"),
);
if match_dns_name(dns_name_slice, hostname) {
return true;
}
}
}
boring::GEN_IPADD => {
has_identifier_san = true;
if let Some(hip) = host_ip {
if let Some(cert_ip) =
ip2_string(&*name.d.ip, &mut cert_ip_buf)
{
if hip == cert_ip {
return true;
}
}
}
}
_ => {}
}
}
}
}
}
}
if !host_is_ip && !has_identifier_san {
let subject = boring::X509_get_subject_name(x509);
if !subject.is_null() {
let mut last: c_int = -1;
loop {
let entry_idx =
boring::X509_NAME_get_index_by_NID(subject, boring::NID_commonName, last);
if entry_idx < 0 {
break;
}
last = entry_idx;
let entry = boring::X509_NAME_get_entry(subject, entry_idx);
if entry.is_null() {
continue;
}
let data = boring::X509_NAME_ENTRY_get_data(entry);
if data.is_null() {
continue;
}
let cn_ptr = boring::ASN1_STRING_get0_data(data);
let cn_len = boring::ASN1_STRING_length(data);
if cn_ptr.is_null() || cn_len <= 0 {
continue;
}
let cn = core::slice::from_raw_parts(
cn_ptr,
usize::try_from(cn_len).expect("int cast"),
);
if match_dns_name(cn, hostname) {
return true;
}
}
}
}
}
false
}
pub fn check_server_identity(ssl_ptr: &mut boring::SSL, hostname: &[u8]) -> bool {
unsafe {
let cert_chain = boring::SSL_get_peer_cert_chain(std::ptr::from_mut(ssl_ptr));
if !cert_chain.is_null() {
let x509 = boring::sk_X509_value(cert_chain, 0);
if let Some(x509) = x509.as_mut() {
return check_x509_server_identity(x509, hostname);
}
}
}
false
}
#[cfg(test)]
mod server_identity_tests {
use super::*;
#[test]
fn idna_full_stop_host_converts_and_rejects_wildcard() {
let ascii =
domain_to_ascii_host("foo\u{3002}bar.example.com".as_bytes()).expect("converts");
assert_eq!(ascii, b"foo.bar.example.com".to_vec());
assert!(
!match_dns_name(b"*.example.com", &ascii),
"two-label host must not match a single-label wildcard"
);
}
#[test]
fn idna_fullwidth_and_halfwidth_full_stops_convert_identically() {
for host in ["foo\u{FF0E}bar.example.com", "foo\u{FF61}bar.example.com"] {
let ascii = domain_to_ascii_host(host.as_bytes()).expect("converts");
assert_eq!(ascii, b"foo.bar.example.com".to_vec());
}
}
#[test]
fn idna_unconvertible_host_matches_nothing() {
assert!(domain_to_ascii_host(b"\xff\xfe\x80").is_none());
}
#[test]
fn wildcard_never_matches_an_empty_first_label() {
let raw = "。example.com".as_bytes();
let effective = domain_to_ascii_host(raw).unwrap_or_else(|| raw.to_vec());
assert!(!match_dns_name(b"*.example.com", &effective[..]));
assert!(!match_dns_name(b"*.example.com", b".example.com"));
}
#[test]
fn ascii_hosts_keep_parity() {
assert!(match_dns_name(b"*.example.com", b"foo.example.com"));
assert!(match_dns_name(b"*.example.com", b"FOO.EXAMPLE.COM"));
assert!(!match_dns_name(b"*.example.com", b"foo.bar.example.com"));
assert!(!match_dns_name(b"*.example.com", b"example.com"));
assert!(match_dns_name(b"example.com", b"example.com"));
}
#[test]
fn idna_ulabel_converts_to_alabel() {
let ascii =
domain_to_ascii_host("b\u{00fc}cher.example.com".as_bytes()).expect("converts");
assert_eq!(ascii, b"xn--bcher-kva.example.com".to_vec());
assert!(match_dns_name(b"*.example.com", &ascii));
}
#[test]
fn unfqdn_strips_exactly_one_trailing_dot() {
assert_eq!(unfqdn(b"example.com."), b"example.com");
assert_eq!(unfqdn(b"example.com"), b"example.com");
assert_eq!(unfqdn(b"example.com.."), b"example.com.");
assert_eq!(unfqdn(b"."), b"");
assert_eq!(unfqdn(b""), b"");
}
#[test]
fn fqdn_host_matches_after_unfqdn() {
let host = unfqdn(b"example.com.");
assert!(match_dns_name(b"example.com", host));
let wild_host = unfqdn(b"foo.example.com.");
assert!(match_dns_name(b"*.example.com", wild_host));
assert!(!match_dns_name(b"example.com", b"example.com."));
}
#[test]
fn trailing_dot_ip_literal_is_still_an_ip() {
assert!(strings::is_ip_address(unfqdn(b"127.0.0.1.")));
assert!(strings::is_ip_address(unfqdn(b"::1.")));
assert!(!strings::is_ip_address(unfqdn(b"example.com.")));
}
}