From b4bf6013946f7e09cd816867f3d2c4a267f33b33 Mon Sep 17 00:00:00 2001 From: Alessandro Ghedini Date: Fri, 26 Sep 2025 07:32:00 +0100 Subject: [PATCH 001/111] Remove support for Hyper v0 --- .github/workflows/ci.yml | 2 - Cargo.toml | 2 - hyper-boring/Cargo.toml | 18 +- hyper-boring/src/lib.rs | 7 +- hyper-boring/src/v0.rs | 345 --------------------------------------- hyper-boring/src/v1.rs | 3 - hyper-boring/tests/v0.rs | 156 ------------------ hyper-boring/tests/v1.rs | 4 +- 8 files changed, 7 insertions(+), 530 deletions(-) delete mode 100644 hyper-boring/src/v0.rs delete mode 100644 hyper-boring/tests/v0.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8f170856..110d8ba71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -371,5 +371,3 @@ jobs: name: Run `rpk,underscore-wildcards` tests - run: cargo test --features pq-experimental,rpk,underscore-wildcards name: Run `pq-experimental,rpk,underscore-wildcards` tests - - run: cargo test -p hyper-boring --features hyper1 - name: Run hyper 1.0 tests for hyper-boring diff --git a/Cargo.toml b/Cargo.toml index 451b2d998..8fd3835ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,10 +40,8 @@ anyhow = "1" antidote = "1.0.0" http = "1" http-body-util = "0.1.2" -http_old = { package = "http", version = "0.2" } hyper = "1" hyper-util = "0.1.6" -hyper_old = { package = "hyper", version = "0.14", default-features = false } linked_hash_set = "0.1" openssl-macros = "0.1.1" tower = "0.4" diff --git a/hyper-boring/Cargo.toml b/hyper-boring/Cargo.toml index 91f744246..77a8a788c 100644 --- a/hyper-boring/Cargo.toml +++ b/hyper-boring/Cargo.toml @@ -16,10 +16,6 @@ features = ["pq-experimental"] rustdoc-args = ["--cfg", "docsrs"] [features] -default = ["runtime"] - -runtime = ["hyper_old/runtime"] - # Use a FIPS-validated version of boringssl. fips = ["tokio-boring/fips"] @@ -39,29 +35,23 @@ fips-link-precompiled = ["tokio-boring/fips-link-precompiled"] # Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/) pq-experimental = ["tokio-boring/pq-experimental"] -# Enable Hyper 1 support -hyper1 = ["dep:http", "dep:hyper", "dep:hyper-util", "dep:tower-service"] - [dependencies] antidote = { workspace = true } -http = { workspace = true, optional = true } -http_old = { workspace = true } -hyper = { workspace = true, optional = true } -hyper-util = { workspace = true, optional = true, features = ["client", "client-legacy"] } -hyper_old = { workspace = true, features = ["client"] } +http = { workspace = true } +hyper = { workspace = true } +hyper-util = { workspace = true, features = ["client", "client-legacy"] } linked_hash_set = { workspace = true } boring = { workspace = true } tokio = { workspace = true } tokio-boring = { workspace = true } tower-layer = { workspace = true } -tower-service = { workspace = true, optional = true } +tower-service = { workspace = true } [dev-dependencies] bytes = { workspace = true } http-body-util = { workspace = true } hyper-util = { workspace = true, features = ["http1", "http2", "service", "tokio"] } hyper = { workspace = true, features = ["server"] } -hyper_old = { workspace = true, features = [ "full" ] } tokio = { workspace = true, features = [ "full" ] } tower = { workspace = true, features = ["util"] } futures = { workspace = true } diff --git a/hyper-boring/src/lib.rs b/hyper-boring/src/lib.rs index 1822e135f..0e1f2b171 100644 --- a/hyper-boring/src/lib.rs +++ b/hyper-boring/src/lib.rs @@ -11,12 +11,9 @@ use std::sync::LazyLock; use tokio_boring::SslStream; mod cache; -mod v0; -/// Hyper 1 support. -#[cfg(feature = "hyper1")] -pub mod v1; +mod v1; -pub use self::v0::*; +pub use self::v1::*; fn key_index() -> Result, ErrorStack> { static IDX: LazyLock> = LazyLock::new(|| Ssl::new_ex_index().unwrap()); diff --git a/hyper-boring/src/v0.rs b/hyper-boring/src/v0.rs deleted file mode 100644 index 03368d32c..000000000 --- a/hyper-boring/src/v0.rs +++ /dev/null @@ -1,345 +0,0 @@ -use crate::cache::{SessionCache, SessionKey}; -use crate::{key_index, HttpsLayerSettings, MaybeHttpsStream}; -use antidote::Mutex; -use boring::error::ErrorStack; -use boring::ssl::{ - ConnectConfiguration, Ssl, SslConnector, SslConnectorBuilder, SslMethod, SslRef, - SslSessionCacheMode, -}; -use http_old::uri::Scheme; -use hyper_old::client::connect::{Connected, Connection}; -use hyper_old::client::HttpConnector; -use hyper_old::service::Service; -use hyper_old::Uri; -use std::error::Error; -use std::future::Future; -use std::net; -use std::pin::Pin; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::{fmt, io}; -use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; -use tower_layer::Layer; - -/// A Connector using BoringSSL to support `http` and `https` schemes. -#[derive(Clone)] -pub struct HttpsConnector { - http: T, - inner: Inner, -} - -#[cfg(feature = "runtime")] -impl HttpsConnector { - /// Creates a a new `HttpsConnector` using default settings. - /// - /// The Hyper `HttpConnector` is used to perform the TCP socket connection. ALPN is configured to support both - /// HTTP/2 and HTTP/1.1. - /// - /// Requires the `runtime` Cargo feature. - pub fn new() -> Result, ErrorStack> { - let mut http = HttpConnector::new(); - http.enforce_http(false); - - HttpsLayer::new().map(|l| l.layer(http)) - } -} - -impl HttpsConnector -where - S: Service + Send, - S::Error: Into>, - S::Future: Unpin + Send + 'static, - T: AsyncRead + AsyncWrite + Connection + Unpin + fmt::Debug + Sync + Send + 'static, -{ - /// Creates a new `HttpsConnector`. - /// - /// The session cache configuration of `ssl` will be overwritten. - pub fn with_connector( - http: S, - ssl: SslConnectorBuilder, - ) -> Result, ErrorStack> { - HttpsLayer::with_connector(ssl).map(|l| l.layer(http)) - } - - /// Registers a callback which can customize the configuration of each connection. - /// - /// Unsuitable to change verify hostflags (with `config.param_mut().set_hostflags(…)`), - /// as they are reset after the callback is executed. Use [`Self::set_ssl_callback`] - /// instead. - pub fn set_callback(&mut self, callback: F) - where - F: Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send, - { - self.inner.callback = Some(Arc::new(callback)); - } - - /// Registers a callback which can customize the `Ssl` of each connection. - pub fn set_ssl_callback(&mut self, callback: F) - where - F: Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send, - { - self.inner.ssl_callback = Some(Arc::new(callback)); - } -} - -/// A layer which wraps services in an `HttpsConnector`. -pub struct HttpsLayer { - inner: Inner, -} - -#[derive(Clone)] -struct Inner { - ssl: SslConnector, - cache: Arc>, - callback: Option, - ssl_callback: Option, -} - -type Callback = - Arc Result<(), ErrorStack> + Sync + Send>; -type SslCallback = Arc Result<(), ErrorStack> + Sync + Send>; - -impl HttpsLayer { - /// Creates a new `HttpsLayer` with default settings. - /// - /// ALPN is configured to support both HTTP/1 and HTTP/1.1. - pub fn new() -> Result { - let mut ssl = SslConnector::builder(SslMethod::tls())?; - - ssl.set_alpn_protos(b"\x02h2\x08http/1.1")?; - - Self::with_connector(ssl) - } - - /// Creates a new `HttpsLayer`. - /// - /// The session cache configuration of `ssl` will be overwritten. - pub fn with_connector(ssl: SslConnectorBuilder) -> Result { - Self::with_connector_and_settings(ssl, Default::default()) - } - - /// Creates a new `HttpsLayer` with settings - pub fn with_connector_and_settings( - mut ssl: SslConnectorBuilder, - settings: HttpsLayerSettings, - ) -> Result { - let cache = Arc::new(Mutex::new(SessionCache::with_capacity( - settings.session_cache_capacity, - ))); - - ssl.set_session_cache_mode(SslSessionCacheMode::CLIENT); - - ssl.set_new_session_callback({ - let cache = cache.clone(); - move |ssl, session| { - if let Some(key) = key_index().ok().and_then(|idx| ssl.ex_data(idx)) { - cache.lock().insert(key.clone(), session); - } - } - }); - - Ok(HttpsLayer { - inner: Inner { - ssl: ssl.build(), - cache, - callback: None, - ssl_callback: None, - }, - }) - } - - /// Registers a callback which can customize the configuration of each connection. - /// - /// Unsuitable to change verify hostflags (with `config.param_mut().set_hostflags(…)`), - /// as they are reset after the callback is executed. Use [`Self::set_ssl_callback`] - /// instead. - pub fn set_callback(&mut self, callback: F) - where - F: Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send, - { - self.inner.callback = Some(Arc::new(callback)); - } - - /// Registers a callback which can customize the `Ssl` of each connection. - pub fn set_ssl_callback(&mut self, callback: F) - where - F: Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send, - { - self.inner.ssl_callback = Some(Arc::new(callback)); - } -} - -impl Layer for HttpsLayer { - type Service = HttpsConnector; - - fn layer(&self, inner: S) -> HttpsConnector { - HttpsConnector { - http: inner, - inner: self.inner.clone(), - } - } -} - -impl Inner { - fn setup_ssl(&self, uri: &Uri, host: &str) -> Result { - let mut conf = self.ssl.configure()?; - - if let Some(ref callback) = self.callback { - callback(&mut conf, uri)?; - } - - let key = SessionKey { - host: host.to_string(), - port: uri.port_u16().unwrap_or(443), - }; - - if let Some(session) = self.cache.lock().get(&key) { - unsafe { - conf.set_session(&session)?; - } - } - - let idx = key_index()?; - conf.set_ex_data(idx, key); - - let mut ssl = conf.into_ssl(host)?; - - if let Some(ref ssl_callback) = self.ssl_callback { - ssl_callback(&mut ssl, uri)?; - } - - Ok(ssl) - } -} - -impl Service for HttpsConnector -where - S: Service + Send, - S::Error: Into>, - S::Future: Unpin + Send + 'static, - S::Response: AsyncRead + AsyncWrite + Connection + Unpin + fmt::Debug + Sync + Send + 'static, -{ - type Response = MaybeHttpsStream; - type Error = Box; - #[allow(clippy::type_complexity)] - type Future = Pin> + Send>>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.http.poll_ready(cx).map_err(Into::into) - } - - fn call(&mut self, uri: Uri) -> Self::Future { - let is_tls_scheme = uri - .scheme() - .map(|s| s == &Scheme::HTTPS || s.as_str() == "wss") - .unwrap_or(false); - - let tls_setup = if is_tls_scheme { - Some((self.inner.clone(), uri.clone())) - } else { - None - }; - - let connect = self.http.call(uri); - - let f = async { - let conn = connect.await.map_err(Into::into)?; - - let (inner, uri) = match tls_setup { - Some((inner, uri)) => (inner, uri), - None => return Ok(MaybeHttpsStream::Http(conn)), - }; - - let mut host = uri.host().ok_or("URI missing host")?; - - // If `host` is an IPv6 address, we must strip away the square brackets that surround - // it (otherwise, boring will fail to parse the host as an IP address, eventually - // causing the handshake to fail due a hostname verification error). - if !host.is_empty() { - let last = host.len() - 1; - let mut chars = host.chars(); - - if (chars.next(), chars.last()) == (Some('['), Some(']')) - && host[1..last].parse::().is_ok() - { - host = &host[1..last]; - } - } - - let ssl = inner.setup_ssl(&uri, host)?; - let stream = tokio_boring::SslStreamBuilder::new(ssl, conn) - .connect() - .await?; - - Ok(MaybeHttpsStream::Https(stream)) - }; - - Box::pin(f) - } -} - -impl Connection for MaybeHttpsStream -where - T: Connection, -{ - fn connected(&self) -> Connected { - match self { - MaybeHttpsStream::Http(s) => s.connected(), - MaybeHttpsStream::Https(s) => { - let mut connected = s.get_ref().connected(); - - if s.ssl().selected_alpn_protocol() == Some(b"h2") { - connected = connected.negotiated_h2(); - } - - connected - } - } - } -} - -impl AsyncRead for MaybeHttpsStream -where - T: AsyncRead + AsyncWrite + Unpin, -{ - fn poll_read( - mut self: Pin<&mut Self>, - ctx: &mut Context<'_>, - buf: &mut ReadBuf<'_>, - ) -> Poll> { - match &mut *self { - MaybeHttpsStream::Http(s) => Pin::new(s).poll_read(ctx, buf), - MaybeHttpsStream::Https(s) => Pin::new(s).poll_read(ctx, buf), - } - } -} - -impl AsyncWrite for MaybeHttpsStream -where - T: AsyncRead + AsyncWrite + Unpin, -{ - fn poll_write( - mut self: Pin<&mut Self>, - ctx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - match &mut *self { - MaybeHttpsStream::Http(s) => Pin::new(s).poll_write(ctx, buf), - MaybeHttpsStream::Https(s) => Pin::new(s).poll_write(ctx, buf), - } - } - - fn poll_flush(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { - match &mut *self { - MaybeHttpsStream::Http(s) => Pin::new(s).poll_flush(ctx), - MaybeHttpsStream::Https(s) => Pin::new(s).poll_flush(ctx), - } - } - - fn poll_shutdown(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { - match &mut *self { - MaybeHttpsStream::Http(s) => Pin::new(s).poll_shutdown(ctx), - MaybeHttpsStream::Https(s) => Pin::new(s).poll_shutdown(ctx), - } - } -} diff --git a/hyper-boring/src/v1.rs b/hyper-boring/src/v1.rs index e1f9a43da..f4cb0168d 100644 --- a/hyper-boring/src/v1.rs +++ b/hyper-boring/src/v1.rs @@ -29,14 +29,11 @@ pub struct HttpsConnector { inner: Inner, } -#[cfg(feature = "runtime")] impl HttpsConnector { /// Creates a a new `HttpsConnector` using default settings. /// /// The Hyper `HttpConnector` is used to perform the TCP socket connection. ALPN is configured to support both /// HTTP/2 and HTTP/1.1. - /// - /// Requires the `runtime` Cargo feature. pub fn new() -> Result, ErrorStack> { let mut http = HttpConnector::new(); http.enforce_http(false); diff --git a/hyper-boring/tests/v0.rs b/hyper-boring/tests/v0.rs deleted file mode 100644 index f52e18512..000000000 --- a/hyper-boring/tests/v0.rs +++ /dev/null @@ -1,156 +0,0 @@ -use boring::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod}; -use futures::StreamExt; -use hyper_boring::HttpsConnector; -use hyper_old::client::HttpConnector; -use hyper_old::server::conn::Http; -use hyper_old::{service, Response}; -use hyper_old::{Body, Client}; -use std::convert::Infallible; -use std::{io, iter}; -use tokio::net::TcpListener; - -#[tokio::test] -#[cfg(feature = "runtime")] -async fn google() { - let ssl = HttpsConnector::new().unwrap(); - let client = Client::builder() - .pool_max_idle_per_host(0) - .build::<_, Body>(ssl); - - for _ in 0..3 { - let resp = client - .get("https://www.google.com".parse().unwrap()) - .await - .expect("connection should succeed"); - let mut body = resp.into_body(); - while body.next().await.transpose().unwrap().is_some() {} - } -} - -#[tokio::test] -async fn localhost() { - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - let port = addr.port(); - - let server = async move { - let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - acceptor.set_session_id_context(b"test").unwrap(); - acceptor - .set_private_key_file("tests/test/key.pem", SslFiletype::PEM) - .unwrap(); - acceptor - .set_certificate_chain_file("tests/test/cert.pem") - .unwrap(); - let acceptor = acceptor.build(); - - for _ in 0..3 { - let stream = listener.accept().await.unwrap().0; - let stream = tokio_boring::accept(&acceptor, stream).await.unwrap(); - - let service = - service::service_fn(|_| async { Ok::<_, io::Error>(Response::new(Body::empty())) }); - - Http::new() - .http1_keep_alive(false) - .serve_connection(stream, service) - .await - .unwrap(); - } - }; - tokio::spawn(server); - - let resolver = - tower::service_fn(move |_name| async move { Ok::<_, Infallible>(iter::once(addr)) }); - - let mut connector = HttpConnector::new_with_resolver(resolver); - - connector.enforce_http(false); - - let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap(); - - ssl.set_ca_file("tests/test/root-ca.pem").unwrap(); - - use std::fs::File; - use std::io::Write; - - let file = File::create("../target/keyfile.log").unwrap(); - ssl.set_keylog_callback(move |_, line| { - let _ = writeln!(&file, "{line}"); - }); - - let ssl = HttpsConnector::with_connector(connector, ssl).unwrap(); - let client = Client::builder().build::<_, Body>(ssl); - - for _ in 0..3 { - let resp = client - .get(format!("https://foobar.com:{port}").parse().unwrap()) - .await - .unwrap(); - assert!(resp.status().is_success(), "{}", resp.status()); - let mut body = resp.into_body(); - while body.next().await.transpose().unwrap().is_some() {} - } -} - -#[tokio::test] -async fn alpn_h2() { - use boring::ssl::{self, AlpnError}; - - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - let port = addr.port(); - - let server = async move { - let mut acceptor = SslAcceptor::mozilla_modern(SslMethod::tls()).unwrap(); - acceptor - .set_certificate_chain_file("tests/test/cert.pem") - .unwrap(); - acceptor - .set_private_key_file("tests/test/key.pem", SslFiletype::PEM) - .unwrap(); - acceptor.set_alpn_select_callback(|_, client| { - ssl::select_next_proto(b"\x02h2", client).ok_or(AlpnError::NOACK) - }); - let acceptor = acceptor.build(); - - let stream = listener.accept().await.unwrap().0; - let stream = tokio_boring::accept(&acceptor, stream).await.unwrap(); - assert_eq!(stream.ssl().selected_alpn_protocol().unwrap(), b"h2"); - - let service = - service::service_fn(|_| async { Ok::<_, io::Error>(Response::new(Body::empty())) }); - - Http::new() - .http2_only(true) - .serve_connection(stream, service) - .await - .unwrap(); - }; - tokio::spawn(server); - - let resolver = - tower::service_fn(move |_name| async move { Ok::<_, Infallible>(iter::once(addr)) }); - - let mut connector = HttpConnector::new_with_resolver(resolver); - - connector.enforce_http(false); - - let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap(); - - ssl.set_ca_file("tests/test/root-ca.pem").unwrap(); - - let mut ssl = HttpsConnector::with_connector(connector, ssl).unwrap(); - - ssl.set_ssl_callback(|ssl, _| ssl.set_alpn_protos(b"\x02h2\x08http/1.1")); - - let client = Client::builder().build::<_, Body>(ssl); - - let resp = client - .get(format!("https://foobar.com:{port}").parse().unwrap()) - .await - .unwrap(); - assert!(resp.status().is_success(), "{}", resp.status()); - let mut body = resp.into_body(); - while body.next().await.transpose().unwrap().is_some() {} -} diff --git a/hyper-boring/tests/v1.rs b/hyper-boring/tests/v1.rs index 441caea6d..4082d2cef 100644 --- a/hyper-boring/tests/v1.rs +++ b/hyper-boring/tests/v1.rs @@ -1,11 +1,9 @@ -#![cfg(feature = "hyper1")] - use boring::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod}; use bytes::Bytes; use futures::StreamExt; use http_body_util::{BodyStream, Empty}; use hyper::{service, Response}; -use hyper_boring::v1::HttpsConnector; +use hyper_boring::HttpsConnector; use hyper_util::client::legacy::connect::HttpConnector; use hyper_util::client::legacy::Client; use hyper_util::rt::{TokioExecutor, TokioIo}; From 974c3d2db0e715df4894ce4b57ea4b276fa93e5f Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 13 Jun 2025 13:56:37 +0100 Subject: [PATCH 002/111] Ensure that ERR_LIB type can be named --- boring-sys/build/main.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index cc85532bb..be9a3fa1f 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -810,7 +810,24 @@ fn generate_bindings(config: &Config) { } let bindings = builder.generate().expect("Unable to generate bindings"); + let mut source_code = Vec::new(); bindings - .write_to_file(config.out_dir.join("bindings.rs")) - .expect("Couldn't write bindings!"); + .write(Box::new(&mut source_code)) + .expect("Couldn't serialize bindings!"); + ensure_err_lib_enum_is_named(&mut source_code); + fs::write(config.out_dir.join("bindings.rs"), source_code).expect("Couldn't write bindings!"); +} + +/// err.h has anonymous `enum { ERR_LIB_NONE = 1 }`, which makes a dodgy `_bindgen_ty_1` name +fn ensure_err_lib_enum_is_named(source_code: &mut Vec) { + let src = String::from_utf8_lossy(source_code); + let enum_type = src + .split_once("ERR_LIB_SSL:") + .and_then(|(_, def)| Some(def.split_once("=")?.0)) + .unwrap_or("_bindgen_ty_1"); + + source_code.extend_from_slice( + format!("\n/// Newtype for [`ERR_LIB_SSL`] constants\npub use {enum_type} as ErrLib;\n") + .as_bytes(), + ); } From 78b8ceaf10fd67126e0f30abd8a3f45aca6cab19 Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 13 Jun 2025 15:34:02 +0100 Subject: [PATCH 003/111] Add more reliable library_reason() --- boring/src/error.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/boring/src/error.rs b/boring/src/error.rs index d2c01db0e..60d8d4f5c 100644 --- a/boring/src/error.rs +++ b/boring/src/error.rs @@ -27,6 +27,8 @@ use std::str; use crate::ffi; +pub use crate::ffi::ErrLib; + /// Collection of [`Error`]s from OpenSSL. /// /// [`Error`]: struct.Error.html @@ -194,11 +196,23 @@ impl Error { } } + /// Get `{lib}_R_{reason}` reason code for the given library, or `None` if the error is from a different library. + /// + /// Libraries are identified by [`ERR_LIB_{name}`(ffi::ERR_LIB_SSL) constants. + #[inline] + #[must_use] + #[track_caller] + pub fn library_reason(&self, library_code: ErrLib) -> Option { + debug_assert!(library_code.0 < ffi::ERR_NUM_LIBS.0); + (self.library_code() == library_code.0 as c_int).then_some(self.reason_code()) + } + /// Returns a raw OpenSSL **packed** error code for this error, which **can't be reliably compared to any error constant**. /// - /// Use [`Error::library_code()`] and [`Error::reason_code()`] instead. + /// Use [`Error::library_code()`] and [`Error::library_reason()`] instead. /// Packed error codes are different than [SSL error codes](crate::ssl::ErrorCode). #[must_use] + #[deprecated(note = "use library_reason() to compare error codes")] pub fn code(&self) -> c_uint { self.code } @@ -223,7 +237,7 @@ impl Error { /// Returns the raw OpenSSL error constant for the library reporting the error (`ERR_LIB_{name}`). /// - /// Error [reason codes](Error::reason_code) are not globally unique, but scoped to each library. + /// Error [reason codes](Error::library_reason) are not globally unique, but scoped to each library. #[must_use] pub fn library_code(&self) -> c_int { ffi::ERR_GET_LIB(self.code) @@ -249,6 +263,7 @@ impl Error { /// Returns [library-specific](Error::library_code) reason code corresponding to some of the `{lib}_R_{reason}` constants. /// /// Reason codes are ambiguous, and different libraries reuse the same numeric values for different errors. + /// Use [`Error::library_reason`] to compare error codes. /// /// For `ERR_LIB_SYS` the reason code is `errno`. `ERR_LIB_USER` can use any values. /// Other libraries may use [`ERR_R_*`](ffi::ERR_R_FATAL) or their own codes. From 4cb7e260a85b7157f14790444f49fcceca085447 Mon Sep 17 00:00:00 2001 From: Alessandro Ghedini Date: Thu, 25 Sep 2025 09:51:52 +0100 Subject: [PATCH 004/111] Clean-up legacy FIPS options Per BoringSSL's FIPS policy, its `main` branch is the "update branch" for FedRAMP compliance's purposes. This means that we can stop using a specific BoringSSL branch when enabling FIPS, as well as a number of hacks that allowed us to build more recent BoringSSL versions with an older pre-compiled FIPS modules. This also required slightly updating the main BoringSSL submodule, as the previous version had an issue when building with the FIPS option enabled. This is turn required some changes to the PQ patch as well as some APIs that don't seem to be exposed publicly, as well as changing some paths in the other patches. In order to allow a smooth upgrade of internal projects, the `fips-compat` feature is reduced in scope and renamed to `legacy-compat-deprecated` so that we can incrementally upgrade internal BoringSSL forks. In practice this shouldn't really be something anyone else would need, since in order to work it requires a specific mix of BoringSSL version and backported patches. --- .github/workflows/ci.yml | 16 +- .gitmodules | 3 - boring-sys/Cargo.toml | 41 +- boring-sys/build/config.rs | 24 +- boring-sys/build/main.rs | 120 +- boring-sys/deps/boringssl | 2 +- boring-sys/deps/boringssl-fips | 1 - boring-sys/patches/boring-pq.patch | 2962 ++++++++--------- boring-sys/patches/rpk.patch | 102 +- boring-sys/patches/underscore-wildcards.patch | 45 +- boring/Cargo.toml | 24 +- boring/src/bio.rs | 4 +- boring/src/fips.rs | 12 +- boring/src/lib.rs | 1 - boring/src/ssl/mod.rs | 115 +- boring/src/ssl/test/mod.rs | 4 - boring/src/x509/mod.rs | 4 +- boring/src/x509/tests/trusted_first.rs | 4 +- hyper-boring/Cargo.toml | 15 +- tokio-boring/Cargo.toml | 13 - 20 files changed, 1464 insertions(+), 2048 deletions(-) delete mode 160000 boring-sys/deps/boringssl-fips diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 110d8ba71..8ee8b46c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -202,6 +202,10 @@ jobs: run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} shell: bash - run: rustup target add ${{ matrix.target }} + - name: Install golang + uses: actions/setup-go@v5 + with: + go-version: '>=1.22.0' - name: Install target-specific APT dependencies if: "matrix.apt_packages != ''" run: sudo apt update && sudo apt install -y ${{ matrix.apt_packages }} @@ -255,18 +259,10 @@ jobs: - name: Install Rust (rustup) run: rustup update stable --no-self-update && rustup default stable shell: bash - - name: Install Clang-12 - uses: KyleMayes/install-llvm-action@v1 - with: - version: "12.0.0" - directory: ${{ runner.temp }}/llvm - name: Install golang uses: actions/setup-go@v5 with: go-version: '>=1.22.0' - - name: Add clang++-12 link - working-directory: ${{ runner.temp }}/llvm/bin - run: ln -s clang clang++-12 - name: Run tests run: cargo test --features fips - name: Test boring-sys cargo publish (FIPS) @@ -296,6 +292,10 @@ jobs: - name: Install Rust (rustup) run: rustup update stable --no-self-update && rustup default stable && rustup target add ${{ matrix.target }} shell: bash + - name: Install golang + uses: actions/setup-go@v5 + with: + go-version: '>=1.22.0' - name: Install ${{ matrix.target }} toolchain run: brew tap messense/macos-cross-toolchains && brew install ${{ matrix.target }} - name: Set BORING_BSSL_SYSROOT diff --git a/.gitmodules b/.gitmodules index 93bfb089f..c1e5a527e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,6 +2,3 @@ path = boring-sys/deps/boringssl url = https://github.com/google/boringssl.git ignore = dirty -[submodule "boring-sys/deps/boringssl-fips"] - path = boring-sys/deps/boringssl-fips - url = https://github.com/google/boringssl.git diff --git a/boring-sys/Cargo.toml b/boring-sys/Cargo.toml index 166d62f55..86ff731c3 100644 --- a/boring-sys/Cargo.toml +++ b/boring-sys/Cargo.toml @@ -19,35 +19,22 @@ include = [ "/*.toml", "/LICENSE-MIT", "/cmake/*.cmake", - # boringssl (non-FIPS) - "/deps/boringssl/src/util/32-bit-toolchain.cmake", "/deps/boringssl/**/*.[chS]", "/deps/boringssl/**/*.asm", - "/deps/boringssl/sources.json", - "/deps/boringssl/src/crypto/obj/obj_mac.num", - "/deps/boringssl/src/crypto/obj/objects.txt", + "/deps/boringssl/**/*.pl", + "/deps/boringssl/**/*.go", + "/deps/boringssl/**/*.cmake", + "/deps/boringssl/**/go.mod", + "/deps/boringssl/**/go.sum", + "/deps/boringssl/crypto/obj/obj_mac.num", + "/deps/boringssl/crypto/obj/objects.txt", + "/deps/boringssl/crypto/err/*.errordata", "/deps/boringssl/**/*.bzl", - "/deps/boringssl/src/**/*.cc", + "/deps/boringssl/**/*.cc", "/deps/boringssl/**/CMakeLists.txt", "/deps/boringssl/**/sources.cmake", + "/deps/boringssl/**/util/go_tests.txt", "/deps/boringssl/LICENSE", - # boringssl (FIPS) - "/deps/boringssl-fips/src/util/32-bit-toolchain.cmake", - "/deps/boringssl-fips/**/*.[chS]", - "/deps/boringssl-fips/**/*.asm", - "/deps/boringssl-fips/**/*.pl", - "/deps/boringssl-fips/**/*.go", - "/deps/boringssl-fips/**/go.mod", - "/deps/boringssl-fips/**/go.sum", - "/deps/boringssl-fips/sources.json", - "/deps/boringssl-fips/crypto/obj/obj_mac.num", - "/deps/boringssl-fips/crypto/obj/objects.txt", - "/deps/boringssl-fips/crypto/err/*.errordata", - "/deps/boringssl-fips/**/*.bzl", - "/deps/boringssl-fips/**/*.cc", - "/deps/boringssl-fips/**/CMakeLists.txt", - "/deps/boringssl-fips/**/sources.cmake", - "/deps/boringssl-fips/LICENSE", "/build/*", "/src", "/patches", @@ -66,14 +53,6 @@ rustdoc-args = ["--cfg", "docsrs"] # for instructions and more details on the boringssl FIPS flag. fips = [] -# Use a precompiled FIPS-validated version of BoringSSL. Meant to be used with -# FIPS-20230428 or newer. Users must set `BORING_BSSL_FIPS_PATH` to use this -# feature, or else the build will fail. -fips-precompiled = [] - -# Link with precompiled FIPS-validated `bcm.o` module. -fips-link-precompiled = [] - # Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250) rpk = [] diff --git a/boring-sys/build/config.rs b/boring-sys/build/config.rs index 5d93eda4a..f586b9684 100644 --- a/boring-sys/build/config.rs +++ b/boring-sys/build/config.rs @@ -16,8 +16,6 @@ pub(crate) struct Config { pub(crate) struct Features { pub(crate) fips: bool, - pub(crate) fips_precompiled: bool, - pub(crate) fips_link_precompiled: bool, pub(crate) pq_experimental: bool, pub(crate) rpk: bool, pub(crate) underscore_wildcards: bool, @@ -27,7 +25,6 @@ pub(crate) struct Env { pub(crate) path: Option, pub(crate) include_path: Option, pub(crate) source_path: Option, - pub(crate) precompiled_bcm_o: Option, pub(crate) assume_patched: bool, pub(crate) sysroot: Option, pub(crate) compiler_external_toolchain: Option, @@ -81,10 +78,6 @@ impl Config { panic!("`fips` and `rpk` features are mutually exclusive"); } - if self.features.fips_precompiled && self.features.rpk { - panic!("`fips-precompiled` and `rpk` features are mutually exclusive"); - } - let is_precompiled_native_lib = self.env.path.is_some(); let is_external_native_lib_source = !is_precompiled_native_lib && self.env.source_path.is_none(); @@ -107,32 +100,18 @@ impl Config { "cargo:warning=precompiled BoringSSL was provided, so patches will be ignored" ); } - - // todo(rmehra): should this even be a restriction? why not let people link a custom bcm.o? - // precompiled boringssl will include libcrypto.a - if is_precompiled_native_lib && self.features.fips_link_precompiled { - panic!("precompiled BoringSSL was provided, so FIPS configuration can't be applied"); - } - - if !is_precompiled_native_lib && self.features.fips_precompiled { - panic!("`fips-precompiled` feature requires `BORING_BSSL_FIPS_PATH` to be set"); - } } } impl Features { fn from_env() -> Self { let fips = env::var_os("CARGO_FEATURE_FIPS").is_some(); - let fips_precompiled = env::var_os("CARGO_FEATURE_FIPS_PRECOMPILED").is_some(); - let fips_link_precompiled = env::var_os("CARGO_FEATURE_FIPS_LINK_PRECOMPILED").is_some(); let pq_experimental = env::var_os("CARGO_FEATURE_PQ_EXPERIMENTAL").is_some(); let rpk = env::var_os("CARGO_FEATURE_RPK").is_some(); let underscore_wildcards = env::var_os("CARGO_FEATURE_UNDERSCORE_WILDCARDS").is_some(); Self { fips, - fips_precompiled, - fips_link_precompiled, pq_experimental, rpk, underscore_wildcards, @@ -140,7 +119,7 @@ impl Features { } pub(crate) fn is_fips_like(&self) -> bool { - self.fips || self.fips_precompiled || self.fips_link_precompiled + self.fips } } @@ -175,7 +154,6 @@ impl Env { path: boringssl_var("BORING_BSSL_PATH").map(PathBuf::from), include_path: boringssl_var("BORING_BSSL_INCLUDE_PATH").map(PathBuf::from), source_path: boringssl_var("BORING_BSSL_SOURCE_PATH").map(PathBuf::from), - precompiled_bcm_o: boringssl_var("BORING_BSSL_PRECOMPILED_BCM_O").map(PathBuf::from), assume_patched: boringssl_var("BORING_BSSL_ASSUME_PATCHED") .is_some_and(|v| !v.is_empty()), sysroot: boringssl_var("BORING_BSSL_SYSROOT").map(PathBuf::from), diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index be9a3fa1f..c95f1cd4e 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -50,6 +50,7 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[ &[ ("CMAKE_OSX_ARCHITECTURES", "arm64"), ("CMAKE_OSX_SYSROOT", "iphoneos"), + ("CMAKE_MACOSX_BUNDLE", "OFF"), ], ), ( @@ -57,6 +58,7 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[ &[ ("CMAKE_OSX_ARCHITECTURES", "arm64"), ("CMAKE_OSX_SYSROOT", "iphonesimulator"), + ("CMAKE_MACOSX_BUNDLE", "OFF"), ], ), ( @@ -64,6 +66,7 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[ &[ ("CMAKE_OSX_ARCHITECTURES", "x86_64"), ("CMAKE_OSX_SYSROOT", "iphonesimulator"), + ("CMAKE_MACOSX_BUNDLE", "OFF"), ], ), // macOS @@ -114,11 +117,7 @@ fn get_boringssl_source_path(config: &Config) -> &PathBuf { static SOURCE_PATH: OnceLock = OnceLock::new(); SOURCE_PATH.get_or_init(|| { - let submodule_dir = if config.features.fips { - "boringssl-fips" - } else { - "boringssl" - }; + let submodule_dir = "boringssl"; let src_path = config.out_dir.join(submodule_dir); @@ -304,7 +303,7 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { config .manifest_dir .join(src_path) - .join("src/util/32-bit-toolchain.cmake") + .join("util/32-bit-toolchain.cmake") .as_os_str(), ); } @@ -340,55 +339,6 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { boringssl_cmake } -/// Verify that the toolchains match -/// See "Installation Instructions" under section 12.1. -// TODO: maybe this should also verify the Go and Ninja versions? But those haven't been an issue in practice ... -fn verify_fips_clang_version() -> (&'static str, &'static str) { - fn version(tool: &str) -> Option { - let output = match Command::new(tool).arg("--version").output() { - Ok(o) => o, - Err(e) => { - println!("cargo:warning=missing {tool}, trying other compilers: {e}"); - // NOTE: hard-codes that the loop below checks the version - return None; - } - }; - if !output.status.success() { - return Some(String::new()); - } - let output = std::str::from_utf8(&output.stdout).expect("invalid utf8 output"); - Some(output.lines().next().expect("empty output").to_string()) - } - - const REQUIRED_CLANG_VERSION: &str = "12.0.0"; - for (cc, cxx) in [ - ("clang-12", "clang++-12"), - ("clang", "clang++"), - ("cc", "c++"), - ] { - let (Some(cc_version), Some(cxx_version)) = (version(cc), version(cxx)) else { - continue; - }; - - if cc_version.contains(REQUIRED_CLANG_VERSION) { - assert!( - cxx_version.contains(REQUIRED_CLANG_VERSION), - "mismatched versions of cc and c++" - ); - return (cc, cxx); - } else if cc == "cc" { - panic!( - "unsupported clang version \"{cc_version}\": FIPS requires clang {REQUIRED_CLANG_VERSION}" - ); - } else if !cc_version.is_empty() { - println!( - "cargo:warning=FIPS requires clang version {REQUIRED_CLANG_VERSION}, skipping incompatible version \"{cc_version}\"" - ); - } - } - unreachable!() -} - fn pick_best_android_ndk_toolchain(toolchains_dir: &Path) -> std::io::Result { let toolchains = std::fs::read_dir(toolchains_dir)?.collect::, _>>()?; // First look for one of the toolchains that Google has documented. @@ -591,66 +541,17 @@ fn built_boring_source_path(config: &Config) -> &PathBuf { } if config.features.fips { - let (clang, clangxx) = verify_fips_clang_version(); - cfg.define("CMAKE_C_COMPILER", clang) - .define("CMAKE_CXX_COMPILER", clangxx) - .define("CMAKE_ASM_COMPILER", clang) + cfg.define("CMAKE_C_COMPILER", "clang") + .define("CMAKE_CXX_COMPILER", "clang++") + .define("CMAKE_ASM_COMPILER", "clang") .define("FIPS", "1"); } - if config.features.fips_link_precompiled { - cfg.define("FIPS", "1"); - } - cfg.build_target("ssl").build(); cfg.build_target("crypto").build() }) } -fn link_in_precompiled_bcm_o(config: &Config) { - println!("cargo:warning=linking in precompiled `bcm.o` module"); - - let bssl_dir = built_boring_source_path(config); - let bcm_o_src_path = config.env.precompiled_bcm_o.as_ref() - .expect("`fips-link-precompiled` requires `BORING_BSSL_FIPS_PRECOMPILED_BCM_O` env variable to be specified"); - - let libcrypto_path = bssl_dir - .join("build/crypto/libcrypto.a") - .canonicalize() - .unwrap(); - - let bcm_o_dst_path = bssl_dir.join("build/bcm-fips.o"); - - fs::copy(bcm_o_src_path, &bcm_o_dst_path).unwrap(); - - // check that fips module is named as expected - let out = run_command( - Command::new("ar") - .arg("t") - .arg(&libcrypto_path) - .arg("bcm.o"), - ) - .unwrap(); - - assert_eq!( - String::from_utf8(out.stdout).unwrap().trim(), - "bcm.o", - "failed to verify FIPS module name" - ); - - // insert fips bcm.o before bcm.o into libcrypto.a, - // so for all duplicate symbols the older fips bcm.o is used - // (this causes the need for extra linker flags to deal with duplicate symbols) - // (as long as the newer module does not define new symbols, one may also remove it, - // but once there are new symbols it would cause missing symbols at linking stage) - run_command( - Command::new("ar") - .args(["rb", "bcm.o"]) - .args([&libcrypto_path, &bcm_o_dst_path]), - ) - .unwrap(); -} - fn get_cpp_runtime_lib(config: &Config) -> Option { if let Some(ref cpp_lib) = config.env.cpp_runtime_lib { return cpp_lib.clone().into_string().ok(); @@ -709,10 +610,6 @@ fn emit_link_directives(config: &Config) { ); } - if config.features.fips_link_precompiled { - link_in_precompiled_bcm_o(config); - } - if let Some(cpp_lib) = get_cpp_runtime_lib(config) { println!("cargo:rustc-link-lib={cpp_lib}"); } @@ -785,7 +682,6 @@ fn generate_bindings(config: &Config) { "des.h", "dtls1.h", "hkdf.h", - #[cfg(not(feature = "fips"))] "hpke.h", "hmac.h", "hrss.h", diff --git a/boring-sys/deps/boringssl b/boring-sys/deps/boringssl index 44b3df6f0..478b28ab1 160000 --- a/boring-sys/deps/boringssl +++ b/boring-sys/deps/boringssl @@ -1 +1 @@ -Subproject commit 44b3df6f03d85c901767250329c571db405122d5 +Subproject commit 478b28ab12f2001a03261624261fd041f5439706 diff --git a/boring-sys/deps/boringssl-fips b/boring-sys/deps/boringssl-fips deleted file mode 160000 index 853ca1ea1..000000000 --- a/boring-sys/deps/boringssl-fips +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 853ca1ea1168dff08011e5d42d94609cc0ca2e27 diff --git a/boring-sys/patches/boring-pq.patch b/boring-sys/patches/boring-pq.patch index 384880044..fb5df8b9e 100644 --- a/boring-sys/patches/boring-pq.patch +++ b/boring-sys/patches/boring-pq.patch @@ -66,141 +66,26 @@ Cf RTG-2076 RTG-2051 RTG-2508 RTG-2707 RTG-2607 RTG-3239 delete mode 100644 src/crypto/kyber/kyber_test.cc delete mode 100644 src/crypto/kyber/kyber_tests.txt -diff --git a/BUILD.generated.bzl b/BUILD.generated.bzl -index 738e1055f..9466757a2 100644 ---- a/BUILD.generated.bzl -+++ b/BUILD.generated.bzl -@@ -253,7 +253,6 @@ crypto_internal_headers = [ - "src/crypto/fipsmodule/tls/internal.h", - "src/crypto/hrss/internal.h", - "src/crypto/internal.h", -- "src/crypto/kyber/internal.h", - "src/crypto/lhash/internal.h", - "src/crypto/obj/obj_dat.h", - "src/crypto/pkcs7/internal.h", -@@ -382,8 +381,8 @@ crypto_sources = [ - "src/crypto/fipsmodule/fips_shared_support.c", - "src/crypto/hpke/hpke.c", - "src/crypto/hrss/hrss.c", -- "src/crypto/kyber/keccak.c", -- "src/crypto/kyber/kyber.c", -+ "src/crypto/kyber/kyber512.c", -+ "src/crypto/kyber/kyber768.c", - "src/crypto/lhash/lhash.c", - "src/crypto/mem.c", - "src/crypto/obj/obj.c", -diff --git a/BUILD.generated_tests.bzl b/BUILD.generated_tests.bzl -index 92dec1e01..8f70dedc0 100644 ---- a/BUILD.generated_tests.bzl -+++ b/BUILD.generated_tests.bzl -@@ -40,7 +40,6 @@ test_support_sources = [ - "src/crypto/fipsmodule/tls/internal.h", - "src/crypto/hrss/internal.h", - "src/crypto/internal.h", -- "src/crypto/kyber/internal.h", - "src/crypto/lhash/internal.h", - "src/crypto/obj/obj_dat.h", - "src/crypto/pkcs7/internal.h", -@@ -124,7 +123,6 @@ crypto_test_sources = [ - "src/crypto/hpke/hpke_test.cc", - "src/crypto/hrss/hrss_test.cc", - "src/crypto/impl_dispatch_test.cc", -- "src/crypto/kyber/kyber_test.cc", - "src/crypto/lhash/lhash_test.cc", - "src/crypto/obj/obj_test.cc", - "src/crypto/pem/pem_test.cc", -@@ -218,8 +216,6 @@ crypto_test_data = [ - "src/crypto/fipsmodule/rand/ctrdrbg_vectors.txt", - "src/crypto/hmac_extra/hmac_tests.txt", - "src/crypto/hpke/hpke_test_vectors.txt", -- "src/crypto/kyber/keccak_tests.txt", -- "src/crypto/kyber/kyber_tests.txt", - "src/crypto/pkcs8/test/empty_password.p12", - "src/crypto/pkcs8/test/no_encryption.p12", - "src/crypto/pkcs8/test/nss.p12", -diff --git a/CMakeLists.txt b/CMakeLists.txt -index faed2befa..931c0e3a8 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -375,8 +375,8 @@ add_library( - src/crypto/fipsmodule/fips_shared_support.c - src/crypto/hpke/hpke.c - src/crypto/hrss/hrss.c -- src/crypto/kyber/keccak.c -- src/crypto/kyber/kyber.c -+ src/crypto/kyber/kyber512.c -+ src/crypto/kyber/kyber768.c - src/crypto/lhash/lhash.c - src/crypto/mem.c - src/crypto/obj/obj.c -diff --git a/sources.json b/sources.json -index 4c0048e1d..f6ea5c40f 100644 ---- a/sources.json -+++ b/sources.json -@@ -111,8 +111,8 @@ - "src/crypto/fipsmodule/fips_shared_support.c", - "src/crypto/hpke/hpke.c", - "src/crypto/hrss/hrss.c", -- "src/crypto/kyber/keccak.c", -- "src/crypto/kyber/kyber.c", -+ "src/crypto/kyber/kyber512.c", -+ "src/crypto/kyber/kyber768.c", - "src/crypto/lhash/lhash.c", - "src/crypto/mem.c", - "src/crypto/obj/obj.c", -@@ -549,7 +549,6 @@ - "src/crypto/hpke/hpke_test.cc", - "src/crypto/hrss/hrss_test.cc", - "src/crypto/impl_dispatch_test.cc", -- "src/crypto/kyber/kyber_test.cc", - "src/crypto/lhash/lhash_test.cc", - "src/crypto/obj/obj_test.cc", - "src/crypto/pem/pem_test.cc", -@@ -634,8 +633,6 @@ - "src/crypto/fipsmodule/rand/ctrdrbg_vectors.txt", - "src/crypto/hmac_extra/hmac_tests.txt", - "src/crypto/hpke/hpke_test_vectors.txt", -- "src/crypto/kyber/keccak_tests.txt", -- "src/crypto/kyber/kyber_tests.txt", - "src/crypto/pkcs8/test/empty_password.p12", - "src/crypto/pkcs8/test/no_encryption.p12", - "src/crypto/pkcs8/test/nss.p12", -@@ -1060,4 +1057,4 @@ - "urandom_test": [ - "src/crypto/fipsmodule/rand/urandom_test.cc" - ] --} -\ No newline at end of file -+} -diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt -index cdb5ddca1..2052fa791 100644 ---- a/src/crypto/CMakeLists.txt -+++ b/src/crypto/CMakeLists.txt -@@ -170,8 +170,8 @@ add_library( - ex_data.c +diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt +index a594b9e9d..ed468237f 100644 +--- a/crypto/CMakeLists.txt ++++ b/crypto/CMakeLists.txt +@@ -176,7 +176,8 @@ add_library( hpke/hpke.c hrss/hrss.c -- kyber/keccak.c + keccak/keccak.c - kyber/kyber.c + kyber/kyber512.c + kyber/kyber768.c lhash/lhash.c mem.c obj/obj.c -@@ -400,7 +400,6 @@ add_executable( - hmac_extra/hmac_test.cc - hrss/hrss_test.cc - impl_dispatch_test.cc -- kyber/kyber_test.cc - lhash/lhash_test.cc - obj/obj_test.cc - pem/pem_test.cc -diff --git a/src/crypto/kyber/internal.h b/src/crypto/kyber/internal.h +diff --git a/crypto/kyber/internal.h b/crypto/kyber/internal.h deleted file mode 100644 -index b3bfa86b8..000000000 ---- a/src/crypto/kyber/internal.h +index b11211726..000000000 +--- a/crypto/kyber/internal.h +++ /dev/null -@@ -1,91 +0,0 @@ +@@ -1,60 +0,0 @@ -/* Copyright (c) 2023, Google Inc. - * - * Permission to use, copy, modify, and/or distribute this software for any @@ -235,37 +120,6 @@ index b3bfa86b8..000000000 -// necessary to generate a key. -#define KYBER_GENERATE_KEY_ENTROPY 64 - --struct BORINGSSL_keccak_st { -- uint64_t state[25]; -- size_t rate_bytes; -- size_t offset; --}; -- --enum boringssl_keccak_config_t { -- boringssl_sha3_256, -- boringssl_sha3_512, -- boringssl_shake128, -- boringssl_shake256, --}; -- --// BORINGSSL_keccak hashes |in_len| bytes from |in| and writes |out_len| bytes --// of output to |out|. If the |config| specifies a fixed-output function, like --// SHA3-256, then |out_len| must be the correct length for that function. --OPENSSL_EXPORT void BORINGSSL_keccak(uint8_t *out, size_t out_len, -- const uint8_t *in, size_t in_len, -- enum boringssl_keccak_config_t config); -- --// BORINGSSL_keccak_init absorbs |in_len| bytes from |in| and sets up |ctx| for --// squeezing. The |config| must specify a SHAKE variant, otherwise callers --// should use |BORINGSSL_keccak|. --OPENSSL_EXPORT void BORINGSSL_keccak_init( -- struct BORINGSSL_keccak_st *ctx, const uint8_t *in, size_t in_len, -- enum boringssl_keccak_config_t config); -- --// BORINGSSL_keccak_squeeze writes |out_len| bytes to |out| from |ctx|. --OPENSSL_EXPORT void BORINGSSL_keccak_squeeze(struct BORINGSSL_keccak_st *ctx, -- uint8_t *out, size_t out_len); -- -// KYBER_generate_key_external_entropy is a deterministic function to create a -// pair of Kyber768 keys, using the supplied entropy. The entropy needs to be -// uniformly random generated. This function is should only be used for tests, @@ -292,221 +146,11 @@ index b3bfa86b8..000000000 -#endif - -#endif // OPENSSL_HEADER_CRYPTO_KYBER_INTERNAL_H -diff --git a/src/crypto/kyber/keccak.c b/src/crypto/kyber/keccak.c -deleted file mode 100644 -index f1c012d11..000000000 ---- a/src/crypto/kyber/keccak.c -+++ /dev/null -@@ -1,204 +0,0 @@ --/* Copyright (c) 2023, Google Inc. -- * -- * Permission to use, copy, modify, and/or distribute this software for any -- * purpose with or without fee is hereby granted, provided that the above -- * copyright notice and this permission notice appear in all copies. -- * -- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -- --#include -- --#include --#include -- --#include "../internal.h" --#include "./internal.h" -- -- --// keccak_f implements the Keccak-1600 permutation as described at --// https://keccak.team/keccak_specs_summary.html. Each lane is represented as a --// 64-bit value and the 5×5 lanes are stored as an array in row-major order. --static void keccak_f(uint64_t state[25]) { -- static const int kNumRounds = 24; -- for (int round = 0; round < kNumRounds; round++) { -- // θ step -- uint64_t c[5]; -- for (int x = 0; x < 5; x++) { -- c[x] = state[x] ^ state[x + 5] ^ state[x + 10] ^ state[x + 15] ^ -- state[x + 20]; -- } -- -- for (int x = 0; x < 5; x++) { -- const uint64_t d = c[(x + 4) % 5] ^ CRYPTO_rotl_u64(c[(x + 1) % 5], 1); -- for (int y = 0; y < 5; y++) { -- state[y * 5 + x] ^= d; -- } -- } -- -- // ρ and π steps. -- // -- // These steps involve a mapping of the state matrix. Each input point, -- // (x,y), is rotated and written to the point (y, 2x + 3y). In the Keccak -- // pseudo-code a separate array is used because an in-place operation would -- // overwrite some values that are subsequently needed. However, the mapping -- // forms a trail through 24 of the 25 values so we can do it in place with -- // only a single temporary variable. -- // -- // Start with (1, 0). The value here will be mapped and end up at (0, 2). -- // That value will end up at (2, 1), then (1, 2), and so on. After 24 -- // steps, 24 of the 25 values have been hit (as this mapping is injective) -- // and the sequence will repeat. All that remains is to handle the element -- // at (0, 0), but the rotation for that element is zero, and it goes to (0, -- // 0), so we can ignore it. -- static const uint8_t kIndexes[24] = {10, 7, 11, 17, 18, 3, 5, 16, -- 8, 21, 24, 4, 15, 23, 19, 13, -- 12, 2, 20, 14, 22, 9, 6, 1}; -- static const uint8_t kRotations[24] = {1, 3, 6, 10, 15, 21, 28, 36, -- 45, 55, 2, 14, 27, 41, 56, 8, -- 25, 43, 62, 18, 39, 61, 20, 44}; -- uint64_t prev_value = state[1]; -- for (int i = 0; i < 24; i++) { -- const uint64_t value = CRYPTO_rotl_u64(prev_value, kRotations[i]); -- const size_t index = kIndexes[i]; -- prev_value = state[index]; -- state[index] = value; -- } -- -- // χ step -- for (int y = 0; y < 5; y++) { -- const int row_index = 5 * y; -- const uint64_t orig_x0 = state[row_index]; -- const uint64_t orig_x1 = state[row_index + 1]; -- state[row_index] ^= ~orig_x1 & state[row_index + 2]; -- state[row_index + 1] ^= ~state[row_index + 2] & state[row_index + 3]; -- state[row_index + 2] ^= ~state[row_index + 3] & state[row_index + 4]; -- state[row_index + 3] ^= ~state[row_index + 4] & orig_x0; -- state[row_index + 4] ^= ~orig_x0 & orig_x1; -- } -- -- // ι step -- // -- // From https://keccak.team/files/Keccak-reference-3.0.pdf, section -- // 1.2, the round constants are based on the output of a LFSR. Thus, as -- // suggested in the appendix of of -- // https://keccak.team/keccak_specs_summary.html, the values are -- // simply encoded here. -- static const uint64_t kRoundConstants[24] = { -- 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, -- 0x8000000080008000, 0x000000000000808b, 0x0000000080000001, -- 0x8000000080008081, 0x8000000000008009, 0x000000000000008a, -- 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, -- 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, -- 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, -- 0x000000000000800a, 0x800000008000000a, 0x8000000080008081, -- 0x8000000000008080, 0x0000000080000001, 0x8000000080008008, -- }; -- -- state[0] ^= kRoundConstants[round]; -- } --} -- --static void keccak_init(struct BORINGSSL_keccak_st *ctx, -- size_t *out_required_out_len, const uint8_t *in, -- size_t in_len, enum boringssl_keccak_config_t config) { -- size_t capacity_bytes; -- uint8_t terminator; -- switch (config) { -- case boringssl_sha3_256: -- capacity_bytes = 512 / 8; -- *out_required_out_len = 32; -- terminator = 0x06; -- break; -- case boringssl_sha3_512: -- capacity_bytes = 1024 / 8; -- *out_required_out_len = 64; -- terminator = 0x06; -- break; -- case boringssl_shake128: -- capacity_bytes = 256 / 8; -- *out_required_out_len = 0; -- terminator = 0x1f; -- break; -- case boringssl_shake256: -- capacity_bytes = 512 / 8; -- *out_required_out_len = 0; -- terminator = 0x1f; -- break; -- default: -- abort(); -- } -- -- OPENSSL_memset(ctx, 0, sizeof(*ctx)); -- ctx->rate_bytes = 200 - capacity_bytes; -- assert(ctx->rate_bytes % 8 == 0); -- const size_t rate_words = ctx->rate_bytes / 8; -- -- while (in_len >= ctx->rate_bytes) { -- for (size_t i = 0; i < rate_words; i++) { -- ctx->state[i] ^= CRYPTO_load_u64_le(in + 8 * i); -- } -- keccak_f(ctx->state); -- in += ctx->rate_bytes; -- in_len -= ctx->rate_bytes; -- } -- -- // XOR the final block. Accessing |ctx->state| as a |uint8_t*| is allowed by -- // strict aliasing because we require |uint8_t| to be a character type. -- uint8_t *state_bytes = (uint8_t *)ctx->state; -- assert(in_len < ctx->rate_bytes); -- for (size_t i = 0; i < in_len; i++) { -- state_bytes[i] ^= in[i]; -- } -- state_bytes[in_len] ^= terminator; -- state_bytes[ctx->rate_bytes - 1] ^= 0x80; -- keccak_f(ctx->state); --} -- --void BORINGSSL_keccak(uint8_t *out, size_t out_len, const uint8_t *in, -- size_t in_len, enum boringssl_keccak_config_t config) { -- struct BORINGSSL_keccak_st ctx; -- size_t required_out_len; -- keccak_init(&ctx, &required_out_len, in, in_len, config); -- if (required_out_len != 0 && out_len != required_out_len) { -- abort(); -- } -- BORINGSSL_keccak_squeeze(&ctx, out, out_len); --} -- --void BORINGSSL_keccak_init(struct BORINGSSL_keccak_st *ctx, const uint8_t *in, -- size_t in_len, -- enum boringssl_keccak_config_t config) { -- size_t required_out_len; -- keccak_init(ctx, &required_out_len, in, in_len, config); -- if (required_out_len != 0) { -- abort(); -- } --} -- --void BORINGSSL_keccak_squeeze(struct BORINGSSL_keccak_st *ctx, uint8_t *out, -- size_t out_len) { -- // Accessing |ctx->state| as a |uint8_t*| is allowed by strict aliasing -- // because we require |uint8_t| to be a character type. -- const uint8_t *state_bytes = (const uint8_t *)ctx->state; -- while (out_len) { -- size_t remaining = ctx->rate_bytes - ctx->offset; -- size_t todo = out_len; -- if (todo > remaining) { -- todo = remaining; -- } -- OPENSSL_memcpy(out, &state_bytes[ctx->offset], todo); -- out += todo; -- out_len -= todo; -- ctx->offset += todo; -- if (ctx->offset == ctx->rate_bytes) { -- keccak_f(ctx->state); -- ctx->offset = 0; -- } -- } --} -diff --git a/src/crypto/kyber/kyber.c b/src/crypto/kyber/kyber.c -index 776c085f9..ccb5b3d9b 100644 ---- a/src/crypto/kyber/kyber.c -+++ b/src/crypto/kyber/kyber.c -@@ -1,833 +1,2426 @@ +diff --git a/crypto/kyber/kyber.c b/crypto/kyber/kyber.c +index d3ea02090..ccb5b3d9b 100644 +--- a/crypto/kyber/kyber.c ++++ b/crypto/kyber/kyber.c +@@ -1,835 +1,2426 @@ -/* Copyright (c) 2023, Google Inc. - * - * Permission to use, copy, modify, and/or distribute this software for any @@ -547,17 +191,17 @@ index 776c085f9..ccb5b3d9b 100644 +// implementation or https://github.com/cloudflare/circl/tree/main/pke/kyber +// +// - Option to keep A stored in private key. - --#include ++ +#ifndef KYBER_K +#error "Don't compile this file direcly" +#endif --#include --#include -+#include + #include +#include +-#include +-#include +- -#include -#include +#include @@ -565,9 +209,29 @@ index 776c085f9..ccb5b3d9b 100644 +#include #include "../internal.h" +-#include "../keccak/internal.h" -#include "./internal.h" -- -- ++ ++#if (KYBER_K == 2) ++#define KYBER_NAMESPACE(s) KYBER512_##s ++#elif (KYBER_K == 3) ++#define KYBER_NAMESPACE(s) KYBER768_##s ++#elif (KYBER_K == 4) ++#define KYBER_NAMESPACE(s) KYBER1024_##s ++#else ++#error "KYBER_K must be in {2,3,4}" ++#endif ++ ++#define public_key KYBER_NAMESPACE(public_key) ++#define private_key KYBER_NAMESPACE(private_key) ++ ++#define generate_key KYBER_NAMESPACE(generate_key) ++#define encap KYBER_NAMESPACE(encap) ++#define decap KYBER_NAMESPACE(decap) ++#define marshal_public_key KYBER_NAMESPACE(marshal_public_key) ++#define parse_public_key KYBER_NAMESPACE(parse_public_key) + + -// See -// https://pq-crystals.org/kyber/data/kyber-specification-round3-20210804.pdf - @@ -602,9 +266,10 @@ index 776c085f9..ccb5b3d9b 100644 -} matrix; - -// This bit of Python will be referenced in some of the following comments: --// + // -// p = 3329 --// ++// params.h + // -// def bitreverse(i): -// ret = 0 -// for n in range(7): @@ -613,7 +278,9 @@ index 776c085f9..ccb5b3d9b 100644 -// ret |= bit -// i >>= 1 -// return ret -- ++#define KYBER_N 256 ++#define KYBER_Q 3329 + -// kNTTRoots = [pow(17, bitreverse(i), p) for i in range(128)] -static const uint16_t kNTTRoots[128] = { - 1, 1729, 2580, 3289, 2642, 630, 1897, 848, 1062, 1919, 193, 797, @@ -627,110 +294,6 @@ index 776c085f9..ccb5b3d9b 100644 - 1584, 2298, 2037, 3220, 375, 2549, 2090, 1645, 1063, 319, 2773, 757, - 2099, 561, 2466, 2594, 2804, 1092, 403, 1026, 1143, 2150, 2775, 886, - 1722, 1212, 1874, 1029, 2110, 2935, 885, 2154, --}; - --// kInverseNTTRoots = [pow(17, -bitreverse(i), p) for i in range(128)] --static const uint16_t kInverseNTTRoots[128] = { -- 1, 1600, 40, 749, 2481, 1432, 2699, 687, 1583, 2760, 69, 543, -- 2532, 3136, 1410, 2267, 2508, 1355, 450, 936, 447, 2794, 1235, 1903, -- 1996, 1089, 3273, 283, 1853, 1990, 882, 3033, 2419, 2102, 219, 855, -- 2681, 1848, 712, 682, 927, 1795, 461, 1891, 2877, 2522, 1894, 1010, -- 1414, 2009, 3296, 464, 2697, 816, 1352, 2679, 1274, 1052, 1025, 2132, -- 1573, 76, 2998, 3040, 1175, 2444, 394, 1219, 2300, 1455, 2117, 1607, -- 2443, 554, 1179, 2186, 2303, 2926, 2237, 525, 735, 863, 2768, 1230, -- 2572, 556, 3010, 2266, 1684, 1239, 780, 2954, 109, 1292, 1031, 1745, -- 2688, 3061, 992, 2596, 941, 892, 1021, 2390, 642, 1868, 2377, 1482, -- 1540, 540, 1678, 1626, 279, 314, 1173, 2573, 3096, 48, 667, 1920, -- 2229, 1041, 2606, 1692, 680, 2746, 568, 3312, --}; -+#if (KYBER_K == 2) -+#define KYBER_NAMESPACE(s) KYBER512_##s -+#elif (KYBER_K == 3) -+#define KYBER_NAMESPACE(s) KYBER768_##s -+#elif (KYBER_K == 4) -+#define KYBER_NAMESPACE(s) KYBER1024_##s -+#else -+#error "KYBER_K must be in {2,3,4}" -+#endif - --// kModRoots = [pow(17, 2*bitreverse(i) + 1, p) for i in range(128)] --static const uint16_t kModRoots[128] = { -- 17, 3312, 2761, 568, 583, 2746, 2649, 680, 1637, 1692, 723, 2606, -- 2288, 1041, 1100, 2229, 1409, 1920, 2662, 667, 3281, 48, 233, 3096, -- 756, 2573, 2156, 1173, 3015, 314, 3050, 279, 1703, 1626, 1651, 1678, -- 2789, 540, 1789, 1540, 1847, 1482, 952, 2377, 1461, 1868, 2687, 642, -- 939, 2390, 2308, 1021, 2437, 892, 2388, 941, 733, 2596, 2337, 992, -- 268, 3061, 641, 2688, 1584, 1745, 2298, 1031, 2037, 1292, 3220, 109, -- 375, 2954, 2549, 780, 2090, 1239, 1645, 1684, 1063, 2266, 319, 3010, -- 2773, 556, 757, 2572, 2099, 1230, 561, 2768, 2466, 863, 2594, 735, -- 2804, 525, 1092, 2237, 403, 2926, 1026, 2303, 1143, 2186, 2150, 1179, -- 2775, 554, 886, 2443, 1722, 1607, 1212, 2117, 1874, 1455, 1029, 2300, -- 2110, 1219, 2935, 394, 885, 2444, 2154, 1175, --}; -+#define public_key KYBER_NAMESPACE(public_key) -+#define private_key KYBER_NAMESPACE(private_key) - --// reduce_once reduces 0 <= x < 2*kPrime, mod kPrime. --static uint16_t reduce_once(uint16_t x) { -- assert(x < 2 * kPrime); -- const uint16_t subtracted = x - kPrime; -- uint16_t mask = 0u - (subtracted >> 15); -- // On Aarch64, omitting a |value_barrier_u16| results in a 2x speedup of Kyber -- // overall and Clang still produces constant-time code using `csel`. On other -- // platforms & compilers on godbolt that we care about, this code also -- // produces constant-time output. -- return (mask & x) | (~mask & subtracted); --} -- --// constant time reduce x mod kPrime using Barrett reduction. x must be less --// than kPrime + 2×kPrime². --static uint16_t reduce(uint32_t x) { -- assert(x < kPrime + 2u * kPrime * kPrime); -- uint64_t product = (uint64_t)x * kBarrettMultiplier; -- uint32_t quotient = product >> kBarrettShift; -- uint32_t remainder = x - quotient * kPrime; -- return reduce_once(remainder); --} -- --static void scalar_zero(scalar *out) { OPENSSL_memset(out, 0, sizeof(*out)); } -- --static void vector_zero(vector *out) { OPENSSL_memset(out, 0, sizeof(*out)); } -- --// In place number theoretic transform of a given scalar. --// Note that Kyber's kPrime 3329 does not have a 512th root of unity, so this --// transform leaves off the last iteration of the usual FFT code, with the 128 --// relevant roots of unity being stored in |kNTTRoots|. This means the output --// should be seen as 128 elements in GF(3329^2), with the coefficients of the --// elements being consecutive entries in |s->c|. --static void scalar_ntt(scalar *s) { -- int offset = DEGREE; -- // `int` is used here because using `size_t` throughout caused a ~5% slowdown -- // with Clang 14 on Aarch64. -- for (int step = 1; step < DEGREE / 2; step <<= 1) { -- offset >>= 1; -- int k = 0; -- for (int i = 0; i < step; i++) { -- const uint32_t step_root = kNTTRoots[i + step]; -- for (int j = k; j < k + offset; j++) { -- uint16_t odd = reduce(step_root * s->c[j + offset]); -- uint16_t even = s->c[j]; -- s->c[j] = reduce_once(odd + even); -- s->c[j + offset] = reduce_once(even - odd + kPrime); -- } -- k += 2 * offset; -+#define generate_key KYBER_NAMESPACE(generate_key) -+#define encap KYBER_NAMESPACE(encap) -+#define decap KYBER_NAMESPACE(decap) -+#define marshal_public_key KYBER_NAMESPACE(marshal_public_key) -+#define parse_public_key KYBER_NAMESPACE(parse_public_key) -+ -+ -+// -+// params.h -+// -+#define KYBER_N 256 -+#define KYBER_Q 3329 -+ +#define KYBER_SYMBYTES 32 /* size in bytes of hashes, and seeds */ +#define KYBER_SSBYTES 32 /* size in bytes of shared key */ + @@ -1112,9 +675,9 @@ index 776c085f9..ccb5b3d9b 100644 + a = (d >> (6*j+0)) & 0x7; + b = (d >> (6*j+3)) & 0x7; + r->coeffs[4*i+j] = a - b; - } - } - } ++ } ++ } ++} +#endif + +static void poly_cbd_eta1(poly *r, const uint8_t buf[KYBER_ETA1*KYBER_N/4]) @@ -1127,10 +690,7 @@ index 776c085f9..ccb5b3d9b 100644 +#error "This implementation requires eta1 in {2,3}" +#endif +} - --static void vector_ntt(vector *a) { -- for (int i = 0; i < RANK; i++) { -- scalar_ntt(&a->v[i]); ++ +static void poly_cbd_eta2(poly *r, const uint8_t buf[KYBER_ETA2*KYBER_N/4]) +{ +#if KYBER_ETA2 == 2 @@ -1157,8 +717,21 @@ index 776c085f9..ccb5b3d9b 100644 + 5, 69, 37, 101, 21, 85, 53, 117, 13, 77, 45, 109, 29, 93, 61, 125, + 3, 67, 35, 99, 19, 83, 51, 115, 11, 75, 43, 107, 27, 91, 59, 123, + 7, 71, 39, 103, 23, 87, 55, 119, 15, 79, 47, 111, 31, 95, 63, 127 -+}; -+ + }; + +-// kInverseNTTRoots = [pow(17, -bitreverse(i), p) for i in range(128)] +-static const uint16_t kInverseNTTRoots[128] = { +- 1, 1600, 40, 749, 2481, 1432, 2699, 687, 1583, 2760, 69, 543, +- 2532, 3136, 1410, 2267, 2508, 1355, 450, 936, 447, 2794, 1235, 1903, +- 1996, 1089, 3273, 283, 1853, 1990, 882, 3033, 2419, 2102, 219, 855, +- 2681, 1848, 712, 682, 927, 1795, 461, 1891, 2877, 2522, 1894, 1010, +- 1414, 2009, 3296, 464, 2697, 816, 1352, 2679, 1274, 1052, 1025, 2132, +- 1573, 76, 2998, 3040, 1175, 2444, 394, 1219, 2300, 1455, 2117, 1607, +- 2443, 554, 1179, 2186, 2303, 2926, 2237, 525, 735, 863, 2768, 1230, +- 2572, 556, 3010, 2266, 1684, 1239, 780, 2954, 109, 1292, 1031, 1745, +- 2688, 3061, 992, 2596, 941, 892, 1021, 2390, 642, 1868, 2377, 1482, +- 1540, 540, 1678, 1626, 279, 314, 1173, 2573, 3096, 48, 667, 1920, +- 2229, 1041, 2606, 1692, 680, 2746, 568, 3312, +void init_ntt() { + unsigned int i; + int16_t tmp[128]; @@ -1173,8 +746,8 @@ index 776c085f9..ccb5b3d9b 100644 + zetas[i] -= KYBER_Q; + if(zetas[i] < -KYBER_Q/2) + zetas[i] += KYBER_Q; - } - } ++ } ++} +*/ + +static const int16_t zetas[128] = { @@ -1194,41 +767,35 @@ index 776c085f9..ccb5b3d9b 100644 + -1215, -136, 1218, -1335, -874, 220, -1187, -1659, + -1185, -1530, -1278, 794, -1510, -854, -870, 478, + -108, -308, 996, 991, 958, -1460, 1522, 1628 -+}; -+ -+/************************************************* -+* Name: fqmul -+* -+* Description: Multiplication followed by Montgomery reduction -+* -+* Arguments: - int16_t a: first factor -+* - int16_t b: second factor -+* -+* Returns 16-bit integer congruent to a*b*R^{-1} mod q -+**************************************************/ -+static int16_t fqmul(int16_t a, int16_t b) { -+ return montgomery_reduce((int32_t)a*b); -+} + }; --// In place inverse number theoretic transform of a given scalar, with pairs of --// entries of s->v being interpreted as elements of GF(3329^2). Just as with the --// number theoretic transform, this leaves off the first step of the normal iFFT --// to account for the fact that 3329 does not have a 512th root of unity, using --// the precomputed 128 roots of unity stored in |kInverseNTTRoots|. --static void scalar_inverse_ntt(scalar *s) { -- int step = DEGREE / 2; -- // `int` is used here because using `size_t` throughout caused a ~5% slowdown -- // with Clang 14 on Aarch64. -- for (int offset = 2; offset < DEGREE; offset <<= 1) { -- step >>= 1; -- int k = 0; -- for (int i = 0; i < step; i++) { -- uint32_t step_root = kInverseNTTRoots[i + step]; -- for (int j = k; j < k + offset; j++) { -- uint16_t odd = s->c[j + offset]; -- uint16_t even = s->c[j]; -- s->c[j] = reduce_once(odd + even); -- s->c[j + offset] = reduce(step_root * (even - odd + kPrime)); +-// kModRoots = [pow(17, 2*bitreverse(i) + 1, p) for i in range(128)] +-static const uint16_t kModRoots[128] = { +- 17, 3312, 2761, 568, 583, 2746, 2649, 680, 1637, 1692, 723, 2606, +- 2288, 1041, 1100, 2229, 1409, 1920, 2662, 667, 3281, 48, 233, 3096, +- 756, 2573, 2156, 1173, 3015, 314, 3050, 279, 1703, 1626, 1651, 1678, +- 2789, 540, 1789, 1540, 1847, 1482, 952, 2377, 1461, 1868, 2687, 642, +- 939, 2390, 2308, 1021, 2437, 892, 2388, 941, 733, 2596, 2337, 992, +- 268, 3061, 641, 2688, 1584, 1745, 2298, 1031, 2037, 1292, 3220, 109, +- 375, 2954, 2549, 780, 2090, 1239, 1645, 1684, 1063, 2266, 319, 3010, +- 2773, 556, 757, 2572, 2099, 1230, 561, 2768, 2466, 863, 2594, 735, +- 2804, 525, 1092, 2237, 403, 2926, 1026, 2303, 1143, 2186, 2150, 1179, +- 2775, 554, 886, 2443, 1722, 1607, 1212, 2117, 1874, 1455, 1029, 2300, +- 2110, 1219, 2935, 394, 885, 2444, 2154, 1175, ++/************************************************* ++* Name: fqmul ++* ++* Description: Multiplication followed by Montgomery reduction ++* ++* Arguments: - int16_t a: first factor ++* - int16_t b: second factor ++* ++* Returns 16-bit integer congruent to a*b*R^{-1} mod q ++**************************************************/ ++static int16_t fqmul(int16_t a, int16_t b) { ++ return montgomery_reduce((int32_t)a*b); ++} ++ +/************************************************* +* Name: ntt +* @@ -1249,18 +816,11 @@ index 776c085f9..ccb5b3d9b 100644 + t = fqmul(zeta, r[j + len]); + r[j + len] = r[j] - t; + r[j] = r[j] + t; - } -- k += 2 * offset; - } - } -- for (int i = 0; i < DEGREE; i++) { -- s->c[i] = reduce(s->c[i] * kInverseDegree); -- } - } - --static void vector_inverse_ntt(vector *a) { -- for (int i = 0; i < RANK; i++) { -- scalar_inverse_ntt(&a->v[i]); ++ } ++ } ++ } ++} ++ +/************************************************* +* Name: invntt_tomont +* @@ -1286,7 +846,7 @@ index 776c085f9..ccb5b3d9b 100644 + r[j + len] = fqmul(zeta, r[j + len]); + } + } - } ++ } + + for(j = 0; j < 256; j++) + r[j] = fqmul(r[j], f); @@ -1310,11 +870,8 @@ index 776c085f9..ccb5b3d9b 100644 + r[0] += fqmul(a[0], b[0]); + r[1] = fqmul(a[0], b[1]); + r[1] += fqmul(a[1], b[0]); - } - --static void scalar_add(scalar *lhs, const scalar *rhs) { -- for (int i = 0; i < DEGREE; i++) { -- lhs->c[i] = reduce_once(lhs->c[i] + rhs->c[i]); ++} ++ +// +// poly.c +// @@ -1353,7 +910,7 @@ index 776c085f9..ccb5b3d9b 100644 + r[2] = t[4] | (t[5] << 4); + r[3] = t[6] | (t[7] << 4); + r += 4; - } ++ } +#elif (KYBER_POLYCOMPRESSEDBYTES == 160) + for(i=0;ic[i] = reduce_once(lhs->c[i] - rhs->c[i] + kPrime); ++} ++ +/************************************************* +* Name: poly_decompress +* @@ -1418,29 +972,12 @@ index 776c085f9..ccb5b3d9b 100644 + + for(j=0;j<8;j++) + r->coeffs[8*i+j] = ((uint32_t)(t[j] & 31)*KYBER_Q + 16) >> 5; - } ++ } +#else +#error "KYBER_POLYCOMPRESSEDBYTES needs to be in {128, 160}" +#endif - } - --// Multiplying two scalars in the number theoretically transformed state. Since --// 3329 does not have a 512th root of unity, this means we have to interpret --// the 2*ith and (2*i+1)th entries of the scalar as elements of GF(3329)[X]/(X^2 --// - 17^(2*bitreverse(i)+1)) The value of 17^(2*bitreverse(i)+1) mod 3329 is --// stored in the precomputed |kModRoots| table. Note that our Barrett transform --// only allows us to multipy two reduced numbers together, so we need some --// intermediate reduction steps, even if an uint64_t could hold 3 multiplied --// numbers. --static void scalar_mult(scalar *out, const scalar *lhs, const scalar *rhs) { -- for (int i = 0; i < DEGREE / 2; i++) { -- uint32_t real_real = (uint32_t)lhs->c[2 * i] * rhs->c[2 * i]; -- uint32_t img_img = (uint32_t)lhs->c[2 * i + 1] * rhs->c[2 * i + 1]; -- uint32_t real_img = (uint32_t)lhs->c[2 * i] * rhs->c[2 * i + 1]; -- uint32_t img_real = (uint32_t)lhs->c[2 * i + 1] * rhs->c[2 * i]; -- out->c[2 * i] = -- reduce(real_real + (uint32_t)reduce(img_img) * kModRoots[i]); -- out->c[2 * i + 1] = reduce(img_real + real_img); ++} ++ +/************************************************* +* Name: poly_tobytes +* @@ -1464,12 +1001,9 @@ index 776c085f9..ccb5b3d9b 100644 + r[3*i+0] = (t0 >> 0); + r[3*i+1] = (t0 >> 8) | (t1 << 4); + r[3*i+2] = (t1 >> 4); - } - } - --static void vector_add(vector *lhs, const vector *rhs) { -- for (int i = 0; i < RANK; i++) { -- scalar_add(&lhs->v[i], &rhs->v[i]); ++ } ++} ++ +/************************************************* +* Name: poly_frombytes +* @@ -1486,16 +1020,9 @@ index 776c085f9..ccb5b3d9b 100644 + for(i=0;icoeffs[2*i] = ((a[3*i+0] >> 0) | ((uint16_t)a[3*i+1] << 8)) & 0xFFF; + r->coeffs[2*i+1] = ((a[3*i+1] >> 4) | ((uint16_t)a[3*i+2] << 4)) & 0xFFF; - } - } - --static void matrix_mult(vector *out, const matrix *m, const vector *a) { -- vector_zero(out); -- for (int i = 0; i < RANK; i++) { -- for (int j = 0; j < RANK; j++) { -- scalar product; -- scalar_mult(&product, &m->v[i][j], &a->v[j]); -- scalar_add(&out->v[i], &product); ++ } ++} ++ +/************************************************* +* Name: poly_frommsg +* @@ -1517,18 +1044,10 @@ index 776c085f9..ccb5b3d9b 100644 + for(j=0;j<8;j++) { + mask = -(int16_t)value_barrier_u32((msg[i] >> j)&1); + r->coeffs[8*i+j] = mask & ((KYBER_Q+1)/2); - } - } - } - --static void matrix_mult_transpose(vector *out, const matrix *m, -- const vector *a) { -- vector_zero(out); -- for (int i = 0; i < RANK; i++) { -- for (int j = 0; j < RANK; j++) { -- scalar product; -- scalar_mult(&product, &m->v[j][i], &a->v[j]); -- scalar_add(&out->v[i], &product); ++ } ++ } ++} ++ +/************************************************* +* Name: poly_tomsg +* @@ -1552,18 +1071,10 @@ index 776c085f9..ccb5b3d9b 100644 + t >>= 28; + t &= 1; + msg[i] |= t << j; - } - } - } - --static void scalar_inner_product(scalar *out, const vector *lhs, -- const vector *rhs) { -- scalar_zero(out); -- for (int i = 0; i < RANK; i++) { -- scalar product; -- scalar_mult(&product, &lhs->v[i], &rhs->v[i]); -- scalar_add(out, &product); -- } ++ } ++ } ++} ++ +/************************************************* +* Name: poly_getnoise_eta1 +* @@ -1581,32 +1092,8 @@ index 776c085f9..ccb5b3d9b 100644 + uint8_t buf[KYBER_ETA1*KYBER_N/4]; + prf(buf, sizeof(buf), seed, nonce); + poly_cbd_eta1(r, buf); - } - --// Algorithm 1 of the Kyber spec. Rejection samples a Keccak stream to get --// uniformly distributed elements. This is used for matrix expansion and only --// operates on public inputs. --static void scalar_from_keccak_vartime(scalar *out, -- struct BORINGSSL_keccak_st *keccak_ctx) { -- assert(keccak_ctx->offset == 0); -- assert(keccak_ctx->rate_bytes == 168); -- static_assert(168 % 3 == 0, "block and coefficient boundaries do not align"); -- -- int done = 0; -- while (done < DEGREE) { -- uint8_t block[168]; -- BORINGSSL_keccak_squeeze(keccak_ctx, block, sizeof(block)); -- for (size_t i = 0; i < sizeof(block) && done < DEGREE; i += 3) { -- uint16_t d1 = block[i] + 256 * (block[i + 1] % 16); -- uint16_t d2 = block[i + 1] / 16 + 16 * block[i + 2]; -- if (d1 < kPrime) { -- out->c[done++] = d1; -- } -- if (d2 < kPrime && done < DEGREE) { -- out->c[done++] = d2; -- } -- } -- } ++} ++ +/************************************************* +* Name: poly_getnoise_eta2 +* @@ -1624,34 +1111,8 @@ index 776c085f9..ccb5b3d9b 100644 + uint8_t buf[KYBER_ETA2*KYBER_N/4]; + prf(buf, sizeof(buf), seed, nonce); + poly_cbd_eta2(r, buf); - } - --// Algorithm 2 of the Kyber spec, with eta fixed to two and the PRF call --// included. Creates binominally distributed elements by sampling 2*|eta| bits, --// and setting the coefficient to the count of the first bits minus the count of --// the second bits, resulting in a centered binomial distribution. Since eta is --// two this gives -2/2 with a probability of 1/16, -1/1 with probability 1/4, --// and 0 with probability 3/8. --static void scalar_centered_binomial_distribution_eta_2_with_prf( -- scalar *out, const uint8_t input[33]) { -- uint8_t entropy[128]; -- static_assert(sizeof(entropy) == 2 * /*kEta=*/2 * DEGREE / 8, ""); -- BORINGSSL_keccak(entropy, sizeof(entropy), input, 33, boringssl_shake256); -- -- for (int i = 0; i < DEGREE; i += 2) { -- uint8_t byte = entropy[i / 2]; -- -- uint16_t value = kPrime; -- value += (byte & 1) + ((byte >> 1) & 1); -- value -= ((byte >> 2) & 1) + ((byte >> 3) & 1); -- out->c[i] = reduce_once(value); -- -- byte >>= 4; -- value = kPrime; -- value += (byte & 1) + ((byte >> 1) & 1); -- value -= ((byte >> 2) & 1) + ((byte >> 3) & 1); -- out->c[i + 1] = reduce_once(value); -- } ++} ++ + +/************************************************* +* Name: poly_ntt @@ -1666,19 +1127,8 @@ index 776c085f9..ccb5b3d9b 100644 +{ + ntt(r->coeffs); + poly_reduce(r); - } - --// Generates a secret vector by using --// |scalar_centered_binomial_distribution_eta_2_with_prf|, using the given seed --// appending and incrementing |counter| for entry of the vector. --static void vector_generate_secret_eta_2(vector *out, uint8_t *counter, -- const uint8_t seed[32]) { -- uint8_t input[33]; -- OPENSSL_memcpy(input, seed, 32); -- for (int i = 0; i < RANK; i++) { -- input[32] = (*counter)++; -- scalar_centered_binomial_distribution_eta_2_with_prf(&out->v[i], input); -- } ++} ++ +/************************************************* +* Name: poly_invntt_tomont +* @@ -1691,21 +1141,8 @@ index 776c085f9..ccb5b3d9b 100644 +static void poly_invntt_tomont(poly *r) +{ + invntt(r->coeffs); - } - --// Expands the matrix of a seed for key generation and for encaps-CPA. --static void matrix_expand(matrix *out, const uint8_t rho[32]) { -- uint8_t input[34]; -- OPENSSL_memcpy(input, rho, 32); -- for (int i = 0; i < RANK; i++) { -- for (int j = 0; j < RANK; j++) { -- input[32] = i; -- input[33] = j; -- struct BORINGSSL_keccak_st keccak_ctx; -- BORINGSSL_keccak_init(&keccak_ctx, input, sizeof(input), -- boringssl_shake128); -- scalar_from_keccak_vartime(&out->v[i][j], &keccak_ctx); -- } ++} ++ +/************************************************* +* Name: poly_basemul_montgomery +* @@ -1721,35 +1158,9 @@ index 776c085f9..ccb5b3d9b 100644 + for(i=0;icoeffs[4*i], &a->coeffs[4*i], &b->coeffs[4*i], zetas[64+i]); + basemul(&r->coeffs[4*i+2], &a->coeffs[4*i+2], &b->coeffs[4*i+2], -zetas[64+i]); - } - } - --static const uint8_t kMasks[8] = {0x01, 0x03, 0x07, 0x0f, -- 0x1f, 0x3f, 0x7f, 0xff}; -- --static void scalar_encode(uint8_t *out, const scalar *s, int bits) { -- assert(bits <= (int)sizeof(*s->c) * 8 && bits != 1); -- -- uint8_t out_byte = 0; -- int out_byte_bits = 0; -- -- for (int i = 0; i < DEGREE; i++) { -- uint16_t element = s->c[i]; -- int element_bits_done = 0; -- -- while (element_bits_done < bits) { -- int chunk_bits = bits - element_bits_done; -- int out_bits_remaining = 8 - out_byte_bits; -- if (chunk_bits >= out_bits_remaining) { -- chunk_bits = out_bits_remaining; -- out_byte |= (element & kMasks[chunk_bits - 1]) << out_byte_bits; -- *out = out_byte; -- out++; -- out_byte_bits = 0; -- out_byte = 0; -- } else { -- out_byte |= (element & kMasks[chunk_bits - 1]) << out_byte_bits; -- out_byte_bits += chunk_bits; ++ } ++} ++ +/************************************************* +* Name: poly_tomont +* @@ -1844,10 +1255,8 @@ index 776c085f9..ccb5b3d9b 100644 + d0 *= 645084; + d0 >>= 31; + t[k] = d0 & 0x7ff; - } - -- element_bits_done += chunk_bits; -- element >>= chunk_bits; ++ } ++ + r[ 0] = (t[0] >> 0); + r[ 1] = (t[0] >> 8) | (t[1] << 3); + r[ 2] = (t[1] >> 5) | (t[2] << 6); @@ -1860,8 +1269,8 @@ index 776c085f9..ccb5b3d9b 100644 + r[ 9] = (t[6] >> 6) | (t[7] << 5); + r[10] = (t[7] >> 3); + r += 11; - } - } ++ } ++ } +#elif (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 320)) + uint16_t t[4]; + for(i=0;i>= 32; + t[k] = d0 & 0x3ff; + } - -- if (out_byte_bits > 0) { -- *out = out_byte; ++ + r[0] = (t[0] >> 0); + r[1] = (t[0] >> 8) | (t[1] << 2); + r[2] = (t[1] >> 6) | (t[2] << 4); @@ -1886,18 +1293,12 @@ index 776c085f9..ccb5b3d9b 100644 + r[4] = (t[3] >> 2); + r += 5; + } - } ++ } +#else +#error "KYBER_POLYVECCOMPRESSEDBYTES needs to be in {320*KYBER_K, 352*KYBER_K}" +#endif - } - --// scalar_encode_1 is |scalar_encode| specialised for |bits| == 1. --static void scalar_encode_1(uint8_t out[32], const scalar *s) { -- for (int i = 0; i < DEGREE; i += 8) { -- uint8_t out_byte = 0; -- for (int j = 0; j < 8; j++) { -- out_byte |= (s->c[i + j] & 1) << j; ++} ++ +/************************************************* +* Name: polyvec_decompress +* @@ -1942,22 +1343,13 @@ index 776c085f9..ccb5b3d9b 100644 + + for(k=0;k<4;k++) + r->vec[i].coeffs[4*j+k] = ((uint32_t)(t[k] & 0x3FF)*KYBER_Q + 512) >> 10; - } -- *out = out_byte; -- out++; - } ++ } ++ } +#else +#error "KYBER_POLYVECCOMPRESSEDBYTES needs to be in {320*KYBER_K, 352*KYBER_K}" +#endif - } - --// Encodes an entire vector into 32*|RANK|*|bits| bytes. Note that since 256 --// (DEGREE) is divisible by 8, the individual vector entries will always fill a --// whole number of bytes, so we do not need to worry about bit packing here. --static void vector_encode(uint8_t *out, const vector *a, int bits) { -- for (int i = 0; i < RANK; i++) { -- scalar_encode(out + i * bits * DEGREE / 8, &a->v[i], bits); -- } ++} ++ +/************************************************* +* Name: polyvec_tobytes +* @@ -1972,13 +1364,8 @@ index 776c085f9..ccb5b3d9b 100644 + unsigned int i; + for(i=0;ivec[i]); - } - --// scalar_decode parses |DEGREE * bits| bits from |in| into |DEGREE| values in --// |out|. It returns one on success and zero if any parsed value is >= --// |kPrime|. --static int scalar_decode(scalar *out, const uint8_t *in, int bits) { -- assert(bits <= (int)sizeof(*out->c) * 8 && bits != 1); ++} ++ +/************************************************* +* Name: polyvec_frombytes +* @@ -1995,9 +1382,7 @@ index 776c085f9..ccb5b3d9b 100644 + for(i=0;ivec[i], a+i*KYBER_POLYBYTES); +} - -- uint8_t in_byte = 0; -- int in_byte_bits_left = 0; ++ +/************************************************* +* Name: polyvec_ntt +* @@ -2011,10 +1396,7 @@ index 776c085f9..ccb5b3d9b 100644 + for(i=0;ivec[i]); +} - -- for (int i = 0; i < DEGREE; i++) { -- uint16_t element = 0; -- int element_bits_done = 0; ++ +/************************************************* +* Name: polyvec_invntt_tomont +* @@ -2029,13 +1411,7 @@ index 776c085f9..ccb5b3d9b 100644 + for(i=0;ivec[i]); +} - -- while (element_bits_done < bits) { -- if (in_byte_bits_left == 0) { -- in_byte = *in; -- in++; -- in_byte_bits_left = 8; -- } ++ +/************************************************* +* Name: polyvec_basemul_acc_montgomery +* @@ -2056,17 +1432,10 @@ index 776c085f9..ccb5b3d9b 100644 + poly_basemul_montgomery(&t, &a->vec[i], &b->vec[i]); + poly_add(r, r, &t); + } - -- int chunk_bits = bits - element_bits_done; -- if (chunk_bits > in_byte_bits_left) { -- chunk_bits = in_byte_bits_left; -- } ++ + poly_reduce(r); +} - -- element |= (in_byte & kMasks[chunk_bits - 1]) << element_bits_done; -- in_byte_bits_left -= chunk_bits; -- in_byte >>= chunk_bits; ++ +/************************************************* +* Name: polyvec_reduce +* @@ -2082,9 +1451,7 @@ index 776c085f9..ccb5b3d9b 100644 + for(i=0;ivec[i]); +} - -- element_bits_done += chunk_bits; -- } ++ +/************************************************* +* Name: polyvec_add +* @@ -2100,12 +1467,7 @@ index 776c085f9..ccb5b3d9b 100644 + for(i=0;ivec[i], &a->vec[i], &b->vec[i]); +} - -- if (element >= kPrime) { -- return 0; -- } -- out->c[i] = element; -- } ++ +// +// indcpa.c +// @@ -2154,21 +1516,12 @@ index 776c085f9..ccb5b3d9b 100644 + + if(verify(repacked, packedpk, KYBER_POLYVECBYTES) != 0) + return 0; - ++ + for(i=0;ic[i + j] = in_byte & 1; -- in_byte >>= 1; -- } ++ return 1; ++} ++ +/************************************************* +* Name: pack_sk +* @@ -2259,17 +1612,11 @@ index 776c085f9..ccb5b3d9b 100644 + r[ctr++] = val0; + if(ctr < len && val1 < KYBER_Q) + r[ctr++] = val1; - } ++ } + + return ctr; - } - --// Decodes 32*|RANK|*|bits| bytes from |in| into |out|. It returns one on --// success or zero if any parsed value is >= |kPrime|. --static int vector_decode(vector *out, const uint8_t *in, int bits) { -- for (int i = 0; i < RANK; i++) { -- if (!scalar_decode(&out->v[i], in + i * bits * DEGREE / 8, bits)) { -- return 0; ++} ++ +#define gen_a(A,B) gen_matrix(A,B,0) +#define gen_at(A,B) gen_matrix(A,B,1) + @@ -2313,52 +1660,10 @@ index 776c085f9..ccb5b3d9b 100644 + buflen = off + XOF_BLOCKBYTES; + ctr += rej_uniform(a[i].vec[j].coeffs + ctr, KYBER_N - ctr, buf, buflen); + } - } - } -- return 1; - } - --// Compresses (lossily) an input |x| mod 3329 into |bits| many bits by grouping --// numbers close to each other together. The formula used is --// round(2^|bits|/kPrime*x) mod 2^|bits|. --// Uses Barrett reduction to achieve constant time. Since we need both the --// remainder (for rounding) and the quotient (as the result), we cannot use --// |reduce| here, but need to do the Barrett reduction directly. --static uint16_t compress(uint16_t x, int bits) { -- uint32_t product = (uint32_t)x << bits; -- uint32_t quotient = ((uint64_t)product * kBarrettMultiplier) >> kBarrettShift; -- uint32_t remainder = product - quotient * kPrime; -- -- // Adjust the quotient to round correctly: -- // 0 <= remainder <= kHalfPrime round to 0 -- // kHalfPrime < remainder <= kPrime + kHalfPrime round to 1 -- // kPrime + kHalfPrime < remainder < 2 * kPrime round to 2 -- assert(remainder < 2u * kPrime); -- quotient += 1 & constant_time_lt_w(kHalfPrime, remainder); -- quotient += 1 & constant_time_lt_w(kPrime + kHalfPrime, remainder); -- return quotient & ((1 << bits) - 1); --} -- --// Decompresses |x| by using an equi-distant representative. The formula is --// round(kPrime/2^|bits|*x). Note that 2^|bits| being the divisor allows us to --// implement this logic using only bit operations. --static uint16_t decompress(uint16_t x, int bits) { -- uint32_t product = (uint32_t)x * kPrime; -- uint32_t power = 1 << bits; -- // This is |product| % power, since |power| is a power of 2. -- uint32_t remainder = product & (power - 1); -- // This is |product| / power, since |power| is a power of 2. -- uint32_t lower = product >> bits; -- // The rounding logic works since the first half of numbers mod |power| have a -- // 0 as first bit, and the second half has a 1 as first bit, since |power| is -- // a power of 2. As a 12 bit number, |remainder| is always positive, so we -- // will shift in 0s for a right shift. -- return lower + (remainder >> (bits - 1)); --} -- --static void scalar_compress(scalar *s, int bits) { -- for (int i = 0; i < DEGREE; i++) { -- s->c[i] = compress(s->c[i], bits); ++ } ++ } ++} ++ +/************************************************* +* Name: indcpa_keypair +* @@ -2398,19 +1703,15 @@ index 776c085f9..ccb5b3d9b 100644 + for(i=0;ic[i] = decompress(s->c[i], bits); -- } ++} ++ +/************************************************* +* Name: indcpa_enc +* @@ -2469,12 +1770,8 @@ index 776c085f9..ccb5b3d9b 100644 + + pack_ciphertext(c, &b, &v); + return 1; - } - --static void vector_compress(vector *a, int bits) { -- for (int i = 0; i < RANK; i++) { -- scalar_compress(&a->v[i], bits); -- } ++} ++ +/************************************************* +* Name: indcpa_dec +* @@ -2506,12 +1803,8 @@ index 776c085f9..ccb5b3d9b 100644 + poly_reduce(&mp); + + poly_tomsg(m, &mp); - } - --static void vector_decompress(vector *a, int bits) { -- for (int i = 0; i < RANK; i++) { -- scalar_decompress(&a->v[i], bits); -- } ++} ++ +// +// fips202.c +// @@ -2541,13 +1834,8 @@ index 776c085f9..ccb5b3d9b 100644 + r |= (uint64_t)x[i] << 8*i; + + return r; - } - --struct public_key { -- vector t; -- uint8_t rho[32]; -- uint8_t public_key_hash[32]; -- matrix m; ++} ++ +/************************************************* +* Name: store64 +* @@ -2591,13 +1879,16 @@ index 776c085f9..ccb5b3d9b 100644 + (uint64_t)0x8000000080008008ULL }; --static struct public_key *public_key_from_external( -- const struct KYBER_public_key *external) { -- static_assert(sizeof(struct KYBER_public_key) >= sizeof(struct public_key), -- "Kyber public key is too small"); -- static_assert(alignof(struct KYBER_public_key) >= alignof(struct public_key), -- "Kyber public key align incorrect"); -- return (struct public_key *)external; +-// reduce_once reduces 0 <= x < 2*kPrime, mod kPrime. +-static uint16_t reduce_once(uint16_t x) { +- assert(x < 2 * kPrime); +- const uint16_t subtracted = x - kPrime; +- uint16_t mask = 0u - (subtracted >> 15); +- // On Aarch64, omitting a |value_barrier_u16| results in a 2x speedup of Kyber +- // overall and Clang still produces constant-time code using `csel`. On other +- // platforms & compilers on godbolt that we care about, this code also +- // produces constant-time output. +- return (mask & x) | (~mask & subtracted); +/************************************************* +* Name: KeccakF1600_StatePermute +* @@ -2869,36 +2160,17 @@ index 776c085f9..ccb5b3d9b 100644 + state[24] = Asu; } --struct private_key { -- struct public_key pub; -- vector s; -- uint8_t fo_failure_secret[32]; --}; +-// constant time reduce x mod kPrime using Barrett reduction. x must be less +-// than kPrime + 2×kPrime². +-static uint16_t reduce(uint32_t x) { +- assert(x < kPrime + 2u * kPrime * kPrime); +- uint64_t product = (uint64_t)x * kBarrettMultiplier; +- uint32_t quotient = (uint32_t)(product >> kBarrettShift); +- uint32_t remainder = x - quotient * kPrime; +- return reduce_once(remainder); +-} --static struct private_key *private_key_from_external( -- const struct KYBER_private_key *external) { -- static_assert(sizeof(struct KYBER_private_key) >= sizeof(struct private_key), -- "Kyber private key too small"); -- static_assert( -- alignof(struct KYBER_private_key) >= alignof(struct private_key), -- "Kyber private key align incorrect"); -- return (struct private_key *)external; --} -- --// Calls |KYBER_generate_key_external_entropy| with random bytes from --// |RAND_bytes|. --void KYBER_generate_key(uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], -- struct KYBER_private_key *out_private_key) { -- uint8_t entropy[KYBER_GENERATE_KEY_ENTROPY]; -- RAND_bytes(entropy, sizeof(entropy)); -- KYBER_generate_key_external_entropy(out_encoded_public_key, out_private_key, -- entropy); --} -- --static int kyber_marshal_public_key(CBB *out, const struct public_key *pub) { -- uint8_t *vector_output; -- if (!CBB_add_space(out, &vector_output, kEncodedVectorSize)) { -- return 0; +-static void scalar_zero(scalar *out) { OPENSSL_memset(out, 0, sizeof(*out)); } +/************************************************* +* Name: keccak_squeeze +* @@ -2921,20 +2193,41 @@ index 776c085f9..ccb5b3d9b 100644 + unsigned int r) +{ + unsigned int i; -+ + +-static void vector_zero(vector *out) { OPENSSL_memset(out, 0, sizeof(*out)); } +- +-// In place number theoretic transform of a given scalar. +-// Note that Kyber's kPrime 3329 does not have a 512th root of unity, so this +-// transform leaves off the last iteration of the usual FFT code, with the 128 +-// relevant roots of unity being stored in |kNTTRoots|. This means the output +-// should be seen as 128 elements in GF(3329^2), with the coefficients of the +-// elements being consecutive entries in |s->c|. +-static void scalar_ntt(scalar *s) { +- int offset = DEGREE; +- // `int` is used here because using `size_t` throughout caused a ~5% slowdown +- // with Clang 14 on Aarch64. +- for (int step = 1; step < DEGREE / 2; step <<= 1) { +- offset >>= 1; +- int k = 0; +- for (int i = 0; i < step; i++) { +- const uint32_t step_root = kNTTRoots[i + step]; +- for (int j = k; j < k + offset; j++) { +- uint16_t odd = reduce(step_root * s->c[j + offset]); +- uint16_t even = s->c[j]; +- s->c[j] = reduce_once(odd + even); +- s->c[j + offset] = reduce_once(even - odd + kPrime); +- } +- k += 2 * offset; + while(outlen) { + if(pos == r) { + KeccakF1600_StatePermute(s); + pos = 0; -+ } + } + for(i=pos;i < r && i < pos+outlen; i++) + *out++ = s[i/8] >> 8*(i%8); + outlen -= i-pos; + pos = i; - } -- vector_encode(vector_output, &pub->t, kLog2Prime); -- if (!CBB_add_bytes(out, pub->rho, sizeof(pub->rho))) { -- return 0; ++ } + + return pos; +} @@ -2966,8 +2259,7 @@ index 776c085f9..ccb5b3d9b 100644 + inlen -= r-pos; + KeccakF1600_StatePermute(s); + pos = 0; - } -- return 1; ++ } + + for(i=pos;ipub.rho, hashed, sizeof(priv->pub.rho)); -- matrix_expand(&priv->pub.m, rho); -- uint8_t counter = 0; -- vector_generate_secret_eta_2(&priv->s, &counter, sigma); -- vector_ntt(&priv->s); -- vector error; -- vector_generate_secret_eta_2(&error, &counter, sigma); -- vector_ntt(&error); -- matrix_mult_transpose(&priv->pub.t, &priv->pub.m, &priv->s); -- vector_add(&priv->pub.t, &error); -- -- CBB cbb; -- CBB_init_fixed(&cbb, out_encoded_public_key, KYBER_PUBLIC_KEY_BYTES); -- if (!kyber_marshal_public_key(&cbb, &priv->pub)) { -- abort(); ++} ++ + +/************************************************* +* Name: keccak_absorb_once @@ -3048,138 +2313,8 @@ index 776c085f9..ccb5b3d9b 100644 + in += r; + inlen -= r; + KeccakF1600_StatePermute(s); - } - -- BORINGSSL_keccak(priv->pub.public_key_hash, sizeof(priv->pub.public_key_hash), -- out_encoded_public_key, KYBER_PUBLIC_KEY_BYTES, -- boringssl_sha3_256); -- OPENSSL_memcpy(priv->fo_failure_secret, entropy + 32, 32); --} -- --void KYBER_public_from_private(struct KYBER_public_key *out_public_key, -- const struct KYBER_private_key *private_key) { -- struct public_key *const pub = public_key_from_external(out_public_key); -- const struct private_key *const priv = private_key_from_external(private_key); -- *pub = priv->pub; --} -- --// Algorithm 5 of the Kyber spec. Encrypts a message with given randomness to --// the ciphertext in |out|. Without applying the Fujisaki-Okamoto transform this --// would not result in a CCA secure scheme, since lattice schemes are vulnerable --// to decryption failure oracles. --static void encrypt_cpa(uint8_t out[KYBER_CIPHERTEXT_BYTES], -- const struct public_key *pub, const uint8_t message[32], -- const uint8_t randomness[32]) { -- uint8_t counter = 0; -- vector secret; -- vector_generate_secret_eta_2(&secret, &counter, randomness); -- vector_ntt(&secret); -- vector error; -- vector_generate_secret_eta_2(&error, &counter, randomness); -- uint8_t input[33]; -- OPENSSL_memcpy(input, randomness, 32); -- input[32] = counter; -- scalar scalar_error; -- scalar_centered_binomial_distribution_eta_2_with_prf(&scalar_error, input); -- vector u; -- matrix_mult(&u, &pub->m, &secret); -- vector_inverse_ntt(&u); -- vector_add(&u, &error); -- scalar v; -- scalar_inner_product(&v, &pub->t, &secret); -- scalar_inverse_ntt(&v); -- scalar_add(&v, &scalar_error); -- scalar expanded_message; -- scalar_decode_1(&expanded_message, message); -- scalar_decompress(&expanded_message, 1); -- scalar_add(&v, &expanded_message); -- vector_compress(&u, kDU); -- vector_encode(out, &u, kDU); -- scalar_compress(&v, kDV); -- scalar_encode(out + kCompressedVectorSize, &v, kDV); --} -- --// Calls KYBER_encap_external_entropy| with random bytes from |RAND_bytes| --void KYBER_encap(uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], -- uint8_t *out_shared_secret, size_t out_shared_secret_len, -- const struct KYBER_public_key *public_key) { -- uint8_t entropy[KYBER_ENCAP_ENTROPY]; -- RAND_bytes(entropy, KYBER_ENCAP_ENTROPY); -- KYBER_encap_external_entropy(out_ciphertext, out_shared_secret, -- out_shared_secret_len, public_key, entropy); --} -- --// Algorithm 8 of the Kyber spec, safe for line 2 of the spec. The spec there --// hashes the output of the system's random number generator, since the FO --// transform will reveal it to the decrypting party. There is no reason to do --// this when a secure random number generator is used. When an insecure random --// number generator is used, the caller should switch to a secure one before --// calling this method. --void KYBER_encap_external_entropy( -- uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], uint8_t *out_shared_secret, -- size_t out_shared_secret_len, const struct KYBER_public_key *public_key, -- const uint8_t entropy[KYBER_ENCAP_ENTROPY]) { -- const struct public_key *pub = public_key_from_external(public_key); -- uint8_t input[64]; -- OPENSSL_memcpy(input, entropy, KYBER_ENCAP_ENTROPY); -- OPENSSL_memcpy(input + KYBER_ENCAP_ENTROPY, pub->public_key_hash, -- sizeof(input) - KYBER_ENCAP_ENTROPY); -- uint8_t prekey_and_randomness[64]; -- BORINGSSL_keccak(prekey_and_randomness, sizeof(prekey_and_randomness), input, -- sizeof(input), boringssl_sha3_512); -- encrypt_cpa(out_ciphertext, pub, entropy, prekey_and_randomness + 32); -- BORINGSSL_keccak(prekey_and_randomness + 32, 32, out_ciphertext, -- KYBER_CIPHERTEXT_BYTES, boringssl_sha3_256); -- BORINGSSL_keccak(out_shared_secret, out_shared_secret_len, -- prekey_and_randomness, sizeof(prekey_and_randomness), -- boringssl_shake256); --} -- --// Algorithm 6 of the Kyber spec. --static void decrypt_cpa(uint8_t out[32], const struct private_key *priv, -- const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES]) { -- vector u; -- vector_decode(&u, ciphertext, kDU); -- vector_decompress(&u, kDU); -- vector_ntt(&u); -- scalar v; -- scalar_decode(&v, ciphertext + kCompressedVectorSize, kDV); -- scalar_decompress(&v, kDV); -- scalar mask; -- scalar_inner_product(&mask, &priv->s, &u); -- scalar_inverse_ntt(&mask); -- scalar_sub(&v, &mask); -- scalar_compress(&v, 1); -- scalar_encode_1(out, &v); --} -- --// Algorithm 9 of the Kyber spec, performing the FO transform by running --// encrypt_cpa on the decrypted message. The spec does not allow the decryption --// failure to be passed on to the caller, and instead returns a result that is --// deterministic but unpredictable to anyone without knowledge of the private --// key. --void KYBER_decap(uint8_t *out_shared_secret, size_t out_shared_secret_len, -- const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES], -- const struct KYBER_private_key *private_key) { -- const struct private_key *priv = private_key_from_external(private_key); -- uint8_t decrypted[64]; -- decrypt_cpa(decrypted, priv, ciphertext); -- OPENSSL_memcpy(decrypted + 32, priv->pub.public_key_hash, -- sizeof(decrypted) - 32); -- uint8_t prekey_and_randomness[64]; -- BORINGSSL_keccak(prekey_and_randomness, sizeof(prekey_and_randomness), -- decrypted, sizeof(decrypted), boringssl_sha3_512); -- uint8_t expected_ciphertext[KYBER_CIPHERTEXT_BYTES]; -- encrypt_cpa(expected_ciphertext, &priv->pub, decrypted, -- prekey_and_randomness + 32); -- uint8_t mask = -- constant_time_eq_int_8(CRYPTO_memcmp(ciphertext, expected_ciphertext, -- sizeof(expected_ciphertext)), -- 0); -- uint8_t input[64]; -- for (int i = 0; i < 32; i++) { -- input[i] = constant_time_select_8(mask, prekey_and_randomness[i], -- priv->fo_failure_secret[i]); ++ } ++ + for(i=0;iv[i]); +- } + +/************************************************* +* Name: shake128_absorb_once @@ -3239,34 +2371,53 @@ index 776c085f9..ccb5b3d9b 100644 + state->pos = SHAKE128_RATE; } --// kyber_parse_public_key_no_hash parses |in| into |pub| but doesn't calculate --// the value of |pub->public_key_hash|. --static int kyber_parse_public_key_no_hash(struct public_key *pub, CBS *in) { -- CBS t_bytes; -- if (!CBS_get_bytes(in, &t_bytes, kEncodedVectorSize) || -- !vector_decode(&pub->t, CBS_data(&t_bytes), kLog2Prime) || -- !CBS_copy_bytes(in, pub->rho, sizeof(pub->rho))) { -- return 0; -- } -- matrix_expand(&pub->m, pub->rho); -- return 1; -+/************************************************* -+* Name: shake128_squeezeblocks -+* -+* Description: Squeeze step of SHAKE128 XOF. Squeezes full blocks of -+* SHAKE128_RATE bytes each. Can be called multiple times -+* to keep squeezing. Assumes new block has not yet been -+* started (state->pos = SHAKE128_RATE). -+* -+* Arguments: - uint8_t *out: pointer to output blocks -+* - size_t nblocks: number of blocks to be squeezed (written to output) -+* - keccak_state *s: pointer to input/output Keccak state -+**************************************************/ -+static void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) +-// In place inverse number theoretic transform of a given scalar, with pairs of +-// entries of s->v being interpreted as elements of GF(3329^2). Just as with the +-// number theoretic transform, this leaves off the first step of the normal iFFT +-// to account for the fact that 3329 does not have a 512th root of unity, using +-// the precomputed 128 roots of unity stored in |kInverseNTTRoots|. +-static void scalar_inverse_ntt(scalar *s) { +- int step = DEGREE / 2; +- // `int` is used here because using `size_t` throughout caused a ~5% slowdown +- // with Clang 14 on Aarch64. +- for (int offset = 2; offset < DEGREE; offset <<= 1) { +- step >>= 1; +- int k = 0; +- for (int i = 0; i < step; i++) { +- uint32_t step_root = kInverseNTTRoots[i + step]; +- for (int j = k; j < k + offset; j++) { +- uint16_t odd = s->c[j + offset]; +- uint16_t even = s->c[j]; +- s->c[j] = reduce_once(odd + even); +- s->c[j + offset] = reduce(step_root * (even - odd + kPrime)); +- } +- k += 2 * offset; +- } +- } +- for (int i = 0; i < DEGREE; i++) { +- s->c[i] = reduce(s->c[i] * kInverseDegree); +- } ++/************************************************* ++* Name: shake128_squeezeblocks ++* ++* Description: Squeeze step of SHAKE128 XOF. Squeezes full blocks of ++* SHAKE128_RATE bytes each. Can be called multiple times ++* to keep squeezing. Assumes new block has not yet been ++* started (state->pos = SHAKE128_RATE). ++* ++* Arguments: - uint8_t *out: pointer to output blocks ++* - size_t nblocks: number of blocks to be squeezed (written to output) ++* - keccak_state *s: pointer to input/output Keccak state ++**************************************************/ ++static void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) +{ + keccak_squeezeblocks(out, nblocks, state->s, SHAKE128_RATE); -+} -+ + } + +-static void vector_inverse_ntt(vector *a) { +- for (int i = 0; i < RANK; i++) { +- scalar_inverse_ntt(&a->v[i]); +- } +/************************************************* +* Name: shake256_squeeze +* @@ -3280,8 +2431,12 @@ index 776c085f9..ccb5b3d9b 100644 +static void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state) +{ + state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE256_RATE); -+} -+ + } + +-static void scalar_add(scalar *lhs, const scalar *rhs) { +- for (int i = 0; i < DEGREE; i++) { +- lhs->c[i] = reduce_once(lhs->c[i] + rhs->c[i]); +- } +/************************************************* +* Name: shake256_absorb_once +* @@ -3295,8 +2450,12 @@ index 776c085f9..ccb5b3d9b 100644 +{ + keccak_absorb_once(state->s, SHAKE256_RATE, in, inlen, 0x1F); + state->pos = SHAKE256_RATE; -+} -+ + } + +-static void scalar_sub(scalar *lhs, const scalar *rhs) { +- for (int i = 0; i < DEGREE; i++) { +- lhs->c[i] = reduce_once(lhs->c[i] - rhs->c[i] + kPrime); +- } +/************************************************* +* Name: shake256_squeezeblocks +* @@ -3312,8 +2471,26 @@ index 776c085f9..ccb5b3d9b 100644 +static void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) +{ + keccak_squeezeblocks(out, nblocks, state->s, SHAKE256_RATE); -+} -+ + } + +-// Multiplying two scalars in the number theoretically transformed state. Since +-// 3329 does not have a 512th root of unity, this means we have to interpret +-// the 2*ith and (2*i+1)th entries of the scalar as elements of GF(3329)[X]/(X^2 +-// - 17^(2*bitreverse(i)+1)) The value of 17^(2*bitreverse(i)+1) mod 3329 is +-// stored in the precomputed |kModRoots| table. Note that our Barrett transform +-// only allows us to multipy two reduced numbers together, so we need some +-// intermediate reduction steps, even if an uint64_t could hold 3 multiplied +-// numbers. +-static void scalar_mult(scalar *out, const scalar *lhs, const scalar *rhs) { +- for (int i = 0; i < DEGREE / 2; i++) { +- uint32_t real_real = (uint32_t)lhs->c[2 * i] * rhs->c[2 * i]; +- uint32_t img_img = (uint32_t)lhs->c[2 * i + 1] * rhs->c[2 * i + 1]; +- uint32_t real_img = (uint32_t)lhs->c[2 * i] * rhs->c[2 * i + 1]; +- uint32_t img_real = (uint32_t)lhs->c[2 * i + 1] * rhs->c[2 * i]; +- out->c[2 * i] = +- reduce(real_real + (uint32_t)reduce(img_img) * kModRoots[i]); +- out->c[2 * i + 1] = reduce(img_real + real_img); +- } +/************************************************* +* Name: shake256_absorb +* @@ -3326,8 +2503,12 @@ index 776c085f9..ccb5b3d9b 100644 +static void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen) +{ + state->pos = keccak_absorb(state->s, state->pos, SHAKE256_RATE, in, inlen); -+} -+ + } + +-static void vector_add(vector *lhs, const vector *rhs) { +- for (int i = 0; i < RANK; i++) { +- scalar_add(&lhs->v[i], &rhs->v[i]); +- } +/************************************************* +* Name: shake256_finalize +* @@ -3339,8 +2520,17 @@ index 776c085f9..ccb5b3d9b 100644 +{ + keccak_finalize(state->s, state->pos, SHAKE256_RATE, 0x1F); + state->pos = SHAKE256_RATE; -+} -+ + } + +-static void matrix_mult(vector *out, const matrix *m, const vector *a) { +- vector_zero(out); +- for (int i = 0; i < RANK; i++) { +- for (int j = 0; j < RANK; j++) { +- scalar product; +- scalar_mult(&product, &m->v[i][j], &a->v[j]); +- scalar_add(&out->v[i], &product); +- } +- } +/************************************************* +* Name: keccak_init +* @@ -3353,8 +2543,18 @@ index 776c085f9..ccb5b3d9b 100644 + unsigned int i; + for(i=0;i<25;i++) + s[i] = 0; -+} -+ + } + +-static void matrix_mult_transpose(vector *out, const matrix *m, +- const vector *a) { +- vector_zero(out); +- for (int i = 0; i < RANK; i++) { +- for (int j = 0; j < RANK; j++) { +- scalar product; +- scalar_mult(&product, &m->v[j][i], &a->v[j]); +- scalar_add(&out->v[i], &product); +- } +- } +/************************************************* +* Name: shake256_init +* @@ -3366,8 +2566,16 @@ index 776c085f9..ccb5b3d9b 100644 +{ + keccak_init(state->s); + state->pos = 0; -+} -+ + } + +-static void scalar_inner_product(scalar *out, const vector *lhs, +- const vector *rhs) { +- scalar_zero(out); +- for (int i = 0; i < RANK; i++) { +- scalar product; +- scalar_mult(&product, &lhs->v[i], &rhs->v[i]); +- scalar_add(out, &product); +- } + +/************************************************* +* Name: shake256 @@ -3390,8 +2598,16 @@ index 776c085f9..ccb5b3d9b 100644 + outlen -= nblocks*SHAKE256_RATE; + out += nblocks*SHAKE256_RATE; + shake256_squeeze(out, outlen, &state); -+} -+ + } + +-// Algorithm 1 of the Kyber spec. Rejection samples a Keccak stream to get +-// uniformly distributed elements. This is used for matrix expansion and only +-// operates on public inputs. +-static void scalar_from_keccak_vartime(scalar *out, +- struct BORINGSSL_keccak_st *keccak_ctx) { +- assert(keccak_ctx->squeeze_offset == 0); +- assert(keccak_ctx->rate_bytes == 168); +- static_assert(168 % 3 == 0, "block and coefficient boundaries do not align"); +/************************************************* +* Name: sha3_256 +* @@ -3405,13 +2621,39 @@ index 776c085f9..ccb5b3d9b 100644 +{ + unsigned int i; + uint64_t s[25]; -+ + +- int done = 0; +- while (done < DEGREE) { +- uint8_t block[168]; +- BORINGSSL_keccak_squeeze(keccak_ctx, block, sizeof(block)); +- for (size_t i = 0; i < sizeof(block) && done < DEGREE; i += 3) { +- uint16_t d1 = block[i] + 256 * (block[i + 1] % 16); +- uint16_t d2 = block[i + 1] / 16 + 16 * block[i + 2]; +- if (d1 < kPrime) { +- out->c[done++] = d1; +- } +- if (d2 < kPrime && done < DEGREE) { +- out->c[done++] = d2; +- } +- } +- } + keccak_absorb_once(s, SHA3_256_RATE, in, inlen, 0x06); + KeccakF1600_StatePermute(s); + for(i=0;i<4;i++) + store64(h+8*i,s[i]); -+} -+ + } + +-// Algorithm 2 of the Kyber spec, with eta fixed to two and the PRF call +-// included. Creates binominally distributed elements by sampling 2*|eta| bits, +-// and setting the coefficient to the count of the first bits minus the count of +-// the second bits, resulting in a centered binomial distribution. Since eta is +-// two this gives -2/2 with a probability of 1/16, -1/1 with probability 1/4, +-// and 0 with probability 3/8. +-static void scalar_centered_binomial_distribution_eta_2_with_prf( +- scalar *out, const uint8_t input[33]) { +- uint8_t entropy[128]; +- static_assert(sizeof(entropy) == 2 * /*kEta=*/2 * DEGREE / 8, ""); +- BORINGSSL_keccak(entropy, sizeof(entropy), input, 33, boringssl_shake256); +/************************************************* +* Name: sha3_512 +* @@ -3425,13 +2667,38 @@ index 776c085f9..ccb5b3d9b 100644 +{ + unsigned int i; + uint64_t s[25]; -+ + +- for (int i = 0; i < DEGREE; i += 2) { +- uint8_t byte = entropy[i / 2]; +- +- uint16_t value = kPrime; +- value += (byte & 1) + ((byte >> 1) & 1); +- value -= ((byte >> 2) & 1) + ((byte >> 3) & 1); +- out->c[i] = reduce_once(value); +- +- byte >>= 4; +- value = kPrime; +- value += (byte & 1) + ((byte >> 1) & 1); +- value -= ((byte >> 2) & 1) + ((byte >> 3) & 1); +- out->c[i + 1] = reduce_once(value); +- } + keccak_absorb_once(s, SHA3_512_RATE, in, inlen, 0x06); + KeccakF1600_StatePermute(s); + for(i=0;i<8;i++) + store64(h+8*i,s[i]); -+} -+ + } + +-// Generates a secret vector by using +-// |scalar_centered_binomial_distribution_eta_2_with_prf|, using the given seed +-// appending and incrementing |counter| for entry of the vector. +-static void vector_generate_secret_eta_2(vector *out, uint8_t *counter, +- const uint8_t seed[32]) { +- uint8_t input[33]; +- OPENSSL_memcpy(input, seed, 32); +- for (int i = 0; i < RANK; i++) { +- input[32] = (*counter)++; +- scalar_centered_binomial_distribution_eta_2_with_prf(&out->v[i], input); +- } +// +// symmetric-shake.c +// @@ -3460,11 +2727,20 @@ index 776c085f9..ccb5b3d9b 100644 + shake128_absorb_once(state, extseed, sizeof(extseed)); } --int KYBER_parse_public_key(struct KYBER_public_key *public_key, CBS *in) { -- struct public_key *pub = public_key_from_external(public_key); -- CBS orig_in = *in; -- if (!kyber_parse_public_key_no_hash(pub, in) || // -- CBS_len(in) != 0) { +-// Expands the matrix of a seed for key generation and for encaps-CPA. +-static void matrix_expand(matrix *out, const uint8_t rho[32]) { +- uint8_t input[34]; +- OPENSSL_memcpy(input, rho, 32); +- for (int i = 0; i < RANK; i++) { +- for (int j = 0; j < RANK; j++) { +- input[32] = i; +- input[33] = j; +- struct BORINGSSL_keccak_st keccak_ctx; +- BORINGSSL_keccak_init(&keccak_ctx, boringssl_shake128); +- BORINGSSL_keccak_absorb(&keccak_ctx, input, sizeof(input)); +- scalar_from_keccak_vartime(&out->v[i][j], &keccak_ctx); +- } +- } +/************************************************* +* Name: kyber_shake256_prf +* @@ -3484,12 +2760,16 @@ index 776c085f9..ccb5b3d9b 100644 + extkey[KYBER_SYMBYTES] = nonce; + + shake256(out, outlen, extkey, sizeof(extkey)); -+} -+ + } + +-static const uint8_t kMasks[8] = {0x01, 0x03, 0x07, 0x0f, +- 0x1f, 0x3f, 0x7f, 0xff}; +// +// kem.c +// -+ + +-static void scalar_encode(uint8_t *out, const scalar *s, int bits) { +- assert(bits <= (int)sizeof(*s->c) * 8 && bits != 1); +// Modified crypto_kem_keypair to BoringSSL style API +void generate_key(struct public_key *out_pub, struct private_key *out_priv, + const uint8_t seed[KYBER_GENERATE_KEY_BYTES]) @@ -3497,41 +2777,147 @@ index 776c085f9..ccb5b3d9b 100644 + size_t i; + uint8_t* pk = &out_pub->opaque[0]; + uint8_t* sk = &out_priv->opaque[0]; -+ -+ indcpa_keypair(pk, sk, seed); -+ for(i=0;iopaque[0]; -+ uint8_t *ct = out_ciphertext; -+ -+ uint8_t buf[2*KYBER_SYMBYTES]; -+ /* Will contain key, coins */ -+ uint8_t kr[2*KYBER_SYMBYTES]; -+ -+ memcpy(buf, seed, KYBER_SYMBYTES); -+ + +- uint8_t out_byte = 0; +- int out_byte_bits = 0; +- +- for (int i = 0; i < DEGREE; i++) { +- uint16_t element = s->c[i]; +- int element_bits_done = 0; +- +- while (element_bits_done < bits) { +- int chunk_bits = bits - element_bits_done; +- int out_bits_remaining = 8 - out_byte_bits; +- if (chunk_bits >= out_bits_remaining) { +- chunk_bits = out_bits_remaining; +- out_byte |= (element & kMasks[chunk_bits - 1]) << out_byte_bits; +- *out = out_byte; +- out++; +- out_byte_bits = 0; +- out_byte = 0; +- } else { +- out_byte |= (element & kMasks[chunk_bits - 1]) << out_byte_bits; +- out_byte_bits += chunk_bits; +- } +- +- element_bits_done += chunk_bits; +- element >>= chunk_bits; +- } +- } +- +- if (out_byte_bits > 0) { +- *out = out_byte; +- } ++ indcpa_keypair(pk, sk, seed); ++ for(i=0;ic[i + j] & 1) << j; +- } +- *out = out_byte; +- out++; +- } +-} ++// Modified crypto_kem_enc to BoringSSL style API ++int encap(uint8_t out_ciphertext[KYBER_CIPHERTEXTBYTES], ++ uint8_t ss[KYBER_KEY_BYTES], ++ const struct public_key *in_pub, ++ const uint8_t seed[KYBER_ENCAP_BYTES], int mlkem) ++{ ++ const uint8_t *pk = &in_pub->opaque[0]; ++ uint8_t *ct = out_ciphertext; ++ ++ uint8_t buf[2*KYBER_SYMBYTES]; ++ /* Will contain key, coins */ ++ uint8_t kr[2*KYBER_SYMBYTES]; + +-// Encodes an entire vector into 32*|RANK|*|bits| bytes. Note that since 256 +-// (DEGREE) is divisible by 8, the individual vector entries will always fill a +-// whole number of bytes, so we do not need to worry about bit packing here. +-static void vector_encode(uint8_t *out, const vector *a, int bits) { +- for (int i = 0; i < RANK; i++) { +- scalar_encode(out + i * bits * DEGREE / 8, &a->v[i], bits); +- } +-} ++ memcpy(buf, seed, KYBER_SYMBYTES); + +-// scalar_decode parses |DEGREE * bits| bits from |in| into |DEGREE| values in +-// |out|. It returns one on success and zero if any parsed value is >= +-// |kPrime|. +-static int scalar_decode(scalar *out, const uint8_t *in, int bits) { +- assert(bits <= (int)sizeof(*out->c) * 8 && bits != 1); + /* Don't release system RNG output */ + hash_h(buf, buf, KYBER_SYMBYTES); -+ + +- uint8_t in_byte = 0; +- int in_byte_bits_left = 0; + /* Multitarget countermeasure for coins + contributory KEM */ + hash_h(buf+KYBER_SYMBYTES, pk, KYBER_PUBLICKEYBYTES); + hash_g(kr, buf, 2*KYBER_SYMBYTES); -+ + +- for (int i = 0; i < DEGREE; i++) { +- uint16_t element = 0; +- int element_bits_done = 0; + /* coins are in kr+KYBER_SYMBYTES */ + if(!indcpa_enc(ct, buf, pk, kr+KYBER_SYMBYTES)) - return 0; -+ ++ return 0; + +- while (element_bits_done < bits) { +- if (in_byte_bits_left == 0) { +- in_byte = *in; +- in++; +- in_byte_bits_left = 8; +- } +- +- int chunk_bits = bits - element_bits_done; +- if (chunk_bits > in_byte_bits_left) { +- chunk_bits = in_byte_bits_left; +- } +- +- element |= (in_byte & kMasks[chunk_bits - 1]) << element_bits_done; +- in_byte_bits_left -= chunk_bits; +- in_byte >>= chunk_bits; +- +- element_bits_done += chunk_bits; +- } +- +- if (element >= kPrime) { +- return 0; +- } +- out->c[i] = element; +- } +- +- return 1; +-} +- +-// scalar_decode_1 is |scalar_decode| specialised for |bits| == 1. +-static void scalar_decode_1(scalar *out, const uint8_t in[32]) { +- for (int i = 0; i < DEGREE; i += 8) { +- uint8_t in_byte = *in; +- in++; +- for (int j = 0; j < 8; j++) { +- out->c[i + j] = in_byte & 1; +- in_byte >>= 1; +- } +- } +-} +- +-// Decodes 32*|RANK|*|bits| bytes from |in| into |out|. It returns one on +-// success or zero if any parsed value is >= |kPrime|. +-static int vector_decode(vector *out, const uint8_t *in, int bits) { +- for (int i = 0; i < RANK; i++) { +- if (!scalar_decode(&out->v[i], in + i * bits * DEGREE / 8, bits)) { +- return 0; +- } + if (mlkem == 1) { + memcpy(ss, kr, KYBER_SYMBYTES); + } else { @@ -3540,46 +2926,385 @@ index 776c085f9..ccb5b3d9b 100644 + /* hash concatenation of pre-k and H(c) to k */ + kdf(ss, kr, 2*KYBER_SYMBYTES); } + return 1; + } + +-// Compresses (lossily) an input |x| mod 3329 into |bits| many bits by grouping +-// numbers close to each other together. The formula used is +-// round(2^|bits|/kPrime*x) mod 2^|bits|. +-// Uses Barrett reduction to achieve constant time. Since we need both the +-// remainder (for rounding) and the quotient (as the result), we cannot use +-// |reduce| here, but need to do the Barrett reduction directly. +-static uint16_t compress(uint16_t x, int bits) { +- uint32_t shifted = (uint32_t)x << bits; +- uint64_t product = (uint64_t)shifted * kBarrettMultiplier; +- uint32_t quotient = (uint32_t)(product >> kBarrettShift); +- uint32_t remainder = shifted - quotient * kPrime; ++// Modified crypto_kem_decap to BoringSSL style API ++void decap(uint8_t out_shared_key[KYBER_SSBYTES], ++ const struct private_key *in_priv, ++ const uint8_t *ct, size_t ciphertext_len, int mlkem) ++{ ++ uint8_t *ss = out_shared_key; ++ const uint8_t *sk = &in_priv->opaque[0]; + +- // Adjust the quotient to round correctly: +- // 0 <= remainder <= kHalfPrime round to 0 +- // kHalfPrime < remainder <= kPrime + kHalfPrime round to 1 +- // kPrime + kHalfPrime < remainder < 2 * kPrime round to 2 +- assert(remainder < 2u * kPrime); +- quotient += 1 & constant_time_lt_w(kHalfPrime, remainder); +- quotient += 1 & constant_time_lt_w(kPrime + kHalfPrime, remainder); +- return quotient & ((1 << bits) - 1); +-} ++ size_t i; ++ int fail = 1; ++ uint8_t buf[2*KYBER_SYMBYTES]; ++ /* Will contain key, coins */ ++ uint8_t kr[2*KYBER_SYMBYTES]; ++ uint8_t cmp[KYBER_CIPHERTEXTBYTES]; ++ const uint8_t *pk = sk+KYBER_INDCPA_SECRETKEYBYTES; + +-// Decompresses |x| by using an equi-distant representative. The formula is +-// round(kPrime/2^|bits|*x). Note that 2^|bits| being the divisor allows us to +-// implement this logic using only bit operations. +-static uint16_t decompress(uint16_t x, int bits) { +- uint32_t product = (uint32_t)x * kPrime; +- uint32_t power = 1 << bits; +- // This is |product| % power, since |power| is a power of 2. +- uint32_t remainder = product & (power - 1); +- // This is |product| / power, since |power| is a power of 2. +- uint32_t lower = product >> bits; +- // The rounding logic works since the first half of numbers mod |power| have a +- // 0 as first bit, and the second half has a 1 as first bit, since |power| is +- // a power of 2. As a 12 bit number, |remainder| is always positive, so we +- // will shift in 0s for a right shift. +- return lower + (remainder >> (bits - 1)); +-} ++ if (ciphertext_len == KYBER_CIPHERTEXTBYTES) { ++ indcpa_dec(buf, ct, sk); + +-static void scalar_compress(scalar *s, int bits) { +- for (int i = 0; i < DEGREE; i++) { +- s->c[i] = compress(s->c[i], bits); ++ /* Multitarget countermeasure for coins + contributory KEM */ ++ for(i=0;ic[i] = decompress(s->c[i], bits); +- } ++void marshal_public_key(uint8_t out[KYBER_PUBLICKEYBYTES], ++ const struct public_key *in_pub) { ++ memcpy(out, &in_pub->opaque, KYBER_PUBLICKEYBYTES); + } + +-static void vector_compress(vector *a, int bits) { +- for (int i = 0; i < RANK; i++) { +- scalar_compress(&a->v[i], bits); +- } +-} +- +-static void vector_decompress(vector *a, int bits) { +- for (int i = 0; i < RANK; i++) { +- scalar_decompress(&a->v[i], bits); +- } +-} +- +-struct public_key { +- vector t; +- uint8_t rho[32]; +- uint8_t public_key_hash[32]; +- matrix m; +-}; +- +-static struct public_key *public_key_from_external( +- const struct KYBER_public_key *external) { +- static_assert(sizeof(struct KYBER_public_key) >= sizeof(struct public_key), +- "Kyber public key is too small"); +- static_assert(alignof(struct KYBER_public_key) >= alignof(struct public_key), +- "Kyber public key align incorrect"); +- return (struct public_key *)external; +-} +- +-struct private_key { +- struct public_key pub; +- vector s; +- uint8_t fo_failure_secret[32]; +-}; +- +-static struct private_key *private_key_from_external( +- const struct KYBER_private_key *external) { +- static_assert(sizeof(struct KYBER_private_key) >= sizeof(struct private_key), +- "Kyber private key too small"); +- static_assert( +- alignof(struct KYBER_private_key) >= alignof(struct private_key), +- "Kyber private key align incorrect"); +- return (struct private_key *)external; +-} +- +-// Calls |KYBER_generate_key_external_entropy| with random bytes from +-// |RAND_bytes|. +-void KYBER_generate_key(uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], +- struct KYBER_private_key *out_private_key) { +- uint8_t entropy[KYBER_GENERATE_KEY_ENTROPY]; +- RAND_bytes(entropy, sizeof(entropy)); +- KYBER_generate_key_external_entropy(out_encoded_public_key, out_private_key, +- entropy); +-} +- +-static int kyber_marshal_public_key(CBB *out, const struct public_key *pub) { +- uint8_t *vector_output; +- if (!CBB_add_space(out, &vector_output, kEncodedVectorSize)) { +- return 0; +- } +- vector_encode(vector_output, &pub->t, kLog2Prime); +- if (!CBB_add_bytes(out, pub->rho, sizeof(pub->rho))) { +- return 0; +- } +- return 1; +-} +- +-// Algorithms 4 and 7 of the Kyber spec. Algorithms are combined since key +-// generation is not part of the FO transform, and the spec uses Algorithm 7 to +-// specify the actual key format. +-void KYBER_generate_key_external_entropy( +- uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], +- struct KYBER_private_key *out_private_key, +- const uint8_t entropy[KYBER_GENERATE_KEY_ENTROPY]) { +- struct private_key *priv = private_key_from_external(out_private_key); +- uint8_t hashed[64]; +- BORINGSSL_keccak(hashed, sizeof(hashed), entropy, 32, boringssl_sha3_512); +- const uint8_t *const rho = hashed; +- const uint8_t *const sigma = hashed + 32; +- OPENSSL_memcpy(priv->pub.rho, hashed, sizeof(priv->pub.rho)); +- matrix_expand(&priv->pub.m, rho); +- uint8_t counter = 0; +- vector_generate_secret_eta_2(&priv->s, &counter, sigma); +- vector_ntt(&priv->s); +- vector error; +- vector_generate_secret_eta_2(&error, &counter, sigma); +- vector_ntt(&error); +- matrix_mult_transpose(&priv->pub.t, &priv->pub.m, &priv->s); +- vector_add(&priv->pub.t, &error); +- +- CBB cbb; +- CBB_init_fixed(&cbb, out_encoded_public_key, KYBER_PUBLIC_KEY_BYTES); +- if (!kyber_marshal_public_key(&cbb, &priv->pub)) { +- abort(); +- } +- +- BORINGSSL_keccak(priv->pub.public_key_hash, sizeof(priv->pub.public_key_hash), +- out_encoded_public_key, KYBER_PUBLIC_KEY_BYTES, +- boringssl_sha3_256); +- OPENSSL_memcpy(priv->fo_failure_secret, entropy + 32, 32); +-} +- +-void KYBER_public_from_private(struct KYBER_public_key *out_public_key, +- const struct KYBER_private_key *private_key) { +- struct public_key *const pub = public_key_from_external(out_public_key); +- const struct private_key *const priv = private_key_from_external(private_key); +- *pub = priv->pub; +-} +- +-// Algorithm 5 of the Kyber spec. Encrypts a message with given randomness to +-// the ciphertext in |out|. Without applying the Fujisaki-Okamoto transform this +-// would not result in a CCA secure scheme, since lattice schemes are vulnerable +-// to decryption failure oracles. +-static void encrypt_cpa(uint8_t out[KYBER_CIPHERTEXT_BYTES], +- const struct public_key *pub, const uint8_t message[32], +- const uint8_t randomness[32]) { +- uint8_t counter = 0; +- vector secret; +- vector_generate_secret_eta_2(&secret, &counter, randomness); +- vector_ntt(&secret); +- vector error; +- vector_generate_secret_eta_2(&error, &counter, randomness); +- uint8_t input[33]; +- OPENSSL_memcpy(input, randomness, 32); +- input[32] = counter; +- scalar scalar_error; +- scalar_centered_binomial_distribution_eta_2_with_prf(&scalar_error, input); +- vector u; +- matrix_mult(&u, &pub->m, &secret); +- vector_inverse_ntt(&u); +- vector_add(&u, &error); +- scalar v; +- scalar_inner_product(&v, &pub->t, &secret); +- scalar_inverse_ntt(&v); +- scalar_add(&v, &scalar_error); +- scalar expanded_message; +- scalar_decode_1(&expanded_message, message); +- scalar_decompress(&expanded_message, 1); +- scalar_add(&v, &expanded_message); +- vector_compress(&u, kDU); +- vector_encode(out, &u, kDU); +- scalar_compress(&v, kDV); +- scalar_encode(out + kCompressedVectorSize, &v, kDV); +-} +- +-// Calls KYBER_encap_external_entropy| with random bytes from |RAND_bytes| +-void KYBER_encap(uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], +- uint8_t *out_shared_secret, size_t out_shared_secret_len, +- const struct KYBER_public_key *public_key) { +- uint8_t entropy[KYBER_ENCAP_ENTROPY]; +- RAND_bytes(entropy, KYBER_ENCAP_ENTROPY); +- KYBER_encap_external_entropy(out_ciphertext, out_shared_secret, +- out_shared_secret_len, public_key, entropy); +-} +- +-// Algorithm 8 of the Kyber spec, safe for line 2 of the spec. The spec there +-// hashes the output of the system's random number generator, since the FO +-// transform will reveal it to the decrypting party. There is no reason to do +-// this when a secure random number generator is used. When an insecure random +-// number generator is used, the caller should switch to a secure one before +-// calling this method. +-void KYBER_encap_external_entropy( +- uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], uint8_t *out_shared_secret, +- size_t out_shared_secret_len, const struct KYBER_public_key *public_key, +- const uint8_t entropy[KYBER_ENCAP_ENTROPY]) { +- const struct public_key *pub = public_key_from_external(public_key); +- uint8_t input[64]; +- OPENSSL_memcpy(input, entropy, KYBER_ENCAP_ENTROPY); +- OPENSSL_memcpy(input + KYBER_ENCAP_ENTROPY, pub->public_key_hash, +- sizeof(input) - KYBER_ENCAP_ENTROPY); +- uint8_t prekey_and_randomness[64]; +- BORINGSSL_keccak(prekey_and_randomness, sizeof(prekey_and_randomness), input, +- sizeof(input), boringssl_sha3_512); +- encrypt_cpa(out_ciphertext, pub, entropy, prekey_and_randomness + 32); +- BORINGSSL_keccak(prekey_and_randomness + 32, 32, out_ciphertext, +- KYBER_CIPHERTEXT_BYTES, boringssl_sha3_256); +- BORINGSSL_keccak(out_shared_secret, out_shared_secret_len, +- prekey_and_randomness, sizeof(prekey_and_randomness), +- boringssl_shake256); +-} +- +-// Algorithm 6 of the Kyber spec. +-static void decrypt_cpa(uint8_t out[32], const struct private_key *priv, +- const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES]) { +- vector u; +- vector_decode(&u, ciphertext, kDU); +- vector_decompress(&u, kDU); +- vector_ntt(&u); +- scalar v; +- scalar_decode(&v, ciphertext + kCompressedVectorSize, kDV); +- scalar_decompress(&v, kDV); +- scalar mask; +- scalar_inner_product(&mask, &priv->s, &u); +- scalar_inverse_ntt(&mask); +- scalar_sub(&v, &mask); +- scalar_compress(&v, 1); +- scalar_encode_1(out, &v); +-} +- +-// Algorithm 9 of the Kyber spec, performing the FO transform by running +-// encrypt_cpa on the decrypted message. The spec does not allow the decryption +-// failure to be passed on to the caller, and instead returns a result that is +-// deterministic but unpredictable to anyone without knowledge of the private +-// key. +-void KYBER_decap(uint8_t *out_shared_secret, size_t out_shared_secret_len, +- const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES], +- const struct KYBER_private_key *private_key) { +- const struct private_key *priv = private_key_from_external(private_key); +- uint8_t decrypted[64]; +- decrypt_cpa(decrypted, priv, ciphertext); +- OPENSSL_memcpy(decrypted + 32, priv->pub.public_key_hash, +- sizeof(decrypted) - 32); +- uint8_t prekey_and_randomness[64]; +- BORINGSSL_keccak(prekey_and_randomness, sizeof(prekey_and_randomness), +- decrypted, sizeof(decrypted), boringssl_sha3_512); +- uint8_t expected_ciphertext[KYBER_CIPHERTEXT_BYTES]; +- encrypt_cpa(expected_ciphertext, &priv->pub, decrypted, +- prekey_and_randomness + 32); +- uint8_t mask = +- constant_time_eq_int_8(CRYPTO_memcmp(ciphertext, expected_ciphertext, +- sizeof(expected_ciphertext)), +- 0); +- uint8_t input[64]; +- for (int i = 0; i < 32; i++) { +- input[i] = constant_time_select_8(mask, prekey_and_randomness[i], +- priv->fo_failure_secret[i]); +- } +- BORINGSSL_keccak(input + 32, 32, ciphertext, KYBER_CIPHERTEXT_BYTES, +- boringssl_sha3_256); +- BORINGSSL_keccak(out_shared_secret, out_shared_secret_len, input, +- sizeof(input), boringssl_shake256); +-} +- +-int KYBER_marshal_public_key(CBB *out, +- const struct KYBER_public_key *public_key) { +- return kyber_marshal_public_key(out, public_key_from_external(public_key)); +-} +- +-// kyber_parse_public_key_no_hash parses |in| into |pub| but doesn't calculate +-// the value of |pub->public_key_hash|. +-static int kyber_parse_public_key_no_hash(struct public_key *pub, CBS *in) { +- CBS t_bytes; +- if (!CBS_get_bytes(in, &t_bytes, kEncodedVectorSize) || +- !vector_decode(&pub->t, CBS_data(&t_bytes), kLog2Prime) || +- !CBS_copy_bytes(in, pub->rho, sizeof(pub->rho))) { +- return 0; +- } +- matrix_expand(&pub->m, pub->rho); +- return 1; +-} +- +-int KYBER_parse_public_key(struct KYBER_public_key *public_key, CBS *in) { +- struct public_key *pub = public_key_from_external(public_key); +- CBS orig_in = *in; +- if (!kyber_parse_public_key_no_hash(pub, in) || // +- CBS_len(in) != 0) { +- return 0; +- } - BORINGSSL_keccak(pub->public_key_hash, sizeof(pub->public_key_hash), - CBS_data(&orig_in), CBS_len(&orig_in), boringssl_sha3_256); - return 1; - } - +- return 1; +-} +- -int KYBER_marshal_private_key(CBB *out, - const struct KYBER_private_key *private_key) { - const struct private_key *const priv = private_key_from_external(private_key); - uint8_t *s_output; - if (!CBB_add_space(out, &s_output, kEncodedVectorSize)) { - return 0; -+// Modified crypto_kem_decap to BoringSSL style API -+void decap(uint8_t out_shared_key[KYBER_SSBYTES], -+ const struct private_key *in_priv, -+ const uint8_t *ct, size_t ciphertext_len, int mlkem) -+{ -+ uint8_t *ss = out_shared_key; -+ const uint8_t *sk = &in_priv->opaque[0]; -+ -+ size_t i; -+ int fail = 1; -+ uint8_t buf[2*KYBER_SYMBYTES]; -+ /* Will contain key, coins */ -+ uint8_t kr[2*KYBER_SYMBYTES]; -+ uint8_t cmp[KYBER_CIPHERTEXTBYTES]; -+ const uint8_t *pk = sk+KYBER_INDCPA_SECRETKEYBYTES; -+ -+ if (ciphertext_len == KYBER_CIPHERTEXTBYTES) { -+ indcpa_dec(buf, ct, sk); -+ -+ /* Multitarget countermeasure for coins + contributory KEM */ -+ for(i=0;is, kLog2Prime); - if (!kyber_marshal_public_key(out, &priv->pub) || - !CBB_add_bytes(out, priv->pub.public_key_hash, @@ -3587,45 +3312,14 @@ index 776c085f9..ccb5b3d9b 100644 - !CBB_add_bytes(out, priv->fo_failure_secret, - sizeof(priv->fo_failure_secret))) { - return 0; -+ -+ if (mlkem == 1) { -+ /* Compute shared secret in case of rejection: ss2 = PRF(z || c). */ -+ uint8_t ss2[KYBER_SYMBYTES]; -+ keccak_state ks; -+ shake256_init(&ks); -+ shake256_absorb( -+ &ks, -+ sk + KYBER_SECRETKEYBYTES - KYBER_SYMBYTES, -+ KYBER_SYMBYTES -+ ); -+ shake256_absorb(&ks, ct, ciphertext_len); -+ shake256_finalize(&ks); -+ shake256_squeeze(ss2, KYBER_SYMBYTES, &ks); -+ -+ /* Set ss2 to the real shared secret if c = c' */ -+ cmov(ss2, kr, KYBER_SYMBYTES, 1-fail); -+ memcpy(ss, ss2, KYBER_SYMBYTES); -+ } else { -+ /* overwrite coins in kr with H(c) */ -+ hash_h(kr+KYBER_SYMBYTES, ct, ciphertext_len); -+ -+ /* Overwrite pre-k with z on re-encryption failure */ -+ cmov(kr, sk+KYBER_SECRETKEYBYTES-KYBER_SYMBYTES, KYBER_SYMBYTES, fail); -+ -+ /* hash concatenation of pre-k and H(c) to k */ -+ kdf(ss, kr, 2*KYBER_SYMBYTES); - } +- } - return 1; - } - +-} +- -int KYBER_parse_private_key(struct KYBER_private_key *out_private_key, - CBS *in) { - struct private_key *const priv = private_key_from_external(out_private_key); -+void marshal_public_key(uint8_t out[KYBER_PUBLICKEYBYTES], -+ const struct public_key *in_pub) { -+ memcpy(out, &in_pub->opaque, KYBER_PUBLICKEYBYTES); -+} - +- - CBS s_bytes; - if (!CBS_get_bytes(in, &s_bytes, kEncodedVectorSize) || - !vector_decode(&priv->s, CBS_data(&s_bytes), kLog2Prime) || @@ -3642,33 +3336,33 @@ index 776c085f9..ccb5b3d9b 100644 + const uint8_t in[KYBER_PUBLICKEYBYTES]) { + memcpy(&out->opaque, in, KYBER_PUBLICKEYBYTES); } -diff --git a/src/crypto/kyber/kyber512.c b/src/crypto/kyber/kyber512.c +diff --git a/crypto/kyber/kyber512.c b/crypto/kyber/kyber512.c new file mode 100644 index 000000000..21eed11a2 --- /dev/null -+++ b/src/crypto/kyber/kyber512.c ++++ b/crypto/kyber/kyber512.c @@ -0,0 +1,5 @@ +#define KYBER_K 2 + +#include "kyber.c" + + -diff --git a/src/crypto/kyber/kyber768.c b/src/crypto/kyber/kyber768.c +diff --git a/crypto/kyber/kyber768.c b/crypto/kyber/kyber768.c new file mode 100644 index 000000000..3e572b72e --- /dev/null -+++ b/src/crypto/kyber/kyber768.c ++++ b/crypto/kyber/kyber768.c @@ -0,0 +1,4 @@ +#define KYBER_K 3 + +#include "kyber.c" + -diff --git a/src/crypto/kyber/kyber_test.cc b/src/crypto/kyber/kyber_test.cc +diff --git a/crypto/kyber/kyber_test.cc b/crypto/kyber/kyber_test.cc deleted file mode 100644 -index eb76b5bd7..000000000 ---- a/src/crypto/kyber/kyber_test.cc +index b9daa87d3..000000000 +--- a/crypto/kyber/kyber_test.cc +++ /dev/null -@@ -1,229 +0,0 @@ +@@ -1,184 +0,0 @@ -/* Copyright (c) 2023, Google Inc. - * - * Permission to use, copy, modify, and/or distribute this software for any @@ -3695,55 +3389,10 @@ index eb76b5bd7..000000000 - -#include "../test/file_test.h" -#include "../test/test_util.h" +-#include "../keccak/internal.h" -#include "./internal.h" - - --static void KeccakFileTest(FileTest *t) { -- std::vector input, sha3_256_expected, sha3_512_expected, -- shake128_expected, shake256_expected; -- ASSERT_TRUE(t->GetBytes(&input, "Input")); -- ASSERT_TRUE(t->GetBytes(&sha3_256_expected, "SHA3-256")); -- ASSERT_TRUE(t->GetBytes(&sha3_512_expected, "SHA3-512")); -- ASSERT_TRUE(t->GetBytes(&shake128_expected, "SHAKE-128")); -- ASSERT_TRUE(t->GetBytes(&shake256_expected, "SHAKE-256")); -- -- uint8_t sha3_256_digest[32]; -- BORINGSSL_keccak(sha3_256_digest, sizeof(sha3_256_digest), input.data(), -- input.size(), boringssl_sha3_256); -- uint8_t sha3_512_digest[64]; -- BORINGSSL_keccak(sha3_512_digest, sizeof(sha3_512_digest), input.data(), -- input.size(), boringssl_sha3_512); -- uint8_t shake128_output[512]; -- BORINGSSL_keccak(shake128_output, sizeof(shake128_output), input.data(), -- input.size(), boringssl_shake128); -- uint8_t shake256_output[512]; -- BORINGSSL_keccak(shake256_output, sizeof(shake256_output), input.data(), -- input.size(), boringssl_shake256); -- -- EXPECT_EQ(Bytes(sha3_256_expected), Bytes(sha3_256_digest)); -- EXPECT_EQ(Bytes(sha3_512_expected), Bytes(sha3_512_digest)); -- EXPECT_EQ(Bytes(shake128_expected), Bytes(shake128_output)); -- EXPECT_EQ(Bytes(shake256_expected), Bytes(shake256_output)); -- -- struct BORINGSSL_keccak_st ctx; -- -- BORINGSSL_keccak_init(&ctx, input.data(), input.size(), boringssl_shake128); -- for (size_t i = 0; i < sizeof(shake128_output); i++) { -- BORINGSSL_keccak_squeeze(&ctx, &shake128_output[i], 1); -- } -- EXPECT_EQ(Bytes(shake128_expected), Bytes(shake128_output)); -- -- BORINGSSL_keccak_init(&ctx, input.data(), input.size(), boringssl_shake256); -- for (size_t i = 0; i < sizeof(shake256_output); i++) { -- BORINGSSL_keccak_squeeze(&ctx, &shake256_output[i], 1); -- } -- EXPECT_EQ(Bytes(shake256_expected), Bytes(shake256_output)); --} -- --TEST(KyberTest, KeccakTestVectors) { -- FileTestGTest("crypto/kyber/keccak_tests.txt", KeccakFileTest); --} -- -template -static std::vector Marshal(int (*marshal_func)(CBB *, const T *), - const T *t) { @@ -3898,10 +3547,10 @@ index eb76b5bd7..000000000 -TEST(KyberTest, TestVectors) { - FileTestGTest("crypto/kyber/kyber_tests.txt", KyberFileTest); -} -diff --git a/src/crypto/obj/obj_dat.h b/src/crypto/obj/obj_dat.h -index 654b3c08e..6cef2c079 100644 ---- a/src/crypto/obj/obj_dat.h -+++ b/src/crypto/obj/obj_dat.h +diff --git a/crypto/obj/obj_dat.h b/crypto/obj/obj_dat.h +index 71ef2d2bd..74b99b098 100644 +--- a/crypto/obj/obj_dat.h ++++ b/crypto/obj/obj_dat.h @@ -57,7 +57,7 @@ /* This file is generated by crypto/obj/objects.go. */ @@ -3911,7 +3560,7 @@ index 654b3c08e..6cef2c079 100644 static const uint8_t kObjectData[] = { /* NID_rsadsi */ -@@ -8784,6 +8784,13 @@ static const ASN1_OBJECT kObjects[NUM_NID] = { +@@ -8783,6 +8783,13 @@ static const ASN1_OBJECT kObjects[NUM_NID] = { {"HKDF", "hkdf", NID_hkdf, 0, NULL, 0}, {"X25519Kyber768Draft00", "X25519Kyber768Draft00", NID_X25519Kyber768Draft00, 0, NULL, 0}, @@ -3925,7 +3574,7 @@ index 654b3c08e..6cef2c079 100644 }; static const uint16_t kNIDsInShortNameOrder[] = { -@@ -8916,6 +8923,7 @@ static const uint16_t kNIDsInShortNameOrder[] = { +@@ -8915,6 +8922,7 @@ static const uint16_t kNIDsInShortNameOrder[] = { 18 /* OU */, 749 /* Oakley-EC2N-3 */, 750 /* Oakley-EC2N-4 */, @@ -3933,9 +3582,9 @@ index 654b3c08e..6cef2c079 100644 9 /* PBE-MD2-DES */, 168 /* PBE-MD2-RC2-64 */, 10 /* PBE-MD5-DES */, -@@ -8982,7 +8990,10 @@ static const uint16_t kNIDsInShortNameOrder[] = { +@@ -8980,7 +8988,10 @@ static const uint16_t kNIDsInShortNameOrder[] = { + 143 /* SXNetID */, 458 /* UID */, - 0 /* UNDEF */, 948 /* X25519 */, + 965 /* X25519Kyber512Draft00 */, 964 /* X25519Kyber768Draft00 */, @@ -3944,7 +3593,7 @@ index 654b3c08e..6cef2c079 100644 961 /* X448 */, 11 /* X500 */, 378 /* X500algorithms */, -@@ -9829,6 +9840,7 @@ static const uint16_t kNIDsInLongNameOrder[] = { +@@ -9827,6 +9838,7 @@ static const uint16_t kNIDsInLongNameOrder[] = { 366 /* OCSP Nonce */, 371 /* OCSP Service Locator */, 180 /* OCSP Signing */, @@ -3952,7 +3601,7 @@ index 654b3c08e..6cef2c079 100644 161 /* PBES2 */, 69 /* PBKDF2 */, 162 /* PBMAC1 */, -@@ -9853,7 +9865,10 @@ static const uint16_t kNIDsInLongNameOrder[] = { +@@ -9851,7 +9863,10 @@ static const uint16_t kNIDsInLongNameOrder[] = { 133 /* Time Stamping */, 375 /* Trust Root */, 948 /* X25519 */, @@ -3963,10 +3612,10 @@ index 654b3c08e..6cef2c079 100644 961 /* X448 */, 12 /* X509 */, 402 /* X509v3 AC Targeting */, -diff --git a/src/crypto/obj/obj_mac.num b/src/crypto/obj/obj_mac.num +diff --git a/crypto/obj/obj_mac.num b/crypto/obj/obj_mac.num index a0519acee..2a46adfe8 100644 ---- a/src/crypto/obj/obj_mac.num -+++ b/src/crypto/obj/obj_mac.num +--- a/crypto/obj/obj_mac.num ++++ b/crypto/obj/obj_mac.num @@ -952,3 +952,7 @@ X448 961 sha512_256 962 hkdf 963 @@ -3975,10 +3624,10 @@ index a0519acee..2a46adfe8 100644 +P256Kyber768Draft00 966 +X25519Kyber768Draft00Old 967 +X25519MLKEM768 968 -diff --git a/src/crypto/obj/objects.txt b/src/crypto/obj/objects.txt +diff --git a/crypto/obj/objects.txt b/crypto/obj/objects.txt index 3ad32ea3d..347fc556a 100644 ---- a/src/crypto/obj/objects.txt -+++ b/src/crypto/obj/objects.txt +--- a/crypto/obj/objects.txt ++++ b/crypto/obj/objects.txt @@ -1332,8 +1332,12 @@ secg-scheme 14 3 : dhSinglePass-cofactorDH-sha512kdf-scheme : dh-std-kdf : dh-cofactor-kdf @@ -3993,10 +3642,10 @@ index 3ad32ea3d..347fc556a 100644 # See RFC 8410. 1 3 101 110 : X25519 -diff --git a/src/include/openssl/kyber.h b/src/include/openssl/kyber.h +diff --git a/include/openssl/kyber.h b/include/openssl/kyber.h index cafae9d17..a05eb8957 100644 ---- a/src/include/openssl/kyber.h -+++ b/src/include/openssl/kyber.h +--- a/include/openssl/kyber.h ++++ b/include/openssl/kyber.h @@ -1,17 +1,3 @@ -/* Copyright (c) 2023, Google Inc. - * @@ -4038,15 +3687,7 @@ index cafae9d17..a05eb8957 100644 - } opaque; +struct KYBER512_private_key { + uint8_t opaque[KYBER512_PRIVATE_KEY_BYTES]; - }; -- --// KYBER_private_key contains a Kyber768 private key. The contents of this --// object should never leave the address space since the format is unstable. --struct KYBER_private_key { -- union { -- uint8_t bytes[512 * (3 + 3 + 9) + 32 + 32 + 32]; -- uint16_t alignment; -- } opaque; ++}; +struct KYBER768_private_key { + uint8_t opaque[KYBER768_PRIVATE_KEY_BYTES]; +}; @@ -4057,17 +3698,34 @@ index cafae9d17..a05eb8957 100644 + uint8_t opaque[KYBER768_PUBLIC_KEY_BYTES]; }; +-// KYBER_private_key contains a Kyber768 private key. The contents of this +-// object should never leave the address space since the format is unstable. +-struct KYBER_private_key { +- union { +- uint8_t bytes[512 * (3 + 3 + 9) + 32 + 32 + 32]; +- uint16_t alignment; +- } opaque; +-}; ++// KYBER_GENERATE_KEY_BYTES is the number of bytes of entropy needed to ++// generate a keypair. ++#define KYBER_GENERATE_KEY_BYTES 64 + -// KYBER_PUBLIC_KEY_BYTES is the number of bytes in an encoded Kyber768 public -// key. -#define KYBER_PUBLIC_KEY_BYTES 1184 -- ++// KYBER_ENCAP_BYTES is the number of bytes of entropy needed to encapsulate a ++// session key. ++#define KYBER_ENCAP_BYTES 32 + -// KYBER_generate_key generates a random public/private key pair, writes the -// encoded public key to |out_encoded_public_key| and sets |out_private_key| to -// the private key. -OPENSSL_EXPORT void KYBER_generate_key( - uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], - struct KYBER_private_key *out_private_key); -- ++// KYBER_KEY_BYTES is the number of bytes in a shared key. ++#define KYBER_KEY_BYTES 32 + -// KYBER_public_from_private sets |*out_public_key| to the public key that -// corresponds to |private_key|. (This is faster than parsing the output of -// |KYBER_generate_key| if, for some reason, you need to encapsulate to a key @@ -4075,10 +3733,20 @@ index cafae9d17..a05eb8957 100644 -OPENSSL_EXPORT void KYBER_public_from_private( - struct KYBER_public_key *out_public_key, - const struct KYBER_private_key *private_key); -- ++// KYBER512_generate_key is a deterministic function that outputs a public and ++// private key based on the given entropy. ++OPENSSL_EXPORT void KYBER512_generate_key( ++ struct KYBER512_public_key *out_pub, struct KYBER512_private_key *out_priv, ++ const uint8_t input[KYBER_GENERATE_KEY_BYTES]); + -// KYBER_CIPHERTEXT_BYTES is number of bytes in the Kyber768 ciphertext. -#define KYBER_CIPHERTEXT_BYTES 1088 -- ++// KYBER768_generate_key is a deterministic function that outputs a public and ++// private key based on the given entropy. ++OPENSSL_EXPORT void KYBER768_generate_key( ++ struct KYBER768_public_key *out_pub, struct KYBER768_private_key *out_priv, ++ const uint8_t input[KYBER_GENERATE_KEY_BYTES]); + -// KYBER_encap encrypts a random secret key of length |out_shared_secret_len| to -// |public_key|, writes the ciphertext to |ciphertext|, and writes the random -// key to |out_shared_secret|. The party calling |KYBER_decap| must already know @@ -4087,7 +3755,15 @@ index cafae9d17..a05eb8957 100644 - uint8_t *out_shared_secret, - size_t out_shared_secret_len, - const struct KYBER_public_key *public_key); -- ++// KYBER512_encap is a deterministic function the generates and encrypts a random ++// session key from the given entropy, writing those values to |out_shared_key| ++// and |out_ciphertext|, respectively. If |mlkem| is 1, will use ML-KEM-512. ++OPENSSL_EXPORT int KYBER512_encap(uint8_t out_ciphertext[KYBER512_CIPHERTEXT_BYTES], ++ uint8_t out_shared_key[KYBER_KEY_BYTES], ++ const struct KYBER512_public_key *in_pub, ++ const uint8_t in[KYBER_ENCAP_BYTES], ++ int mlkem); + -// KYBER_decap decrypts a key of length |out_shared_secret_len| from -// |ciphertext| using |private_key| and writes it to |out_shared_secret|. If -// |ciphertext| is invalid, |out_shared_secret| is filled with a key that @@ -4100,72 +3776,6 @@ index cafae9d17..a05eb8957 100644 - uint8_t *out_shared_secret, size_t out_shared_secret_len, - const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES], - const struct KYBER_private_key *private_key); -- -- --// Serialisation of keys. -- --// KYBER_marshal_public_key serializes |public_key| to |out| in the standard --// format for Kyber public keys. It returns one on success or zero on allocation --// error. --OPENSSL_EXPORT int KYBER_marshal_public_key( -- CBB *out, const struct KYBER_public_key *public_key); -- --// KYBER_parse_public_key parses a public key, in the format generated by --// |KYBER_marshal_public_key|, from |in| and writes the result to --// |out_public_key|. It returns one on success or zero on parse error or if --// there are trailing bytes in |in|. --OPENSSL_EXPORT int KYBER_parse_public_key( -- struct KYBER_public_key *out_public_key, CBS *in); -- --// KYBER_marshal_private_key serializes |private_key| to |out| in the standard --// format for Kyber private keys. It returns one on success or zero on --// allocation error. --OPENSSL_EXPORT int KYBER_marshal_private_key( -- CBB *out, const struct KYBER_private_key *private_key); -- --// KYBER_PRIVATE_KEY_BYTES is the length of the data produced by --// |KYBER_marshal_private_key|. --#define KYBER_PRIVATE_KEY_BYTES 2400 -- --// KYBER_parse_private_key parses a private key, in the format generated by --// |KYBER_marshal_private_key|, from |in| and writes the result to --// |out_private_key|. It returns one on success or zero on parse error or if --// there are trailing bytes in |in|. --OPENSSL_EXPORT int KYBER_parse_private_key( -- struct KYBER_private_key *out_private_key, CBS *in); -- -+// KYBER_GENERATE_KEY_BYTES is the number of bytes of entropy needed to -+// generate a keypair. -+#define KYBER_GENERATE_KEY_BYTES 64 -+ -+// KYBER_ENCAP_BYTES is the number of bytes of entropy needed to encapsulate a -+// session key. -+#define KYBER_ENCAP_BYTES 32 -+ -+// KYBER_KEY_BYTES is the number of bytes in a shared key. -+#define KYBER_KEY_BYTES 32 -+ -+// KYBER512_generate_key is a deterministic function that outputs a public and -+// private key based on the given entropy. -+OPENSSL_EXPORT void KYBER512_generate_key( -+ struct KYBER512_public_key *out_pub, struct KYBER512_private_key *out_priv, -+ const uint8_t input[KYBER_GENERATE_KEY_BYTES]); -+ -+// KYBER768_generate_key is a deterministic function that outputs a public and -+// private key based on the given entropy. -+OPENSSL_EXPORT void KYBER768_generate_key( -+ struct KYBER768_public_key *out_pub, struct KYBER768_private_key *out_priv, -+ const uint8_t input[KYBER_GENERATE_KEY_BYTES]); -+ -+// KYBER512_encap is a deterministic function the generates and encrypts a random -+// session key from the given entropy, writing those values to |out_shared_key| -+// and |out_ciphertext|, respectively. If |mlkem| is 1, will use ML-KEM-512. -+OPENSSL_EXPORT int KYBER512_encap(uint8_t out_ciphertext[KYBER512_CIPHERTEXT_BYTES], -+ uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER512_public_key *in_pub, -+ const uint8_t in[KYBER_ENCAP_BYTES], -+ int mlkem); -+ +// KYBER768_encap is a deterministic function the generates and encrypts a random +// session key from the given entropy, writing those values to |out_shared_key| +// and |out_ciphertext|, respectively. If |mlkem| is 1, will use ML-KEM-768. @@ -4174,7 +3784,7 @@ index cafae9d17..a05eb8957 100644 + const struct KYBER768_public_key *in_pub, + const uint8_t in[KYBER_ENCAP_BYTES], + int mlkem); -+ + +// KYBER_decap decrypts a session key from |ciphertext_len| bytes of +// |ciphertext|. If the ciphertext is valid, the decrypted key is written to +// |out_shared_key|. Otherwise a key dervied from |ciphertext| and a secret key (kept @@ -4185,7 +3795,8 @@ index cafae9d17..a05eb8957 100644 + const struct KYBER512_private_key *in_priv, + const uint8_t *ciphertext, size_t ciphertext_len, + int mlkem); -+ + +-// Serialisation of keys. +// KYBER_decap decrypts a session key from |ciphertext_len| bytes of +// |ciphertext|. If the ciphertext is valid, the decrypted key is written to +// |out_shared_key|. Otherwise a key dervied from |ciphertext| and a secret key (kept @@ -4196,29 +3807,56 @@ index cafae9d17..a05eb8957 100644 + const struct KYBER768_private_key *in_priv, + const uint8_t *ciphertext, size_t ciphertext_len, + int mlkem); -+ + +-// KYBER_marshal_public_key serializes |public_key| to |out| in the standard +-// format for Kyber public keys. It returns one on success or zero on allocation +-// error. +-OPENSSL_EXPORT int KYBER_marshal_public_key( +- CBB *out, const struct KYBER_public_key *public_key); +// KYBER512_marshal_public_key serialises |in_pub| to |out|. +OPENSSL_EXPORT void KYBER512_marshal_public_key( + uint8_t out[KYBER512_PUBLIC_KEY_BYTES], const struct KYBER512_public_key *in_pub); -+ + +-// KYBER_parse_public_key parses a public key, in the format generated by +-// |KYBER_marshal_public_key|, from |in| and writes the result to +-// |out_public_key|. It returns one on success or zero on parse error or if +-// there are trailing bytes in |in|. +-OPENSSL_EXPORT int KYBER_parse_public_key( +- struct KYBER_public_key *out_public_key, CBS *in); +// KYBER768_marshal_public_key serialises |in_pub| to |out|. +OPENSSL_EXPORT void KYBER768_marshal_public_key( + uint8_t out[KYBER768_PUBLIC_KEY_BYTES], const struct KYBER768_public_key *in_pub); -+ + +-// KYBER_marshal_private_key serializes |private_key| to |out| in the standard +-// format for Kyber private keys. It returns one on success or zero on +-// allocation error. +-OPENSSL_EXPORT int KYBER_marshal_private_key( +- CBB *out, const struct KYBER_private_key *private_key); +- +-// KYBER_PRIVATE_KEY_BYTES is the length of the data produced by +-// |KYBER_marshal_private_key|. +-#define KYBER_PRIVATE_KEY_BYTES 2400 +- +-// KYBER_parse_private_key parses a private key, in the format generated by +-// |KYBER_marshal_private_key|, from |in| and writes the result to +-// |out_private_key|. It returns one on success or zero on parse error or if +-// there are trailing bytes in |in|. +-OPENSSL_EXPORT int KYBER_parse_private_key( +- struct KYBER_private_key *out_private_key, CBS *in); +// KYBER512_parse_public_key sets |*out| to the public-key encoded in |in|. +OPENSSL_EXPORT void KYBER512_parse_public_key( + struct KYBER512_public_key *out, const uint8_t in[KYBER512_PUBLIC_KEY_BYTES]); -+ + +// KYBER768_parse_public_key sets |*out| to the public-key encoded in |in|. +OPENSSL_EXPORT void KYBER768_parse_public_key( + struct KYBER768_public_key *out, const uint8_t in[KYBER768_PUBLIC_KEY_BYTES]); #if defined(__cplusplus) } // extern C -diff --git a/src/include/openssl/nid.h b/src/include/openssl/nid.h +diff --git a/include/openssl/nid.h b/include/openssl/nid.h index 4dd8841b1..5b102c610 100644 ---- a/src/include/openssl/nid.h -+++ b/src/include/openssl/nid.h +--- a/include/openssl/nid.h ++++ b/include/openssl/nid.h @@ -4255,6 +4255,18 @@ extern "C" { #define SN_X25519Kyber768Draft00 "X25519Kyber768Draft00" #define NID_X25519Kyber768Draft00 964 @@ -4238,53 +3876,60 @@ index 4dd8841b1..5b102c610 100644 #if defined(__cplusplus) } /* extern C */ -diff --git a/src/include/openssl/ssl.h b/src/include/openssl/ssl.h -index 53aa9b453..f9683f4cf 100644 ---- a/src/include/openssl/ssl.h -+++ b/src/include/openssl/ssl.h -@@ -2378,6 +2378,10 @@ OPENSSL_EXPORT int SSL_set1_curves_list(SSL *ssl, const char *curves); - #define SSL_CURVE_SECP521R1 25 - #define SSL_CURVE_X25519 29 - #define SSL_CURVE_X25519_KYBER768_DRAFT00 0x6399 -+#define SSL_CURVE_X25519_KYBER512_DRAFT00 0xfe30 -+#define SSL_CURVE_X25519_KYBER768_DRAFT00_OLD 0xfe31 -+#define SSL_CURVE_P256_KYBER768_DRAFT00 0xfe32 -+#define SSL_CURVE_X25519_MLKEM768 0x11ec +diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h +index 003e0a5f7..884685ba9 100644 +--- a/include/openssl/ssl.h ++++ b/include/openssl/ssl.h +@@ -2363,6 +2363,10 @@ OPENSSL_EXPORT size_t SSL_CTX_get_num_tickets(const SSL_CTX *ctx); + #define SSL_GROUP_SECP521R1 25 + #define SSL_GROUP_X25519 29 + #define SSL_GROUP_X25519_KYBER768_DRAFT00 0x6399 ++#define SSL_GROUP_X25519_KYBER512_DRAFT00 0xfe30 ++#define SSL_GROUP_X25519_KYBER768_DRAFT00_OLD 0xfe31 ++#define SSL_GROUP_P256_KYBER768_DRAFT00 0xfe32 ++#define SSL_GROUP_X25519_MLKEM768 0x11ec - // SSL_get_curve_id returns the ID of the curve used by |ssl|'s most recently - // completed handshake or 0 if not applicable. -diff --git a/src/sources.cmake b/src/sources.cmake -index 5c7e881bf..3c0770cf3 100644 ---- a/src/sources.cmake -+++ b/src/sources.cmake -@@ -66,8 +66,6 @@ set( - crypto/fipsmodule/rand/ctrdrbg_vectors.txt + // SSL_CTX_set1_group_ids sets the preferred groups for |ctx| to |group_ids|. + // Each element of |group_ids| should be one of the |SSL_GROUP_*| constants. It +diff --git a/sources.cmake b/sources.cmake +index ba2f5bc9e..d7ef5153a 100644 +--- a/sources.cmake ++++ b/sources.cmake +@@ -52,7 +52,6 @@ set( + crypto/hrss/hrss_test.cc + crypto/impl_dispatch_test.cc + crypto/keccak/keccak_test.cc +- crypto/kyber/kyber_test.cc + crypto/lhash/lhash_test.cc + crypto/obj/obj_test.cc + crypto/pem/pem_test.cc +@@ -145,7 +144,6 @@ set( crypto/hmac_extra/hmac_tests.txt crypto/hpke/hpke_test_vectors.txt -- crypto/kyber/keccak_tests.txt + crypto/keccak/keccak_tests.txt - crypto/kyber/kyber_tests.txt crypto/pkcs8/test/empty_password.p12 crypto/pkcs8/test/no_encryption.p12 crypto/pkcs8/test/nss.p12 -diff --git a/src/ssl/extensions.cc b/src/ssl/extensions.cc -index 5ee280221..aae3e6a7f 100644 ---- a/src/ssl/extensions.cc -+++ b/src/ssl/extensions.cc +diff --git a/ssl/extensions.cc b/ssl/extensions.cc +index b13400097..4655b1881 100644 +--- a/ssl/extensions.cc ++++ b/ssl/extensions.cc @@ -207,6 +207,10 @@ static bool tls1_check_duplicate_extensions(const CBS *cbs) { static bool is_post_quantum_group(uint16_t id) { switch (id) { - case SSL_CURVE_X25519_KYBER768_DRAFT00: -+ case SSL_CURVE_X25519_KYBER768_DRAFT00_OLD: -+ case SSL_CURVE_X25519_KYBER512_DRAFT00: -+ case SSL_CURVE_P256_KYBER768_DRAFT00: -+ case SSL_CURVE_X25519_MLKEM768: + case SSL_GROUP_X25519_KYBER768_DRAFT00: ++ case SSL_GROUP_X25519_KYBER768_DRAFT00_OLD: ++ case SSL_GROUP_X25519_KYBER512_DRAFT00: ++ case SSL_GROUP_P256_KYBER768_DRAFT00: ++ case SSL_GROUP_X25519_MLKEM768: return true; default: return false; -diff --git a/src/ssl/ssl_key_share.cc b/src/ssl/ssl_key_share.cc -index 09a9ad380..d7a8f0a80 100644 ---- a/src/ssl/ssl_key_share.cc -+++ b/src/ssl/ssl_key_share.cc +diff --git a/ssl/ssl_key_share.cc b/ssl/ssl_key_share.cc +index 694bec11d..3e4d2e7c4 100644 +--- a/ssl/ssl_key_share.cc ++++ b/ssl/ssl_key_share.cc @@ -26,6 +26,7 @@ #include #include @@ -4293,7 +3938,7 @@ index 09a9ad380..d7a8f0a80 100644 #include #include #include -@@ -193,63 +194,292 @@ class X25519KeyShare : public SSLKeyShare { +@@ -191,63 +192,145 @@ class X25519KeyShare : public SSLKeyShare { uint8_t private_key_[32]; }; @@ -4302,18 +3947,27 @@ index 09a9ad380..d7a8f0a80 100644 public: - X25519Kyber768KeyShare() {} + P256Kyber768Draft00KeyShare() {} -+ -+ uint16_t GroupID() const override { return SSL_CURVE_P256_KYBER768_DRAFT00; } -+ -+ bool Generate(CBB *out) override { + +- uint16_t GroupID() const override { +- return SSL_GROUP_X25519_KYBER768_DRAFT00; +- } ++ uint16_t GroupID() const override { return SSL_GROUP_P256_KYBER768_DRAFT00; } + + bool Generate(CBB *out) override { +- uint8_t x25519_public_key[32]; +- X25519_keypair(x25519_public_key, x25519_private_key_); + assert(!p256_private_key_); -+ + +- uint8_t kyber_public_key[KYBER_PUBLIC_KEY_BYTES]; +- KYBER_generate_key(kyber_public_key, &kyber_private_key_); + // Set up a shared |BN_CTX| for P-256 operations. + UniquePtr bn_ctx(BN_CTX_new()); + if (!bn_ctx) { + return false; + } -+ + +- if (!CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key)) || +- !CBB_add_bytes(out, kyber_public_key, sizeof(kyber_public_key))) { + BN_CTXScope scope(bn_ctx.get()); + + // Generate a P-256 private key. @@ -4345,33 +3999,58 @@ index 09a9ad380..d7a8f0a80 100644 + + uint8_t kyber_public_key_bytes[KYBER768_PUBLIC_KEY_BYTES]; + KYBER768_marshal_public_key(kyber_public_key_bytes, &kyber_public_key); - -- uint16_t GroupID() const override { -- return SSL_CURVE_X25519_KYBER768_DRAFT00; ++ + if (!CBB_add_bytes(out, kyber_public_key_bytes, + sizeof(kyber_public_key_bytes))) { -+ return false; -+ } -+ -+ return true; + return false; + } + + return true; } +- bool Encap(CBB *out_ciphertext, Array *out_secret, +- uint8_t *out_alert, Span peer_key) override { +- Array secret; +- if (!secret.Init(32 + 32)) { +- return false; +- } + bool Encap(CBB *out_public_key, Array *out_secret, + uint8_t *out_alert, Span peer_key) override { + assert(!p256_private_key_); -+ + +- uint8_t x25519_public_key[32]; +- X25519_keypair(x25519_public_key, x25519_private_key_); +- KYBER_public_key peer_kyber_pub; +- CBS peer_key_cbs; +- CBS peer_x25519_cbs; +- CBS peer_kyber_cbs; +- CBS_init(&peer_key_cbs, peer_key.data(), peer_key.size()); +- if (!CBS_get_bytes(&peer_key_cbs, &peer_x25519_cbs, 32) || +- !CBS_get_bytes(&peer_key_cbs, &peer_kyber_cbs, +- KYBER_PUBLIC_KEY_BYTES) || +- CBS_len(&peer_key_cbs) != 0 || +- !X25519(secret.data(), x25519_private_key_, +- CBS_data(&peer_x25519_cbs)) || +- !KYBER_parse_public_key(&peer_kyber_pub, &peer_kyber_cbs)) { + if (peer_key.size() != 65 + KYBER768_PUBLIC_KEY_BYTES) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } -+ + *out_alert = SSL_AD_DECODE_ERROR; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + +- uint8_t kyber_ciphertext[KYBER_CIPHERTEXT_BYTES]; +- KYBER_encap(kyber_ciphertext, secret.data() + 32, secret.size() - 32, +- &peer_kyber_pub); + // Set up a shared |BN_CTX| for P-256 operations. + UniquePtr bn_ctx(BN_CTX_new()); + if (!bn_ctx) { + return false; + } -+ + +- if (!CBB_add_bytes(out_ciphertext, x25519_public_key, +- sizeof(x25519_public_key)) || +- !CBB_add_bytes(out_ciphertext, kyber_ciphertext, +- sizeof(kyber_ciphertext))) { + BN_CTXScope scope(bn_ctx.get()); + + UniquePtr group; @@ -4440,30 +4119,35 @@ index 09a9ad380..d7a8f0a80 100644 + return false; + } + if(!CBB_add_bytes(out_public_key, ciphertext, sizeof(ciphertext))) { -+ return false; -+ } -+ -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ bool Decap(Array *out_secret, uint8_t *out_alert, + return false; + } + +@@ -256,30 +339,380 @@ class X25519Kyber768KeyShare : public SSLKeyShare { + } + + bool Decap(Array *out_secret, uint8_t *out_alert, +- Span ciphertext) override { + Span peer_key) override { + assert(p256_private_key_); -+ *out_alert = SSL_AD_INTERNAL_ERROR; -+ -+ Array secret; + *out_alert = SSL_AD_INTERNAL_ERROR; + + Array secret; +- if (!secret.Init(32 + 32)) { + if (!secret.Init(32 + KYBER_KEY_BYTES)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } -+ + return false; + } + +- if (ciphertext.size() != 32 + KYBER_CIPHERTEXT_BYTES || +- !X25519(secret.data(), x25519_private_key_, ciphertext.data())) { + if (peer_key.size() != 65 + KYBER768_CIPHERTEXT_BYTES) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } -+ + *out_alert = SSL_AD_DECODE_ERROR; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + +- KYBER_decap(secret.data() + 32, secret.size() - 32, ciphertext.data() + 32, +- &kyber_private_key_); + // Set up a shared |BN_CTX| for P-256 operations. + UniquePtr bn_ctx(BN_CTX_new()); + if (!bn_ctx) { @@ -4523,18 +4207,16 @@ index 09a9ad380..d7a8f0a80 100644 +class X25519Kyber768Draft00KeyShare : public SSLKeyShare { + public: + X25519Kyber768Draft00KeyShare(uint16_t group_id) : group_id_(group_id) { -+ assert(group_id == SSL_CURVE_X25519_KYBER768_DRAFT00 -+ || group_id == SSL_CURVE_X25519_KYBER768_DRAFT00_OLD); ++ assert(group_id == SSL_GROUP_X25519_KYBER768_DRAFT00 ++ || group_id == SSL_GROUP_X25519_KYBER768_DRAFT00_OLD); + } + + uint16_t GroupID() const override { return group_id_; } + - bool Generate(CBB *out) override { - uint8_t x25519_public_key[32]; - X25519_keypair(x25519_public_key, x25519_private_key_); - -- uint8_t kyber_public_key[KYBER_PUBLIC_KEY_BYTES]; -- KYBER_generate_key(kyber_public_key, &kyber_private_key_); ++ bool Generate(CBB *out) override { ++ uint8_t x25519_public_key[32]; ++ X25519_keypair(x25519_public_key, x25519_private_key_); ++ + uint8_t kyber_entropy[KYBER_GENERATE_KEY_BYTES]; + KYBER768_public_key kyber_public_key; + RAND_bytes(kyber_entropy, sizeof(kyber_entropy)); @@ -4542,42 +4224,26 @@ index 09a9ad380..d7a8f0a80 100644 + + uint8_t kyber_public_key_bytes[KYBER768_PUBLIC_KEY_BYTES]; + KYBER768_marshal_public_key(kyber_public_key_bytes, &kyber_public_key); - - if (!CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key)) || -- !CBB_add_bytes(out, kyber_public_key, sizeof(kyber_public_key))) { ++ ++ if (!CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key)) || + !CBB_add_bytes(out, kyber_public_key_bytes, + sizeof(kyber_public_key_bytes))) { - return false; - } - - return true; - } - -- bool Encap(CBB *out_ciphertext, Array *out_secret, -- uint8_t *out_alert, Span peer_key) override { ++ return false; ++ } ++ ++ return true; ++ } ++ + bool Encap(CBB *out_public_key, Array *out_secret, + uint8_t *out_alert, Span peer_key) override { - Array secret; -- if (!secret.Init(32 + 32)) { ++ Array secret; + if (!secret.Init(32 + KYBER_KEY_BYTES)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); - return false; - } - - uint8_t x25519_public_key[32]; - X25519_keypair(x25519_public_key, x25519_private_key_); -- KYBER_public_key peer_kyber_pub; -- CBS peer_key_cbs; -- CBS peer_x25519_cbs; -- CBS peer_kyber_cbs; -- CBS_init(&peer_key_cbs, peer_key.data(), peer_key.size()); -- if (!CBS_get_bytes(&peer_key_cbs, &peer_x25519_cbs, 32) || -- !CBS_get_bytes(&peer_key_cbs, &peer_kyber_cbs, -- KYBER_PUBLIC_KEY_BYTES) || -- CBS_len(&peer_key_cbs) != 0 || -- !X25519(secret.data(), x25519_private_key_, -- CBS_data(&peer_x25519_cbs)) || -- !KYBER_parse_public_key(&peer_kyber_pub, &peer_kyber_cbs)) { ++ return false; ++ } ++ ++ uint8_t x25519_public_key[32]; ++ X25519_keypair(x25519_public_key, x25519_private_key_); + + KYBER768_public_key peer_public_key; + if (peer_key.size() != 32 + KYBER768_PUBLIC_KEY_BYTES) { @@ -4589,36 +4255,30 @@ index 09a9ad380..d7a8f0a80 100644 + KYBER768_parse_public_key(&peer_public_key, peer_key.data() + 32); + + if (!X25519(secret.data(), x25519_private_key_, peer_key.data())) { - *out_alert = SSL_AD_DECODE_ERROR; - OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); - return false; - } - -- uint8_t kyber_ciphertext[KYBER_CIPHERTEXT_BYTES]; -- KYBER_encap(kyber_ciphertext, secret.data() + 32, secret.size() - 32, -- &peer_kyber_pub); ++ *out_alert = SSL_AD_DECODE_ERROR; ++ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); ++ return false; ++ } ++ + uint8_t ciphertext[KYBER768_CIPHERTEXT_BYTES]; + uint8_t entropy[KYBER_ENCAP_BYTES]; + RAND_bytes(entropy, sizeof(entropy)); - -- if (!CBB_add_bytes(out_ciphertext, x25519_public_key, ++ + if(!KYBER768_encap(ciphertext, secret.data() + 32, &peer_public_key, entropy, 0)) { + *out_alert = SSL_AD_ILLEGAL_PARAMETER; + return false; + } + if(!CBB_add_bytes(out_public_key, x25519_public_key, - sizeof(x25519_public_key)) || -- !CBB_add_bytes(out_ciphertext, kyber_ciphertext, -- sizeof(kyber_ciphertext))) { ++ sizeof(x25519_public_key)) || + !CBB_add_bytes(out_public_key, ciphertext, sizeof(ciphertext))) { - return false; - } - -@@ -258,30 +488,233 @@ class X25519Kyber768KeyShare : public SSLKeyShare { - } - - bool Decap(Array *out_secret, uint8_t *out_alert, -- Span ciphertext) override { ++ return false; ++ } ++ ++ *out_secret = std::move(secret); ++ return true; ++ } ++ ++ bool Decap(Array *out_secret, uint8_t *out_alert, + Span peer_key) override { + *out_alert = SSL_AD_INTERNAL_ERROR; + @@ -4638,12 +4298,13 @@ index 09a9ad380..d7a8f0a80 100644 + KYBER768_decap(secret.data() + 32, &kyber_private_key_, + peer_key.data() + 32, peer_key.size() - 32, 0); + -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ private: -+ uint8_t x25519_private_key_[32]; + *out_secret = std::move(secret); + return true; + } + + private: + uint8_t x25519_private_key_[32]; +- KYBER_private_key kyber_private_key_; + KYBER768_private_key kyber_private_key_; + uint16_t group_id_; +}; @@ -4652,7 +4313,7 @@ index 09a9ad380..d7a8f0a80 100644 + public: + X25519MLKEM768KeyShare() {} + -+ uint16_t GroupID() const override { return SSL_CURVE_X25519_MLKEM768; } ++ uint16_t GroupID() const override { return SSL_GROUP_X25519_MLKEM768; } + + bool Generate(CBB *out) override { + uint8_t x25519_public_key[32]; @@ -4720,27 +4381,22 @@ index 09a9ad380..d7a8f0a80 100644 + + bool Decap(Array *out_secret, uint8_t *out_alert, + Span peer_key) override { - *out_alert = SSL_AD_INTERNAL_ERROR; - - Array secret; -- if (!secret.Init(32 + 32)) { ++ *out_alert = SSL_AD_INTERNAL_ERROR; ++ ++ Array secret; + if (!secret.Init(32 + KYBER_KEY_BYTES)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); - return false; - } - -- if (ciphertext.size() != 32 + KYBER_CIPHERTEXT_BYTES || -- !X25519(secret.data(), x25519_private_key_, ciphertext.data())) { ++ return false; ++ } ++ + if (peer_key.size() != KYBER768_CIPHERTEXT_BYTES + 32 || + !X25519(secret.data() + 32, x25519_private_key_, + peer_key.data() + KYBER768_CIPHERTEXT_BYTES )) { - *out_alert = SSL_AD_DECODE_ERROR; - OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); - return false; - } - -- KYBER_decap(secret.data() + 32, secret.size() - 32, ciphertext.data() + 32, -- &kyber_private_key_); ++ *out_alert = SSL_AD_DECODE_ERROR; ++ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); ++ return false; ++ } ++ + KYBER768_decap(secret.data(), &kyber_private_key_, + peer_key.data(), peer_key.size() - 32, 1); + @@ -4757,7 +4413,7 @@ index 09a9ad380..d7a8f0a80 100644 + public: + X25519Kyber512Draft00KeyShare() {} + -+ uint16_t GroupID() const override { return SSL_CURVE_X25519_KYBER512_DRAFT00; } ++ uint16_t GroupID() const override { return SSL_GROUP_X25519_KYBER512_DRAFT00; } + + bool Generate(CBB *out) override { + uint8_t x25519_public_key[32]; @@ -4844,113 +4500,112 @@ index 09a9ad380..d7a8f0a80 100644 + KYBER512_decap(secret.data() + 32, &kyber_private_key_, + peer_key.data() + 32, peer_key.size() - 32, 0); + - *out_secret = std::move(secret); - return true; - } - - private: - uint8_t x25519_private_key_[32]; -- KYBER_private_key kyber_private_key_; ++ *out_secret = std::move(secret); ++ return true; ++ } ++ ++ private: ++ uint8_t x25519_private_key_[32]; + KYBER512_private_key kyber_private_key_; }; constexpr NamedGroup kNamedGroups[] = { -@@ -290,8 +723,16 @@ constexpr NamedGroup kNamedGroups[] = { - {NID_secp384r1, SSL_CURVE_SECP384R1, "P-384", "secp384r1"}, - {NID_secp521r1, SSL_CURVE_SECP521R1, "P-521", "secp521r1"}, - {NID_X25519, SSL_CURVE_X25519, "X25519", "x25519"}, -+ {NID_X25519Kyber512Draft00, SSL_CURVE_X25519_KYBER512_DRAFT00, +@@ -288,8 +721,16 @@ constexpr NamedGroup kNamedGroups[] = { + {NID_secp384r1, SSL_GROUP_SECP384R1, "P-384", "secp384r1"}, + {NID_secp521r1, SSL_GROUP_SECP521R1, "P-521", "secp521r1"}, + {NID_X25519, SSL_GROUP_X25519, "X25519", "x25519"}, ++ {NID_X25519Kyber512Draft00, SSL_GROUP_X25519_KYBER512_DRAFT00, + "X25519Kyber512Draft00", "Xyber512D00"}, - {NID_X25519Kyber768Draft00, SSL_CURVE_X25519_KYBER768_DRAFT00, + {NID_X25519Kyber768Draft00, SSL_GROUP_X25519_KYBER768_DRAFT00, - "X25519Kyber768Draft00", ""}, + "X25519Kyber768Draft00", "Xyber768D00"}, -+ {NID_X25519Kyber768Draft00Old, SSL_CURVE_X25519_KYBER768_DRAFT00_OLD, ++ {NID_X25519Kyber768Draft00Old, SSL_GROUP_X25519_KYBER768_DRAFT00_OLD, + "X25519Kyber768Draft00Old", "Xyber768D00Old"}, -+ {NID_P256Kyber768Draft00, SSL_CURVE_P256_KYBER768_DRAFT00, ++ {NID_P256Kyber768Draft00, SSL_GROUP_P256_KYBER768_DRAFT00, + "P256Kyber768Draft00", "P256Kyber768D00"}, -+ {NID_X25519MLKEM768, SSL_CURVE_X25519_MLKEM768, ++ {NID_X25519MLKEM768, SSL_GROUP_X25519_MLKEM768, + "X25519MLKEM768", "X25519MLKEM768"} }; } // namespace -@@ -312,8 +753,18 @@ UniquePtr SSLKeyShare::Create(uint16_t group_id) { - return MakeUnique(NID_secp521r1, SSL_CURVE_SECP521R1); - case SSL_CURVE_X25519: +@@ -310,8 +751,18 @@ UniquePtr SSLKeyShare::Create(uint16_t group_id) { + return MakeUnique(EC_group_p521(), SSL_GROUP_SECP521R1); + case SSL_GROUP_X25519: return MakeUnique(); -+ case SSL_CURVE_X25519_KYBER512_DRAFT00: ++ case SSL_GROUP_X25519_KYBER512_DRAFT00: + return UniquePtr(New()); - case SSL_CURVE_X25519_KYBER768_DRAFT00: + case SSL_GROUP_X25519_KYBER768_DRAFT00: - return MakeUnique(); + return UniquePtr(New( + group_id)); -+ case SSL_CURVE_X25519_KYBER768_DRAFT00_OLD: ++ case SSL_GROUP_X25519_KYBER768_DRAFT00_OLD: + return UniquePtr(New( + group_id)); -+ case SSL_CURVE_P256_KYBER768_DRAFT00: ++ case SSL_GROUP_P256_KYBER768_DRAFT00: + return UniquePtr(New()); -+ case SSL_CURVE_X25519_MLKEM768: ++ case SSL_GROUP_X25519_MLKEM768: + return UniquePtr(New()); default: return nullptr; } -diff --git a/src/ssl/ssl_lib.cc b/src/ssl/ssl_lib.cc -index 838761af5..9eb201d37 100644 ---- a/src/ssl/ssl_lib.cc -+++ b/src/ssl/ssl_lib.cc -@@ -3151,7 +3151,7 @@ namespace fips202205 { +diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc +index 58b68e675..38c8e906c 100644 +--- a/ssl/ssl_lib.cc ++++ b/ssl/ssl_lib.cc +@@ -3260,7 +3260,7 @@ namespace fips202205 { // Section 3.3.1 // "The server shall be configured to only use cipher suites that are // composed entirely of NIST approved algorithms" --static const int kCurves[] = {NID_X9_62_prime256v1, NID_secp384r1}; -+static const int kCurves[] = {NID_P256Kyber768Draft00, NID_X9_62_prime256v1, NID_secp384r1}; +-static const uint16_t kGroups[] = {SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1}; ++static const uint16_t kGroups[] = {SSL_GROUP_P256_KYBER768_DRAFT00, SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1}; static const uint16_t kSigAlgs[] = { SSL_SIGN_RSA_PKCS1_SHA256, -diff --git a/src/ssl/ssl_test.cc b/src/ssl/ssl_test.cc -index ef43a9e98..22178b5f6 100644 ---- a/src/ssl/ssl_test.cc -+++ b/src/ssl/ssl_test.cc -@@ -409,7 +409,34 @@ static const CurveTest kCurveTests[] = { +diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc +index a8f4f215b..e0ebb505e 100644 +--- a/ssl/ssl_test.cc ++++ b/ssl/ssl_test.cc +@@ -484,7 +484,34 @@ static const CurveTest kCurveTests[] = { "P-256:X25519Kyber768Draft00", - { SSL_CURVE_SECP256R1, SSL_CURVE_X25519_KYBER768_DRAFT00 }, + { SSL_GROUP_SECP256R1, SSL_GROUP_X25519_KYBER768_DRAFT00 }, }, - + { + "Xyber512D00", -+ { SSL_CURVE_X25519_KYBER512_DRAFT00 }, ++ { SSL_GROUP_X25519_KYBER512_DRAFT00 }, + }, + { + "Xyber768D00", -+ { SSL_CURVE_X25519_KYBER768_DRAFT00 }, ++ { SSL_GROUP_X25519_KYBER768_DRAFT00 }, + }, + { + "Xyber768D00:Xyber768D00Old", -+ { SSL_CURVE_X25519_KYBER768_DRAFT00, SSL_CURVE_X25519_KYBER768_DRAFT00_OLD }, ++ { SSL_GROUP_X25519_KYBER768_DRAFT00, SSL_GROUP_X25519_KYBER768_DRAFT00_OLD }, + }, + { + "P-256:Xyber512D00", -+ { SSL_CURVE_SECP256R1, SSL_CURVE_X25519_KYBER512_DRAFT00 }, ++ { SSL_GROUP_SECP256R1, SSL_GROUP_X25519_KYBER512_DRAFT00 }, + }, + { + "P256Kyber768D00", -+ { SSL_CURVE_P256_KYBER768_DRAFT00 }, ++ { SSL_GROUP_P256_KYBER768_DRAFT00 }, + }, + { + "X25519MLKEM768", -+ { SSL_CURVE_X25519_MLKEM768 }, ++ { SSL_GROUP_X25519_MLKEM768 }, + }, + { + "P-256:P256Kyber768D00", -+ { SSL_CURVE_SECP256R1, SSL_CURVE_P256_KYBER768_DRAFT00 }, ++ { SSL_GROUP_SECP256R1, SSL_GROUP_P256_KYBER768_DRAFT00 }, + }, { "P-256:P-384:P-521:X25519", { -diff --git a/src/tool/speed.cc b/src/tool/speed.cc -index 5b0205953..6b3c67dab 100644 ---- a/src/tool/speed.cc -+++ b/src/tool/speed.cc -@@ -904,6 +904,116 @@ static bool SpeedScrypt(const std::string &selected) { +diff --git a/tool/speed.cc b/tool/speed.cc +index 942dcade1..f31e9e244 100644 +--- a/tool/speed.cc ++++ b/tool/speed.cc +@@ -1018,6 +1018,116 @@ static bool SpeedScrypt(const std::string &selected) { return true; } @@ -5067,7 +4722,7 @@ index 5b0205953..6b3c67dab 100644 static bool SpeedHRSS(const std::string &selected) { if (!selected.empty() && selected != "HRSS") { return true; -@@ -958,55 +1068,6 @@ static bool SpeedHRSS(const std::string &selected) { +@@ -1079,55 +1189,6 @@ static bool SpeedHRSS(const std::string &selected) { return true; } @@ -5078,39 +4733,39 @@ index 5b0205953..6b3c67dab 100644 - - TimeResults results; - -- KYBER_private_key priv; -- uint8_t encoded_public_key[KYBER_PUBLIC_KEY_BYTES]; - uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES]; - // This ciphertext is nonsense, but Kyber decap is constant-time so, for the - // purposes of timing, it's fine. - memset(ciphertext, 42, sizeof(ciphertext)); -- if (!TimeFunction(&results, -- [&priv, &encoded_public_key, &ciphertext]() -> bool { -- uint8_t shared_secret[32]; -- KYBER_generate_key(encoded_public_key, &priv); -- KYBER_decap(shared_secret, sizeof(shared_secret), -- ciphertext, &priv); -- return true; -- })) { +- if (!TimeFunctionParallel(&results, [&]() -> bool { +- KYBER_private_key priv; +- uint8_t encoded_public_key[KYBER_PUBLIC_KEY_BYTES]; +- KYBER_generate_key(encoded_public_key, &priv); +- uint8_t shared_secret[32]; +- KYBER_decap(shared_secret, sizeof(shared_secret), ciphertext, &priv); +- return true; +- })) { - fprintf(stderr, "Failed to time KYBER_generate_key + KYBER_decap.\n"); - return false; - } - - results.Print("Kyber generate + decap"); - +- KYBER_private_key priv; +- uint8_t encoded_public_key[KYBER_PUBLIC_KEY_BYTES]; +- KYBER_generate_key(encoded_public_key, &priv); - KYBER_public_key pub; -- if (!TimeFunction( -- &results, [&pub, &ciphertext, &encoded_public_key]() -> bool { -- CBS encoded_public_key_cbs; -- CBS_init(&encoded_public_key_cbs, encoded_public_key, -- sizeof(encoded_public_key)); -- if (!KYBER_parse_public_key(&pub, &encoded_public_key_cbs)) { -- return false; -- } -- uint8_t shared_secret[32]; -- KYBER_encap(ciphertext, shared_secret, sizeof(shared_secret), &pub); -- return true; -- })) { +- if (!TimeFunctionParallel(&results, [&]() -> bool { +- CBS encoded_public_key_cbs; +- CBS_init(&encoded_public_key_cbs, encoded_public_key, +- sizeof(encoded_public_key)); +- if (!KYBER_parse_public_key(&pub, &encoded_public_key_cbs)) { +- return false; +- } +- uint8_t shared_secret[32]; +- KYBER_encap(ciphertext, shared_secret, sizeof(shared_secret), &pub); +- return true; +- })) { - fprintf(stderr, "Failed to time KYBER_encap.\n"); - return false; - } @@ -5120,19 +4775,16 @@ index 5b0205953..6b3c67dab 100644 - return true; -} - - static bool SpeedHashToCurve(const std::string &selected) { - if (!selected.empty() && selected.find("hashtocurve") == std::string::npos) { + static bool SpeedSpx(const std::string &selected) { + if (!selected.empty() && selected.find("spx") == std::string::npos) { return true; -@@ -1487,7 +1548,8 @@ bool Speed(const std::vector &args) { - !SpeedScrypt(selected) || - !SpeedRSAKeyGen(selected) || - !SpeedHRSS(selected) || -- !SpeedKyber(selected) || +@@ -1661,7 +1722,8 @@ bool Speed(const std::vector &args) { + !SpeedScrypt(selected) || // + !SpeedRSAKeyGen(selected) || // + !SpeedHRSS(selected) || // +- !SpeedKyber(selected) || // + !SpeedKyber512(selected) || + !SpeedKyber768(selected) || - !SpeedHashToCurve(selected) || + !SpeedSpx(selected) || // + !SpeedHashToCurve(selected) || // !SpeedTrustToken("TrustToken-Exp1-Batch1", TRUST_TOKEN_experiment_v1(), 1, - selected) || --- -2.46.0 - diff --git a/boring-sys/patches/rpk.patch b/boring-sys/patches/rpk.patch index bc2e3a8f6..edf977088 100644 --- a/boring-sys/patches/rpk.patch +++ b/boring-sys/patches/rpk.patch @@ -1,7 +1,7 @@ -diff --git a/src/include/openssl/ssl.h b/src/include/openssl/ssl.h -index 53aa9b453..87309c3e1 100644 ---- a/src/include/openssl/ssl.h -+++ b/src/include/openssl/ssl.h +diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h +index 003e0a5f7..b8f8d49c8 100644 +--- a/include/openssl/ssl.h ++++ b/include/openssl/ssl.h @@ -138,6 +138,25 @@ * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR * OTHERWISE. @@ -28,7 +28,7 @@ index 53aa9b453..87309c3e1 100644 #ifndef OPENSSL_HEADER_SSL_H #define OPENSSL_HEADER_SSL_H -@@ -1136,6 +1155,16 @@ OPENSSL_EXPORT int SSL_CTX_set_chain_and_key( +@@ -1138,6 +1157,16 @@ OPENSSL_EXPORT int SSL_CTX_set_chain_and_key( SSL_CTX *ctx, CRYPTO_BUFFER *const *certs, size_t num_certs, EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method); @@ -45,7 +45,7 @@ index 53aa9b453..87309c3e1 100644 // SSL_set_chain_and_key sets the certificate chain and private key for a TLS // client or server. References to the given |CRYPTO_BUFFER| and |EVP_PKEY| // objects are added as needed. Exactly one of |privkey| or |privkey_method| -@@ -1144,6 +1173,16 @@ OPENSSL_EXPORT int SSL_set_chain_and_key( +@@ -1146,6 +1175,16 @@ OPENSSL_EXPORT int SSL_set_chain_and_key( SSL *ssl, CRYPTO_BUFFER *const *certs, size_t num_certs, EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method); @@ -62,8 +62,8 @@ index 53aa9b453..87309c3e1 100644 // SSL_CTX_get0_chain returns the list of |CRYPTO_BUFFER|s that were set by // |SSL_CTX_set_chain_and_key|. Reference counts are not incremented by this // call. The return value may be |NULL| if no chain has been set. -@@ -3023,6 +3062,21 @@ OPENSSL_EXPORT void SSL_get0_peer_application_settings(const SSL *ssl, - OPENSSL_EXPORT int SSL_has_application_settings(const SSL *ssl); +@@ -3041,6 +3080,21 @@ OPENSSL_EXPORT int SSL_has_application_settings(const SSL *ssl); + OPENSSL_EXPORT void SSL_set_alps_use_new_codepoint(SSL *ssl, int use_new); +// Server Certificate Type. @@ -84,10 +84,10 @@ index 53aa9b453..87309c3e1 100644 // Certificate compression. // // Certificates in TLS 1.3 can be compressed (RFC 8879). BoringSSL supports this -diff --git a/src/include/openssl/tls1.h b/src/include/openssl/tls1.h -index 772fb87a3..be605c1aa 100644 ---- a/src/include/openssl/tls1.h -+++ b/src/include/openssl/tls1.h +diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h +index c1207a3b7..ac6ed222a 100644 +--- a/include/openssl/tls1.h ++++ b/include/openssl/tls1.h @@ -146,6 +146,25 @@ * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR * OTHERWISE. @@ -124,10 +124,10 @@ index 772fb87a3..be605c1aa 100644 // ExtensionType value from RFC 7685 #define TLSEXT_TYPE_padding 21 -diff --git a/src/ssl/extensions.cc b/src/ssl/extensions.cc -index 5ee280221..2692e5478 100644 ---- a/src/ssl/extensions.cc -+++ b/src/ssl/extensions.cc +diff --git a/ssl/extensions.cc b/ssl/extensions.cc +index b13400097..8694712fd 100644 +--- a/ssl/extensions.cc ++++ b/ssl/extensions.cc @@ -105,6 +105,25 @@ * This product includes cryptographic software written by Eric Young * (eay@cryptsoft.com). This product includes software written by Tim @@ -154,7 +154,7 @@ index 5ee280221..2692e5478 100644 #include -@@ -3094,6 +3113,146 @@ bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert, +@@ -3108,6 +3127,146 @@ bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert, return true; } @@ -301,9 +301,9 @@ index 5ee280221..2692e5478 100644 // kExtensions contains all the supported extensions. static const struct tls_extension kExtensions[] = { { -@@ -3267,6 +3426,13 @@ static const struct tls_extension kExtensions[] = { +@@ -3289,6 +3448,13 @@ static const struct tls_extension kExtensions[] = { ignore_parse_clienthello, - ext_alps_add_serverhello, + ext_alps_add_serverhello_old, }, + { + TLSEXT_TYPE_server_certificate_type, @@ -315,10 +315,10 @@ index 5ee280221..2692e5478 100644 }; #define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension)) -diff --git a/src/ssl/handshake.cc b/src/ssl/handshake.cc -index 8d5a23872..b9ac70dfe 100644 ---- a/src/ssl/handshake.cc -+++ b/src/ssl/handshake.cc +diff --git a/ssl/handshake.cc b/ssl/handshake.cc +index 8d5a23872..c8ca629e8 100644 +--- a/ssl/handshake.cc ++++ b/ssl/handshake.cc @@ -109,6 +109,25 @@ * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. * ECC cipher suite support in OpenSSL originally developed by @@ -345,14 +345,14 @@ index 8d5a23872..b9ac70dfe 100644 #include -@@ -150,6 +169,7 @@ SSL_HANDSHAKE::SSL_HANDSHAKE(SSL *ssl_arg) +@@ -148,6 +167,7 @@ SSL_HANDSHAKE::SSL_HANDSHAKE(SSL *ssl_arg) + handback(false), + hints_requested(false), cert_compression_negotiated(false), + server_certificate_type_negotiated(false), apply_jdk11_workaround(false), can_release_private_key(false), channel_id_negotiated(false) { - assert(ssl); - @@ -365,7 +385,21 @@ enum ssl_verify_result_t ssl_verify_peer_cert(SSL_HANDSHAKE *hs) { uint8_t alert = SSL_AD_CERTIFICATE_UNKNOWN; @@ -376,10 +376,10 @@ index 8d5a23872..b9ac70dfe 100644 ret = hs->config->custom_verify_callback(ssl, &alert); switch (ret) { case ssl_verify_ok: -diff --git a/src/ssl/internal.h b/src/ssl/internal.h -index 1e6da2153..f04888384 100644 ---- a/src/ssl/internal.h -+++ b/src/ssl/internal.h +diff --git a/ssl/internal.h b/ssl/internal.h +index c9facb699..d7363e729 100644 +--- a/ssl/internal.h ++++ b/ssl/internal.h @@ -138,6 +138,25 @@ * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR * OTHERWISE. @@ -406,7 +406,7 @@ index 1e6da2153..f04888384 100644 #ifndef OPENSSL_HEADER_SSL_INTERNAL_H #define OPENSSL_HEADER_SSL_INTERNAL_H -@@ -1286,6 +1305,8 @@ int ssl_write_buffer_flush(SSL *ssl); +@@ -1311,6 +1330,8 @@ int ssl_write_buffer_flush(SSL *ssl); // configured. bool ssl_has_certificate(const SSL_HANDSHAKE *hs); @@ -415,7 +415,7 @@ index 1e6da2153..f04888384 100644 // ssl_parse_cert_chain parses a certificate list from |cbs| in the format used // by a TLS Certificate message. On success, it advances |cbs| and returns // true. Otherwise, it returns false and sets |*out_alert| to an alert to send -@@ -1887,6 +1908,8 @@ struct SSL_HANDSHAKE { +@@ -1912,6 +1933,8 @@ struct SSL_HANDSHAKE { // |cert_compression_negotiated| is true. uint16_t cert_compression_alg_id; @@ -424,7 +424,7 @@ index 1e6da2153..f04888384 100644 // ech_hpke_ctx is the HPKE context used in ECH. On the server, it is // initialized if |ech_status| is |ssl_ech_accepted|. On the client, it is // initialized if |selected_ech_config| is not nullptr. -@@ -2037,6 +2060,8 @@ struct SSL_HANDSHAKE { +@@ -2062,6 +2085,8 @@ struct SSL_HANDSHAKE { // cert_compression_negotiated is true iff |cert_compression_alg_id| is valid. bool cert_compression_negotiated : 1; @@ -433,7 +433,7 @@ index 1e6da2153..f04888384 100644 // apply_jdk11_workaround is true if the peer is probably a JDK 11 client // which implemented TLS 1.3 incorrectly. bool apply_jdk11_workaround : 1; -@@ -3049,6 +3074,9 @@ struct SSL_CONFIG { +@@ -3074,6 +3099,9 @@ struct SSL_CONFIG { // along with their corresponding ALPS values. GrowableArray alps_configs; @@ -443,7 +443,7 @@ index 1e6da2153..f04888384 100644 // Contains the QUIC transport params that this endpoint will send. Array quic_transport_params; -@@ -3648,6 +3676,9 @@ struct ssl_ctx_st { +@@ -3666,6 +3694,9 @@ struct ssl_ctx_st { // format. bssl::Array alpn_client_proto_list; @@ -453,10 +453,10 @@ index 1e6da2153..f04888384 100644 // SRTP profiles we are willing to do from RFC 5764 bssl::UniquePtr srtp_profiles; -diff --git a/src/ssl/ssl_cert.cc b/src/ssl/ssl_cert.cc +diff --git a/ssl/ssl_cert.cc b/ssl/ssl_cert.cc index aa46a8bb6..d90840fce 100644 ---- a/src/ssl/ssl_cert.cc -+++ b/src/ssl/ssl_cert.cc +--- a/ssl/ssl_cert.cc ++++ b/ssl/ssl_cert.cc @@ -111,6 +111,25 @@ * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. * ECC cipher suite support in OpenSSL originally developed by @@ -573,10 +573,10 @@ index aa46a8bb6..d90840fce 100644 const STACK_OF(CRYPTO_BUFFER)* SSL_CTX_get0_chain(const SSL_CTX *ctx) { return ctx->cert->chain.get(); } -diff --git a/src/ssl/ssl_lib.cc b/src/ssl/ssl_lib.cc -index 838761af5..e4f1a12b7 100644 ---- a/src/ssl/ssl_lib.cc -+++ b/src/ssl/ssl_lib.cc +diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc +index 58b68e675..384debbd3 100644 +--- a/ssl/ssl_lib.cc ++++ b/ssl/ssl_lib.cc @@ -137,6 +137,25 @@ * SPECIFICALLY DISCLAIMS ANY LIABILITY FOR CLAIMS BROUGHT BY YOU OR ANY * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR @@ -615,8 +615,8 @@ index 838761af5..e4f1a12b7 100644 if (!ssl->method->ssl_new(ssl.get()) || !ssl->ctx->x509_method->ssl_new(ssl->s3->hs.get())) { return nullptr; -@@ -3140,6 +3164,53 @@ int SSL_CTX_set_tlsext_status_arg(SSL_CTX *ctx, void *arg) { - return 1; +@@ -3249,6 +3273,53 @@ int SSL_set1_curves_list(SSL *ssl, const char *curves) { + return SSL_set1_groups_list(ssl, curves); } +int SSL_CTX_set_server_raw_public_key_certificate(SSL_CTX *ctx, @@ -669,10 +669,10 @@ index 838761af5..e4f1a12b7 100644 namespace fips202205 { // (References are to SP 800-52r2): -diff --git a/src/ssl/tls13_both.cc b/src/ssl/tls13_both.cc +diff --git a/ssl/tls13_both.cc b/ssl/tls13_both.cc index 5ab5a1c93..79135613e 100644 ---- a/src/ssl/tls13_both.cc -+++ b/src/ssl/tls13_both.cc +--- a/ssl/tls13_both.cc ++++ b/ssl/tls13_both.cc @@ -11,6 +11,25 @@ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN @@ -750,10 +750,10 @@ index 5ab5a1c93..79135613e 100644 if (!ssl_has_certificate(hs)) { return ssl_add_message_cbb(ssl, cbb.get()); } -diff --git a/src/ssl/tls13_server.cc b/src/ssl/tls13_server.cc -index 9d26f4e00..a92689761 100644 ---- a/src/ssl/tls13_server.cc -+++ b/src/ssl/tls13_server.cc +diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc +index 707cf846b..6916606c2 100644 +--- a/ssl/tls13_server.cc ++++ b/ssl/tls13_server.cc @@ -11,6 +11,25 @@ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN diff --git a/boring-sys/patches/underscore-wildcards.patch b/boring-sys/patches/underscore-wildcards.patch index f281b3a1a..38e406a22 100644 --- a/boring-sys/patches/underscore-wildcards.patch +++ b/boring-sys/patches/underscore-wildcards.patch @@ -1,21 +1,10 @@ https://github.com/google/boringssl/compare/master...cloudflare:boringssl:underscore-wildcards ---- a/src/crypto/x509v3/v3_utl.c -+++ b/src/crypto/x509v3/v3_utl.c -@@ -790,7 +790,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len, - // Check that the part matched by the wildcard contains only - // permitted characters and only matches a single label. - for (p = wildcard_start; p != wildcard_end; ++p) { -- if (!OPENSSL_isalnum(*p) && *p != '-') { -+ if (!OPENSSL_isalnum(*p) && *p != '-' && -+ !(*p == '_' && -+ (flags & X509_CHECK_FLAG_UNDERSCORE_WILDCARDS))) { - return 0; - } - } ---- a/src/crypto/x509/x509_test.cc -+++ b/src/crypto/x509/x509_test.cc -@@ -4500,6 +4500,31 @@ TEST(X509Test, Names) { +diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc +index 9699b5a75..b0e9b34a6 100644 +--- a/crypto/x509/x509_test.cc ++++ b/crypto/x509/x509_test.cc +@@ -4420,6 +4420,31 @@ TEST(X509Test, Names) { /*invalid_emails=*/{}, /*flags=*/0, }, @@ -47,9 +36,26 @@ https://github.com/google/boringssl/compare/master...cloudflare:boringssl:unders }; size_t i = 0; ---- a/src/include/openssl/x509c3.h -+++ b/src/include/openssl/x509v3.h -@@ -4497,6 +4497,8 @@ OPENSSL_EXPORT int X509_PURPOSE_get_id(const X509_PURPOSE *); +diff --git a/crypto/x509v3/v3_utl.c b/crypto/x509v3/v3_utl.c +index bbc82e283..e61e1901d 100644 +--- a/crypto/x509v3/v3_utl.c ++++ b/crypto/x509v3/v3_utl.c +@@ -790,7 +790,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len, + // Check that the part matched by the wildcard contains only + // permitted characters and only matches a single label. + for (p = wildcard_start; p != wildcard_end; ++p) { +- if (!OPENSSL_isalnum(*p) && *p != '-') { ++ if (!OPENSSL_isalnum(*p) && *p != '-' && ++ !(*p == '_' && ++ (flags & X509_CHECK_FLAG_UNDERSCORE_WILDCARDS))) { + return 0; + } + } +diff --git a/include/openssl/x509v3.h b/include/openssl/x509v3.h +index 2a2e02c2e..24e0604b0 100644 +--- a/include/openssl/x509v3.h ++++ b/include/openssl/x509v3.h +@@ -939,6 +939,8 @@ OPENSSL_EXPORT STACK_OF(OPENSSL_STRING) *X509_get1_ocsp(X509 *x); #define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS 0 // Skip the subject common name fallback if subjectAltNames is missing. #define X509_CHECK_FLAG_NEVER_CHECK_SUBJECT 0x20 @@ -58,4 +64,3 @@ https://github.com/google/boringssl/compare/master...cloudflare:boringssl:unders OPENSSL_EXPORT int X509_check_host(X509 *x, const char *chk, size_t chklen, unsigned int flags, char **peername); --- diff --git a/boring/Cargo.toml b/boring/Cargo.toml index f9a3527b9..caac99f23 100644 --- a/boring/Cargo.toml +++ b/boring/Cargo.toml @@ -19,29 +19,11 @@ rustdoc-args = ["--cfg", "docsrs"] [features] # Controlling the build -# NOTE: This feature is deprecated. It is needed for the submoduled -# boringssl-fips, which is extremely old and requires modifications to the -# bindings, as some newer APIs don't exist and some function signatures have -# changed. It is highly recommended to use `fips-precompiled` instead. -# -# This feature sets `fips-compat` on behalf of the user to guarantee bindings -# compatibility with the submoduled boringssl-fips. -# # Use a FIPS-validated version of BoringSSL. -fips = ["fips-compat", "boring-sys/fips"] +fips = ["boring-sys/fips"] -# Build with compatibility for the submoduled boringssl-fips, without enabling -# the `fips` feature itself (useful e.g. if `fips-link-precompiled` is used -# with an older BoringSSL version). -fips-compat = [] - -# Use a precompiled FIPS-validated version of BoringSSL. Meant to be used with -# FIPS-20230428 or newer. Users must set `BORING_BSSL_FIPS_PATH` to use this -# feature, or else the build will fail. -fips-precompiled = ["boring-sys/fips-precompiled"] - -# Link with precompiled FIPS-validated `bcm.o` module. -fips-link-precompiled = ["boring-sys/fips-link-precompiled"] +# **DO NOT USE** This will be removed without warning in future releases. +legacy-compat-deprecated = [] # Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250) # This feature is necessary in order to compile the bindings for the diff --git a/boring/src/bio.rs b/boring/src/bio.rs index 71120606f..2e6b2572d 100644 --- a/boring/src/bio.rs +++ b/boring/src/bio.rs @@ -19,9 +19,9 @@ impl Drop for MemBioSlice<'_> { impl<'a> MemBioSlice<'a> { pub fn new(buf: &'a [u8]) -> Result, ErrorStack> { - #[cfg(not(feature = "fips-compat"))] + #[cfg(not(feature = "legacy-compat-deprecated"))] type BufLen = isize; - #[cfg(feature = "fips-compat")] + #[cfg(feature = "legacy-compat-deprecated")] type BufLen = libc::c_int; ffi::init(); diff --git a/boring/src/fips.rs b/boring/src/fips.rs index 8e4512264..708b0903f 100644 --- a/boring/src/fips.rs +++ b/boring/src/fips.rs @@ -15,16 +15,8 @@ pub fn enabled() -> bool { #[test] fn is_enabled() { - #[cfg(any( - feature = "fips", - feature = "fips-precompiled", - feature = "fips-link-precompiled" - ))] + #[cfg(feature = "fips")] assert!(enabled()); - #[cfg(not(any( - feature = "fips", - feature = "fips-precompiled", - feature = "fips-link-precompiled" - )))] + #[cfg(not(feature = "fips"))] assert!(!enabled()); } diff --git a/boring/src/lib.rs b/boring/src/lib.rs index 4b84b7c5d..77f3e726f 100644 --- a/boring/src/lib.rs +++ b/boring/src/lib.rs @@ -137,7 +137,6 @@ pub mod error; pub mod ex_data; pub mod fips; pub mod hash; -#[cfg(not(feature = "fips"))] pub mod hpke; pub mod memcmp; pub mod nid; diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 9cd40405c..00fa5ba97 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -104,7 +104,6 @@ pub use self::async_callbacks::{ pub use self::connector::{ ConnectConfiguration, SslAcceptor, SslAcceptorBuilder, SslConnector, SslConnectorBuilder, }; -#[cfg(not(feature = "fips"))] pub use self::ech::{SslEchKeys, SslEchKeysRef}; pub use self::error::{Error, ErrorCode, HandshakeError}; @@ -112,7 +111,6 @@ mod async_callbacks; mod bio; mod callbacks; mod connector; -#[cfg(not(feature = "fips"))] mod ech; mod error; mod mut_only; @@ -708,45 +706,32 @@ pub struct SslCurveNid(c_int); pub struct SslCurve(c_int); impl SslCurve { - pub const SECP224R1: SslCurve = SslCurve(ffi::SSL_CURVE_SECP224R1 as _); + pub const SECP224R1: SslCurve = SslCurve(ffi::SSL_GROUP_SECP224R1 as _); - pub const SECP256R1: SslCurve = SslCurve(ffi::SSL_CURVE_SECP256R1 as _); + pub const SECP256R1: SslCurve = SslCurve(ffi::SSL_GROUP_SECP256R1 as _); - pub const SECP384R1: SslCurve = SslCurve(ffi::SSL_CURVE_SECP384R1 as _); + pub const SECP384R1: SslCurve = SslCurve(ffi::SSL_GROUP_SECP384R1 as _); - pub const SECP521R1: SslCurve = SslCurve(ffi::SSL_CURVE_SECP521R1 as _); + pub const SECP521R1: SslCurve = SslCurve(ffi::SSL_GROUP_SECP521R1 as _); - pub const X25519: SslCurve = SslCurve(ffi::SSL_CURVE_X25519 as _); + pub const X25519: SslCurve = SslCurve(ffi::SSL_GROUP_X25519 as _); - #[cfg(not(any(feature = "fips", feature = "fips-precompiled")))] pub const X25519_KYBER768_DRAFT00: SslCurve = - SslCurve(ffi::SSL_CURVE_X25519_KYBER768_DRAFT00 as _); + SslCurve(ffi::SSL_GROUP_X25519_KYBER768_DRAFT00 as _); - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] + #[cfg(feature = "pq-experimental")] pub const X25519_KYBER768_DRAFT00_OLD: SslCurve = - SslCurve(ffi::SSL_CURVE_X25519_KYBER768_DRAFT00_OLD as _); + SslCurve(ffi::SSL_GROUP_X25519_KYBER768_DRAFT00_OLD as _); - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] + #[cfg(feature = "pq-experimental")] pub const X25519_KYBER512_DRAFT00: SslCurve = - SslCurve(ffi::SSL_CURVE_X25519_KYBER512_DRAFT00 as _); + SslCurve(ffi::SSL_GROUP_X25519_KYBER512_DRAFT00 as _); - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - pub const P256_KYBER768_DRAFT00: SslCurve = SslCurve(ffi::SSL_CURVE_P256_KYBER768_DRAFT00 as _); + #[cfg(feature = "pq-experimental")] + pub const P256_KYBER768_DRAFT00: SslCurve = SslCurve(ffi::SSL_GROUP_P256_KYBER768_DRAFT00 as _); - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - pub const X25519_MLKEM768: SslCurve = SslCurve(ffi::SSL_CURVE_X25519_MLKEM768 as _); + #[cfg(feature = "pq-experimental")] + pub const X25519_MLKEM768: SslCurve = SslCurve(ffi::SSL_GROUP_X25519_MLKEM768 as _); /// Returns the curve name #[corresponds(SSL_get_curve_name)] @@ -766,7 +751,7 @@ impl SslCurve { // against the absence of the `kx-safe-default` feature and thus this function is never used. // // **NOTE**: This function only exists because the version of boringssl we currently use does - // not expose SSL_CTX_set1_group_ids. Because `SslRef::curve()` returns the public SSL_CURVE id + // not expose SSL_CTX_set1_group_ids. Because `SslRef::curve()` returns the public SSL_GROUP id // as opposed to the internal NID, but `SslContextBuilder::set_curves()` requires the internal // NID, we need this mapping in place to avoid breaking changes to the public API. Once the // underlying boringssl version is upgraded, this should be removed in favor of the new @@ -774,33 +759,20 @@ impl SslCurve { #[allow(dead_code)] pub fn nid(&self) -> Option { match self.0 { - ffi::SSL_CURVE_SECP224R1 => Some(ffi::NID_secp224r1), - ffi::SSL_CURVE_SECP256R1 => Some(ffi::NID_X9_62_prime256v1), - ffi::SSL_CURVE_SECP384R1 => Some(ffi::NID_secp384r1), - ffi::SSL_CURVE_SECP521R1 => Some(ffi::NID_secp521r1), - ffi::SSL_CURVE_X25519 => Some(ffi::NID_X25519), - #[cfg(not(any(feature = "fips", feature = "fips-precompiled")))] - ffi::SSL_CURVE_X25519_KYBER768_DRAFT00 => Some(ffi::NID_X25519Kyber768Draft00), - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - ffi::SSL_CURVE_X25519_KYBER768_DRAFT00_OLD => Some(ffi::NID_X25519Kyber768Draft00Old), - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - ffi::SSL_CURVE_X25519_KYBER512_DRAFT00 => Some(ffi::NID_X25519Kyber512Draft00), - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - ffi::SSL_CURVE_P256_KYBER768_DRAFT00 => Some(ffi::NID_P256Kyber768Draft00), - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - ffi::SSL_CURVE_X25519_MLKEM768 => Some(ffi::NID_X25519MLKEM768), + ffi::SSL_GROUP_SECP224R1 => Some(ffi::NID_secp224r1), + ffi::SSL_GROUP_SECP256R1 => Some(ffi::NID_X9_62_prime256v1), + ffi::SSL_GROUP_SECP384R1 => Some(ffi::NID_secp384r1), + ffi::SSL_GROUP_SECP521R1 => Some(ffi::NID_secp521r1), + ffi::SSL_GROUP_X25519 => Some(ffi::NID_X25519), + ffi::SSL_GROUP_X25519_KYBER768_DRAFT00 => Some(ffi::NID_X25519Kyber768Draft00), + #[cfg(feature = "pq-experimental")] + ffi::SSL_GROUP_X25519_KYBER768_DRAFT00_OLD => Some(ffi::NID_X25519Kyber768Draft00Old), + #[cfg(feature = "pq-experimental")] + ffi::SSL_GROUP_X25519_KYBER512_DRAFT00 => Some(ffi::NID_X25519Kyber512Draft00), + #[cfg(feature = "pq-experimental")] + ffi::SSL_GROUP_P256_KYBER768_DRAFT00 => Some(ffi::NID_P256Kyber768Draft00), + #[cfg(feature = "pq-experimental")] + ffi::SSL_GROUP_X25519_MLKEM768 => Some(ffi::NID_X25519MLKEM768), _ => None, } .map(SslCurveNid) @@ -809,12 +781,11 @@ impl SslCurve { /// A compliance policy. #[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg(not(feature = "fips-compat"))] pub struct CompliancePolicy(ffi::ssl_compliance_policy_t); -#[cfg(not(feature = "fips-compat"))] impl CompliancePolicy { /// Does nothing, however setting this does not undo other policies, so trying to set this is an error. + #[cfg(not(feature = "legacy-compat-deprecated"))] pub const NONE: Self = Self(ffi::ssl_compliance_policy_t::ssl_compliance_policy_none); /// Configures a TLS connection to try and be compliant with NIST requirements, but does not guarantee success. @@ -824,6 +795,7 @@ impl CompliancePolicy { /// Partially configures a TLS connection to be compliant with WPA3. Callers must enforce certificate chain requirements themselves. /// Use of this policy is less secure than the default and not recommended. + #[cfg(not(feature = "legacy-compat-deprecated"))] pub const WPA3_192_202304: Self = Self(ffi::ssl_compliance_policy_t::ssl_compliance_policy_wpa3_192_202304); } @@ -1609,7 +1581,10 @@ impl SslContextBuilder { #[corresponds(SSL_CTX_set_alpn_protos)] pub fn set_alpn_protos(&mut self, protocols: &[u8]) -> Result<(), ErrorStack> { unsafe { - #[cfg_attr(not(feature = "fips-compat"), allow(clippy::unnecessary_cast))] + #[cfg_attr( + not(feature = "legacy-compat-deprecated"), + allow(clippy::unnecessary_cast) + )] { assert!(protocols.len() <= ProtosLen::MAX as usize); } @@ -2009,7 +1984,6 @@ impl SslContextBuilder { /// version of BoringSSL which doesn't yet include these APIs. /// Once the submoduled fips commit is upgraded, these gates can be removed. #[corresponds(SSL_CTX_set_permute_extensions)] - #[cfg(not(feature = "fips-compat"))] pub fn set_permute_extensions(&mut self, enabled: bool) { unsafe { ffi::SSL_CTX_set_permute_extensions(self.as_ptr(), enabled as _) } } @@ -2087,7 +2061,6 @@ impl SslContextBuilder { /// /// This feature isn't available in the certified version of BoringSSL. #[corresponds(SSL_CTX_set_compliance_policy)] - #[cfg(not(feature = "fips-compat"))] pub fn set_compliance_policy(&mut self, policy: CompliancePolicy) -> Result<(), ErrorStack> { unsafe { cvt_0i(ffi::SSL_CTX_set_compliance_policy(self.as_ptr(), policy.0)).map(|_| ()) } } @@ -2108,7 +2081,6 @@ impl SslContextBuilder { /// ECHConfigs to allow stale DNS caches to update. Unlike most `SSL_CTX` APIs, this function /// is safe to call even after the `SSL_CTX` has been associated with connections on various /// threads. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_CTX_set1_ech_keys)] pub fn set_ech_keys(&self, keys: &SslEchKeys) -> Result<(), ErrorStack> { unsafe { cvt(ffi::SSL_CTX_set1_ech_keys(self.as_ptr(), keys.as_ptr())).map(|_| ()) } @@ -2376,7 +2348,6 @@ impl SslContextRef { /// ECHConfigs to allow stale DNS caches to update. Unlike most `SSL_CTX` APIs, this function /// is safe to call even after the `SSL_CTX` has been associated with connections on various /// threads. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_CTX_set1_ech_keys)] pub fn set_ech_keys(&self, keys: &SslEchKeys) -> Result<(), ErrorStack> { unsafe { cvt(ffi::SSL_CTX_set1_ech_keys(self.as_ptr(), keys.as_ptr())).map(|_| ()) } @@ -2390,9 +2361,9 @@ impl SslContextRef { #[derive(Debug)] pub struct GetSessionPendingError; -#[cfg(not(feature = "fips-compat"))] +#[cfg(not(feature = "legacy-compat-deprecated"))] type ProtosLen = usize; -#[cfg(feature = "fips-compat")] +#[cfg(feature = "legacy-compat-deprecated")] type ProtosLen = libc::c_uint; /// Information about the state of a cipher. @@ -3161,7 +3132,6 @@ impl SslRef { /// Note: This is gated to non-fips because the fips feature builds with a separate /// version of BoringSSL which doesn't yet include these APIs. /// Once the submoduled fips commit is upgraded, these gates can be removed. - #[cfg(not(feature = "fips-compat"))] pub fn set_permute_extensions(&mut self, enabled: bool) { unsafe { ffi::SSL_set_permute_extensions(self.as_ptr(), enabled as _) } } @@ -3172,7 +3142,10 @@ impl SslRef { #[corresponds(SSL_set_alpn_protos)] pub fn set_alpn_protos(&mut self, protocols: &[u8]) -> Result<(), ErrorStack> { unsafe { - #[cfg_attr(not(feature = "fips-compat"), allow(clippy::unnecessary_cast))] + #[cfg_attr( + not(feature = "legacy-compat-deprecated"), + allow(clippy::unnecessary_cast) + )] { assert!(protocols.len() <= ProtosLen::MAX as usize); } @@ -3886,7 +3859,6 @@ impl SslRef { /// Clients should use `get_ech_name_override` to verify the server certificate in case of ECH /// rejection, and follow up with `get_ech_retry_configs` to retry the connection with a fresh /// set of ECHConfigs. If the retry also fails, clients should report a connection failure. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_set1_ech_config_list)] pub fn set_ech_config_list(&mut self, ech_config_list: &[u8]) -> Result<(), ErrorStack> { unsafe { @@ -3905,7 +3877,6 @@ impl SslRef { /// Clients should call this function when handling an `SSL_R_ECH_REJECTED` error code to /// recover from potential key mismatches. If the result is `Some`, the client should retry the /// connection using the returned `ECHConfigList`. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_get0_ech_retry_configs)] #[must_use] pub fn get_ech_retry_configs(&self) -> Option<&[u8]> { @@ -3928,7 +3899,6 @@ impl SslRef { /// Clients should call this function during the certificate verification callback to /// ensure the server's certificate is valid for the public name, which is required to /// authenticate retry configs. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_get0_ech_name_override)] #[must_use] pub fn get_ech_name_override(&self) -> Option<&[u8]> { @@ -3946,7 +3916,6 @@ impl SslRef { } // Whether or not `SSL` negotiated ECH. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_ech_accepted)] #[must_use] pub fn ech_accepted(&self) -> bool { @@ -3954,7 +3923,6 @@ impl SslRef { } // Whether or not to enable ECH grease on `SSL`. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_set_enable_ech_grease)] pub fn set_enable_ech_grease(&self, enable: bool) { let enable = if enable { 1 } else { 0 }; @@ -3965,7 +3933,6 @@ impl SslRef { } /// Sets the compliance policy on `SSL`. - #[cfg(not(feature = "fips-compat"))] #[corresponds(SSL_set_compliance_policy)] pub fn set_compliance_policy(&mut self, policy: CompliancePolicy) -> Result<(), ErrorStack> { unsafe { cvt_0i(ffi::SSL_set_compliance_policy(self.as_ptr(), policy.0)).map(|_| ()) } diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index 9b7b024c3..6ac6ca751 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -22,13 +22,11 @@ use crate::x509::store::X509StoreBuilder; use crate::x509::verify::X509CheckFlags; use crate::x509::{X509Name, X509}; -#[cfg(not(feature = "fips"))] use super::CompliancePolicy; mod cert_compressor; mod cert_verify; mod custom_verify; -#[cfg(not(feature = "fips"))] mod ech; mod private_key_method; mod server; @@ -1037,7 +1035,6 @@ fn test_get_ciphers() { } #[test] -#[cfg(not(feature = "fips"))] fn test_set_compliance() { let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_compliance_policy(CompliancePolicy::FIPS_202205) @@ -1118,7 +1115,6 @@ fn test_info_callback() { assert!(CALLED_BACK.load(Ordering::Relaxed)); } -#[cfg(not(feature = "fips-compat"))] #[test] fn test_ssl_set_compliance() { let ctx = SslContext::builder(SslMethod::tls()).unwrap().build(); diff --git a/boring/src/x509/mod.rs b/boring/src/x509/mod.rs index 8bf96d339..17115d685 100644 --- a/boring/src/x509/mod.rs +++ b/boring/src/x509/mod.rs @@ -1120,9 +1120,9 @@ impl X509NameBuilder { } } -#[cfg(not(feature = "fips-compat"))] +#[cfg(not(feature = "legacy-compat-deprecated"))] type ValueLen = isize; -#[cfg(feature = "fips-compat")] +#[cfg(feature = "legacy-compat-deprecated")] type ValueLen = i32; foreign_type_and_impl_send_sync! { diff --git a/boring/src/x509/tests/trusted_first.rs b/boring/src/x509/tests/trusted_first.rs index b5e714b53..9f49ffe3c 100644 --- a/boring/src/x509/tests/trusted_first.rs +++ b/boring/src/x509/tests/trusted_first.rs @@ -15,7 +15,7 @@ fn test_verify_cert() { assert_eq!(Ok(()), verify(&leaf, &[&root1], &[&intermediate], |_| {})); - #[cfg(not(feature = "fips-compat"))] + #[cfg(not(feature = "legacy-compat-deprecated"))] assert_eq!( Ok(()), verify( @@ -26,7 +26,7 @@ fn test_verify_cert() { ) ); - #[cfg(feature = "fips-compat")] + #[cfg(feature = "legacy-compat-deprecated")] assert_eq!( Err(X509VerifyError::CERT_HAS_EXPIRED), verify( diff --git a/hyper-boring/Cargo.toml b/hyper-boring/Cargo.toml index 77a8a788c..25f360fd8 100644 --- a/hyper-boring/Cargo.toml +++ b/hyper-boring/Cargo.toml @@ -17,20 +17,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] # Use a FIPS-validated version of boringssl. -fips = ["tokio-boring/fips"] - -# Use a FIPS build of BoringSSL, but don't set "fips-compat". -# -# As of boringSSL commit a430310d6563c0734ddafca7731570dfb683dc19, we no longer -# need to make exceptions for the types of BufLen, ProtosLen, and ValueLen, -# which means the "fips-compat" feature is no longer needed. -# -# TODO(cjpatton) Delete this feature and modify "fips" so that it doesn't imply -# "fips-compat". -fips-precompiled = ["tokio-boring/fips-precompiled"] - -# Link with precompiled FIPS-validated `bcm.o` module. -fips-link-precompiled = ["tokio-boring/fips-link-precompiled"] +fips = ["boring/fips", "tokio-boring/fips"] # Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/) pq-experimental = ["tokio-boring/pq-experimental"] diff --git a/tokio-boring/Cargo.toml b/tokio-boring/Cargo.toml index 75c64129c..c57353415 100644 --- a/tokio-boring/Cargo.toml +++ b/tokio-boring/Cargo.toml @@ -19,19 +19,6 @@ rustdoc-args = ["--cfg", "docsrs"] # Use a FIPS-validated version of boringssl. fips = ["boring/fips", "boring-sys/fips"] -# Use a FIPS build of BoringSSL, but don't set "fips-compat". -# -# As of boringSSL commit a430310d6563c0734ddafca7731570dfb683dc19, we no longer -# need to make exceptions for the types of BufLen, ProtosLen, and ValueLen, -# which means the "fips-compat" feature is no longer needed. -# -# TODO(cjpatton) Delete this feature and modify "fips" so that it doesn't imply -# "fips-compat". -fips-precompiled = ["boring/fips-precompiled"] - -# Link with precompiled FIPS-validated `bcm.o` module. -fips-link-precompiled = ["boring/fips-link-precompiled", "boring-sys/fips-link-precompiled"] - # Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/) pq-experimental = ["boring/pq-experimental"] From 0fc992bd76632c51fcb8028d2abd0fca5c4277ad Mon Sep 17 00:00:00 2001 From: Rushil Mehra Date: Wed, 19 Feb 2025 00:47:00 -0800 Subject: [PATCH 005/111] Align SslStream APIs with upstream SslStream::new() is fallible, but `SslStream::from_raw_parts()` and `SslStreamBuilder::new()` now unwrap. Upstream has also deprecated the `SslStreamBuilder`, maybe we should do the same. --- boring/src/ssl/mod.rs | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 00fa5ba97..baefe9bfb 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -4040,26 +4040,23 @@ where } impl SslStream { - fn new_base(ssl: Ssl, stream: S) -> Self { - unsafe { - let (bio, method) = bio::new(stream).unwrap(); - ffi::SSL_set_bio(ssl.as_ptr(), bio, bio); - - SslStream { - ssl: ManuallyDrop::new(ssl), - method: ManuallyDrop::new(method), - _p: PhantomData, - } - } - } - /// Creates a new `SslStream`. /// /// This function performs no IO; the stream will not have performed any part of the handshake /// with the peer. The `connect` and `accept` methods can be used to /// explicitly perform the handshake. pub fn new(ssl: Ssl, stream: S) -> Result { - Ok(Self::new_base(ssl, stream)) + let (bio, method) = bio::new(stream)?; + + unsafe { + ffi::SSL_set_bio(ssl.as_ptr(), bio, bio); + } + + Ok(SslStream { + ssl: ManuallyDrop::new(ssl), + method: ManuallyDrop::new(method), + _p: PhantomData, + }) } /// Constructs an `SslStream` from a pointer to the underlying OpenSSL `SSL` struct. @@ -4071,7 +4068,7 @@ impl SslStream { /// The caller must ensure the pointer is valid. pub unsafe fn from_raw_parts(ssl: *mut ffi::SSL, stream: S) -> Self { let ssl = Ssl::from_ptr(ssl); - Self::new_base(ssl, stream) + Self::new(ssl, stream).unwrap() } /// Like `read`, but takes a possibly-uninitialized slice. @@ -4338,7 +4335,7 @@ where /// Begin creating an `SslStream` atop `stream` pub fn new(ssl: Ssl, stream: S) -> Self { Self { - inner: SslStream::new_base(ssl, stream), + inner: SslStream::new(ssl, stream).unwrap(), } } From 8abba360d35c3640f433289c8e661bec84bbe685 Mon Sep 17 00:00:00 2001 From: Rushil Mehra Date: Wed, 19 Feb 2025 00:54:03 -0800 Subject: [PATCH 006/111] `Ssl::new_from_ref` -> `Ssl::new()` --- boring/src/ssl/mod.rs | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index baefe9bfb..49788c3e6 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -2749,32 +2749,13 @@ impl Ssl { } } - /// Creates a new `Ssl`. - /// - // FIXME should take &SslContextRef - #[corresponds(SSL_new)] - pub fn new(ctx: &SslContext) -> Result { - unsafe { - let ptr = cvt_p(ffi::SSL_new(ctx.as_ptr()))?; - let mut ssl = Ssl::from_ptr(ptr); - ssl.set_ex_data(*SESSION_CTX_INDEX, ctx.clone()); - - Ok(ssl) - } - } - /// Creates a new [`Ssl`]. - /// - /// This function does the same as [`Self:new`] except that it takes &[SslContextRef]. - // Both functions exist for backward compatibility (no breaking API). #[corresponds(SSL_new)] - pub fn new_from_ref(ctx: &SslContextRef) -> Result { + pub fn new(ctx: &SslContextRef) -> Result { unsafe { let ptr = cvt_p(ffi::SSL_new(ctx.as_ptr()))?; let mut ssl = Ssl::from_ptr(ptr); - SSL_CTX_up_ref(ctx.as_ptr()); - let ctx_owned = SslContext::from_ptr(ctx.as_ptr()); - ssl.set_ex_data(*SESSION_CTX_INDEX, ctx_owned); + ssl.set_ex_data(*SESSION_CTX_INDEX, ctx.to_owned()); Ok(ssl) } From 646ae33c613dfeca4cffd95540e9c9a7d7489a37 Mon Sep 17 00:00:00 2001 From: Rushil Mehra Date: Wed, 19 Feb 2025 01:25:20 -0800 Subject: [PATCH 007/111] X509Builder::append_extension2 -> X509Builder::append_extension --- boring/examples/mk_certs.rs | 18 ++++++++++-------- boring/src/pkcs12.rs | 2 +- boring/src/x509/mod.rs | 9 +-------- boring/src/x509/tests/mod.rs | 14 ++++++++------ 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/boring/examples/mk_certs.rs b/boring/examples/mk_certs.rs index 1fc4993d8..6b4c6e01e 100644 --- a/boring/examples/mk_certs.rs +++ b/boring/examples/mk_certs.rs @@ -43,18 +43,19 @@ fn mk_ca_cert() -> Result<(X509, PKey), ErrorStack> { let not_after = Asn1Time::days_from_now(365)?; cert_builder.set_not_after(¬_after)?; - cert_builder.append_extension(BasicConstraints::new().critical().ca().build()?)?; + cert_builder.append_extension(BasicConstraints::new().critical().ca().build()?.as_ref())?; cert_builder.append_extension( KeyUsage::new() .critical() .key_cert_sign() .crl_sign() - .build()?, + .build()? + .as_ref(), )?; let subject_key_identifier = SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(None, None))?; - cert_builder.append_extension(subject_key_identifier)?; + cert_builder.append_extension(&subject_key_identifier)?; cert_builder.sign(&privkey, MessageDigest::sha256())?; let cert = cert_builder.build(); @@ -106,7 +107,7 @@ fn mk_ca_signed_cert( let not_after = Asn1Time::days_from_now(365)?; cert_builder.set_not_after(¬_after)?; - cert_builder.append_extension(BasicConstraints::new().build()?)?; + cert_builder.append_extension(BasicConstraints::new().build()?.as_ref())?; cert_builder.append_extension( KeyUsage::new() @@ -114,24 +115,25 @@ fn mk_ca_signed_cert( .non_repudiation() .digital_signature() .key_encipherment() - .build()?, + .build()? + .as_ref(), )?; let subject_key_identifier = SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(Some(ca_cert), None))?; - cert_builder.append_extension(subject_key_identifier)?; + cert_builder.append_extension(&subject_key_identifier)?; let auth_key_identifier = AuthorityKeyIdentifier::new() .keyid(false) .issuer(false) .build(&cert_builder.x509v3_context(Some(ca_cert), None))?; - cert_builder.append_extension(auth_key_identifier)?; + cert_builder.append_extension(&auth_key_identifier)?; let subject_alt_name = SubjectAlternativeName::new() .dns("*.example.com") .dns("hello.com") .build(&cert_builder.x509v3_context(Some(ca_cert), None))?; - cert_builder.append_extension(subject_alt_name)?; + cert_builder.append_extension(&subject_alt_name)?; cert_builder.sign(ca_privkey, MessageDigest::sha256())?; let cert = cert_builder.build(); diff --git a/boring/src/pkcs12.rs b/boring/src/pkcs12.rs index dd255e3a7..e8fb7c12e 100644 --- a/boring/src/pkcs12.rs +++ b/boring/src/pkcs12.rs @@ -260,7 +260,7 @@ mod test { .unwrap(); builder.set_subject_name(&name).unwrap(); builder.set_issuer_name(&name).unwrap(); - builder.append_extension(key_usage).unwrap(); + builder.append_extension(&key_usage).unwrap(); builder.set_pubkey(&pkey).unwrap(); builder.sign(&pkey, MessageDigest::sha256()).unwrap(); let cert = builder.build(); diff --git a/boring/src/x509/mod.rs b/boring/src/x509/mod.rs index 17115d685..cd5d428e8 100644 --- a/boring/src/x509/mod.rs +++ b/boring/src/x509/mod.rs @@ -484,16 +484,9 @@ impl X509Builder { } } - /// Adds an X509 extension value to the certificate. - /// - /// This works just as `append_extension` except it takes ownership of the `X509Extension`. - pub fn append_extension(&mut self, extension: X509Extension) -> Result<(), ErrorStack> { - self.append_extension2(&extension) - } - /// Adds an X509 extension value to the certificate. #[corresponds(X509_add_ext)] - pub fn append_extension2(&mut self, extension: &X509ExtensionRef) -> Result<(), ErrorStack> { + pub fn append_extension(&mut self, extension: &X509ExtensionRef) -> Result<(), ErrorStack> { unsafe { cvt(ffi::X509_add_ext(self.0.as_ptr(), extension.as_ptr(), -1))?; Ok(()) diff --git a/boring/src/x509/tests/mod.rs b/boring/src/x509/tests/mod.rs index 0ab054ab9..371cd9b63 100644 --- a/boring/src/x509/tests/mod.rs +++ b/boring/src/x509/tests/mod.rs @@ -250,34 +250,36 @@ fn x509_builder() { .unwrap(); let basic_constraints = BasicConstraints::new().critical().ca().build().unwrap(); - builder.append_extension(basic_constraints).unwrap(); + builder + .append_extension(basic_constraints.as_ref()) + .unwrap(); let key_usage = KeyUsage::new() .digital_signature() .key_encipherment() .build() .unwrap(); - builder.append_extension(key_usage).unwrap(); + builder.append_extension(&key_usage).unwrap(); let ext_key_usage = ExtendedKeyUsage::new() .client_auth() .server_auth() .other("2.999.1") .build() .unwrap(); - builder.append_extension(ext_key_usage).unwrap(); + builder.append_extension(&ext_key_usage).unwrap(); let subject_key_identifier = SubjectKeyIdentifier::new() .build(&builder.x509v3_context(None, None)) .unwrap(); - builder.append_extension(subject_key_identifier).unwrap(); + builder.append_extension(&subject_key_identifier).unwrap(); let authority_key_identifier = AuthorityKeyIdentifier::new() .keyid(true) .build(&builder.x509v3_context(None, None)) .unwrap(); - builder.append_extension(authority_key_identifier).unwrap(); + builder.append_extension(&authority_key_identifier).unwrap(); let subject_alternative_name = SubjectAlternativeName::new() .dns("example.com") .build(&builder.x509v3_context(None, None)) .unwrap(); - builder.append_extension(subject_alternative_name).unwrap(); + builder.append_extension(&subject_alternative_name).unwrap(); builder.sign(&pkey, MessageDigest::sha256()).unwrap(); From 72dabe1d8577b090a6cd7b0560ce8f4bae6c396c Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Mon, 29 Sep 2025 17:16:57 -0700 Subject: [PATCH 008/111] Remove the "kx-*" features The "kx-*" features control default key exchange preferences. Its implementation requires disabling APIs for manually setting curve preferences via `set_curves()` or `set_curves_list()`. In practice, most teams need to be able to override default preferences at runtime anyway, which means these features were never really used. This commit gets rid of them, thereby reducing some complexity in the API. --- .github/workflows/ci.yml | 2 -- boring/Cargo.toml | 22 ---------------- boring/src/ssl/mod.rs | 54 -------------------------------------- boring/src/ssl/test/mod.rs | 25 ------------------ 4 files changed, 103 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ee8b46c7..46e621e77 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -363,8 +363,6 @@ jobs: name: Run `underscore-wildcards` tests - run: cargo test --features pq-experimental,rpk name: Run `pq-experimental,rpk` tests - - run: cargo test --features kx-safe-default,pq-experimental - name: Run `kx-safe-default` tests - run: cargo test --features pq-experimental,underscore-wildcards name: Run `pq-experimental,underscore-wildcards` tests - run: cargo test --features rpk,underscore-wildcards diff --git a/boring/Cargo.toml b/boring/Cargo.toml index caac99f23..bc9dba220 100644 --- a/boring/Cargo.toml +++ b/boring/Cargo.toml @@ -44,28 +44,6 @@ pq-experimental = ["boring-sys/pq-experimental"] # those for `pq-experimental` feature apply. underscore-wildcards = ["boring-sys/underscore-wildcards"] -# Controlling key exchange preferences at compile time - -# Choose key exchange preferences at compile time. This prevents the user from -# choosing their own preferences. -kx-safe-default = [] - -# Support PQ key exchange. The client will prefer classical key exchange, but -# will upgrade to PQ key exchange if requested by the server. This is the -# safest option if you don't know if the peer supports PQ key exchange. This -# feature implies "kx-safe-default". -kx-client-pq-supported = ["kx-safe-default"] - -# Prefer PQ key exchange. The client will prefer PQ exchange, but fallback to -# classical key exchange if requested by the server. This is the best option if -# you know the peer supports PQ key exchange. This feature implies -# "kx-safe-default" and "kx-client-pq-supported". -kx-client-pq-preferred = ["kx-safe-default", "kx-client-pq-supported"] - -# Disable key exchange involving non-NIST key exchange on the client side. -# Implies "kx-safe-default". -kx-client-nist-required = ["kx-safe-default"] - [dependencies] bitflags = { workspace = true } foreign-types = { workspace = true } diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 49788c3e6..302f99ba1 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -747,16 +747,12 @@ impl SslCurve { } } - // We need to allow dead_code here because `SslRef::set_curves` is conditionally compiled - // against the absence of the `kx-safe-default` feature and thus this function is never used. - // // **NOTE**: This function only exists because the version of boringssl we currently use does // not expose SSL_CTX_set1_group_ids. Because `SslRef::curve()` returns the public SSL_GROUP id // as opposed to the internal NID, but `SslContextBuilder::set_curves()` requires the internal // NID, we need this mapping in place to avoid breaking changes to the public API. Once the // underlying boringssl version is upgraded, this should be removed in favor of the new // SSL_CTX_set1_group_ids API. - #[allow(dead_code)] pub fn nid(&self) -> Option { match self.0 { ffi::SSL_GROUP_SECP224R1 => Some(ffi::NID_secp224r1), @@ -2017,11 +2013,6 @@ impl SslContextBuilder { } /// Sets the context's supported curves. - // - // If the "kx-*" flags are used to set key exchange preference, then don't allow the user to - // set them here. This ensures we don't override the user's preference without telling them: - // when the flags are used, the preferences are set just before connecting or accepting. - #[cfg(not(feature = "kx-safe-default"))] #[corresponds(SSL_CTX_set1_curves_list)] pub fn set_curves_list(&mut self, curves: &str) -> Result<(), ErrorStack> { let curves = CString::new(curves).map_err(ErrorStack::internal_error)?; @@ -2035,12 +2026,7 @@ impl SslContextBuilder { } /// Sets the context's supported curves. - // - // If the "kx-*" flags are used to set key exchange preference, then don't allow the user to - // set them here. This ensures we don't override the user's preference without telling them: - // when the flags are used, the preferences are set just before connecting or accepting. #[corresponds(SSL_CTX_set1_curves)] - #[cfg(not(feature = "kx-safe-default"))] pub fn set_curves(&mut self, curves: &[SslCurve]) -> Result<(), ErrorStack> { let curves: Vec = curves .iter() @@ -2915,40 +2901,6 @@ impl SslRef { } } - #[cfg(feature = "kx-safe-default")] - fn client_set_default_curves_list(&mut self) { - let curves = if cfg!(feature = "kx-client-pq-preferred") { - if cfg!(feature = "kx-client-nist-required") { - "P256Kyber768Draft00:P-256:P-384:P-521" - } else { - "X25519MLKEM768:X25519Kyber768Draft00:X25519:P256Kyber768Draft00:P-256:P-384:P-521" - } - } else if cfg!(feature = "kx-client-pq-supported") { - if cfg!(feature = "kx-client-nist-required") { - "P-256:P-384:P-521:P256Kyber768Draft00" - } else { - "X25519:P-256:P-384:P-521:X25519MLKEM768:X25519Kyber768Draft00:P256Kyber768Draft00" - } - } else { - if cfg!(feature = "kx-client-nist-required") { - "P-256:P-384:P-521" - } else { - "X25519:P-256:P-384:P-521" - } - }; - - self.set_curves_list(curves) - .expect("invalid default client curves list"); - } - - #[cfg(feature = "kx-safe-default")] - fn server_set_default_curves_list(&mut self) { - self.set_curves_list( - "X25519MLKEM768:X25519Kyber768Draft00:P256Kyber768Draft00:X25519:P-256:P-384", - ) - .expect("invalid default server curves list"); - } - /// Returns the [`SslCurve`] used for this `SslRef`. #[corresponds(SSL_get_curve_id)] #[must_use] @@ -4341,9 +4293,6 @@ where pub fn setup_connect(mut self) -> MidHandshakeSslStream { self.set_connect_state(); - #[cfg(feature = "kx-safe-default")] - self.inner.ssl.client_set_default_curves_list(); - MidHandshakeSslStream { stream: self.inner, error: Error { @@ -4373,9 +4322,6 @@ where pub fn setup_accept(mut self) -> MidHandshakeSslStream { self.set_accept_state(); - #[cfg(feature = "kx-safe-default")] - self.inner.ssl.server_set_default_curves_list(); - MidHandshakeSslStream { stream: self.inner, error: Error { diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index 6ac6ca751..0a4f6243f 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -952,30 +952,6 @@ fn sni_callback_swapped_ctx() { assert!(CALLED_BACK.load(Ordering::SeqCst)); } -#[cfg(feature = "kx-safe-default")] -#[test] -fn client_set_default_curves_list() { - let ssl_ctx = crate::ssl::SslContextBuilder::new(SslMethod::tls()) - .unwrap() - .build(); - let mut ssl = Ssl::new(&ssl_ctx).unwrap(); - - // Panics if Kyber768 missing in boringSSL. - ssl.client_set_default_curves_list(); -} - -#[cfg(feature = "kx-safe-default")] -#[test] -fn server_set_default_curves_list() { - let ssl_ctx = crate::ssl::SslContextBuilder::new(SslMethod::tls()) - .unwrap() - .build(); - let mut ssl = Ssl::new(&ssl_ctx).unwrap(); - - // Panics if Kyber768 missing in boringSSL. - ssl.server_set_default_curves_list(); -} - #[test] fn get_curve() { let server = Server::builder().build(); @@ -994,7 +970,6 @@ fn get_curve_name() { assert_eq!(SslCurve::X25519.name(), Some("X25519")); } -#[cfg(not(feature = "kx-safe-default"))] #[test] fn set_curves() { let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); From 21735accf89606c54d9150eacb2852a9f42fd9e2 Mon Sep 17 00:00:00 2001 From: Bas Westerbaan Date: Tue, 30 Sep 2025 11:56:56 +0200 Subject: [PATCH 009/111] pq: fix MSVC C4146 warning --- boring-sys/patches/boring-pq.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boring-sys/patches/boring-pq.patch b/boring-sys/patches/boring-pq.patch index fb5df8b9e..1f13962a6 100644 --- a/boring-sys/patches/boring-pq.patch +++ b/boring-sys/patches/boring-pq.patch @@ -503,7 +503,7 @@ index d3ea02090..ccb5b3d9b 100644 + for(i=0;i> 63; ++ return (0-(uint64_t)r) >> 63; +} + +/************************************************* From b46d77087e38cbeafb7767ead0b8db84ca9bb887 Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Mon, 29 Sep 2025 16:10:50 -0700 Subject: [PATCH 010/111] Remove `SslCurve` API This is incompatible with the latest internal FIPS build. Namely, the various group identifiers have been renamed since the previous version. --- boring/src/ssl/mod.rs | 123 ------------------------------------- boring/src/ssl/test/mod.rs | 33 +--------- 2 files changed, 1 insertion(+), 155 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 302f99ba1..574f001de 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -695,86 +695,6 @@ impl From for SslSignatureAlgorithm { } } -/// Numeric identifier of a TLS curve. -#[repr(transparent)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct SslCurveNid(c_int); - -/// A TLS Curve. -#[repr(transparent)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct SslCurve(c_int); - -impl SslCurve { - pub const SECP224R1: SslCurve = SslCurve(ffi::SSL_GROUP_SECP224R1 as _); - - pub const SECP256R1: SslCurve = SslCurve(ffi::SSL_GROUP_SECP256R1 as _); - - pub const SECP384R1: SslCurve = SslCurve(ffi::SSL_GROUP_SECP384R1 as _); - - pub const SECP521R1: SslCurve = SslCurve(ffi::SSL_GROUP_SECP521R1 as _); - - pub const X25519: SslCurve = SslCurve(ffi::SSL_GROUP_X25519 as _); - - pub const X25519_KYBER768_DRAFT00: SslCurve = - SslCurve(ffi::SSL_GROUP_X25519_KYBER768_DRAFT00 as _); - - #[cfg(feature = "pq-experimental")] - pub const X25519_KYBER768_DRAFT00_OLD: SslCurve = - SslCurve(ffi::SSL_GROUP_X25519_KYBER768_DRAFT00_OLD as _); - - #[cfg(feature = "pq-experimental")] - pub const X25519_KYBER512_DRAFT00: SslCurve = - SslCurve(ffi::SSL_GROUP_X25519_KYBER512_DRAFT00 as _); - - #[cfg(feature = "pq-experimental")] - pub const P256_KYBER768_DRAFT00: SslCurve = SslCurve(ffi::SSL_GROUP_P256_KYBER768_DRAFT00 as _); - - #[cfg(feature = "pq-experimental")] - pub const X25519_MLKEM768: SslCurve = SslCurve(ffi::SSL_GROUP_X25519_MLKEM768 as _); - - /// Returns the curve name - #[corresponds(SSL_get_curve_name)] - #[must_use] - pub fn name(&self) -> Option<&'static str> { - unsafe { - let ptr = ffi::SSL_get_curve_name(self.0 as u16); - if ptr.is_null() { - return None; - } - - CStr::from_ptr(ptr).to_str().ok() - } - } - - // **NOTE**: This function only exists because the version of boringssl we currently use does - // not expose SSL_CTX_set1_group_ids. Because `SslRef::curve()` returns the public SSL_GROUP id - // as opposed to the internal NID, but `SslContextBuilder::set_curves()` requires the internal - // NID, we need this mapping in place to avoid breaking changes to the public API. Once the - // underlying boringssl version is upgraded, this should be removed in favor of the new - // SSL_CTX_set1_group_ids API. - pub fn nid(&self) -> Option { - match self.0 { - ffi::SSL_GROUP_SECP224R1 => Some(ffi::NID_secp224r1), - ffi::SSL_GROUP_SECP256R1 => Some(ffi::NID_X9_62_prime256v1), - ffi::SSL_GROUP_SECP384R1 => Some(ffi::NID_secp384r1), - ffi::SSL_GROUP_SECP521R1 => Some(ffi::NID_secp521r1), - ffi::SSL_GROUP_X25519 => Some(ffi::NID_X25519), - ffi::SSL_GROUP_X25519_KYBER768_DRAFT00 => Some(ffi::NID_X25519Kyber768Draft00), - #[cfg(feature = "pq-experimental")] - ffi::SSL_GROUP_X25519_KYBER768_DRAFT00_OLD => Some(ffi::NID_X25519Kyber768Draft00Old), - #[cfg(feature = "pq-experimental")] - ffi::SSL_GROUP_X25519_KYBER512_DRAFT00 => Some(ffi::NID_X25519Kyber512Draft00), - #[cfg(feature = "pq-experimental")] - ffi::SSL_GROUP_P256_KYBER768_DRAFT00 => Some(ffi::NID_P256Kyber768Draft00), - #[cfg(feature = "pq-experimental")] - ffi::SSL_GROUP_X25519_MLKEM768 => Some(ffi::NID_X25519MLKEM768), - _ => None, - } - .map(SslCurveNid) - } -} - /// A compliance policy. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct CompliancePolicy(ffi::ssl_compliance_policy_t); @@ -2025,24 +1945,6 @@ impl SslContextBuilder { } } - /// Sets the context's supported curves. - #[corresponds(SSL_CTX_set1_curves)] - pub fn set_curves(&mut self, curves: &[SslCurve]) -> Result<(), ErrorStack> { - let curves: Vec = curves - .iter() - .filter_map(|curve| curve.nid().map(|nid| nid.0)) - .collect(); - - unsafe { - cvt_0i(ffi::SSL_CTX_set1_curves( - self.as_ptr(), - curves.as_ptr() as *const _, - curves.len(), - )) - .map(|_| ()) - } - } - /// Sets the context's compliance policy. /// /// This feature isn't available in the certified version of BoringSSL. @@ -2887,31 +2789,6 @@ impl SslRef { } } - /// Sets the ongoing session's supported groups by their named identifiers - /// (formerly referred to as curves). - #[corresponds(SSL_set1_groups)] - pub fn set_group_nids(&mut self, group_nids: &[SslCurveNid]) -> Result<(), ErrorStack> { - unsafe { - cvt_0i(ffi::SSL_set1_curves( - self.as_ptr(), - group_nids.as_ptr() as *const _, - group_nids.len(), - )) - .map(|_| ()) - } - } - - /// Returns the [`SslCurve`] used for this `SslRef`. - #[corresponds(SSL_get_curve_id)] - #[must_use] - pub fn curve(&self) -> Option { - let curve_id = unsafe { ffi::SSL_get_curve_id(self.as_ptr()) }; - if curve_id == 0 { - return None; - } - Some(SslCurve(curve_id.into())) - } - /// Returns an `ErrorCode` value for the most recent operation on this `SslRef`. #[corresponds(SSL_get_error)] #[must_use] diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index 0a4f6243f..a5d937a4d 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -13,9 +13,8 @@ use crate::pkey::PKey; use crate::srtp::SrtpProfileId; use crate::ssl::test::server::Server; use crate::ssl::SslVersion; -use crate::ssl::{self, SslCurve}; use crate::ssl::{ - ExtensionType, ShutdownResult, ShutdownState, Ssl, SslAcceptor, SslAcceptorBuilder, + self, ExtensionType, ShutdownResult, ShutdownState, Ssl, SslAcceptor, SslAcceptorBuilder, SslConnector, SslContext, SslFiletype, SslMethod, SslOptions, SslStream, SslVerifyMode, }; use crate::x509::store::X509StoreBuilder; @@ -952,36 +951,6 @@ fn sni_callback_swapped_ctx() { assert!(CALLED_BACK.load(Ordering::SeqCst)); } -#[test] -fn get_curve() { - let server = Server::builder().build(); - let client = server.client_with_root_ca(); - let client_stream = client.connect(); - let curve = client_stream.ssl().curve().expect("curve"); - assert!(curve.name().is_some()); -} - -#[test] -fn get_curve_name() { - assert_eq!(SslCurve::SECP224R1.name(), Some("P-224")); - assert_eq!(SslCurve::SECP256R1.name(), Some("P-256")); - assert_eq!(SslCurve::SECP384R1.name(), Some("P-384")); - assert_eq!(SslCurve::SECP521R1.name(), Some("P-521")); - assert_eq!(SslCurve::X25519.name(), Some("X25519")); -} - -#[test] -fn set_curves() { - let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); - ctx.set_curves(&[ - SslCurve::SECP224R1, - SslCurve::SECP256R1, - SslCurve::SECP384R1, - SslCurve::X25519, - ]) - .expect("Failed to set curves"); -} - #[test] fn test_get_ciphers() { let ctx_builder = SslContext::builder(SslMethod::tls()).unwrap(); From 7078f61077d1904a513383fae9e8868db1635e86 Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Mon, 29 Sep 2025 16:13:56 -0700 Subject: [PATCH 011/111] Remove outdated comments on FIPS API compatibility --- boring/src/ssl/mod.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 574f001de..3d91ad943 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -1895,10 +1895,6 @@ impl SslContextBuilder { } /// Configures whether ClientHello extensions should be permuted. - /// - /// Note: This is gated to non-fips because the fips feature builds with a separate - /// version of BoringSSL which doesn't yet include these APIs. - /// Once the submoduled fips commit is upgraded, these gates can be removed. #[corresponds(SSL_CTX_set_permute_extensions)] pub fn set_permute_extensions(&mut self, enabled: bool) { unsafe { ffi::SSL_CTX_set_permute_extensions(self.as_ptr(), enabled as _) } @@ -2938,10 +2934,6 @@ impl SslRef { /// Configures whether ClientHello extensions should be permuted. #[corresponds(SSL_set_permute_extensions)] - /// - /// Note: This is gated to non-fips because the fips feature builds with a separate - /// version of BoringSSL which doesn't yet include these APIs. - /// Once the submoduled fips commit is upgraded, these gates can be removed. pub fn set_permute_extensions(&mut self, enabled: bool) { unsafe { ffi::SSL_set_permute_extensions(self.as_ptr(), enabled as _) } } From 1c51c7ee3bbee43f75f9b449c45d0b701a1522a8 Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Tue, 30 Sep 2025 07:51:37 -0700 Subject: [PATCH 012/111] Add back the `curve()` method on `SslRef` Instead of returning an `SslCurve`, just return the `u16` returned by BoringSSL. --- boring/src/ssl/mod.rs | 11 +++++++++++ boring/src/ssl/test/mod.rs | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 3d91ad943..b14e3530f 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -2785,6 +2785,17 @@ impl SslRef { } } + /// Returns the curve ID (aka group ID) used for this `SslRef`. + #[corresponds(SSL_get_curve_id)] + #[must_use] + pub fn curve(&self) -> Option { + let curve_id = unsafe { ffi::SSL_get_curve_id(self.as_ptr()) }; + if curve_id == 0 { + return None; + } + Some(curve_id) + } + /// Returns an `ErrorCode` value for the most recent operation on this `SslRef`. #[corresponds(SSL_get_error)] #[must_use] diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index a5d937a4d..e779c0400 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -951,6 +951,15 @@ fn sni_callback_swapped_ctx() { assert!(CALLED_BACK.load(Ordering::SeqCst)); } +#[test] +fn get_curve() { + let server = Server::builder().build(); + let client = server.client_with_root_ca(); + let client_stream = client.connect(); + let curve = client_stream.ssl().curve(); + assert!(curve.is_some()); +} + #[test] fn test_get_ciphers() { let ctx_builder = SslContext::builder(SslMethod::tls()).unwrap(); From 4ce1308e1c0f0c8cce2a5c52a2c50b6c7251c3e1 Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 26 Sep 2025 18:09:24 +0100 Subject: [PATCH 013/111] Make rpk feature flag additive --- boring/src/ssl/mod.rs | 46 ++++++++++++++----------------------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index b14e3530f..5d12e686d 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -858,7 +858,11 @@ impl SslContextBuilder { init(); let ctx = cvt_p(ffi::SSL_CTX_new(SslMethod::tls_with_buffer().as_ptr()))?; - Ok(SslContextBuilder::from_ptr(ctx, true)) + let mut builder = SslContextBuilder::from_ptr(ctx); + builder.is_rpk = true; + builder.set_ex_data(*RPK_FLAG_INDEX, true); + + Ok(builder) } } @@ -897,48 +901,28 @@ impl SslContextBuilder { unsafe { init(); let ctx = cvt_p(ffi::SSL_CTX_new(method.as_ptr()))?; - - #[cfg(feature = "rpk")] - { - Ok(SslContextBuilder::from_ptr(ctx, false)) - } - - #[cfg(not(feature = "rpk"))] - { - Ok(SslContextBuilder::from_ptr(ctx)) - } + Ok(SslContextBuilder::from_ptr(ctx)) } } /// Creates an `SslContextBuilder` from a pointer to a raw OpenSSL value. /// - /// # Safety - /// - /// The caller must ensure that the pointer is valid and uniquely owned by the builder. - #[cfg(feature = "rpk")] - pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX, is_rpk: bool) -> SslContextBuilder { - let ctx = SslContext::from_ptr(ctx); - let mut builder = SslContextBuilder { - ctx, - is_rpk, - has_shared_cert_store: false, - }; - - builder.set_ex_data(*RPK_FLAG_INDEX, is_rpk); - - builder - } - - /// Creates an `SslContextBuilder` from a pointer to a raw OpenSSL value. + #[cfg_attr( + feature = "rpk", + doc = "Keeps previous RPK state. Use `new_rpk()` to enable RPK." + )] /// /// # Safety /// /// The caller must ensure that the pointer is valid and uniquely owned by the builder. - #[cfg(not(feature = "rpk"))] + /// The context must own its cert store exclusively. pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX) -> SslContextBuilder { + let ctx = SslContext::from_ptr(ctx); SslContextBuilder { - ctx: SslContext::from_ptr(ctx), + #[cfg(feature = "rpk")] + is_rpk: ctx.is_rpk(), has_shared_cert_store: false, + ctx, } } From b3521e55231928745d592aab34a6ca047d9c1032 Mon Sep 17 00:00:00 2001 From: Alessandro Ghedini Date: Tue, 30 Sep 2025 16:29:18 +0100 Subject: [PATCH 014/111] Add SslRef::curve_name() --- boring/src/ssl/mod.rs | 16 ++++++++++++++++ boring/src/ssl/test/mod.rs | 2 ++ 2 files changed, 18 insertions(+) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 5d12e686d..c8e45bc74 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -2780,6 +2780,22 @@ impl SslRef { Some(curve_id) } + /// Returns the curve name used for this `SslRef`. + #[corresponds(SSL_get_curve_name)] + #[must_use] + pub fn curve_name(&self) -> Option<&'static str> { + let curve_id = self.curve()?; + + unsafe { + let ptr = ffi::SSL_get_curve_name(curve_id); + if ptr.is_null() { + return None; + } + + CStr::from_ptr(ptr).to_str().ok() + } + } + /// Returns an `ErrorCode` value for the most recent operation on this `SslRef`. #[corresponds(SSL_get_error)] #[must_use] diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index e779c0400..f4ce2102e 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -958,6 +958,8 @@ fn get_curve() { let client_stream = client.connect(); let curve = client_stream.ssl().curve(); assert!(curve.is_some()); + let curve_name = client_stream.ssl().curve_name(); + assert!(curve_name.is_some()); } #[test] From c49282f112c2750d8740b7baed9c00a14dc8f8c3 Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Sat, 8 Mar 2025 10:35:46 -0800 Subject: [PATCH 015/111] Add set_ticket_key_callback (SSL_CTX_set_tlsext_ticket_key_cb) Add a wrapper for the `SSL_CTX_set_tlsext_ticket_key_cb`, which allows consumers to configure the EVP_CIPHER_CTX and HMAC_CTX used for encrypting/decrypting session tickets. See https://docs.openssl.org/1.0.2/man3/SSL_CTX_set_tlsext_ticket_key_cb/ for more details. --- boring/src/ssl/callbacks.rs | 41 ++++++ boring/src/ssl/mod.rs | 81 +++++++++++ boring/src/ssl/test/mod.rs | 1 + boring/src/ssl/test/session_resumption.rs | 159 ++++++++++++++++++++++ 4 files changed, 282 insertions(+) create mode 100644 boring/src/ssl/test/session_resumption.rs diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index f618e591d..eca7756f7 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -8,6 +8,7 @@ use super::{ }; use crate::error::ErrorStack; use crate::ffi; +use crate::ssl::TicketKeyCallbackResult; use crate::x509::{X509StoreContext, X509StoreContextRef}; use foreign_types::ForeignType; use foreign_types::ForeignTypeRef; @@ -269,6 +270,46 @@ where } } +pub(super) unsafe extern "C" fn raw_ticket_key( + ssl: *mut ffi::SSL, + key_name: *mut u8, + iv: *mut u8, + evp_ctx: *mut ffi::EVP_CIPHER_CTX, + hmac_ctx: *mut ffi::HMAC_CTX, + encrypt: c_int, +) -> c_int +where + F: Fn( + &SslRef, + &mut [u8; 16], + *mut u8, + *mut ffi::EVP_CIPHER_CTX, + *mut ffi::HMAC_CTX, + bool, + ) -> TicketKeyCallbackResult + + 'static + + Sync + + Send, +{ + // SAFETY: boring provides valid inputs. + let ssl = unsafe { SslRef::from_ptr_mut(ssl) }; + + let ssl_context = ssl.ssl_context().to_owned(); + let callback = ssl_context + .ex_data::(SslContext::cached_ex_index::()) + .expect("expected session resumption callback"); + + // Safety: the callback guarantees that key_name is 16 bytes + let key_name = + unsafe { slice::from_raw_parts_mut(key_name, ffi::SSL_TICKET_KEY_NAME_LEN as usize) }; + let key_name = <&mut [u8; 16]>::try_from(key_name).expect("boring provides a 16-byte key name"); + + // When encrypting a new ticket, encrypt will be one. + let encrypt = encrypt == 1; + + callback(ssl, key_name, iv, evp_ctx, hmac_ctx, encrypt).into() +} + pub(super) unsafe extern "C" fn raw_alpn_select( ssl: *mut ffi::SSL, out: *mut *const c_uchar, diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index c8e45bc74..586a81dc0 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -804,6 +804,50 @@ pub enum SslInfoCallbackValue { Alert(SslInfoCallbackAlert), } +/// Ticket key callback status. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum TicketKeyCallbackResult { + /// Abort the handshake. + Error, + + /// The peer supplied session ticket was not recognized. Continue with a full handshake. + /// + /// # Note + /// + /// This is a decryption specific status code. + DecryptTicketUnrecognized, + + /// Resumption callback was successful. + /// + /// When in decryption mode, attempt an abbreviated handshake via session resumption. When in + /// encryption mode, provide a new ticket to the client. + Success, + + /// Resumption callback was successful. Attempt an abbreviated handshake, and additionally + /// provide new session tickets to the peer. + /// + /// Session resumption short-circuits some security checks of a full-handshake, in exchange for + /// potential performance gains. For this reason, a session ticket should only be valid for a + /// limited time. Providing the peer with renewed session tickets allows them to continue + /// session resumption with the new tickets. + /// + /// # Note + /// + /// This is a decryption specific status code. + DecryptSuccessRenew, +} + +impl From for c_int { + fn from(value: TicketKeyCallbackResult) -> Self { + match value { + TicketKeyCallbackResult::Error => -1, + TicketKeyCallbackResult::DecryptTicketUnrecognized => 0, + TicketKeyCallbackResult::Success => 1, + TicketKeyCallbackResult::DecryptSuccessRenew => 2, + } + } +} + #[derive(Hash, Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Debug)] pub struct SslInfoCallbackAlert(c_int); @@ -1080,6 +1124,43 @@ impl SslContextBuilder { } } + /// Configures a custom session ticket key callback for session resumption. + /// + /// Session Resumption uses the security context (aka. session tickets) of a previous + /// connection to establish a new connection via an abbreviated handshake. Skipping portions of + /// a handshake can potentially yield performance gains. + /// + /// An attacker that compromises a server's session ticket key can impersonate the server and, + /// prior to TLS 1.3, retroactively decrypt all application traffic from sessions using that + /// ticket key. Thus ticket keys must be regularly rotated for forward secrecy. + /// + /// # Panics + /// + /// This method panics if this `Ssl` is associated with a RPK context. + #[corresponds(SSL_CTX_set_tlsext_ticket_key_cb)] + pub fn set_ticket_key_callback(&mut self, callback: F) + where + F: Fn( + &SslRef, + &mut [u8; 16], + *mut u8, + *mut ffi::EVP_CIPHER_CTX, + *mut ffi::HMAC_CTX, + bool, + ) -> TicketKeyCallbackResult + + 'static + + Sync + + Send, + { + #[cfg(feature = "rpk")] + assert!(!self.is_rpk, "This API is not supported for RPK"); + + unsafe { + self.replace_ex_data(SslContext::cached_ex_index::(), callback); + ffi::SSL_CTX_set_tlsext_ticket_key_cb(self.as_ptr(), Some(raw_ticket_key::)) + }; + } + /// Sets the certificate verification depth. /// /// If the peer's certificate chain is longer than this value, verification will fail. diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index f4ce2102e..aded182d1 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -30,6 +30,7 @@ mod ech; mod private_key_method; mod server; mod session; +mod session_resumption; mod verify; static ROOT_CERT: &[u8] = include_bytes!("../../../test/root-ca.pem"); diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs new file mode 100644 index 000000000..c7556dfee --- /dev/null +++ b/boring/src/ssl/test/session_resumption.rs @@ -0,0 +1,159 @@ +use super::server::Server; +use crate::ssl::test::MessageDigest; +use crate::ssl::SslRef; +use crate::ssl::SslSession; +use crate::ssl::SslSessionCacheMode; +use crate::ssl::TicketKeyCallbackResult; +use crate::symm::Cipher; +use std::ffi::c_void; +use std::sync::atomic::{AtomicU8, Ordering}; +use std::sync::OnceLock; + +static CUSTOM_ENCRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); +static CUSTOM_DECRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); + +#[test] +fn resume_session() { + static SESSION_TICKET: OnceLock> = OnceLock::new(); + + let mut server = Server::builder(); + server.expected_connections_count(2); + let server = server.build(); + + let mut client = server.client(); + client + .ctx() + .set_session_cache_mode(SslSessionCacheMode::CLIENT); + client.ctx().set_new_session_callback(|_, session| { + let _can_receive_multiple_tickets = SESSION_TICKET.set(session.to_der().unwrap()); + }); + let ssl_stream = client.connect(); + + assert!(!ssl_stream.ssl().session_reused()); + assert!(SESSION_TICKET.get().is_some()); + + // Retrieve the session ticket + let session_ticket = SslSession::from_der(SESSION_TICKET.get().unwrap()).unwrap(); + + // Attempt to resume the connection using the session ticket + let client_2 = server.client(); + let mut ssl_builder = client_2.build().builder(); + unsafe { ssl_builder.ssl().set_session(&session_ticket).unwrap() }; + let ssl_stream_2 = ssl_builder.connect(); + + assert!(ssl_stream_2.ssl().session_reused()); +} + +#[test] +fn custom_callback() { + static SESSION_TICKET: OnceLock> = OnceLock::new(); + + let mut server = Server::builder(); + server.expected_connections_count(2); + server + .ctx() + .set_ticket_key_callback(test_tickey_key_callback); + let server = server.build(); + + let mut client = server.client(); + client + .ctx() + .set_session_cache_mode(SslSessionCacheMode::CLIENT); + client.ctx().set_new_session_callback(|_, session| { + let _can_receive_multiple_tickets = SESSION_TICKET.set(session.to_der().unwrap()); + }); + let ssl_stream = client.connect(); + + assert!(!ssl_stream.ssl().session_reused()); + assert!(SESSION_TICKET.get().is_some()); + assert_eq!(CUSTOM_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 2); + assert_eq!(CUSTOM_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 0); + + // Retrieve the session ticket + let session_ticket = SslSession::from_der(SESSION_TICKET.get().unwrap()).unwrap(); + + // Attempt to resume the connection using the session ticket + let client_2 = server.client(); + let mut ssl_builder = client_2.build().builder(); + unsafe { ssl_builder.ssl().set_session(&session_ticket).unwrap() }; + let ssl_stream_2 = ssl_builder.connect(); + + assert!(ssl_stream_2.ssl().session_reused()); + assert_eq!(CUSTOM_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 4); + assert_eq!(CUSTOM_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 1); +} + +// Custom callback to encrypt and decrypt session tickets +fn test_tickey_key_callback( + _ssl: &SslRef, + _key_name: &mut [u8; 16], + _iv: *mut u8, + evp_ctx: *mut ffi::EVP_CIPHER_CTX, + hmac_ctx: *mut ffi::HMAC_CTX, + encrypt: bool, +) -> TicketKeyCallbackResult { + // These should only be used for testing purposes. + const TEST_CBC_IV: [u8; 16] = [1; 16]; + const TEST_AES_128_CBC_KEY: [u8; 16] = [2; 16]; + const TEST_HMAC_KEY: [u8; 32] = [3; 32]; + + let digest = MessageDigest::sha256(); + let cipher = Cipher::aes_128_cbc(); + + if encrypt { + CUSTOM_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + // Set the encryption context. + let ret = unsafe { + ffi::EVP_EncryptInit_ex( + evp_ctx, + cipher.as_ptr(), + // ENGINE api is deprecated + core::ptr::null_mut(), + TEST_AES_128_CBC_KEY.as_ptr(), + TEST_CBC_IV.as_ptr(), + ) + }; + assert!(ret == 1); + + // Set the hmac context. + let ret = unsafe { + ffi::HMAC_Init_ex( + hmac_ctx, + TEST_HMAC_KEY.as_ptr() as *const c_void, + TEST_HMAC_KEY.len(), + digest.as_ptr(), + // ENGINE api is deprecated + core::ptr::null_mut(), + ) + }; + assert!(ret == 1); + } else { + CUSTOM_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + let ret = unsafe { + ffi::EVP_DecryptInit_ex( + evp_ctx, + cipher.as_ptr(), + // ENGINE api is deprecated + core::ptr::null_mut(), + TEST_AES_128_CBC_KEY.as_ptr(), + TEST_CBC_IV.as_ptr(), + ) + }; + assert!(ret == 1); + + // Set the hmac context. + let ret = unsafe { + ffi::HMAC_Init_ex( + hmac_ctx, + TEST_HMAC_KEY.as_ptr() as *const c_void, + TEST_HMAC_KEY.len(), + digest.as_ptr(), + // ENGINE api is deprecated + core::ptr::null_mut(), + ) + }; + assert!(ret == 1); + } + + TicketKeyCallbackResult::Success +} From ea1d120912468c4fa4cb2714572deed2ef6d6212 Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Mon, 10 Mar 2025 22:30:25 -0700 Subject: [PATCH 016/111] pr comments: safety, receive multiple nst, return status refactor --- boring/src/ssl/callbacks.rs | 2 +- boring/src/ssl/mod.rs | 8 +++++--- boring/src/ssl/test/session_resumption.rs | 16 ++++++++++++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index eca7756f7..a8e3d5caa 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -299,7 +299,7 @@ where .ex_data::(SslContext::cached_ex_index::()) .expect("expected session resumption callback"); - // Safety: the callback guarantees that key_name is 16 bytes + // SAFETY: the callback guarantees that key_name is 16 bytes let key_name = unsafe { slice::from_raw_parts_mut(key_name, ffi::SSL_TICKET_KEY_NAME_LEN as usize) }; let key_name = <&mut [u8; 16]>::try_from(key_name).expect("boring provides a 16-byte key name"); diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 586a81dc0..59f68acd1 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -810,12 +810,14 @@ pub enum TicketKeyCallbackResult { /// Abort the handshake. Error, - /// The peer supplied session ticket was not recognized. Continue with a full handshake. + /// Continue with a full handshake. + /// + /// The peer supplied session ticket was not recognized. /// /// # Note /// /// This is a decryption specific status code. - DecryptTicketUnrecognized, + Noop, /// Resumption callback was successful. /// @@ -841,7 +843,7 @@ impl From for c_int { fn from(value: TicketKeyCallbackResult) -> Self { match value { TicketKeyCallbackResult::Error => -1, - TicketKeyCallbackResult::DecryptTicketUnrecognized => 0, + TicketKeyCallbackResult::Noop => 0, TicketKeyCallbackResult::Success => 1, TicketKeyCallbackResult::DecryptSuccessRenew => 2, } diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs index c7556dfee..3492fff8a 100644 --- a/boring/src/ssl/test/session_resumption.rs +++ b/boring/src/ssl/test/session_resumption.rs @@ -15,6 +15,7 @@ static CUSTOM_DECRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); #[test] fn resume_session() { static SESSION_TICKET: OnceLock> = OnceLock::new(); + static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0); let mut server = Server::builder(); server.expected_connections_count(2); @@ -25,12 +26,17 @@ fn resume_session() { .ctx() .set_session_cache_mode(SslSessionCacheMode::CLIENT); client.ctx().set_new_session_callback(|_, session| { - let _can_receive_multiple_tickets = SESSION_TICKET.set(session.to_der().unwrap()); + NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst); + // The server sends multiple session tickets but we only care to retrieve one. + if SESSION_TICKET.get().is_none() { + SESSION_TICKET.set(session.to_der().unwrap()).unwrap(); + } }); let ssl_stream = client.connect(); assert!(!ssl_stream.ssl().session_reused()); assert!(SESSION_TICKET.get().is_some()); + assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2); // Retrieve the session ticket let session_ticket = SslSession::from_der(SESSION_TICKET.get().unwrap()).unwrap(); @@ -47,6 +53,7 @@ fn resume_session() { #[test] fn custom_callback() { static SESSION_TICKET: OnceLock> = OnceLock::new(); + static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0); let mut server = Server::builder(); server.expected_connections_count(2); @@ -60,7 +67,11 @@ fn custom_callback() { .ctx() .set_session_cache_mode(SslSessionCacheMode::CLIENT); client.ctx().set_new_session_callback(|_, session| { - let _can_receive_multiple_tickets = SESSION_TICKET.set(session.to_der().unwrap()); + NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst); + // The server sends multiple session tickets but we only care to retrieve one. + if SESSION_TICKET.get().is_none() { + SESSION_TICKET.set(session.to_der().unwrap()).unwrap(); + } }); let ssl_stream = client.connect(); @@ -68,6 +79,7 @@ fn custom_callback() { assert!(SESSION_TICKET.get().is_some()); assert_eq!(CUSTOM_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 2); assert_eq!(CUSTOM_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 0); + assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2); // Retrieve the session ticket let session_ticket = SslSession::from_der(SESSION_TICKET.get().unwrap()).unwrap(); From ae783f827344437bcc23453f2e97d94ecfb49d5c Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Tue, 11 Mar 2025 12:10:21 -0700 Subject: [PATCH 017/111] add test case for TicketKeyCallbackResult::Noop --- boring/src/ssl/test/session_resumption.rs | 123 ++++++++++++++++++++-- 1 file changed, 112 insertions(+), 11 deletions(-) diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs index 3492fff8a..638461a76 100644 --- a/boring/src/ssl/test/session_resumption.rs +++ b/boring/src/ssl/test/session_resumption.rs @@ -9,8 +9,10 @@ use std::ffi::c_void; use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::OnceLock; -static CUSTOM_ENCRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); -static CUSTOM_DECRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); +static SUCCESS_ENCRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); +static SUCCESS_DECRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); +static NOOP_ENCRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); +static NOOP_DECRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); #[test] fn resume_session() { @@ -51,7 +53,7 @@ fn resume_session() { } #[test] -fn custom_callback() { +fn custom_callback_success() { static SESSION_TICKET: OnceLock> = OnceLock::new(); static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0); @@ -59,7 +61,7 @@ fn custom_callback() { server.expected_connections_count(2); server .ctx() - .set_ticket_key_callback(test_tickey_key_callback); + .set_ticket_key_callback(test_success_tickey_key_callback); let server = server.build(); let mut client = server.client(); @@ -77,8 +79,8 @@ fn custom_callback() { assert!(!ssl_stream.ssl().session_reused()); assert!(SESSION_TICKET.get().is_some()); - assert_eq!(CUSTOM_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 2); - assert_eq!(CUSTOM_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 0); + assert_eq!(SUCCESS_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 2); + assert_eq!(SUCCESS_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 0); assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2); // Retrieve the session ticket @@ -91,12 +93,111 @@ fn custom_callback() { let ssl_stream_2 = ssl_builder.connect(); assert!(ssl_stream_2.ssl().session_reused()); - assert_eq!(CUSTOM_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 4); - assert_eq!(CUSTOM_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 1); + assert_eq!(SUCCESS_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 4); + assert_eq!(SUCCESS_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 1); +} + +#[test] +fn custom_callback_unrecognized_decryption_ticket() { + static SESSION_TICKET: OnceLock> = OnceLock::new(); + static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0); + + let mut server = Server::builder(); + server.expected_connections_count(2); + server + .ctx() + .set_ticket_key_callback(test_noop_tickey_key_callback); + let server = server.build(); + + let mut client = server.client(); + client + .ctx() + .set_session_cache_mode(SslSessionCacheMode::CLIENT); + client.ctx().set_new_session_callback(|_, session| { + NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst); + // The server sends multiple session tickets but we only care to retrieve one. + if SESSION_TICKET.get().is_none() { + SESSION_TICKET.set(session.to_der().unwrap()).unwrap(); + } + }); + let ssl_stream = client.connect(); + + assert!(!ssl_stream.ssl().session_reused()); + assert!(SESSION_TICKET.get().is_some()); + assert_eq!(NOOP_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 2); + assert_eq!(NOOP_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 0); + assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2); + + // Retrieve the session ticket + let session_ticket = SslSession::from_der(SESSION_TICKET.get().unwrap()).unwrap(); + + // Attempt to resume the connection using the session ticket + let client_2 = server.client(); + let mut ssl_builder = client_2.build().builder(); + unsafe { ssl_builder.ssl().set_session(&session_ticket).unwrap() }; + let ssl_stream_2 = ssl_builder.connect(); + + // Second connection was NOT resumed due to TicketKeyCallbackResult::Noop on decryption + assert!(!ssl_stream_2.ssl().session_reused()); + assert_eq!(NOOP_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 4); + assert_eq!(NOOP_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 1); +} + +// Successfully return a session ticket in encryption mode but return a +// TicketKeyCallbackResult::Noop in decryption mode. +fn test_noop_tickey_key_callback( + _ssl: &SslRef, + _key_name: &mut [u8; 16], + _iv: *mut u8, + evp_ctx: *mut ffi::EVP_CIPHER_CTX, + hmac_ctx: *mut ffi::HMAC_CTX, + encrypt: bool, +) -> TicketKeyCallbackResult { + // These should only be used for testing purposes. + const TEST_CBC_IV: [u8; 16] = [1; 16]; + const TEST_AES_128_CBC_KEY: [u8; 16] = [2; 16]; + const TEST_HMAC_KEY: [u8; 32] = [3; 32]; + + let digest = MessageDigest::sha256(); + let cipher = Cipher::aes_128_cbc(); + + if encrypt { + NOOP_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + // Set the encryption context. + let ret = unsafe { + ffi::EVP_EncryptInit_ex( + evp_ctx, + cipher.as_ptr(), + // ENGINE api is deprecated + core::ptr::null_mut(), + TEST_AES_128_CBC_KEY.as_ptr(), + TEST_CBC_IV.as_ptr(), + ) + }; + assert!(ret == 1); + + // Set the hmac context. + let ret = unsafe { + ffi::HMAC_Init_ex( + hmac_ctx, + TEST_HMAC_KEY.as_ptr() as *const c_void, + TEST_HMAC_KEY.len(), + digest.as_ptr(), + // ENGINE api is deprecated + core::ptr::null_mut(), + ) + }; + assert!(ret == 1); + + TicketKeyCallbackResult::Success + } else { + NOOP_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + TicketKeyCallbackResult::Noop + } } // Custom callback to encrypt and decrypt session tickets -fn test_tickey_key_callback( +fn test_success_tickey_key_callback( _ssl: &SslRef, _key_name: &mut [u8; 16], _iv: *mut u8, @@ -113,7 +214,7 @@ fn test_tickey_key_callback( let cipher = Cipher::aes_128_cbc(); if encrypt { - CUSTOM_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + SUCCESS_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); // Set the encryption context. let ret = unsafe { ffi::EVP_EncryptInit_ex( @@ -140,7 +241,7 @@ fn test_tickey_key_callback( }; assert!(ret == 1); } else { - CUSTOM_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + SUCCESS_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); let ret = unsafe { ffi::EVP_DecryptInit_ex( evp_ctx, From f526b57daa1ddad686e83ca5ac91ed7799767599 Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Tue, 1 Apr 2025 12:19:23 -0700 Subject: [PATCH 018/111] update documentation --- boring/src/ssl/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 59f68acd1..c19c73818 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -812,11 +812,12 @@ pub enum TicketKeyCallbackResult { /// Continue with a full handshake. /// - /// The peer supplied session ticket was not recognized. + /// When in decryption mode, this indicates that the peer supplied session ticket was not + /// recognized. When in encryption mode, this instructs boring to not send a session ticket. /// /// # Note /// - /// This is a decryption specific status code. + /// This is a decryption specific status code when using the submoduled BoringSSL. Noop, /// Resumption callback was successful. From ba85fbb7ad6d0e095e80aee40b7740b904f689ed Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Wed, 2 Apr 2025 09:46:50 -0700 Subject: [PATCH 019/111] simplify tests --- boring/src/ssl/test/session_resumption.rs | 24 +++++++++-------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs index 638461a76..df9c62b72 100644 --- a/boring/src/ssl/test/session_resumption.rs +++ b/boring/src/ssl/test/session_resumption.rs @@ -16,7 +16,7 @@ static NOOP_DECRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); #[test] fn resume_session() { - static SESSION_TICKET: OnceLock> = OnceLock::new(); + static SESSION_TICKET: OnceLock = OnceLock::new(); static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0); let mut server = Server::builder(); @@ -30,9 +30,7 @@ fn resume_session() { client.ctx().set_new_session_callback(|_, session| { NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst); // The server sends multiple session tickets but we only care to retrieve one. - if SESSION_TICKET.get().is_none() { - SESSION_TICKET.set(session.to_der().unwrap()).unwrap(); - } + let _ = SESSION_TICKET.set(session); }); let ssl_stream = client.connect(); @@ -41,7 +39,7 @@ fn resume_session() { assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2); // Retrieve the session ticket - let session_ticket = SslSession::from_der(SESSION_TICKET.get().unwrap()).unwrap(); + let session_ticket = SESSION_TICKET.get().unwrap(); // Attempt to resume the connection using the session ticket let client_2 = server.client(); @@ -54,7 +52,7 @@ fn resume_session() { #[test] fn custom_callback_success() { - static SESSION_TICKET: OnceLock> = OnceLock::new(); + static SESSION_TICKET: OnceLock = OnceLock::new(); static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0); let mut server = Server::builder(); @@ -71,9 +69,7 @@ fn custom_callback_success() { client.ctx().set_new_session_callback(|_, session| { NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst); // The server sends multiple session tickets but we only care to retrieve one. - if SESSION_TICKET.get().is_none() { - SESSION_TICKET.set(session.to_der().unwrap()).unwrap(); - } + let _ = SESSION_TICKET.set(session); }); let ssl_stream = client.connect(); @@ -84,7 +80,7 @@ fn custom_callback_success() { assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2); // Retrieve the session ticket - let session_ticket = SslSession::from_der(SESSION_TICKET.get().unwrap()).unwrap(); + let session_ticket = SESSION_TICKET.get().unwrap(); // Attempt to resume the connection using the session ticket let client_2 = server.client(); @@ -99,7 +95,7 @@ fn custom_callback_success() { #[test] fn custom_callback_unrecognized_decryption_ticket() { - static SESSION_TICKET: OnceLock> = OnceLock::new(); + static SESSION_TICKET: OnceLock = OnceLock::new(); static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0); let mut server = Server::builder(); @@ -116,9 +112,7 @@ fn custom_callback_unrecognized_decryption_ticket() { client.ctx().set_new_session_callback(|_, session| { NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst); // The server sends multiple session tickets but we only care to retrieve one. - if SESSION_TICKET.get().is_none() { - SESSION_TICKET.set(session.to_der().unwrap()).unwrap(); - } + let _ = SESSION_TICKET.set(session); }); let ssl_stream = client.connect(); @@ -129,7 +123,7 @@ fn custom_callback_unrecognized_decryption_ticket() { assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2); // Retrieve the session ticket - let session_ticket = SslSession::from_der(SESSION_TICKET.get().unwrap()).unwrap(); + let session_ticket = SESSION_TICKET.get().unwrap(); // Attempt to resume the connection using the session ticket let client_2 = server.client(); From b9af0ef176bad78dc9e02159b29e5cf1710e5fe0 Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Wed, 2 Apr 2025 10:40:52 -0700 Subject: [PATCH 020/111] clippy --- boring/src/ssl/test/session_resumption.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs index df9c62b72..e604d4101 100644 --- a/boring/src/ssl/test/session_resumption.rs +++ b/boring/src/ssl/test/session_resumption.rs @@ -44,7 +44,7 @@ fn resume_session() { // Attempt to resume the connection using the session ticket let client_2 = server.client(); let mut ssl_builder = client_2.build().builder(); - unsafe { ssl_builder.ssl().set_session(&session_ticket).unwrap() }; + unsafe { ssl_builder.ssl().set_session(session_ticket).unwrap() }; let ssl_stream_2 = ssl_builder.connect(); assert!(ssl_stream_2.ssl().session_reused()); @@ -85,7 +85,7 @@ fn custom_callback_success() { // Attempt to resume the connection using the session ticket let client_2 = server.client(); let mut ssl_builder = client_2.build().builder(); - unsafe { ssl_builder.ssl().set_session(&session_ticket).unwrap() }; + unsafe { ssl_builder.ssl().set_session(session_ticket).unwrap() }; let ssl_stream_2 = ssl_builder.connect(); assert!(ssl_stream_2.ssl().session_reused()); @@ -128,7 +128,7 @@ fn custom_callback_unrecognized_decryption_ticket() { // Attempt to resume the connection using the session ticket let client_2 = server.client(); let mut ssl_builder = client_2.build().builder(); - unsafe { ssl_builder.ssl().set_session(&session_ticket).unwrap() }; + unsafe { ssl_builder.ssl().set_session(session_ticket).unwrap() }; let ssl_stream_2 = ssl_builder.connect(); // Second connection was NOT resumed due to TicketKeyCallbackResult::Noop on decryption From 5cb35db98924f8222e2dabca27a882727e66fa44 Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Mon, 29 Sep 2025 14:15:41 -0700 Subject: [PATCH 021/111] initialize key_name and iv. mark fn as _unsafe to allow for future changes to the api --- boring/src/ssl/callbacks.rs | 16 ++++++++++- boring/src/ssl/mod.rs | 11 +++++-- boring/src/ssl/test/session_resumption.rs | 35 +++++++++++++++-------- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index a8e3d5caa..d1f42f776 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -282,7 +282,7 @@ where F: Fn( &SslRef, &mut [u8; 16], - *mut u8, + &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], *mut ffi::EVP_CIPHER_CTX, *mut ffi::HMAC_CTX, bool, @@ -304,9 +304,23 @@ where unsafe { slice::from_raw_parts_mut(key_name, ffi::SSL_TICKET_KEY_NAME_LEN as usize) }; let key_name = <&mut [u8; 16]>::try_from(key_name).expect("boring provides a 16-byte key name"); + // SAFETY: the callback provides 16 bytes iv + // + // https://github.com/google/boringssl/blob/main/ssl/ssl_session.cc#L331 + let iv = unsafe { core::slice::from_raw_parts_mut(iv, ffi::EVP_MAX_IV_LENGTH as usize) }; + let iv = <&mut [u8; ffi::EVP_MAX_IV_LENGTH as usize]>::try_from(iv) + .expect("boring provides a 16-byte iv"); + // When encrypting a new ticket, encrypt will be one. let encrypt = encrypt == 1; + // Zero-initialize the key_name and iv, since the application is expected to populate these + // fields in the encrypt mode. + if encrypt { + unsafe { ptr::write(key_name, [0; 16]) }; + unsafe { ptr::write(iv, [0; ffi::EVP_MAX_IV_LENGTH as usize]) }; + } + callback(ssl, key_name, iv, evp_ctx, hmac_ctx, encrypt).into() } diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index c19c73818..147ce4698 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -1140,13 +1140,20 @@ impl SslContextBuilder { /// # Panics /// /// This method panics if this `Ssl` is associated with a RPK context. + /// + /// # Safety + /// + /// The application is responsible for correctly setting the key_name, iv, encryption context + /// and hmac context. See the [`SSL_CTX_set_tlsext_ticket_key_cb`] docs for additional info. + /// + /// [`SSL_CTX_set_tlsext_ticket_key_cb`]: https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_CTX_set_tlsext_ticket_key_cb #[corresponds(SSL_CTX_set_tlsext_ticket_key_cb)] - pub fn set_ticket_key_callback(&mut self, callback: F) + pub unsafe fn set_ticket_key_callback_unsafe(&mut self, callback: F) where F: Fn( &SslRef, &mut [u8; 16], - *mut u8, + &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], *mut ffi::EVP_CIPHER_CTX, *mut ffi::HMAC_CTX, bool, diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs index e604d4101..c4b6c2d4b 100644 --- a/boring/src/ssl/test/session_resumption.rs +++ b/boring/src/ssl/test/session_resumption.rs @@ -57,9 +57,11 @@ fn custom_callback_success() { let mut server = Server::builder(); server.expected_connections_count(2); - server - .ctx() - .set_ticket_key_callback(test_success_tickey_key_callback); + unsafe { + server + .ctx() + .set_ticket_key_callback_unsafe(test_success_tickey_key_callback) + }; let server = server.build(); let mut client = server.client(); @@ -100,9 +102,11 @@ fn custom_callback_unrecognized_decryption_ticket() { let mut server = Server::builder(); server.expected_connections_count(2); - server - .ctx() - .set_ticket_key_callback(test_noop_tickey_key_callback); + unsafe { + server + .ctx() + .set_ticket_key_callback_unsafe(test_noop_tickey_key_callback) + }; let server = server.build(); let mut client = server.client(); @@ -141,14 +145,14 @@ fn custom_callback_unrecognized_decryption_ticket() { // TicketKeyCallbackResult::Noop in decryption mode. fn test_noop_tickey_key_callback( _ssl: &SslRef, - _key_name: &mut [u8; 16], - _iv: *mut u8, + key_name: &mut [u8; 16], + iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], evp_ctx: *mut ffi::EVP_CIPHER_CTX, hmac_ctx: *mut ffi::HMAC_CTX, encrypt: bool, ) -> TicketKeyCallbackResult { // These should only be used for testing purposes. - const TEST_CBC_IV: [u8; 16] = [1; 16]; + const TEST_CBC_IV: [u8; ffi::EVP_MAX_IV_LENGTH as usize] = [1; ffi::EVP_MAX_IV_LENGTH as usize]; const TEST_AES_128_CBC_KEY: [u8; 16] = [2; 16]; const TEST_HMAC_KEY: [u8; 32] = [3; 32]; @@ -156,6 +160,9 @@ fn test_noop_tickey_key_callback( let cipher = Cipher::aes_128_cbc(); if encrypt { + assert_eq!(key_name, &[0; 16]); + assert_eq!(iv, &[0; 16]); + NOOP_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); // Set the encryption context. let ret = unsafe { @@ -193,14 +200,14 @@ fn test_noop_tickey_key_callback( // Custom callback to encrypt and decrypt session tickets fn test_success_tickey_key_callback( _ssl: &SslRef, - _key_name: &mut [u8; 16], - _iv: *mut u8, + key_name: &mut [u8; 16], + iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], evp_ctx: *mut ffi::EVP_CIPHER_CTX, hmac_ctx: *mut ffi::HMAC_CTX, encrypt: bool, ) -> TicketKeyCallbackResult { // These should only be used for testing purposes. - const TEST_CBC_IV: [u8; 16] = [1; 16]; + const TEST_CBC_IV: [u8; ffi::EVP_MAX_IV_LENGTH as usize] = [1; ffi::EVP_MAX_IV_LENGTH as usize]; const TEST_AES_128_CBC_KEY: [u8; 16] = [2; 16]; const TEST_HMAC_KEY: [u8; 32] = [3; 32]; @@ -208,6 +215,9 @@ fn test_success_tickey_key_callback( let cipher = Cipher::aes_128_cbc(); if encrypt { + assert_eq!(key_name, &[0; 16]); + assert_eq!(iv, &[0; 16]); + SUCCESS_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); // Set the encryption context. let ret = unsafe { @@ -236,6 +246,7 @@ fn test_success_tickey_key_callback( assert!(ret == 1); } else { SUCCESS_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + // Set the decryption context. let ret = unsafe { ffi::EVP_DecryptInit_ex( evp_ctx, From ac1d71cb54d001b8035f4224ee61c097f8688432 Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 30 Sep 2025 15:06:11 +0100 Subject: [PATCH 022/111] Use MaybeUninit for raw_ticket_key key/iv --- boring/src/ssl/callbacks.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index d1f42f776..958524c8f 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -12,9 +12,9 @@ use crate::ssl::TicketKeyCallbackResult; use crate::x509::{X509StoreContext, X509StoreContextRef}; use foreign_types::ForeignType; use foreign_types::ForeignTypeRef; -use libc::c_char; -use libc::{c_int, c_uchar, c_uint, c_void}; +use libc::{c_char, c_int, c_uchar, c_uint, c_void}; use std::ffi::CStr; +use std::mem::MaybeUninit; use std::ptr; use std::slice; use std::str; @@ -270,6 +270,11 @@ where } } +unsafe fn to_uninit<'a, T: 'a>(ptr: *mut T) -> &'a mut MaybeUninit { + assert!(!ptr.is_null()); + unsafe { &mut *ptr.cast::>() } +} + pub(super) unsafe extern "C" fn raw_ticket_key( ssl: *mut ffi::SSL, key_name: *mut u8, @@ -301,15 +306,12 @@ where // SAFETY: the callback guarantees that key_name is 16 bytes let key_name = - unsafe { slice::from_raw_parts_mut(key_name, ffi::SSL_TICKET_KEY_NAME_LEN as usize) }; - let key_name = <&mut [u8; 16]>::try_from(key_name).expect("boring provides a 16-byte key name"); + unsafe { to_uninit(key_name.cast::<[u8; ffi::SSL_TICKET_KEY_NAME_LEN as usize]>()) }; // SAFETY: the callback provides 16 bytes iv // // https://github.com/google/boringssl/blob/main/ssl/ssl_session.cc#L331 - let iv = unsafe { core::slice::from_raw_parts_mut(iv, ffi::EVP_MAX_IV_LENGTH as usize) }; - let iv = <&mut [u8; ffi::EVP_MAX_IV_LENGTH as usize]>::try_from(iv) - .expect("boring provides a 16-byte iv"); + let iv = unsafe { to_uninit(iv.cast::<[u8; ffi::EVP_MAX_IV_LENGTH as usize]>()) }; // When encrypting a new ticket, encrypt will be one. let encrypt = encrypt == 1; @@ -317,9 +319,11 @@ where // Zero-initialize the key_name and iv, since the application is expected to populate these // fields in the encrypt mode. if encrypt { - unsafe { ptr::write(key_name, [0; 16]) }; - unsafe { ptr::write(iv, [0; ffi::EVP_MAX_IV_LENGTH as usize]) }; + *key_name = MaybeUninit::zeroed(); + *iv = MaybeUninit::zeroed(); } + let key_name = unsafe { key_name.assume_init_mut() }; + let iv = unsafe { iv.assume_init_mut() }; callback(ssl, key_name, iv, evp_ctx, hmac_ctx, encrypt).into() } From ab8513ef8f2d0775cf074f23e2ec0bb03cf00bda Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Tue, 30 Sep 2025 14:44:17 -0700 Subject: [PATCH 023/111] Expose a safe Rust interface for the session resumption callback --- boring/src/hmac.rs | 38 +++++++++ boring/src/lib.rs | 1 + boring/src/ssl/callbacks.rs | 14 +++- boring/src/ssl/mod.rs | 10 ++- boring/src/ssl/test/session_resumption.rs | 95 ++++++----------------- boring/src/symm.rs | 72 +++++++++++++++++ 6 files changed, 153 insertions(+), 77 deletions(-) create mode 100644 boring/src/hmac.rs diff --git a/boring/src/hmac.rs b/boring/src/hmac.rs new file mode 100644 index 000000000..4a5938772 --- /dev/null +++ b/boring/src/hmac.rs @@ -0,0 +1,38 @@ +use crate::cvt; +use crate::error::ErrorStack; +use crate::hash::MessageDigest; +use std::ffi::c_void; + +use foreign_types::ForeignType; + +foreign_type_and_impl_send_sync! { + type CType = ffi::HMAC_CTX; + fn drop = ffi::HMAC_CTX_free; + + pub struct HmacCtx; +} + +impl HmacCtx { + /// Configures HmacCtx to use `md` as the hash function and `key` as the key. + /// + /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/hmac.h.html#HMAC_Init_ex + /// + /// # Safety + /// + /// The caller must ensure HMAC_CTX has been initalized. + pub unsafe fn init(&mut self, key: &[u8], md: &MessageDigest) -> Result<(), ErrorStack> { + ffi::init(); + + unsafe { + cvt(ffi::HMAC_Init_ex( + self.as_ptr(), + key.as_ptr() as *const c_void, + key.len(), + md.as_ptr(), + // ENGINE api is deprecated + core::ptr::null_mut(), + )) + .map(|_| ()) + } + } +} diff --git a/boring/src/lib.rs b/boring/src/lib.rs index 77f3e726f..932bdd354 100644 --- a/boring/src/lib.rs +++ b/boring/src/lib.rs @@ -137,6 +137,7 @@ pub mod error; pub mod ex_data; pub mod fips; pub mod hash; +pub mod hmac; pub mod hpke; pub mod memcmp; pub mod nid; diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index 958524c8f..5598b6af3 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -8,13 +8,15 @@ use super::{ }; use crate::error::ErrorStack; use crate::ffi; +use crate::hmac::HmacCtx; use crate::ssl::TicketKeyCallbackResult; +use crate::symm::CipherCtx; use crate::x509::{X509StoreContext, X509StoreContextRef}; use foreign_types::ForeignType; use foreign_types::ForeignTypeRef; use libc::{c_char, c_int, c_uchar, c_uint, c_void}; use std::ffi::CStr; -use std::mem::MaybeUninit; +use std::mem::{ManuallyDrop, MaybeUninit}; use std::ptr; use std::slice; use std::str; @@ -288,8 +290,8 @@ where &SslRef, &mut [u8; 16], &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], - *mut ffi::EVP_CIPHER_CTX, - *mut ffi::HMAC_CTX, + &mut CipherCtx, + &mut HmacCtx, bool, ) -> TicketKeyCallbackResult + 'static @@ -325,7 +327,11 @@ where let key_name = unsafe { key_name.assume_init_mut() }; let iv = unsafe { iv.assume_init_mut() }; - callback(ssl, key_name, iv, evp_ctx, hmac_ctx, encrypt).into() + // The EVP_CIPHER_CTX and HMAC_CTX are owned by boringSSL. + let mut evp_ctx = ManuallyDrop::new(unsafe { CipherCtx::from_ptr(evp_ctx) }); + let mut hmac_ctx = ManuallyDrop::new(unsafe { HmacCtx::from_ptr(hmac_ctx) }); + + callback(ssl, key_name, iv, &mut evp_ctx, &mut hmac_ctx, encrypt).into() } pub(super) unsafe extern "C" fn raw_alpn_select( diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 147ce4698..1be720ac6 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -81,6 +81,7 @@ use crate::dh::DhRef; use crate::ec::EcKeyRef; use crate::error::ErrorStack; use crate::ex_data::Index; +use crate::hmac::HmacCtx; use crate::nid::Nid; use crate::pkey::{HasPrivate, PKeyRef, Params, Private}; use crate::srtp::{SrtpProtectionProfile, SrtpProtectionProfileRef}; @@ -88,6 +89,7 @@ use crate::ssl::bio::BioMethod; use crate::ssl::callbacks::*; use crate::ssl::error::InnerError; use crate::stack::{Stack, StackRef, Stackable}; +use crate::symm::CipherCtx; use crate::x509::store::{X509Store, X509StoreBuilder, X509StoreBuilderRef, X509StoreRef}; use crate::x509::verify::X509VerifyParamRef; use crate::x509::{ @@ -1137,6 +1139,8 @@ impl SslContextBuilder { /// prior to TLS 1.3, retroactively decrypt all application traffic from sessions using that /// ticket key. Thus ticket keys must be regularly rotated for forward secrecy. /// + /// CipherCtx and HmacCtx are guaranteed to be initialized. + /// /// # Panics /// /// This method panics if this `Ssl` is associated with a RPK context. @@ -1148,14 +1152,14 @@ impl SslContextBuilder { /// /// [`SSL_CTX_set_tlsext_ticket_key_cb`]: https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_CTX_set_tlsext_ticket_key_cb #[corresponds(SSL_CTX_set_tlsext_ticket_key_cb)] - pub unsafe fn set_ticket_key_callback_unsafe(&mut self, callback: F) + pub unsafe fn set_ticket_key_callback(&mut self, callback: F) where F: Fn( &SslRef, &mut [u8; 16], &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], - *mut ffi::EVP_CIPHER_CTX, - *mut ffi::HMAC_CTX, + &mut CipherCtx, + &mut HmacCtx, bool, ) -> TicketKeyCallbackResult + 'static diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs index c4b6c2d4b..2eb62116d 100644 --- a/boring/src/ssl/test/session_resumption.rs +++ b/boring/src/ssl/test/session_resumption.rs @@ -1,11 +1,12 @@ use super::server::Server; use crate::ssl::test::MessageDigest; +use crate::ssl::HmacCtx; use crate::ssl::SslRef; use crate::ssl::SslSession; use crate::ssl::SslSessionCacheMode; use crate::ssl::TicketKeyCallbackResult; use crate::symm::Cipher; -use std::ffi::c_void; +use crate::symm::CipherCtx; use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::OnceLock; @@ -60,7 +61,7 @@ fn custom_callback_success() { unsafe { server .ctx() - .set_ticket_key_callback_unsafe(test_success_tickey_key_callback) + .set_ticket_key_callback(test_success_tickey_key_callback) }; let server = server.build(); @@ -105,7 +106,7 @@ fn custom_callback_unrecognized_decryption_ticket() { unsafe { server .ctx() - .set_ticket_key_callback_unsafe(test_noop_tickey_key_callback) + .set_ticket_key_callback(test_noop_tickey_key_callback) }; let server = server.build(); @@ -147,8 +148,8 @@ fn test_noop_tickey_key_callback( _ssl: &SslRef, key_name: &mut [u8; 16], iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], - evp_ctx: *mut ffi::EVP_CIPHER_CTX, - hmac_ctx: *mut ffi::HMAC_CTX, + evp_ctx: &mut CipherCtx, + hmac_ctx: &mut HmacCtx, encrypt: bool, ) -> TicketKeyCallbackResult { // These should only be used for testing purposes. @@ -164,31 +165,16 @@ fn test_noop_tickey_key_callback( assert_eq!(iv, &[0; 16]); NOOP_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + // Set the encryption context. - let ret = unsafe { - ffi::EVP_EncryptInit_ex( - evp_ctx, - cipher.as_ptr(), - // ENGINE api is deprecated - core::ptr::null_mut(), - TEST_AES_128_CBC_KEY.as_ptr(), - TEST_CBC_IV.as_ptr(), - ) + unsafe { + evp_ctx + .init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV) + .unwrap() }; - assert!(ret == 1); // Set the hmac context. - let ret = unsafe { - ffi::HMAC_Init_ex( - hmac_ctx, - TEST_HMAC_KEY.as_ptr() as *const c_void, - TEST_HMAC_KEY.len(), - digest.as_ptr(), - // ENGINE api is deprecated - core::ptr::null_mut(), - ) - }; - assert!(ret == 1); + unsafe { hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap() }; TicketKeyCallbackResult::Success } else { @@ -202,8 +188,8 @@ fn test_success_tickey_key_callback( _ssl: &SslRef, key_name: &mut [u8; 16], iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], - evp_ctx: *mut ffi::EVP_CIPHER_CTX, - hmac_ctx: *mut ffi::HMAC_CTX, + evp_ctx: &mut CipherCtx, + hmac_ctx: &mut HmacCtx, encrypt: bool, ) -> TicketKeyCallbackResult { // These should only be used for testing purposes. @@ -219,58 +205,27 @@ fn test_success_tickey_key_callback( assert_eq!(iv, &[0; 16]); SUCCESS_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + // Set the encryption context. - let ret = unsafe { - ffi::EVP_EncryptInit_ex( - evp_ctx, - cipher.as_ptr(), - // ENGINE api is deprecated - core::ptr::null_mut(), - TEST_AES_128_CBC_KEY.as_ptr(), - TEST_CBC_IV.as_ptr(), - ) + unsafe { + evp_ctx + .init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV) + .unwrap() }; - assert!(ret == 1); // Set the hmac context. - let ret = unsafe { - ffi::HMAC_Init_ex( - hmac_ctx, - TEST_HMAC_KEY.as_ptr() as *const c_void, - TEST_HMAC_KEY.len(), - digest.as_ptr(), - // ENGINE api is deprecated - core::ptr::null_mut(), - ) - }; - assert!(ret == 1); + unsafe { hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap() }; } else { SUCCESS_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); // Set the decryption context. - let ret = unsafe { - ffi::EVP_DecryptInit_ex( - evp_ctx, - cipher.as_ptr(), - // ENGINE api is deprecated - core::ptr::null_mut(), - TEST_AES_128_CBC_KEY.as_ptr(), - TEST_CBC_IV.as_ptr(), - ) + unsafe { + evp_ctx + .init_decrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV) + .unwrap() }; - assert!(ret == 1); // Set the hmac context. - let ret = unsafe { - ffi::HMAC_Init_ex( - hmac_ctx, - TEST_HMAC_KEY.as_ptr() as *const c_void, - TEST_HMAC_KEY.len(), - digest.as_ptr(), - // ENGINE api is deprecated - core::ptr::null_mut(), - ) - }; - assert!(ret == 1); + unsafe { hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap() }; } TicketKeyCallbackResult::Success diff --git a/boring/src/symm.rs b/boring/src/symm.rs index fff8a4a10..831430f50 100644 --- a/boring/src/symm.rs +++ b/boring/src/symm.rs @@ -53,6 +53,7 @@ //! ``` use crate::ffi; +use foreign_types::ForeignType; use libc::{c_int, c_uint}; use openssl_macros::corresponds; use std::cmp; @@ -68,6 +69,77 @@ pub enum Mode { Decrypt, } +foreign_type_and_impl_send_sync! { + type CType = ffi::EVP_CIPHER_CTX; + fn drop = ffi::EVP_CIPHER_CTX_free; + + pub struct CipherCtx; +} + +impl CipherCtx { + /// Configures CipherCtx for a fresh encryption operation using `cipher`. + /// + /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/cipher.h.html#EVP_EncryptInit_ex + /// + /// # Safety + /// + /// The caller must ensure EVP_CIPHER_CTX has been initalized. + /// + /// The caller is responsible for ensuring the length of `key` and `iv` are appropriate for the + /// chosen Cipher. + pub unsafe fn init_encrypt( + &mut self, + cipher: &Cipher, + key: &[u8], + iv: &[u8; ffi::EVP_MAX_IV_LENGTH as usize], + ) -> Result<(), ErrorStack> { + ffi::init(); + + unsafe { + cvt(ffi::EVP_EncryptInit_ex( + self.as_ptr(), + cipher.as_ptr(), + // ENGINE api is deprecated + ptr::null_mut(), + key.as_ptr(), + iv.as_ptr(), + )) + .map(|_| ()) + } + } + + /// Configures CipherCtx for a fresh decryption operation using `cipher`. + /// + /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/cipher.h.html#EVP_DecryptInit_ex + /// + /// # Safety + /// + /// The caller must ensure EVP_CIPHER_CTX has been initalized. + /// + /// The caller is responsible for ensuring the length of `key` and `iv` are appropriate for the + /// chosen Cipher. + pub unsafe fn init_decrypt( + &mut self, + cipher: &Cipher, + key: &[u8], + iv: &[u8; ffi::EVP_MAX_IV_LENGTH as usize], + ) -> Result<(), ErrorStack> { + ffi::init(); + + unsafe { + cvt(ffi::EVP_DecryptInit_ex( + self.as_ptr(), + cipher.as_ptr(), + // ENGINE api is deprecated + ptr::null_mut(), + key.as_ptr(), + iv.as_ptr(), + )) + .map(|_| ()) + } + } +} + /// Represents a particular cipher algorithm. /// /// See OpenSSL doc at [`EVP_EncryptInit`] for more information on each algorithms. From 8773f0e1faca1352abb1e1b421c327149f36bf86 Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 1 Oct 2025 03:38:59 +0100 Subject: [PATCH 024/111] Use Ref foreign type instead of forgetting --- boring/src/hmac.rs | 8 +++----- boring/src/ssl/callbacks.rs | 16 ++++++++-------- boring/src/ssl/mod.rs | 8 ++++---- boring/src/ssl/test/session_resumption.rs | 12 ++++++------ boring/src/symm.rs | 4 ++-- 5 files changed, 23 insertions(+), 25 deletions(-) diff --git a/boring/src/hmac.rs b/boring/src/hmac.rs index 4a5938772..9816fb576 100644 --- a/boring/src/hmac.rs +++ b/boring/src/hmac.rs @@ -1,9 +1,7 @@ use crate::cvt; use crate::error::ErrorStack; +use crate::foreign_types::ForeignTypeRef; use crate::hash::MessageDigest; -use std::ffi::c_void; - -use foreign_types::ForeignType; foreign_type_and_impl_send_sync! { type CType = ffi::HMAC_CTX; @@ -12,7 +10,7 @@ foreign_type_and_impl_send_sync! { pub struct HmacCtx; } -impl HmacCtx { +impl HmacCtxRef { /// Configures HmacCtx to use `md` as the hash function and `key` as the key. /// /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/hmac.h.html#HMAC_Init_ex @@ -26,7 +24,7 @@ impl HmacCtx { unsafe { cvt(ffi::HMAC_Init_ex( self.as_ptr(), - key.as_ptr() as *const c_void, + key.as_ptr().cast(), key.len(), md.as_ptr(), // ENGINE api is deprecated diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index 5598b6af3..ed724f791 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -8,15 +8,15 @@ use super::{ }; use crate::error::ErrorStack; use crate::ffi; -use crate::hmac::HmacCtx; +use crate::hmac::HmacCtxRef; use crate::ssl::TicketKeyCallbackResult; -use crate::symm::CipherCtx; +use crate::symm::CipherCtxRef; use crate::x509::{X509StoreContext, X509StoreContextRef}; use foreign_types::ForeignType; use foreign_types::ForeignTypeRef; use libc::{c_char, c_int, c_uchar, c_uint, c_void}; use std::ffi::CStr; -use std::mem::{ManuallyDrop, MaybeUninit}; +use std::mem::MaybeUninit; use std::ptr; use std::slice; use std::str; @@ -290,8 +290,8 @@ where &SslRef, &mut [u8; 16], &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], - &mut CipherCtx, - &mut HmacCtx, + &mut CipherCtxRef, + &mut HmacCtxRef, bool, ) -> TicketKeyCallbackResult + 'static @@ -328,10 +328,10 @@ where let iv = unsafe { iv.assume_init_mut() }; // The EVP_CIPHER_CTX and HMAC_CTX are owned by boringSSL. - let mut evp_ctx = ManuallyDrop::new(unsafe { CipherCtx::from_ptr(evp_ctx) }); - let mut hmac_ctx = ManuallyDrop::new(unsafe { HmacCtx::from_ptr(hmac_ctx) }); + let evp_ctx = unsafe { CipherCtxRef::from_ptr_mut(evp_ctx) }; + let hmac_ctx = unsafe { HmacCtxRef::from_ptr_mut(hmac_ctx) }; - callback(ssl, key_name, iv, &mut evp_ctx, &mut hmac_ctx, encrypt).into() + callback(ssl, key_name, iv, evp_ctx, hmac_ctx, encrypt).into() } pub(super) unsafe extern "C" fn raw_alpn_select( diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 1be720ac6..19688c396 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -81,7 +81,7 @@ use crate::dh::DhRef; use crate::ec::EcKeyRef; use crate::error::ErrorStack; use crate::ex_data::Index; -use crate::hmac::HmacCtx; +use crate::hmac::HmacCtxRef; use crate::nid::Nid; use crate::pkey::{HasPrivate, PKeyRef, Params, Private}; use crate::srtp::{SrtpProtectionProfile, SrtpProtectionProfileRef}; @@ -89,7 +89,7 @@ use crate::ssl::bio::BioMethod; use crate::ssl::callbacks::*; use crate::ssl::error::InnerError; use crate::stack::{Stack, StackRef, Stackable}; -use crate::symm::CipherCtx; +use crate::symm::CipherCtxRef; use crate::x509::store::{X509Store, X509StoreBuilder, X509StoreBuilderRef, X509StoreRef}; use crate::x509::verify::X509VerifyParamRef; use crate::x509::{ @@ -1158,8 +1158,8 @@ impl SslContextBuilder { &SslRef, &mut [u8; 16], &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], - &mut CipherCtx, - &mut HmacCtx, + &mut CipherCtxRef, + &mut HmacCtxRef, bool, ) -> TicketKeyCallbackResult + 'static diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs index 2eb62116d..f7f481dbf 100644 --- a/boring/src/ssl/test/session_resumption.rs +++ b/boring/src/ssl/test/session_resumption.rs @@ -1,12 +1,12 @@ use super::server::Server; use crate::ssl::test::MessageDigest; -use crate::ssl::HmacCtx; +use crate::ssl::HmacCtxRef; use crate::ssl::SslRef; use crate::ssl::SslSession; use crate::ssl::SslSessionCacheMode; use crate::ssl::TicketKeyCallbackResult; use crate::symm::Cipher; -use crate::symm::CipherCtx; +use crate::symm::CipherCtxRef; use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::OnceLock; @@ -148,8 +148,8 @@ fn test_noop_tickey_key_callback( _ssl: &SslRef, key_name: &mut [u8; 16], iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], - evp_ctx: &mut CipherCtx, - hmac_ctx: &mut HmacCtx, + evp_ctx: &mut CipherCtxRef, + hmac_ctx: &mut HmacCtxRef, encrypt: bool, ) -> TicketKeyCallbackResult { // These should only be used for testing purposes. @@ -188,8 +188,8 @@ fn test_success_tickey_key_callback( _ssl: &SslRef, key_name: &mut [u8; 16], iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], - evp_ctx: &mut CipherCtx, - hmac_ctx: &mut HmacCtx, + evp_ctx: &mut CipherCtxRef, + hmac_ctx: &mut HmacCtxRef, encrypt: bool, ) -> TicketKeyCallbackResult { // These should only be used for testing purposes. diff --git a/boring/src/symm.rs b/boring/src/symm.rs index 831430f50..1a2dc5997 100644 --- a/boring/src/symm.rs +++ b/boring/src/symm.rs @@ -53,7 +53,7 @@ //! ``` use crate::ffi; -use foreign_types::ForeignType; +use foreign_types::ForeignTypeRef; use libc::{c_int, c_uint}; use openssl_macros::corresponds; use std::cmp; @@ -76,7 +76,7 @@ foreign_type_and_impl_send_sync! { pub struct CipherCtx; } -impl CipherCtx { +impl CipherCtxRef { /// Configures CipherCtx for a fresh encryption operation using `cipher`. /// /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/cipher.h.html#EVP_EncryptInit_ex From 353ea62c17c379dbfcd26bcdeb22c72de382efbb Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Tue, 30 Sep 2025 23:14:09 -0700 Subject: [PATCH 025/111] Convert CipherCtx fns into a safe abstraction. Additional testing. --- boring/src/hmac.rs | 6 +-- boring/src/ssl/test/session_resumption.rs | 54 ++++++++++++++--------- boring/src/symm.rs | 26 +++++------ 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/boring/src/hmac.rs b/boring/src/hmac.rs index 9816fb576..7e50e6137 100644 --- a/boring/src/hmac.rs +++ b/boring/src/hmac.rs @@ -14,11 +14,7 @@ impl HmacCtxRef { /// Configures HmacCtx to use `md` as the hash function and `key` as the key. /// /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/hmac.h.html#HMAC_Init_ex - /// - /// # Safety - /// - /// The caller must ensure HMAC_CTX has been initalized. - pub unsafe fn init(&mut self, key: &[u8], md: &MessageDigest) -> Result<(), ErrorStack> { + pub fn init(&mut self, key: &[u8], md: &MessageDigest) -> Result<(), ErrorStack> { ffi::init(); unsafe { diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs index f7f481dbf..808abe304 100644 --- a/boring/src/ssl/test/session_resumption.rs +++ b/boring/src/ssl/test/session_resumption.rs @@ -153,6 +153,7 @@ fn test_noop_tickey_key_callback( encrypt: bool, ) -> TicketKeyCallbackResult { // These should only be used for testing purposes. + const TEST_KEY_NAME: [u8; 16] = [5; 16]; const TEST_CBC_IV: [u8; ffi::EVP_MAX_IV_LENGTH as usize] = [1; ffi::EVP_MAX_IV_LENGTH as usize]; const TEST_AES_128_CBC_KEY: [u8; 16] = [2; 16]; const TEST_HMAC_KEY: [u8; 32] = [3; 32]; @@ -161,24 +162,29 @@ fn test_noop_tickey_key_callback( let cipher = Cipher::aes_128_cbc(); if encrypt { + NOOP_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + + // Ensure key_name and iv are initialized and set test values. assert_eq!(key_name, &[0; 16]); assert_eq!(iv, &[0; 16]); - - NOOP_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + key_name.copy_from_slice(&TEST_KEY_NAME); + iv.copy_from_slice(&TEST_CBC_IV); // Set the encryption context. - unsafe { - evp_ctx - .init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV) - .unwrap() - }; + evp_ctx + .init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV) + .unwrap(); // Set the hmac context. - unsafe { hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap() }; + hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap(); TicketKeyCallbackResult::Success } else { NOOP_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + + // Check key_name matches. + assert_eq!(key_name, &TEST_KEY_NAME); + TicketKeyCallbackResult::Noop } } @@ -193,6 +199,7 @@ fn test_success_tickey_key_callback( encrypt: bool, ) -> TicketKeyCallbackResult { // These should only be used for testing purposes. + const TEST_KEY_NAME: [u8; 16] = [5; 16]; const TEST_CBC_IV: [u8; ffi::EVP_MAX_IV_LENGTH as usize] = [1; ffi::EVP_MAX_IV_LENGTH as usize]; const TEST_AES_128_CBC_KEY: [u8; 16] = [2; 16]; const TEST_HMAC_KEY: [u8; 32] = [3; 32]; @@ -201,31 +208,34 @@ fn test_success_tickey_key_callback( let cipher = Cipher::aes_128_cbc(); if encrypt { + SUCCESS_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + + // Ensure key_name and iv are initialized and set test values. assert_eq!(key_name, &[0; 16]); assert_eq!(iv, &[0; 16]); - - SUCCESS_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + key_name.copy_from_slice(&TEST_KEY_NAME); + iv.copy_from_slice(&TEST_CBC_IV); // Set the encryption context. - unsafe { - evp_ctx - .init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV) - .unwrap() - }; + evp_ctx + .init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV) + .unwrap(); // Set the hmac context. - unsafe { hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap() }; + hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap(); } else { SUCCESS_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + + // Check key_name matches. + assert_eq!(key_name, &TEST_KEY_NAME); + // Set the decryption context. - unsafe { - evp_ctx - .init_decrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV) - .unwrap() - }; + evp_ctx + .init_decrypt(&cipher, &TEST_AES_128_CBC_KEY, iv) + .unwrap(); // Set the hmac context. - unsafe { hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap() }; + hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap(); } TicketKeyCallbackResult::Success diff --git a/boring/src/symm.rs b/boring/src/symm.rs index 1a2dc5997..38fab76dc 100644 --- a/boring/src/symm.rs +++ b/boring/src/symm.rs @@ -80,14 +80,7 @@ impl CipherCtxRef { /// Configures CipherCtx for a fresh encryption operation using `cipher`. /// /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/cipher.h.html#EVP_EncryptInit_ex - /// - /// # Safety - /// - /// The caller must ensure EVP_CIPHER_CTX has been initalized. - /// - /// The caller is responsible for ensuring the length of `key` and `iv` are appropriate for the - /// chosen Cipher. - pub unsafe fn init_encrypt( + pub fn init_encrypt( &mut self, cipher: &Cipher, key: &[u8], @@ -95,6 +88,10 @@ impl CipherCtxRef { ) -> Result<(), ErrorStack> { ffi::init(); + if key.len() != cipher.key_len() { + return Err(ErrorStack::get()); + } + unsafe { cvt(ffi::EVP_EncryptInit_ex( self.as_ptr(), @@ -111,14 +108,7 @@ impl CipherCtxRef { /// Configures CipherCtx for a fresh decryption operation using `cipher`. /// /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/cipher.h.html#EVP_DecryptInit_ex - /// - /// # Safety - /// - /// The caller must ensure EVP_CIPHER_CTX has been initalized. - /// - /// The caller is responsible for ensuring the length of `key` and `iv` are appropriate for the - /// chosen Cipher. - pub unsafe fn init_decrypt( + pub fn init_decrypt( &mut self, cipher: &Cipher, key: &[u8], @@ -126,6 +116,10 @@ impl CipherCtxRef { ) -> Result<(), ErrorStack> { ffi::init(); + if key.len() != cipher.key_len() { + return Err(ErrorStack::get()); + } + unsafe { cvt(ffi::EVP_DecryptInit_ex( self.as_ptr(), From e3998212eda8d8f51523dfbe24df3638a4a8c9d1 Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 1 Oct 2025 11:50:08 +0100 Subject: [PATCH 026/111] Fix string data conversion in ErrorStack::put() --- boring/src/error.rs | 56 +++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/boring/src/error.rs b/boring/src/error.rs index 60d8d4f5c..5c1ad40bb 100644 --- a/boring/src/error.rs +++ b/boring/src/error.rs @@ -20,6 +20,7 @@ use openssl_macros::corresponds; use std::borrow::Cow; use std::error; use std::ffi::CStr; +use std::ffi::CString; use std::fmt; use std::io; use std::ptr; @@ -58,7 +59,7 @@ impl ErrorStack { /// Used to report errors from the Rust crate #[cold] pub(crate) fn internal_error(err: impl error::Error) -> Self { - Self(vec![Error::new_internal(err.to_string())]) + Self(vec![Error::new_internal(Data::String(err.to_string()))]) } /// Empties the current thread's error queue. @@ -122,7 +123,14 @@ pub struct Error { code: c_uint, file: *const c_char, line: c_uint, - data: Option>, + data: Data, +} + +#[derive(Clone)] +enum Data { + None, + CString(CString), + String(String), } unsafe impl Sync for Error {} @@ -148,11 +156,9 @@ impl Error { // The memory referenced by data is only valid until that slot is overwritten // in the error stack, so we'll need to copy it off if it's dynamic let data = if flags & ffi::ERR_FLAG_STRING != 0 { - Some(Cow::Owned( - CStr::from_ptr(data.cast()).to_string_lossy().into_owned(), - )) + Data::CString(CStr::from_ptr(data.cast()).to_owned()) } else { - None + Data::None }; Some(Error { code, @@ -176,22 +182,8 @@ impl Error { self.file, self.line, ); - let ptr = match self.data { - Some(Cow::Borrowed(data)) => Some(data.as_ptr() as *mut c_char), - Some(Cow::Owned(ref data)) => { - let ptr = ffi::OPENSSL_malloc((data.len() + 1) as _) as *mut c_char; - if ptr.is_null() { - None - } else { - ptr::copy_nonoverlapping(data.as_ptr(), ptr as *mut u8, data.len()); - *ptr.add(data.len()) = 0; - Some(ptr) - } - } - None => None, - }; - if let Some(ptr) = ptr { - ffi::ERR_add_error_data(1, ptr); + if let Some(cstr) = self.data_cstr() { + ffi::ERR_set_error_data(cstr.as_ptr().cast_mut(), ffi::ERR_FLAG_STRING); } } } @@ -297,15 +289,29 @@ impl Error { /// Returns additional data describing the error. #[must_use] pub fn data(&self) -> Option<&str> { - self.data.as_deref() + match &self.data { + Data::None => None, + Data::CString(cstring) => cstring.to_str().ok(), + Data::String(s) => Some(s), + } + } + + #[must_use] + fn data_cstr(&self) -> Option> { + let s = match &self.data { + Data::None => return None, + Data::CString(cstr) => return Some(Cow::Borrowed(cstr)), + Data::String(s) => s.as_str(), + }; + CString::new(s).ok().map(Cow::Owned) } - fn new_internal(msg: String) -> Self { + fn new_internal(msg: Data) -> Self { Self { code: ffi::ERR_PACK(ffi::ERR_LIB_NONE.0 as _, 0, 0) as _, file: BORING_INTERNAL.as_ptr(), line: 0, - data: Some(msg.into()), + data: msg, } } From 5957ce94cc6e81e6a264e2347b3f57cdab857f50 Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 1 Oct 2025 11:59:59 +0100 Subject: [PATCH 027/111] ErrorStack ctor for custom errors --- boring/src/error.rs | 12 ++++++++++++ boring/src/ssl/callbacks.rs | 2 +- boring/src/symm.rs | 4 ++-- boring/src/util.rs | 4 ++-- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/boring/src/error.rs b/boring/src/error.rs index 5c1ad40bb..365643d67 100644 --- a/boring/src/error.rs +++ b/boring/src/error.rs @@ -38,6 +38,9 @@ pub struct ErrorStack(Vec); impl ErrorStack { /// Pops the contents of the OpenSSL error stack, and returns it. + /// + /// This should be used only immediately after calling Boring FFI functions, + /// otherwise the stack may be empty or a leftover from unrelated calls. #[corresponds(ERR_get_error_line_data)] #[must_use = "Use ErrorStack::clear() to drop the error stack"] pub fn get() -> ErrorStack { @@ -62,6 +65,12 @@ impl ErrorStack { Self(vec![Error::new_internal(Data::String(err.to_string()))]) } + /// Used to report errors from the Rust crate + #[cold] + pub(crate) fn internal_error_str(message: &'static str) -> Self { + Self(vec![Error::new_internal(Data::Static(message))]) + } + /// Empties the current thread's error queue. #[corresponds(ERR_clear_error)] pub(crate) fn clear() { @@ -131,6 +140,7 @@ enum Data { None, CString(CString), String(String), + Static(&'static str), } unsafe impl Sync for Error {} @@ -293,6 +303,7 @@ impl Error { Data::None => None, Data::CString(cstring) => cstring.to_str().ok(), Data::String(s) => Some(s), + Data::Static(s) => Some(s), } } @@ -302,6 +313,7 @@ impl Error { Data::None => return None, Data::CString(cstr) => return Some(Cow::Borrowed(cstr)), Data::String(s) => s.as_str(), + Data::Static(s) => s, }; CString::new(s).ok().map(Cow::Owned) } diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index ed724f791..ea0a73c26 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -767,7 +767,7 @@ impl<'a> CryptoBufferBuilder<'a> { let buffer_capacity = unsafe { ffi::CRYPTO_BUFFER_len(self.buffer) }; if self.cursor.position() != buffer_capacity as u64 { // Make sure all bytes in buffer initialized as required by Boring SSL. - return Err(ErrorStack::get()); + return Err(ErrorStack::internal_error_str("invalid len")); } unsafe { let mut result = ptr::null_mut(); diff --git a/boring/src/symm.rs b/boring/src/symm.rs index 38fab76dc..a1346e6e5 100644 --- a/boring/src/symm.rs +++ b/boring/src/symm.rs @@ -89,7 +89,7 @@ impl CipherCtxRef { ffi::init(); if key.len() != cipher.key_len() { - return Err(ErrorStack::get()); + return Err(ErrorStack::internal_error_str("invalid key size")); } unsafe { @@ -117,7 +117,7 @@ impl CipherCtxRef { ffi::init(); if key.len() != cipher.key_len() { - return Err(ErrorStack::get()); + return Err(ErrorStack::internal_error_str("invalid key size")); } unsafe { diff --git a/boring/src/util.rs b/boring/src/util.rs index bb6373c18..d34fd8984 100644 --- a/boring/src/util.rs +++ b/boring/src/util.rs @@ -55,8 +55,8 @@ where match result { Ok(Ok(len)) => len as c_int, - Ok(Err(_)) => { - // FIXME restore error stack + Ok(Err(err)) => { + err.put(); 0 } Err(err) => { From 75ef5232300a7d005578003c2dfc4d2b38cb7b8a Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 1 Oct 2025 11:12:40 +0100 Subject: [PATCH 028/111] Safer CryptoBufferBuilder::build --- boring/src/ssl/callbacks.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index ea0a73c26..f08409b3c 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -16,7 +16,7 @@ use foreign_types::ForeignType; use foreign_types::ForeignTypeRef; use libc::{c_char, c_int, c_uchar, c_uint, c_void}; use std::ffi::CStr; -use std::mem::MaybeUninit; +use std::mem::{self, MaybeUninit}; use std::ptr; use std::slice; use std::str; @@ -769,12 +769,8 @@ impl<'a> CryptoBufferBuilder<'a> { // Make sure all bytes in buffer initialized as required by Boring SSL. return Err(ErrorStack::internal_error_str("invalid len")); } - unsafe { - let mut result = ptr::null_mut(); - ptr::swap(&mut self.buffer, &mut result); - std::mem::forget(self); - Ok(result) - } + // Drop is no-op if the buffer is null + Ok(mem::replace(&mut self.buffer, ptr::null_mut())) } } From 77f612c16c7e8ae008612849e1fbe64f01dcffdb Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 1 Oct 2025 10:40:05 +0100 Subject: [PATCH 029/111] Simplify Error::reason() --- boring/src/error.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/boring/src/error.rs b/boring/src/error.rs index 365643d67..1e8f79467 100644 --- a/boring/src/error.rs +++ b/boring/src/error.rs @@ -103,7 +103,7 @@ impl fmt::Display for ErrorStack { write!( fmt, "[{}]", - err.reason_internal() + err.reason() .or_else(|| err.library()) .unwrap_or("unknown reason") )?; @@ -252,7 +252,10 @@ impl Error { /// Returns the reason for the error. #[must_use] - pub fn reason(&self) -> Option<&'static str> { + pub fn reason(&self) -> Option<&str> { + if self.is_internal() { + return self.data(); + } unsafe { let cstr = ffi::ERR_reason_error_string(self.code); if cstr.is_null() { @@ -330,15 +333,6 @@ impl Error { fn is_internal(&self) -> bool { std::ptr::eq(self.file, BORING_INTERNAL.as_ptr()) } - - // reason() needs 'static - fn reason_internal(&self) -> Option<&str> { - if self.is_internal() { - self.data() - } else { - self.reason() - } - } } impl fmt::Debug for Error { @@ -369,7 +363,7 @@ impl fmt::Display for Error { write!( fmt, "{}\n\nCode: {:08X}\nLoc: {}:{}", - self.reason_internal().unwrap_or("unknown TLS error"), + self.reason().unwrap_or("unknown TLS error"), &self.code, self.file(), self.line() From 5cd912df1db85ec7880d08e577de486452624acb Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Tue, 30 Sep 2025 11:04:57 -0700 Subject: [PATCH 030/111] Remove "pq-experimental", apply PQ patch by default Users can override the new default behavior in the usual way. The expectation is that the build of BoringSSL they provide the feature set implemented by the patch. --- .github/workflows/ci.yml | 10 +--------- boring-sys/Cargo.toml | 16 ++++++---------- boring-sys/build/config.rs | 7 +------ boring-sys/build/main.rs | 12 ++++-------- boring/Cargo.toml | 17 ++++++----------- hyper-boring/Cargo.toml | 5 +---- tokio-boring/Cargo.toml | 5 +---- 7 files changed, 20 insertions(+), 52 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46e621e77..741dde228 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,7 @@ jobs: - name: Run clippy run: cargo clippy --all --all-targets - name: Check docs - run: cargo doc --no-deps -p boring -p boring-sys --features rpk,pq-experimental,underscore-wildcards + run: cargo doc --no-deps -p boring -p boring-sys --features rpk,underscore-wildcards env: DOCS_RS: 1 test: @@ -357,15 +357,7 @@ jobs: shell: bash - run: cargo test --features rpk name: Run `rpk` tests - - run: cargo test --features pq-experimental - name: Run `pq-experimental` tests - run: cargo test --features underscore-wildcards name: Run `underscore-wildcards` tests - - run: cargo test --features pq-experimental,rpk - name: Run `pq-experimental,rpk` tests - - run: cargo test --features pq-experimental,underscore-wildcards - name: Run `pq-experimental,underscore-wildcards` tests - run: cargo test --features rpk,underscore-wildcards name: Run `rpk,underscore-wildcards` tests - - run: cargo test --features pq-experimental,rpk,underscore-wildcards - name: Run `pq-experimental,rpk,underscore-wildcards` tests diff --git a/boring-sys/Cargo.toml b/boring-sys/Cargo.toml index 86ff731c3..ce49709d2 100644 --- a/boring-sys/Cargo.toml +++ b/boring-sys/Cargo.toml @@ -41,7 +41,7 @@ include = [ ] [package.metadata.docs.rs] -features = ["rpk", "pq-experimental", "underscore-wildcards"] +features = ["rpk", "underscore-wildcards"] rustdoc-args = ["--cfg", "docsrs"] [features] @@ -56,16 +56,12 @@ fips = [] # Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250) rpk = [] -# Applies a patch (`patches/boring-pq.patch`) to the boringSSL source code that -# enables support for PQ key exchange. This feature is necessary in order to -# compile the bindings for the default branch of boringSSL (`deps/boringssl`). -# Alternatively, a version of boringSSL that implements the same feature set -# can be provided by setting `BORING_BSSL{,_FIPS}_SOURCE_PATH`. -pq-experimental = [] - # Applies a patch (`patches/underscore-wildcards.patch`) to enable -# `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. Same caveats as -# those for `pq-experimental` feature apply. +# `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. This feature is necessary in +# order to compile the bindings for the default branch of boringSSL +# (`deps/boringssl`). Alternatively, a version of boringSSL that implements the +# same feature set can be provided by setting +# `BORING_BSSL{,_FIPS}_SOURCE_PATH`. underscore-wildcards = [] [build-dependencies] diff --git a/boring-sys/build/config.rs b/boring-sys/build/config.rs index f586b9684..25aaabf40 100644 --- a/boring-sys/build/config.rs +++ b/boring-sys/build/config.rs @@ -16,7 +16,6 @@ pub(crate) struct Config { pub(crate) struct Features { pub(crate) fips: bool, - pub(crate) pq_experimental: bool, pub(crate) rpk: bool, pub(crate) underscore_wildcards: bool, } @@ -89,9 +88,7 @@ impl Config { ); } - let features_with_patches_enabled = self.features.rpk - || self.features.pq_experimental - || self.features.underscore_wildcards; + let features_with_patches_enabled = self.features.rpk || self.features.underscore_wildcards; let patches_required = features_with_patches_enabled && !self.env.assume_patched; @@ -106,13 +103,11 @@ impl Config { impl Features { fn from_env() -> Self { let fips = env::var_os("CARGO_FEATURE_FIPS").is_some(); - let pq_experimental = env::var_os("CARGO_FEATURE_PQ_EXPERIMENTAL").is_some(); let rpk = env::var_os("CARGO_FEATURE_RPK").is_some(); let underscore_wildcards = env::var_os("CARGO_FEATURE_UNDERSCORE_WILDCARDS").is_some(); Self { fips, - pq_experimental, rpk, underscore_wildcards, } diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index c95f1cd4e..22e4bccc1 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -434,14 +434,12 @@ fn ensure_patches_applied(config: &Config) -> io::Result<()> { ); return Ok(()); } else if config.env.source_path.is_some() - && (config.features.rpk - || config.features.pq_experimental - || config.features.underscore_wildcards) + && (config.features.rpk || config.features.underscore_wildcards) { panic!( "BORING_BSSL_ASSUME_PATCHED must be set when setting BORING_BSSL_SOURCE_PATH and using any of the following - features: rpk, pq-experimental, underscore-wildcards" + features: rpk, underscore-wildcards" ); } @@ -456,10 +454,8 @@ fn ensure_patches_applied(config: &Config) -> io::Result<()> { run_command(Command::new("git").arg("init").current_dir(src_path))?; } - if config.features.pq_experimental { - println!("cargo:warning=applying experimental post quantum crypto patch to boringssl"); - apply_patch(config, "boring-pq.patch")?; - } + println!("cargo:warning=applying post quantum crypto patch to boringssl"); + apply_patch(config, "boring-pq.patch")?; if config.features.rpk { println!("cargo:warning=applying RPK patch to boringssl"); diff --git a/boring/Cargo.toml b/boring/Cargo.toml index bc9dba220..d76c92945 100644 --- a/boring/Cargo.toml +++ b/boring/Cargo.toml @@ -13,7 +13,7 @@ edition = { workspace = true } rust-version = "1.80" [package.metadata.docs.rs] -features = ["rpk", "pq-experimental", "underscore-wildcards"] +features = ["rpk", "underscore-wildcards"] rustdoc-args = ["--cfg", "docsrs"] [features] @@ -32,16 +32,11 @@ legacy-compat-deprecated = [] # `BORING_BSSL{,_FIPS}_SOURCE_PATH` and `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`. rpk = ["boring-sys/rpk"] -# Applies a patch to the boringSSL source code that enables support for PQ key -# exchange. This feature is necessary in order to compile the bindings for the -# default branch of boringSSL. Alternatively, a version of boringSSL that -# implements the same feature set can be provided by setting -# `BORING_BSSL{,_FIPS}_SOURCE_PATH` and `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`. -pq-experimental = ["boring-sys/pq-experimental"] - -# Applies a patch to enable -# `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. Same caveats as -# those for `pq-experimental` feature apply. +# Applies a patch to enable `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. This +# feature is necessary in order to compile the bindings for the default branch +# of boringSSL. Alternatively, a version of boringSSL that implements the same +# feature set can be provided by setting `BORING_BSSL{,_FIPS}_SOURCE_PATH` and +# `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`. underscore-wildcards = ["boring-sys/underscore-wildcards"] [dependencies] diff --git a/hyper-boring/Cargo.toml b/hyper-boring/Cargo.toml index 25f360fd8..d0f08aab1 100644 --- a/hyper-boring/Cargo.toml +++ b/hyper-boring/Cargo.toml @@ -12,16 +12,13 @@ exclude = ["test/*"] rust-version = "1.80" [package.metadata.docs.rs] -features = ["pq-experimental"] +features = [] rustdoc-args = ["--cfg", "docsrs"] [features] # Use a FIPS-validated version of boringssl. fips = ["boring/fips", "tokio-boring/fips"] -# Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/) -pq-experimental = ["tokio-boring/pq-experimental"] - [dependencies] antidote = { workspace = true } http = { workspace = true } diff --git a/tokio-boring/Cargo.toml b/tokio-boring/Cargo.toml index c57353415..151638647 100644 --- a/tokio-boring/Cargo.toml +++ b/tokio-boring/Cargo.toml @@ -12,16 +12,13 @@ An implementation of SSL streams for Tokio backed by BoringSSL """ [package.metadata.docs.rs] -features = ["rpk", "pq-experimental"] +features = ["rpk"] rustdoc-args = ["--cfg", "docsrs"] [features] # Use a FIPS-validated version of boringssl. fips = ["boring/fips", "boring-sys/fips"] -# Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/) -pq-experimental = ["boring/pq-experimental"] - # Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250) rpk = ["boring/rpk"] From e23d2d16d4366b9ee687e39d37845d5815ae2856 Mon Sep 17 00:00:00 2001 From: Jaap Aarts Date: Tue, 14 Oct 2025 22:31:38 +0200 Subject: [PATCH 031/111] Update main.rs --- boring-sys/build/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 22e4bccc1..41789cee3 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -556,7 +556,7 @@ fn get_cpp_runtime_lib(config: &Config) -> Option { // TODO(rmehra): figure out how to do this for windows if env::var_os("CARGO_CFG_UNIX").is_some() { match env::var("CARGO_CFG_TARGET_OS").unwrap().as_ref() { - "macos" | "ios" => Some("c++".into()), + "macos" | "ios" | "freebsd" => Some("c++".into()), _ => Some("stdc++".into()), } } else { From 410a96752b39f260705cdd13243ab364e589c5f8 Mon Sep 17 00:00:00 2001 From: Bas Westerbaan Date: Thu, 2 Oct 2025 13:16:15 +0200 Subject: [PATCH 032/111] pq patch: enable PQ by default like upstream The big diff is misleading. Applying each patch to the base 478b28ab12f and comparing them, we see: git range-diff 478b28ab12f2001a03261624261fd041f5439706..adcd4022f75953605a9bf9f6a4a45c0b4fd8ed94 478b28ab12f2001a03261624261fd041f5439706..6f1b1e1f451e61cd2bda0922eecaa8387397ac5a 1: adcd4022f ! 1: 6f1b1e1f4 Add additional post-quantum key agreements @@ Commit message This patch adds: - 1. Support for MLKEM768X25519 under the codepoint 0x11ec. The version - of BoringSSL we patch against did not support it yet. + 1. Support for X25519MLKEM768 under the codepoint 0x11ec. The version + of BoringSSL we patch against did not support it yet. Like recent + upstream, enable by default. 2. Supports for P256Kyber768Draft00 under 0xfe32, which we temporarily need for compliance reasons. (Note that this is not the codepoint @@ ssl/extensions.cc: static bool tls1_check_duplicate_extensions(const CBS *cbs) { return true; default: return false; +@@ ssl/extensions.cc: bool ssl_client_hello_get_extension(const SSL_CLIENT_HELLO *client_hello, + } + + static const uint16_t kDefaultGroups[] = { ++ SSL_GROUP_X25519_MLKEM768, + SSL_GROUP_X25519, + SSL_GROUP_SECP256R1, + SSL_GROUP_SECP384R1, ## ssl/ssl_key_share.cc ## @@ --- boring-sys/patches/boring-pq.patch | 2301 ++++++++++++++-------------- 1 file changed, 1128 insertions(+), 1173 deletions(-) diff --git a/boring-sys/patches/boring-pq.patch b/boring-sys/patches/boring-pq.patch index 1f13962a6..405a0185b 100644 --- a/boring-sys/patches/boring-pq.patch +++ b/boring-sys/patches/boring-pq.patch @@ -1,6 +1,6 @@ -From b98d803dbecc9d6848d8cbffa62b5c943fb75f70 Mon Sep 17 00:00:00 2001 +From 6f1b1e1f451e61cd2bda0922eecaa8387397ac5a Mon Sep 17 00:00:00 2001 From: Bas Westerbaan -Date: Fri, 22 Jul 2022 16:43:48 +0200 +Date: Thu, 2 Oct 2025 13:07:05 +0200 Subject: [PATCH] Add additional post-quantum key agreements BoringSSL upstream has supported the temporary post-quantum @@ -13,8 +13,9 @@ and many browsers are expected to switch to it before the end of 2024. This patch adds: -1. Support for MLKEM768X25519 under the codepoint 0x11ec. The version - of BoringSSL we patch against did not support it yet. +1. Support for X25519MLKEM768 under the codepoint 0x11ec. The version + of BoringSSL we patch against did not support it yet. Like recent + upstream, enable by default. 2. Supports for P256Kyber768Draft00 under 0xfe32, which we temporarily need for compliance reasons. (Note that this is not the codepoint @@ -32,39 +33,29 @@ portable reference implementation, so as to support Kyber512. Cf RTG-2076 RTG-2051 RTG-2508 RTG-2707 RTG-2607 RTG-3239 --- - BUILD.generated.bzl | 5 +- - BUILD.generated_tests.bzl | 4 - - CMakeLists.txt | 4 +- - sources.json | 9 +- - src/crypto/CMakeLists.txt | 5 +- - src/crypto/kyber/internal.h | 91 - - src/crypto/kyber/keccak.c | 204 -- - src/crypto/kyber/keccak_tests.txt | 3071 ----------------------------- - src/crypto/kyber/kyber.c | 3011 +++++++++++++++++++++------- - src/crypto/kyber/kyber512.c | 5 + - src/crypto/kyber/kyber768.c | 4 + - src/crypto/kyber/kyber_test.cc | 229 --- - src/crypto/kyber/kyber_tests.txt | 905 --------- - src/crypto/obj/obj_dat.h | 17 +- - src/crypto/obj/obj_mac.num | 4 + - src/crypto/obj/objects.txt | 6 +- - src/include/openssl/kyber.h | 203 +- - src/include/openssl/nid.h | 12 + - src/include/openssl/ssl.h | 4 + - src/sources.cmake | 2 - - src/ssl/extensions.cc | 4 + - src/ssl/ssl_key_share.cc | 525 ++++- - src/ssl/ssl_lib.cc | 2 +- - src/ssl/ssl_test.cc | 29 +- - src/tool/speed.cc | 162 +- - 26 files changed, 3088 insertions(+), 5433 deletions(-) - delete mode 100644 src/crypto/kyber/internal.h - delete mode 100644 src/crypto/kyber/keccak.c - delete mode 100644 src/crypto/kyber/keccak_tests.txt - create mode 100644 src/crypto/kyber/kyber512.c - create mode 100644 src/crypto/kyber/kyber768.c - delete mode 100644 src/crypto/kyber/kyber_test.cc - delete mode 100644 src/crypto/kyber/kyber_tests.txt + crypto/CMakeLists.txt | 3 +- + crypto/kyber/internal.h | 60 - + crypto/kyber/kyber.c | 3013 +++++++++++++++++++++++++++--------- + crypto/kyber/kyber512.c | 5 + + crypto/kyber/kyber768.c | 4 + + crypto/kyber/kyber_test.cc | 184 --- + crypto/obj/obj_dat.h | 17 +- + crypto/obj/obj_mac.num | 4 + + crypto/obj/objects.txt | 6 +- + include/openssl/kyber.h | 203 ++- + include/openssl/nid.h | 12 + + include/openssl/ssl.h | 4 + + sources.cmake | 2 - + ssl/extensions.cc | 5 + + ssl/ssl_key_share.cc | 525 ++++++- + ssl/ssl_lib.cc | 2 +- + ssl/ssl_test.cc | 29 +- + tool/speed.cc | 162 +- + 18 files changed, 3082 insertions(+), 1158 deletions(-) + delete mode 100644 crypto/kyber/internal.h + create mode 100644 crypto/kyber/kyber512.c + create mode 100644 crypto/kyber/kyber768.c + delete mode 100644 crypto/kyber/kyber_test.cc diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index a594b9e9d..ed468237f 100644 @@ -147,7 +138,7 @@ index b11211726..000000000 - -#endif // OPENSSL_HEADER_CRYPTO_KYBER_INTERNAL_H diff --git a/crypto/kyber/kyber.c b/crypto/kyber/kyber.c -index d3ea02090..ccb5b3d9b 100644 +index d3ea02090..74d092907 100644 --- a/crypto/kyber/kyber.c +++ b/crypto/kyber/kyber.c @@ -1,835 +1,2426 @@ @@ -191,17 +182,17 @@ index d3ea02090..ccb5b3d9b 100644 +// implementation or https://github.com/cloudflare/circl/tree/main/pke/kyber +// +// - Option to keep A stored in private key. -+ + +-#include +#ifndef KYBER_K +#error "Don't compile this file direcly" +#endif - #include -+#include - -#include -#include -- ++#include ++#include + -#include -#include +#include @@ -211,27 +202,8 @@ index d3ea02090..ccb5b3d9b 100644 #include "../internal.h" -#include "../keccak/internal.h" -#include "./internal.h" -+ -+#if (KYBER_K == 2) -+#define KYBER_NAMESPACE(s) KYBER512_##s -+#elif (KYBER_K == 3) -+#define KYBER_NAMESPACE(s) KYBER768_##s -+#elif (KYBER_K == 4) -+#define KYBER_NAMESPACE(s) KYBER1024_##s -+#else -+#error "KYBER_K must be in {2,3,4}" -+#endif -+ -+#define public_key KYBER_NAMESPACE(public_key) -+#define private_key KYBER_NAMESPACE(private_key) -+ -+#define generate_key KYBER_NAMESPACE(generate_key) -+#define encap KYBER_NAMESPACE(encap) -+#define decap KYBER_NAMESPACE(decap) -+#define marshal_public_key KYBER_NAMESPACE(marshal_public_key) -+#define parse_public_key KYBER_NAMESPACE(parse_public_key) - - +- +- -// See -// https://pq-crystals.org/kyber/data/kyber-specification-round3-20210804.pdf - @@ -266,10 +238,9 @@ index d3ea02090..ccb5b3d9b 100644 -} matrix; - -// This bit of Python will be referenced in some of the following comments: - // +-// -// p = 3329 -+// params.h - // +-// -// def bitreverse(i): -// ret = 0 -// for n in range(7): @@ -278,9 +249,7 @@ index d3ea02090..ccb5b3d9b 100644 -// ret |= bit -// i >>= 1 -// return ret -+#define KYBER_N 256 -+#define KYBER_Q 3329 - +- -// kNTTRoots = [pow(17, bitreverse(i), p) for i in range(128)] -static const uint16_t kNTTRoots[128] = { - 1, 1729, 2580, 3289, 2642, 630, 1897, 848, 1062, 1919, 193, 797, @@ -294,6 +263,110 @@ index d3ea02090..ccb5b3d9b 100644 - 1584, 2298, 2037, 3220, 375, 2549, 2090, 1645, 1063, 319, 2773, 757, - 2099, 561, 2466, 2594, 2804, 1092, 403, 1026, 1143, 2150, 2775, 886, - 1722, 1212, 1874, 1029, 2110, 2935, 885, 2154, +-}; + +-// kInverseNTTRoots = [pow(17, -bitreverse(i), p) for i in range(128)] +-static const uint16_t kInverseNTTRoots[128] = { +- 1, 1600, 40, 749, 2481, 1432, 2699, 687, 1583, 2760, 69, 543, +- 2532, 3136, 1410, 2267, 2508, 1355, 450, 936, 447, 2794, 1235, 1903, +- 1996, 1089, 3273, 283, 1853, 1990, 882, 3033, 2419, 2102, 219, 855, +- 2681, 1848, 712, 682, 927, 1795, 461, 1891, 2877, 2522, 1894, 1010, +- 1414, 2009, 3296, 464, 2697, 816, 1352, 2679, 1274, 1052, 1025, 2132, +- 1573, 76, 2998, 3040, 1175, 2444, 394, 1219, 2300, 1455, 2117, 1607, +- 2443, 554, 1179, 2186, 2303, 2926, 2237, 525, 735, 863, 2768, 1230, +- 2572, 556, 3010, 2266, 1684, 1239, 780, 2954, 109, 1292, 1031, 1745, +- 2688, 3061, 992, 2596, 941, 892, 1021, 2390, 642, 1868, 2377, 1482, +- 1540, 540, 1678, 1626, 279, 314, 1173, 2573, 3096, 48, 667, 1920, +- 2229, 1041, 2606, 1692, 680, 2746, 568, 3312, +-}; ++#if (KYBER_K == 2) ++#define KYBER_NAMESPACE(s) KYBER512_##s ++#elif (KYBER_K == 3) ++#define KYBER_NAMESPACE(s) KYBER768_##s ++#elif (KYBER_K == 4) ++#define KYBER_NAMESPACE(s) KYBER1024_##s ++#else ++#error "KYBER_K must be in {2,3,4}" ++#endif + +-// kModRoots = [pow(17, 2*bitreverse(i) + 1, p) for i in range(128)] +-static const uint16_t kModRoots[128] = { +- 17, 3312, 2761, 568, 583, 2746, 2649, 680, 1637, 1692, 723, 2606, +- 2288, 1041, 1100, 2229, 1409, 1920, 2662, 667, 3281, 48, 233, 3096, +- 756, 2573, 2156, 1173, 3015, 314, 3050, 279, 1703, 1626, 1651, 1678, +- 2789, 540, 1789, 1540, 1847, 1482, 952, 2377, 1461, 1868, 2687, 642, +- 939, 2390, 2308, 1021, 2437, 892, 2388, 941, 733, 2596, 2337, 992, +- 268, 3061, 641, 2688, 1584, 1745, 2298, 1031, 2037, 1292, 3220, 109, +- 375, 2954, 2549, 780, 2090, 1239, 1645, 1684, 1063, 2266, 319, 3010, +- 2773, 556, 757, 2572, 2099, 1230, 561, 2768, 2466, 863, 2594, 735, +- 2804, 525, 1092, 2237, 403, 2926, 1026, 2303, 1143, 2186, 2150, 1179, +- 2775, 554, 886, 2443, 1722, 1607, 1212, 2117, 1874, 1455, 1029, 2300, +- 2110, 1219, 2935, 394, 885, 2444, 2154, 1175, +-}; ++#define public_key KYBER_NAMESPACE(public_key) ++#define private_key KYBER_NAMESPACE(private_key) + +-// reduce_once reduces 0 <= x < 2*kPrime, mod kPrime. +-static uint16_t reduce_once(uint16_t x) { +- assert(x < 2 * kPrime); +- const uint16_t subtracted = x - kPrime; +- uint16_t mask = 0u - (subtracted >> 15); +- // On Aarch64, omitting a |value_barrier_u16| results in a 2x speedup of Kyber +- // overall and Clang still produces constant-time code using `csel`. On other +- // platforms & compilers on godbolt that we care about, this code also +- // produces constant-time output. +- return (mask & x) | (~mask & subtracted); +-} +- +-// constant time reduce x mod kPrime using Barrett reduction. x must be less +-// than kPrime + 2×kPrime². +-static uint16_t reduce(uint32_t x) { +- assert(x < kPrime + 2u * kPrime * kPrime); +- uint64_t product = (uint64_t)x * kBarrettMultiplier; +- uint32_t quotient = (uint32_t)(product >> kBarrettShift); +- uint32_t remainder = x - quotient * kPrime; +- return reduce_once(remainder); +-} +- +-static void scalar_zero(scalar *out) { OPENSSL_memset(out, 0, sizeof(*out)); } +- +-static void vector_zero(vector *out) { OPENSSL_memset(out, 0, sizeof(*out)); } +- +-// In place number theoretic transform of a given scalar. +-// Note that Kyber's kPrime 3329 does not have a 512th root of unity, so this +-// transform leaves off the last iteration of the usual FFT code, with the 128 +-// relevant roots of unity being stored in |kNTTRoots|. This means the output +-// should be seen as 128 elements in GF(3329^2), with the coefficients of the +-// elements being consecutive entries in |s->c|. +-static void scalar_ntt(scalar *s) { +- int offset = DEGREE; +- // `int` is used here because using `size_t` throughout caused a ~5% slowdown +- // with Clang 14 on Aarch64. +- for (int step = 1; step < DEGREE / 2; step <<= 1) { +- offset >>= 1; +- int k = 0; +- for (int i = 0; i < step; i++) { +- const uint32_t step_root = kNTTRoots[i + step]; +- for (int j = k; j < k + offset; j++) { +- uint16_t odd = reduce(step_root * s->c[j + offset]); +- uint16_t even = s->c[j]; +- s->c[j] = reduce_once(odd + even); +- s->c[j + offset] = reduce_once(even - odd + kPrime); +- } +- k += 2 * offset; ++#define generate_key KYBER_NAMESPACE(generate_key) ++#define encap KYBER_NAMESPACE(encap) ++#define decap KYBER_NAMESPACE(decap) ++#define marshal_public_key KYBER_NAMESPACE(marshal_public_key) ++#define parse_public_key KYBER_NAMESPACE(parse_public_key) ++ ++ ++// ++// params.h ++// ++#define KYBER_N 256 ++#define KYBER_Q 3329 ++ +#define KYBER_SYMBYTES 32 /* size in bytes of hashes, and seeds */ +#define KYBER_SSBYTES 32 /* size in bytes of shared key */ + @@ -675,9 +748,9 @@ index d3ea02090..ccb5b3d9b 100644 + a = (d >> (6*j+0)) & 0x7; + b = (d >> (6*j+3)) & 0x7; + r->coeffs[4*i+j] = a - b; -+ } -+ } -+} + } + } + } +#endif + +static void poly_cbd_eta1(poly *r, const uint8_t buf[KYBER_ETA1*KYBER_N/4]) @@ -690,7 +763,10 @@ index d3ea02090..ccb5b3d9b 100644 +#error "This implementation requires eta1 in {2,3}" +#endif +} -+ + +-static void vector_ntt(vector *a) { +- for (int i = 0; i < RANK; i++) { +- scalar_ntt(&a->v[i]); +static void poly_cbd_eta2(poly *r, const uint8_t buf[KYBER_ETA2*KYBER_N/4]) +{ +#if KYBER_ETA2 == 2 @@ -717,21 +793,8 @@ index d3ea02090..ccb5b3d9b 100644 + 5, 69, 37, 101, 21, 85, 53, 117, 13, 77, 45, 109, 29, 93, 61, 125, + 3, 67, 35, 99, 19, 83, 51, 115, 11, 75, 43, 107, 27, 91, 59, 123, + 7, 71, 39, 103, 23, 87, 55, 119, 15, 79, 47, 111, 31, 95, 63, 127 - }; - --// kInverseNTTRoots = [pow(17, -bitreverse(i), p) for i in range(128)] --static const uint16_t kInverseNTTRoots[128] = { -- 1, 1600, 40, 749, 2481, 1432, 2699, 687, 1583, 2760, 69, 543, -- 2532, 3136, 1410, 2267, 2508, 1355, 450, 936, 447, 2794, 1235, 1903, -- 1996, 1089, 3273, 283, 1853, 1990, 882, 3033, 2419, 2102, 219, 855, -- 2681, 1848, 712, 682, 927, 1795, 461, 1891, 2877, 2522, 1894, 1010, -- 1414, 2009, 3296, 464, 2697, 816, 1352, 2679, 1274, 1052, 1025, 2132, -- 1573, 76, 2998, 3040, 1175, 2444, 394, 1219, 2300, 1455, 2117, 1607, -- 2443, 554, 1179, 2186, 2303, 2926, 2237, 525, 735, 863, 2768, 1230, -- 2572, 556, 3010, 2266, 1684, 1239, 780, 2954, 109, 1292, 1031, 1745, -- 2688, 3061, 992, 2596, 941, 892, 1021, 2390, 642, 1868, 2377, 1482, -- 1540, 540, 1678, 1626, 279, 314, 1173, 2573, 3096, 48, 667, 1920, -- 2229, 1041, 2606, 1692, 680, 2746, 568, 3312, ++}; ++ +void init_ntt() { + unsigned int i; + int16_t tmp[128]; @@ -746,8 +809,8 @@ index d3ea02090..ccb5b3d9b 100644 + zetas[i] -= KYBER_Q; + if(zetas[i] < -KYBER_Q/2) + zetas[i] += KYBER_Q; -+ } -+} + } + } +*/ + +static const int16_t zetas[128] = { @@ -767,21 +830,8 @@ index d3ea02090..ccb5b3d9b 100644 + -1215, -136, 1218, -1335, -874, 220, -1187, -1659, + -1185, -1530, -1278, 794, -1510, -854, -870, 478, + -108, -308, 996, 991, 958, -1460, 1522, 1628 - }; - --// kModRoots = [pow(17, 2*bitreverse(i) + 1, p) for i in range(128)] --static const uint16_t kModRoots[128] = { -- 17, 3312, 2761, 568, 583, 2746, 2649, 680, 1637, 1692, 723, 2606, -- 2288, 1041, 1100, 2229, 1409, 1920, 2662, 667, 3281, 48, 233, 3096, -- 756, 2573, 2156, 1173, 3015, 314, 3050, 279, 1703, 1626, 1651, 1678, -- 2789, 540, 1789, 1540, 1847, 1482, 952, 2377, 1461, 1868, 2687, 642, -- 939, 2390, 2308, 1021, 2437, 892, 2388, 941, 733, 2596, 2337, 992, -- 268, 3061, 641, 2688, 1584, 1745, 2298, 1031, 2037, 1292, 3220, 109, -- 375, 2954, 2549, 780, 2090, 1239, 1645, 1684, 1063, 2266, 319, 3010, -- 2773, 556, 757, 2572, 2099, 1230, 561, 2768, 2466, 863, 2594, 735, -- 2804, 525, 1092, 2237, 403, 2926, 1026, 2303, 1143, 2186, 2150, 1179, -- 2775, 554, 886, 2443, 1722, 1607, 1212, 2117, 1874, 1455, 1029, 2300, -- 2110, 1219, 2935, 394, 885, 2444, 2154, 1175, ++}; ++ +/************************************************* +* Name: fqmul +* @@ -795,7 +845,26 @@ index d3ea02090..ccb5b3d9b 100644 +static int16_t fqmul(int16_t a, int16_t b) { + return montgomery_reduce((int32_t)a*b); +} -+ + +-// In place inverse number theoretic transform of a given scalar, with pairs of +-// entries of s->v being interpreted as elements of GF(3329^2). Just as with the +-// number theoretic transform, this leaves off the first step of the normal iFFT +-// to account for the fact that 3329 does not have a 512th root of unity, using +-// the precomputed 128 roots of unity stored in |kInverseNTTRoots|. +-static void scalar_inverse_ntt(scalar *s) { +- int step = DEGREE / 2; +- // `int` is used here because using `size_t` throughout caused a ~5% slowdown +- // with Clang 14 on Aarch64. +- for (int offset = 2; offset < DEGREE; offset <<= 1) { +- step >>= 1; +- int k = 0; +- for (int i = 0; i < step; i++) { +- uint32_t step_root = kInverseNTTRoots[i + step]; +- for (int j = k; j < k + offset; j++) { +- uint16_t odd = s->c[j + offset]; +- uint16_t even = s->c[j]; +- s->c[j] = reduce_once(odd + even); +- s->c[j + offset] = reduce(step_root * (even - odd + kPrime)); +/************************************************* +* Name: ntt +* @@ -816,11 +885,18 @@ index d3ea02090..ccb5b3d9b 100644 + t = fqmul(zeta, r[j + len]); + r[j + len] = r[j] - t; + r[j] = r[j] + t; -+ } -+ } -+ } -+} -+ + } +- k += 2 * offset; + } + } +- for (int i = 0; i < DEGREE; i++) { +- s->c[i] = reduce(s->c[i] * kInverseDegree); +- } + } + +-static void vector_inverse_ntt(vector *a) { +- for (int i = 0; i < RANK; i++) { +- scalar_inverse_ntt(&a->v[i]); +/************************************************* +* Name: invntt_tomont +* @@ -846,7 +922,7 @@ index d3ea02090..ccb5b3d9b 100644 + r[j + len] = fqmul(zeta, r[j + len]); + } + } -+ } + } + + for(j = 0; j < 256; j++) + r[j] = fqmul(r[j], f); @@ -870,8 +946,11 @@ index d3ea02090..ccb5b3d9b 100644 + r[0] += fqmul(a[0], b[0]); + r[1] = fqmul(a[0], b[1]); + r[1] += fqmul(a[1], b[0]); -+} -+ + } + +-static void scalar_add(scalar *lhs, const scalar *rhs) { +- for (int i = 0; i < DEGREE; i++) { +- lhs->c[i] = reduce_once(lhs->c[i] + rhs->c[i]); +// +// poly.c +// @@ -910,7 +989,7 @@ index d3ea02090..ccb5b3d9b 100644 + r[2] = t[4] | (t[5] << 4); + r[3] = t[6] | (t[7] << 4); + r += 4; -+ } + } +#elif (KYBER_POLYCOMPRESSEDBYTES == 160) + for(i=0;ic[i] = reduce_once(lhs->c[i] - rhs->c[i] + kPrime); +/************************************************* +* Name: poly_decompress +* @@ -972,18 +1054,35 @@ index d3ea02090..ccb5b3d9b 100644 + + for(j=0;j<8;j++) + r->coeffs[8*i+j] = ((uint32_t)(t[j] & 31)*KYBER_Q + 16) >> 5; -+ } + } +#else +#error "KYBER_POLYCOMPRESSEDBYTES needs to be in {128, 160}" +#endif -+} -+ -+/************************************************* -+* Name: poly_tobytes -+* -+* Description: Serialization of a polynomial -+* -+* Arguments: - uint8_t *r: pointer to output byte array + } + +-// Multiplying two scalars in the number theoretically transformed state. Since +-// 3329 does not have a 512th root of unity, this means we have to interpret +-// the 2*ith and (2*i+1)th entries of the scalar as elements of GF(3329)[X]/(X^2 +-// - 17^(2*bitreverse(i)+1)) The value of 17^(2*bitreverse(i)+1) mod 3329 is +-// stored in the precomputed |kModRoots| table. Note that our Barrett transform +-// only allows us to multipy two reduced numbers together, so we need some +-// intermediate reduction steps, even if an uint64_t could hold 3 multiplied +-// numbers. +-static void scalar_mult(scalar *out, const scalar *lhs, const scalar *rhs) { +- for (int i = 0; i < DEGREE / 2; i++) { +- uint32_t real_real = (uint32_t)lhs->c[2 * i] * rhs->c[2 * i]; +- uint32_t img_img = (uint32_t)lhs->c[2 * i + 1] * rhs->c[2 * i + 1]; +- uint32_t real_img = (uint32_t)lhs->c[2 * i] * rhs->c[2 * i + 1]; +- uint32_t img_real = (uint32_t)lhs->c[2 * i + 1] * rhs->c[2 * i]; +- out->c[2 * i] = +- reduce(real_real + (uint32_t)reduce(img_img) * kModRoots[i]); +- out->c[2 * i + 1] = reduce(img_real + real_img); ++/************************************************* ++* Name: poly_tobytes ++* ++* Description: Serialization of a polynomial ++* ++* Arguments: - uint8_t *r: pointer to output byte array +* (needs space for KYBER_POLYBYTES bytes) +* - const poly *a: pointer to input polynomial +**************************************************/ @@ -1001,9 +1100,12 @@ index d3ea02090..ccb5b3d9b 100644 + r[3*i+0] = (t0 >> 0); + r[3*i+1] = (t0 >> 8) | (t1 << 4); + r[3*i+2] = (t1 >> 4); -+ } -+} -+ + } + } + +-static void vector_add(vector *lhs, const vector *rhs) { +- for (int i = 0; i < RANK; i++) { +- scalar_add(&lhs->v[i], &rhs->v[i]); +/************************************************* +* Name: poly_frombytes +* @@ -1020,9 +1122,16 @@ index d3ea02090..ccb5b3d9b 100644 + for(i=0;icoeffs[2*i] = ((a[3*i+0] >> 0) | ((uint16_t)a[3*i+1] << 8)) & 0xFFF; + r->coeffs[2*i+1] = ((a[3*i+1] >> 4) | ((uint16_t)a[3*i+2] << 4)) & 0xFFF; -+ } -+} -+ + } + } + +-static void matrix_mult(vector *out, const matrix *m, const vector *a) { +- vector_zero(out); +- for (int i = 0; i < RANK; i++) { +- for (int j = 0; j < RANK; j++) { +- scalar product; +- scalar_mult(&product, &m->v[i][j], &a->v[j]); +- scalar_add(&out->v[i], &product); +/************************************************* +* Name: poly_frommsg +* @@ -1044,10 +1153,18 @@ index d3ea02090..ccb5b3d9b 100644 + for(j=0;j<8;j++) { + mask = -(int16_t)value_barrier_u32((msg[i] >> j)&1); + r->coeffs[8*i+j] = mask & ((KYBER_Q+1)/2); -+ } -+ } -+} -+ + } + } + } + +-static void matrix_mult_transpose(vector *out, const matrix *m, +- const vector *a) { +- vector_zero(out); +- for (int i = 0; i < RANK; i++) { +- for (int j = 0; j < RANK; j++) { +- scalar product; +- scalar_mult(&product, &m->v[j][i], &a->v[j]); +- scalar_add(&out->v[i], &product); +/************************************************* +* Name: poly_tomsg +* @@ -1071,10 +1188,18 @@ index d3ea02090..ccb5b3d9b 100644 + t >>= 28; + t &= 1; + msg[i] |= t << j; -+ } -+ } -+} -+ + } + } + } + +-static void scalar_inner_product(scalar *out, const vector *lhs, +- const vector *rhs) { +- scalar_zero(out); +- for (int i = 0; i < RANK; i++) { +- scalar product; +- scalar_mult(&product, &lhs->v[i], &rhs->v[i]); +- scalar_add(out, &product); +- } +/************************************************* +* Name: poly_getnoise_eta1 +* @@ -1092,8 +1217,32 @@ index d3ea02090..ccb5b3d9b 100644 + uint8_t buf[KYBER_ETA1*KYBER_N/4]; + prf(buf, sizeof(buf), seed, nonce); + poly_cbd_eta1(r, buf); -+} -+ + } + +-// Algorithm 1 of the Kyber spec. Rejection samples a Keccak stream to get +-// uniformly distributed elements. This is used for matrix expansion and only +-// operates on public inputs. +-static void scalar_from_keccak_vartime(scalar *out, +- struct BORINGSSL_keccak_st *keccak_ctx) { +- assert(keccak_ctx->squeeze_offset == 0); +- assert(keccak_ctx->rate_bytes == 168); +- static_assert(168 % 3 == 0, "block and coefficient boundaries do not align"); +- +- int done = 0; +- while (done < DEGREE) { +- uint8_t block[168]; +- BORINGSSL_keccak_squeeze(keccak_ctx, block, sizeof(block)); +- for (size_t i = 0; i < sizeof(block) && done < DEGREE; i += 3) { +- uint16_t d1 = block[i] + 256 * (block[i + 1] % 16); +- uint16_t d2 = block[i + 1] / 16 + 16 * block[i + 2]; +- if (d1 < kPrime) { +- out->c[done++] = d1; +- } +- if (d2 < kPrime && done < DEGREE) { +- out->c[done++] = d2; +- } +- } +- } +/************************************************* +* Name: poly_getnoise_eta2 +* @@ -1111,8 +1260,34 @@ index d3ea02090..ccb5b3d9b 100644 + uint8_t buf[KYBER_ETA2*KYBER_N/4]; + prf(buf, sizeof(buf), seed, nonce); + poly_cbd_eta2(r, buf); -+} -+ + } + +-// Algorithm 2 of the Kyber spec, with eta fixed to two and the PRF call +-// included. Creates binominally distributed elements by sampling 2*|eta| bits, +-// and setting the coefficient to the count of the first bits minus the count of +-// the second bits, resulting in a centered binomial distribution. Since eta is +-// two this gives -2/2 with a probability of 1/16, -1/1 with probability 1/4, +-// and 0 with probability 3/8. +-static void scalar_centered_binomial_distribution_eta_2_with_prf( +- scalar *out, const uint8_t input[33]) { +- uint8_t entropy[128]; +- static_assert(sizeof(entropy) == 2 * /*kEta=*/2 * DEGREE / 8, ""); +- BORINGSSL_keccak(entropy, sizeof(entropy), input, 33, boringssl_shake256); +- +- for (int i = 0; i < DEGREE; i += 2) { +- uint8_t byte = entropy[i / 2]; +- +- uint16_t value = kPrime; +- value += (byte & 1) + ((byte >> 1) & 1); +- value -= ((byte >> 2) & 1) + ((byte >> 3) & 1); +- out->c[i] = reduce_once(value); +- +- byte >>= 4; +- value = kPrime; +- value += (byte & 1) + ((byte >> 1) & 1); +- value -= ((byte >> 2) & 1) + ((byte >> 3) & 1); +- out->c[i + 1] = reduce_once(value); +- } + +/************************************************* +* Name: poly_ntt @@ -1127,8 +1302,19 @@ index d3ea02090..ccb5b3d9b 100644 +{ + ntt(r->coeffs); + poly_reduce(r); -+} -+ + } + +-// Generates a secret vector by using +-// |scalar_centered_binomial_distribution_eta_2_with_prf|, using the given seed +-// appending and incrementing |counter| for entry of the vector. +-static void vector_generate_secret_eta_2(vector *out, uint8_t *counter, +- const uint8_t seed[32]) { +- uint8_t input[33]; +- OPENSSL_memcpy(input, seed, 32); +- for (int i = 0; i < RANK; i++) { +- input[32] = (*counter)++; +- scalar_centered_binomial_distribution_eta_2_with_prf(&out->v[i], input); +- } +/************************************************* +* Name: poly_invntt_tomont +* @@ -1141,8 +1327,21 @@ index d3ea02090..ccb5b3d9b 100644 +static void poly_invntt_tomont(poly *r) +{ + invntt(r->coeffs); -+} -+ + } + +-// Expands the matrix of a seed for key generation and for encaps-CPA. +-static void matrix_expand(matrix *out, const uint8_t rho[32]) { +- uint8_t input[34]; +- OPENSSL_memcpy(input, rho, 32); +- for (int i = 0; i < RANK; i++) { +- for (int j = 0; j < RANK; j++) { +- input[32] = i; +- input[33] = j; +- struct BORINGSSL_keccak_st keccak_ctx; +- BORINGSSL_keccak_init(&keccak_ctx, boringssl_shake128); +- BORINGSSL_keccak_absorb(&keccak_ctx, input, sizeof(input)); +- scalar_from_keccak_vartime(&out->v[i][j], &keccak_ctx); +- } +/************************************************* +* Name: poly_basemul_montgomery +* @@ -1158,9 +1357,35 @@ index d3ea02090..ccb5b3d9b 100644 + for(i=0;icoeffs[4*i], &a->coeffs[4*i], &b->coeffs[4*i], zetas[64+i]); + basemul(&r->coeffs[4*i+2], &a->coeffs[4*i+2], &b->coeffs[4*i+2], -zetas[64+i]); -+ } -+} -+ + } + } + +-static const uint8_t kMasks[8] = {0x01, 0x03, 0x07, 0x0f, +- 0x1f, 0x3f, 0x7f, 0xff}; +- +-static void scalar_encode(uint8_t *out, const scalar *s, int bits) { +- assert(bits <= (int)sizeof(*s->c) * 8 && bits != 1); +- +- uint8_t out_byte = 0; +- int out_byte_bits = 0; +- +- for (int i = 0; i < DEGREE; i++) { +- uint16_t element = s->c[i]; +- int element_bits_done = 0; +- +- while (element_bits_done < bits) { +- int chunk_bits = bits - element_bits_done; +- int out_bits_remaining = 8 - out_byte_bits; +- if (chunk_bits >= out_bits_remaining) { +- chunk_bits = out_bits_remaining; +- out_byte |= (element & kMasks[chunk_bits - 1]) << out_byte_bits; +- *out = out_byte; +- out++; +- out_byte_bits = 0; +- out_byte = 0; +- } else { +- out_byte |= (element & kMasks[chunk_bits - 1]) << out_byte_bits; +- out_byte_bits += chunk_bits; +/************************************************* +* Name: poly_tomont +* @@ -1255,8 +1480,10 @@ index d3ea02090..ccb5b3d9b 100644 + d0 *= 645084; + d0 >>= 31; + t[k] = d0 & 0x7ff; -+ } -+ + } + +- element_bits_done += chunk_bits; +- element >>= chunk_bits; + r[ 0] = (t[0] >> 0); + r[ 1] = (t[0] >> 8) | (t[1] << 3); + r[ 2] = (t[1] >> 5) | (t[2] << 6); @@ -1269,8 +1496,8 @@ index d3ea02090..ccb5b3d9b 100644 + r[ 9] = (t[6] >> 6) | (t[7] << 5); + r[10] = (t[7] >> 3); + r += 11; -+ } -+ } + } + } +#elif (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 320)) + uint16_t t[4]; + for(i=0;i>= 32; + t[k] = d0 & 0x3ff; + } -+ + +- if (out_byte_bits > 0) { +- *out = out_byte; + r[0] = (t[0] >> 0); + r[1] = (t[0] >> 8) | (t[1] << 2); + r[2] = (t[1] >> 6) | (t[2] << 4); @@ -1293,12 +1522,18 @@ index d3ea02090..ccb5b3d9b 100644 + r[4] = (t[3] >> 2); + r += 5; + } -+ } + } +#else +#error "KYBER_POLYVECCOMPRESSEDBYTES needs to be in {320*KYBER_K, 352*KYBER_K}" +#endif -+} -+ + } + +-// scalar_encode_1 is |scalar_encode| specialised for |bits| == 1. +-static void scalar_encode_1(uint8_t out[32], const scalar *s) { +- for (int i = 0; i < DEGREE; i += 8) { +- uint8_t out_byte = 0; +- for (int j = 0; j < 8; j++) { +- out_byte |= (s->c[i + j] & 1) << j; +/************************************************* +* Name: polyvec_decompress +* @@ -1343,13 +1578,22 @@ index d3ea02090..ccb5b3d9b 100644 + + for(k=0;k<4;k++) + r->vec[i].coeffs[4*j+k] = ((uint32_t)(t[k] & 0x3FF)*KYBER_Q + 512) >> 10; -+ } -+ } + } +- *out = out_byte; +- out++; + } +#else +#error "KYBER_POLYVECCOMPRESSEDBYTES needs to be in {320*KYBER_K, 352*KYBER_K}" +#endif -+} -+ + } + +-// Encodes an entire vector into 32*|RANK|*|bits| bytes. Note that since 256 +-// (DEGREE) is divisible by 8, the individual vector entries will always fill a +-// whole number of bytes, so we do not need to worry about bit packing here. +-static void vector_encode(uint8_t *out, const vector *a, int bits) { +- for (int i = 0; i < RANK; i++) { +- scalar_encode(out + i * bits * DEGREE / 8, &a->v[i], bits); +- } +/************************************************* +* Name: polyvec_tobytes +* @@ -1364,8 +1608,13 @@ index d3ea02090..ccb5b3d9b 100644 + unsigned int i; + for(i=0;ivec[i]); -+} -+ + } + +-// scalar_decode parses |DEGREE * bits| bits from |in| into |DEGREE| values in +-// |out|. It returns one on success and zero if any parsed value is >= +-// |kPrime|. +-static int scalar_decode(scalar *out, const uint8_t *in, int bits) { +- assert(bits <= (int)sizeof(*out->c) * 8 && bits != 1); +/************************************************* +* Name: polyvec_frombytes +* @@ -1382,7 +1631,9 @@ index d3ea02090..ccb5b3d9b 100644 + for(i=0;ivec[i], a+i*KYBER_POLYBYTES); +} -+ + +- uint8_t in_byte = 0; +- int in_byte_bits_left = 0; +/************************************************* +* Name: polyvec_ntt +* @@ -1396,7 +1647,10 @@ index d3ea02090..ccb5b3d9b 100644 + for(i=0;ivec[i]); +} -+ + +- for (int i = 0; i < DEGREE; i++) { +- uint16_t element = 0; +- int element_bits_done = 0; +/************************************************* +* Name: polyvec_invntt_tomont +* @@ -1411,7 +1665,13 @@ index d3ea02090..ccb5b3d9b 100644 + for(i=0;ivec[i]); +} -+ + +- while (element_bits_done < bits) { +- if (in_byte_bits_left == 0) { +- in_byte = *in; +- in++; +- in_byte_bits_left = 8; +- } +/************************************************* +* Name: polyvec_basemul_acc_montgomery +* @@ -1432,10 +1692,17 @@ index d3ea02090..ccb5b3d9b 100644 + poly_basemul_montgomery(&t, &a->vec[i], &b->vec[i]); + poly_add(r, r, &t); + } -+ + +- int chunk_bits = bits - element_bits_done; +- if (chunk_bits > in_byte_bits_left) { +- chunk_bits = in_byte_bits_left; +- } + poly_reduce(r); +} -+ + +- element |= (in_byte & kMasks[chunk_bits - 1]) << element_bits_done; +- in_byte_bits_left -= chunk_bits; +- in_byte >>= chunk_bits; +/************************************************* +* Name: polyvec_reduce +* @@ -1451,7 +1718,9 @@ index d3ea02090..ccb5b3d9b 100644 + for(i=0;ivec[i]); +} -+ + +- element_bits_done += chunk_bits; +- } +/************************************************* +* Name: polyvec_add +* @@ -1467,7 +1736,12 @@ index d3ea02090..ccb5b3d9b 100644 + for(i=0;ivec[i], &a->vec[i], &b->vec[i]); +} -+ + +- if (element >= kPrime) { +- return 0; +- } +- out->c[i] = element; +- } +// +// indcpa.c +// @@ -1516,12 +1790,21 @@ index d3ea02090..ccb5b3d9b 100644 + + if(verify(repacked, packedpk, KYBER_POLYVECBYTES) != 0) + return 0; -+ + + for(i=0;ic[i + j] = in_byte & 1; +- in_byte >>= 1; +- } +/************************************************* +* Name: pack_sk +* @@ -1612,11 +1895,17 @@ index d3ea02090..ccb5b3d9b 100644 + r[ctr++] = val0; + if(ctr < len && val1 < KYBER_Q) + r[ctr++] = val1; -+ } + } + + return ctr; -+} -+ + } + +-// Decodes 32*|RANK|*|bits| bytes from |in| into |out|. It returns one on +-// success or zero if any parsed value is >= |kPrime|. +-static int vector_decode(vector *out, const uint8_t *in, int bits) { +- for (int i = 0; i < RANK; i++) { +- if (!scalar_decode(&out->v[i], in + i * bits * DEGREE / 8, bits)) { +- return 0; +#define gen_a(A,B) gen_matrix(A,B,0) +#define gen_at(A,B) gen_matrix(A,B,1) + @@ -1660,10 +1949,53 @@ index d3ea02090..ccb5b3d9b 100644 + buflen = off + XOF_BLOCKBYTES; + ctr += rej_uniform(a[i].vec[j].coeffs + ctr, KYBER_N - ctr, buf, buflen); + } -+ } -+ } -+} -+ + } + } +- return 1; + } + +-// Compresses (lossily) an input |x| mod 3329 into |bits| many bits by grouping +-// numbers close to each other together. The formula used is +-// round(2^|bits|/kPrime*x) mod 2^|bits|. +-// Uses Barrett reduction to achieve constant time. Since we need both the +-// remainder (for rounding) and the quotient (as the result), we cannot use +-// |reduce| here, but need to do the Barrett reduction directly. +-static uint16_t compress(uint16_t x, int bits) { +- uint32_t shifted = (uint32_t)x << bits; +- uint64_t product = (uint64_t)shifted * kBarrettMultiplier; +- uint32_t quotient = (uint32_t)(product >> kBarrettShift); +- uint32_t remainder = shifted - quotient * kPrime; +- +- // Adjust the quotient to round correctly: +- // 0 <= remainder <= kHalfPrime round to 0 +- // kHalfPrime < remainder <= kPrime + kHalfPrime round to 1 +- // kPrime + kHalfPrime < remainder < 2 * kPrime round to 2 +- assert(remainder < 2u * kPrime); +- quotient += 1 & constant_time_lt_w(kHalfPrime, remainder); +- quotient += 1 & constant_time_lt_w(kPrime + kHalfPrime, remainder); +- return quotient & ((1 << bits) - 1); +-} +- +-// Decompresses |x| by using an equi-distant representative. The formula is +-// round(kPrime/2^|bits|*x). Note that 2^|bits| being the divisor allows us to +-// implement this logic using only bit operations. +-static uint16_t decompress(uint16_t x, int bits) { +- uint32_t product = (uint32_t)x * kPrime; +- uint32_t power = 1 << bits; +- // This is |product| % power, since |power| is a power of 2. +- uint32_t remainder = product & (power - 1); +- // This is |product| / power, since |power| is a power of 2. +- uint32_t lower = product >> bits; +- // The rounding logic works since the first half of numbers mod |power| have a +- // 0 as first bit, and the second half has a 1 as first bit, since |power| is +- // a power of 2. As a 12 bit number, |remainder| is always positive, so we +- // will shift in 0s for a right shift. +- return lower + (remainder >> (bits - 1)); +-} +- +-static void scalar_compress(scalar *s, int bits) { +- for (int i = 0; i < DEGREE; i++) { +- s->c[i] = compress(s->c[i], bits); +/************************************************* +* Name: indcpa_keypair +* @@ -1703,15 +2035,19 @@ index d3ea02090..ccb5b3d9b 100644 + for(i=0;ic[i] = decompress(s->c[i], bits); +- } +/************************************************* +* Name: indcpa_enc +* @@ -1770,8 +2106,12 @@ index d3ea02090..ccb5b3d9b 100644 + + pack_ciphertext(c, &b, &v); + return 1; -+} -+ + } + +-static void vector_compress(vector *a, int bits) { +- for (int i = 0; i < RANK; i++) { +- scalar_compress(&a->v[i], bits); +- } +/************************************************* +* Name: indcpa_dec +* @@ -1803,8 +2143,12 @@ index d3ea02090..ccb5b3d9b 100644 + poly_reduce(&mp); + + poly_tomsg(m, &mp); -+} -+ + } + +-static void vector_decompress(vector *a, int bits) { +- for (int i = 0; i < RANK; i++) { +- scalar_decompress(&a->v[i], bits); +- } +// +// fips202.c +// @@ -1834,8 +2178,13 @@ index d3ea02090..ccb5b3d9b 100644 + r |= (uint64_t)x[i] << 8*i; + + return r; -+} -+ + } + +-struct public_key { +- vector t; +- uint8_t rho[32]; +- uint8_t public_key_hash[32]; +- matrix m; +/************************************************* +* Name: store64 +* @@ -1879,16 +2228,13 @@ index d3ea02090..ccb5b3d9b 100644 + (uint64_t)0x8000000080008008ULL }; --// reduce_once reduces 0 <= x < 2*kPrime, mod kPrime. --static uint16_t reduce_once(uint16_t x) { -- assert(x < 2 * kPrime); -- const uint16_t subtracted = x - kPrime; -- uint16_t mask = 0u - (subtracted >> 15); -- // On Aarch64, omitting a |value_barrier_u16| results in a 2x speedup of Kyber -- // overall and Clang still produces constant-time code using `csel`. On other -- // platforms & compilers on godbolt that we care about, this code also -- // produces constant-time output. -- return (mask & x) | (~mask & subtracted); +-static struct public_key *public_key_from_external( +- const struct KYBER_public_key *external) { +- static_assert(sizeof(struct KYBER_public_key) >= sizeof(struct public_key), +- "Kyber public key is too small"); +- static_assert(alignof(struct KYBER_public_key) >= alignof(struct public_key), +- "Kyber public key align incorrect"); +- return (struct public_key *)external; +/************************************************* +* Name: KeccakF1600_StatePermute +* @@ -2160,17 +2506,36 @@ index d3ea02090..ccb5b3d9b 100644 + state[24] = Asu; } --// constant time reduce x mod kPrime using Barrett reduction. x must be less --// than kPrime + 2×kPrime². --static uint16_t reduce(uint32_t x) { -- assert(x < kPrime + 2u * kPrime * kPrime); -- uint64_t product = (uint64_t)x * kBarrettMultiplier; -- uint32_t quotient = (uint32_t)(product >> kBarrettShift); -- uint32_t remainder = x - quotient * kPrime; -- return reduce_once(remainder); --} +-struct private_key { +- struct public_key pub; +- vector s; +- uint8_t fo_failure_secret[32]; +-}; --static void scalar_zero(scalar *out) { OPENSSL_memset(out, 0, sizeof(*out)); } +-static struct private_key *private_key_from_external( +- const struct KYBER_private_key *external) { +- static_assert(sizeof(struct KYBER_private_key) >= sizeof(struct private_key), +- "Kyber private key too small"); +- static_assert( +- alignof(struct KYBER_private_key) >= alignof(struct private_key), +- "Kyber private key align incorrect"); +- return (struct private_key *)external; +-} +- +-// Calls |KYBER_generate_key_external_entropy| with random bytes from +-// |RAND_bytes|. +-void KYBER_generate_key(uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], +- struct KYBER_private_key *out_private_key) { +- uint8_t entropy[KYBER_GENERATE_KEY_ENTROPY]; +- RAND_bytes(entropy, sizeof(entropy)); +- KYBER_generate_key_external_entropy(out_encoded_public_key, out_private_key, +- entropy); +-} +- +-static int kyber_marshal_public_key(CBB *out, const struct public_key *pub) { +- uint8_t *vector_output; +- if (!CBB_add_space(out, &vector_output, kEncodedVectorSize)) { +- return 0; +/************************************************* +* Name: keccak_squeeze +* @@ -2193,41 +2558,20 @@ index d3ea02090..ccb5b3d9b 100644 + unsigned int r) +{ + unsigned int i; - --static void vector_zero(vector *out) { OPENSSL_memset(out, 0, sizeof(*out)); } -- --// In place number theoretic transform of a given scalar. --// Note that Kyber's kPrime 3329 does not have a 512th root of unity, so this --// transform leaves off the last iteration of the usual FFT code, with the 128 --// relevant roots of unity being stored in |kNTTRoots|. This means the output --// should be seen as 128 elements in GF(3329^2), with the coefficients of the --// elements being consecutive entries in |s->c|. --static void scalar_ntt(scalar *s) { -- int offset = DEGREE; -- // `int` is used here because using `size_t` throughout caused a ~5% slowdown -- // with Clang 14 on Aarch64. -- for (int step = 1; step < DEGREE / 2; step <<= 1) { -- offset >>= 1; -- int k = 0; -- for (int i = 0; i < step; i++) { -- const uint32_t step_root = kNTTRoots[i + step]; -- for (int j = k; j < k + offset; j++) { -- uint16_t odd = reduce(step_root * s->c[j + offset]); -- uint16_t even = s->c[j]; -- s->c[j] = reduce_once(odd + even); -- s->c[j + offset] = reduce_once(even - odd + kPrime); -- } -- k += 2 * offset; ++ + while(outlen) { + if(pos == r) { + KeccakF1600_StatePermute(s); + pos = 0; - } ++ } + for(i=pos;i < r && i < pos+outlen; i++) + *out++ = s[i/8] >> 8*(i%8); + outlen -= i-pos; + pos = i; -+ } + } +- vector_encode(vector_output, &pub->t, kLog2Prime); +- if (!CBB_add_bytes(out, pub->rho, sizeof(pub->rho))) { +- return 0; + + return pos; +} @@ -2259,7 +2603,8 @@ index d3ea02090..ccb5b3d9b 100644 + inlen -= r-pos; + KeccakF1600_StatePermute(s); + pos = 0; -+ } + } +- return 1; + + for(i=pos;ipub.rho, hashed, sizeof(priv->pub.rho)); +- matrix_expand(&priv->pub.m, rho); +- uint8_t counter = 0; +- vector_generate_secret_eta_2(&priv->s, &counter, sigma); +- vector_ntt(&priv->s); +- vector error; +- vector_generate_secret_eta_2(&error, &counter, sigma); +- vector_ntt(&error); +- matrix_mult_transpose(&priv->pub.t, &priv->pub.m, &priv->s); +- vector_add(&priv->pub.t, &error); +- +- CBB cbb; +- CBB_init_fixed(&cbb, out_encoded_public_key, KYBER_PUBLIC_KEY_BYTES); +- if (!kyber_marshal_public_key(&cbb, &priv->pub)) { +- abort(); ++ ++/************************************************* ++* Name: keccak_absorb_once ++* ++* Description: Absorb step of Keccak; ++* non-incremental, starts by zeroeing the state. ++* ++* Arguments: - uint64_t *s: pointer to (uninitialized) output Keccak state ++* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128) ++* - const uint8_t *in: pointer to input to be absorbed into s ++* - size_t inlen: length of input in bytes ++* - uint8_t p: domain-separation byte for different Keccak-derived functions +**************************************************/ +static void keccak_absorb_once(uint64_t s[25], + unsigned int r, @@ -2313,8 +2685,138 @@ index d3ea02090..ccb5b3d9b 100644 + in += r; + inlen -= r; + KeccakF1600_StatePermute(s); -+ } -+ + } + +- BORINGSSL_keccak(priv->pub.public_key_hash, sizeof(priv->pub.public_key_hash), +- out_encoded_public_key, KYBER_PUBLIC_KEY_BYTES, +- boringssl_sha3_256); +- OPENSSL_memcpy(priv->fo_failure_secret, entropy + 32, 32); +-} +- +-void KYBER_public_from_private(struct KYBER_public_key *out_public_key, +- const struct KYBER_private_key *private_key) { +- struct public_key *const pub = public_key_from_external(out_public_key); +- const struct private_key *const priv = private_key_from_external(private_key); +- *pub = priv->pub; +-} +- +-// Algorithm 5 of the Kyber spec. Encrypts a message with given randomness to +-// the ciphertext in |out|. Without applying the Fujisaki-Okamoto transform this +-// would not result in a CCA secure scheme, since lattice schemes are vulnerable +-// to decryption failure oracles. +-static void encrypt_cpa(uint8_t out[KYBER_CIPHERTEXT_BYTES], +- const struct public_key *pub, const uint8_t message[32], +- const uint8_t randomness[32]) { +- uint8_t counter = 0; +- vector secret; +- vector_generate_secret_eta_2(&secret, &counter, randomness); +- vector_ntt(&secret); +- vector error; +- vector_generate_secret_eta_2(&error, &counter, randomness); +- uint8_t input[33]; +- OPENSSL_memcpy(input, randomness, 32); +- input[32] = counter; +- scalar scalar_error; +- scalar_centered_binomial_distribution_eta_2_with_prf(&scalar_error, input); +- vector u; +- matrix_mult(&u, &pub->m, &secret); +- vector_inverse_ntt(&u); +- vector_add(&u, &error); +- scalar v; +- scalar_inner_product(&v, &pub->t, &secret); +- scalar_inverse_ntt(&v); +- scalar_add(&v, &scalar_error); +- scalar expanded_message; +- scalar_decode_1(&expanded_message, message); +- scalar_decompress(&expanded_message, 1); +- scalar_add(&v, &expanded_message); +- vector_compress(&u, kDU); +- vector_encode(out, &u, kDU); +- scalar_compress(&v, kDV); +- scalar_encode(out + kCompressedVectorSize, &v, kDV); +-} +- +-// Calls KYBER_encap_external_entropy| with random bytes from |RAND_bytes| +-void KYBER_encap(uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], +- uint8_t *out_shared_secret, size_t out_shared_secret_len, +- const struct KYBER_public_key *public_key) { +- uint8_t entropy[KYBER_ENCAP_ENTROPY]; +- RAND_bytes(entropy, KYBER_ENCAP_ENTROPY); +- KYBER_encap_external_entropy(out_ciphertext, out_shared_secret, +- out_shared_secret_len, public_key, entropy); +-} +- +-// Algorithm 8 of the Kyber spec, safe for line 2 of the spec. The spec there +-// hashes the output of the system's random number generator, since the FO +-// transform will reveal it to the decrypting party. There is no reason to do +-// this when a secure random number generator is used. When an insecure random +-// number generator is used, the caller should switch to a secure one before +-// calling this method. +-void KYBER_encap_external_entropy( +- uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], uint8_t *out_shared_secret, +- size_t out_shared_secret_len, const struct KYBER_public_key *public_key, +- const uint8_t entropy[KYBER_ENCAP_ENTROPY]) { +- const struct public_key *pub = public_key_from_external(public_key); +- uint8_t input[64]; +- OPENSSL_memcpy(input, entropy, KYBER_ENCAP_ENTROPY); +- OPENSSL_memcpy(input + KYBER_ENCAP_ENTROPY, pub->public_key_hash, +- sizeof(input) - KYBER_ENCAP_ENTROPY); +- uint8_t prekey_and_randomness[64]; +- BORINGSSL_keccak(prekey_and_randomness, sizeof(prekey_and_randomness), input, +- sizeof(input), boringssl_sha3_512); +- encrypt_cpa(out_ciphertext, pub, entropy, prekey_and_randomness + 32); +- BORINGSSL_keccak(prekey_and_randomness + 32, 32, out_ciphertext, +- KYBER_CIPHERTEXT_BYTES, boringssl_sha3_256); +- BORINGSSL_keccak(out_shared_secret, out_shared_secret_len, +- prekey_and_randomness, sizeof(prekey_and_randomness), +- boringssl_shake256); +-} +- +-// Algorithm 6 of the Kyber spec. +-static void decrypt_cpa(uint8_t out[32], const struct private_key *priv, +- const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES]) { +- vector u; +- vector_decode(&u, ciphertext, kDU); +- vector_decompress(&u, kDU); +- vector_ntt(&u); +- scalar v; +- scalar_decode(&v, ciphertext + kCompressedVectorSize, kDV); +- scalar_decompress(&v, kDV); +- scalar mask; +- scalar_inner_product(&mask, &priv->s, &u); +- scalar_inverse_ntt(&mask); +- scalar_sub(&v, &mask); +- scalar_compress(&v, 1); +- scalar_encode_1(out, &v); +-} +- +-// Algorithm 9 of the Kyber spec, performing the FO transform by running +-// encrypt_cpa on the decrypted message. The spec does not allow the decryption +-// failure to be passed on to the caller, and instead returns a result that is +-// deterministic but unpredictable to anyone without knowledge of the private +-// key. +-void KYBER_decap(uint8_t *out_shared_secret, size_t out_shared_secret_len, +- const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES], +- const struct KYBER_private_key *private_key) { +- const struct private_key *priv = private_key_from_external(private_key); +- uint8_t decrypted[64]; +- decrypt_cpa(decrypted, priv, ciphertext); +- OPENSSL_memcpy(decrypted + 32, priv->pub.public_key_hash, +- sizeof(decrypted) - 32); +- uint8_t prekey_and_randomness[64]; +- BORINGSSL_keccak(prekey_and_randomness, sizeof(prekey_and_randomness), +- decrypted, sizeof(decrypted), boringssl_sha3_512); +- uint8_t expected_ciphertext[KYBER_CIPHERTEXT_BYTES]; +- encrypt_cpa(expected_ciphertext, &priv->pub, decrypted, +- prekey_and_randomness + 32); +- uint8_t mask = +- constant_time_eq_int_8(CRYPTO_memcmp(ciphertext, expected_ciphertext, +- sizeof(expected_ciphertext)), +- 0); +- uint8_t input[64]; +- for (int i = 0; i < 32; i++) { +- input[i] = constant_time_select_8(mask, prekey_and_randomness[i], +- priv->fo_failure_secret[i]); + for(i=0;iv[i]); -- } +-int KYBER_marshal_public_key(CBB *out, +- const struct KYBER_public_key *public_key) { +- return kyber_marshal_public_key(out, public_key_from_external(public_key)); + +/************************************************* +* Name: shake128_absorb_once @@ -2371,32 +2876,17 @@ index d3ea02090..ccb5b3d9b 100644 + state->pos = SHAKE128_RATE; } --// In place inverse number theoretic transform of a given scalar, with pairs of --// entries of s->v being interpreted as elements of GF(3329^2). Just as with the --// number theoretic transform, this leaves off the first step of the normal iFFT --// to account for the fact that 3329 does not have a 512th root of unity, using --// the precomputed 128 roots of unity stored in |kInverseNTTRoots|. --static void scalar_inverse_ntt(scalar *s) { -- int step = DEGREE / 2; -- // `int` is used here because using `size_t` throughout caused a ~5% slowdown -- // with Clang 14 on Aarch64. -- for (int offset = 2; offset < DEGREE; offset <<= 1) { -- step >>= 1; -- int k = 0; -- for (int i = 0; i < step; i++) { -- uint32_t step_root = kInverseNTTRoots[i + step]; -- for (int j = k; j < k + offset; j++) { -- uint16_t odd = s->c[j + offset]; -- uint16_t even = s->c[j]; -- s->c[j] = reduce_once(odd + even); -- s->c[j + offset] = reduce(step_root * (even - odd + kPrime)); -- } -- k += 2 * offset; -- } -- } -- for (int i = 0; i < DEGREE; i++) { -- s->c[i] = reduce(s->c[i] * kInverseDegree); +-// kyber_parse_public_key_no_hash parses |in| into |pub| but doesn't calculate +-// the value of |pub->public_key_hash|. +-static int kyber_parse_public_key_no_hash(struct public_key *pub, CBS *in) { +- CBS t_bytes; +- if (!CBS_get_bytes(in, &t_bytes, kEncodedVectorSize) || +- !vector_decode(&pub->t, CBS_data(&t_bytes), kLog2Prime) || +- !CBS_copy_bytes(in, pub->rho, sizeof(pub->rho))) { +- return 0; - } +- matrix_expand(&pub->m, pub->rho); +- return 1; +/************************************************* +* Name: shake128_squeezeblocks +* @@ -2412,12 +2902,8 @@ index d3ea02090..ccb5b3d9b 100644 +static void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) +{ + keccak_squeezeblocks(out, nblocks, state->s, SHAKE128_RATE); - } - --static void vector_inverse_ntt(vector *a) { -- for (int i = 0; i < RANK; i++) { -- scalar_inverse_ntt(&a->v[i]); -- } ++} ++ +/************************************************* +* Name: shake256_squeeze +* @@ -2431,12 +2917,8 @@ index d3ea02090..ccb5b3d9b 100644 +static void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state) +{ + state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE256_RATE); - } - --static void scalar_add(scalar *lhs, const scalar *rhs) { -- for (int i = 0; i < DEGREE; i++) { -- lhs->c[i] = reduce_once(lhs->c[i] + rhs->c[i]); -- } ++} ++ +/************************************************* +* Name: shake256_absorb_once +* @@ -2450,12 +2932,8 @@ index d3ea02090..ccb5b3d9b 100644 +{ + keccak_absorb_once(state->s, SHAKE256_RATE, in, inlen, 0x1F); + state->pos = SHAKE256_RATE; - } - --static void scalar_sub(scalar *lhs, const scalar *rhs) { -- for (int i = 0; i < DEGREE; i++) { -- lhs->c[i] = reduce_once(lhs->c[i] - rhs->c[i] + kPrime); -- } ++} ++ +/************************************************* +* Name: shake256_squeezeblocks +* @@ -2471,26 +2949,8 @@ index d3ea02090..ccb5b3d9b 100644 +static void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) +{ + keccak_squeezeblocks(out, nblocks, state->s, SHAKE256_RATE); - } - --// Multiplying two scalars in the number theoretically transformed state. Since --// 3329 does not have a 512th root of unity, this means we have to interpret --// the 2*ith and (2*i+1)th entries of the scalar as elements of GF(3329)[X]/(X^2 --// - 17^(2*bitreverse(i)+1)) The value of 17^(2*bitreverse(i)+1) mod 3329 is --// stored in the precomputed |kModRoots| table. Note that our Barrett transform --// only allows us to multipy two reduced numbers together, so we need some --// intermediate reduction steps, even if an uint64_t could hold 3 multiplied --// numbers. --static void scalar_mult(scalar *out, const scalar *lhs, const scalar *rhs) { -- for (int i = 0; i < DEGREE / 2; i++) { -- uint32_t real_real = (uint32_t)lhs->c[2 * i] * rhs->c[2 * i]; -- uint32_t img_img = (uint32_t)lhs->c[2 * i + 1] * rhs->c[2 * i + 1]; -- uint32_t real_img = (uint32_t)lhs->c[2 * i] * rhs->c[2 * i + 1]; -- uint32_t img_real = (uint32_t)lhs->c[2 * i + 1] * rhs->c[2 * i]; -- out->c[2 * i] = -- reduce(real_real + (uint32_t)reduce(img_img) * kModRoots[i]); -- out->c[2 * i + 1] = reduce(img_real + real_img); -- } ++} ++ +/************************************************* +* Name: shake256_absorb +* @@ -2503,12 +2963,8 @@ index d3ea02090..ccb5b3d9b 100644 +static void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen) +{ + state->pos = keccak_absorb(state->s, state->pos, SHAKE256_RATE, in, inlen); - } - --static void vector_add(vector *lhs, const vector *rhs) { -- for (int i = 0; i < RANK; i++) { -- scalar_add(&lhs->v[i], &rhs->v[i]); -- } ++} ++ +/************************************************* +* Name: shake256_finalize +* @@ -2520,17 +2976,8 @@ index d3ea02090..ccb5b3d9b 100644 +{ + keccak_finalize(state->s, state->pos, SHAKE256_RATE, 0x1F); + state->pos = SHAKE256_RATE; - } - --static void matrix_mult(vector *out, const matrix *m, const vector *a) { -- vector_zero(out); -- for (int i = 0; i < RANK; i++) { -- for (int j = 0; j < RANK; j++) { -- scalar product; -- scalar_mult(&product, &m->v[i][j], &a->v[j]); -- scalar_add(&out->v[i], &product); -- } -- } ++} ++ +/************************************************* +* Name: keccak_init +* @@ -2543,18 +2990,8 @@ index d3ea02090..ccb5b3d9b 100644 + unsigned int i; + for(i=0;i<25;i++) + s[i] = 0; - } - --static void matrix_mult_transpose(vector *out, const matrix *m, -- const vector *a) { -- vector_zero(out); -- for (int i = 0; i < RANK; i++) { -- for (int j = 0; j < RANK; j++) { -- scalar product; -- scalar_mult(&product, &m->v[j][i], &a->v[j]); -- scalar_add(&out->v[i], &product); -- } -- } ++} ++ +/************************************************* +* Name: shake256_init +* @@ -2566,16 +3003,8 @@ index d3ea02090..ccb5b3d9b 100644 +{ + keccak_init(state->s); + state->pos = 0; - } - --static void scalar_inner_product(scalar *out, const vector *lhs, -- const vector *rhs) { -- scalar_zero(out); -- for (int i = 0; i < RANK; i++) { -- scalar product; -- scalar_mult(&product, &lhs->v[i], &rhs->v[i]); -- scalar_add(out, &product); -- } ++} ++ + +/************************************************* +* Name: shake256 @@ -2598,16 +3027,8 @@ index d3ea02090..ccb5b3d9b 100644 + outlen -= nblocks*SHAKE256_RATE; + out += nblocks*SHAKE256_RATE; + shake256_squeeze(out, outlen, &state); - } - --// Algorithm 1 of the Kyber spec. Rejection samples a Keccak stream to get --// uniformly distributed elements. This is used for matrix expansion and only --// operates on public inputs. --static void scalar_from_keccak_vartime(scalar *out, -- struct BORINGSSL_keccak_st *keccak_ctx) { -- assert(keccak_ctx->squeeze_offset == 0); -- assert(keccak_ctx->rate_bytes == 168); -- static_assert(168 % 3 == 0, "block and coefficient boundaries do not align"); ++} ++ +/************************************************* +* Name: sha3_256 +* @@ -2621,39 +3042,13 @@ index d3ea02090..ccb5b3d9b 100644 +{ + unsigned int i; + uint64_t s[25]; - -- int done = 0; -- while (done < DEGREE) { -- uint8_t block[168]; -- BORINGSSL_keccak_squeeze(keccak_ctx, block, sizeof(block)); -- for (size_t i = 0; i < sizeof(block) && done < DEGREE; i += 3) { -- uint16_t d1 = block[i] + 256 * (block[i + 1] % 16); -- uint16_t d2 = block[i + 1] / 16 + 16 * block[i + 2]; -- if (d1 < kPrime) { -- out->c[done++] = d1; -- } -- if (d2 < kPrime && done < DEGREE) { -- out->c[done++] = d2; -- } -- } -- } ++ + keccak_absorb_once(s, SHA3_256_RATE, in, inlen, 0x06); + KeccakF1600_StatePermute(s); + for(i=0;i<4;i++) + store64(h+8*i,s[i]); - } - --// Algorithm 2 of the Kyber spec, with eta fixed to two and the PRF call --// included. Creates binominally distributed elements by sampling 2*|eta| bits, --// and setting the coefficient to the count of the first bits minus the count of --// the second bits, resulting in a centered binomial distribution. Since eta is --// two this gives -2/2 with a probability of 1/16, -1/1 with probability 1/4, --// and 0 with probability 3/8. --static void scalar_centered_binomial_distribution_eta_2_with_prf( -- scalar *out, const uint8_t input[33]) { -- uint8_t entropy[128]; -- static_assert(sizeof(entropy) == 2 * /*kEta=*/2 * DEGREE / 8, ""); -- BORINGSSL_keccak(entropy, sizeof(entropy), input, 33, boringssl_shake256); ++} ++ +/************************************************* +* Name: sha3_512 +* @@ -2667,38 +3062,13 @@ index d3ea02090..ccb5b3d9b 100644 +{ + unsigned int i; + uint64_t s[25]; - -- for (int i = 0; i < DEGREE; i += 2) { -- uint8_t byte = entropy[i / 2]; -- -- uint16_t value = kPrime; -- value += (byte & 1) + ((byte >> 1) & 1); -- value -= ((byte >> 2) & 1) + ((byte >> 3) & 1); -- out->c[i] = reduce_once(value); -- -- byte >>= 4; -- value = kPrime; -- value += (byte & 1) + ((byte >> 1) & 1); -- value -= ((byte >> 2) & 1) + ((byte >> 3) & 1); -- out->c[i + 1] = reduce_once(value); -- } ++ + keccak_absorb_once(s, SHA3_512_RATE, in, inlen, 0x06); + KeccakF1600_StatePermute(s); + for(i=0;i<8;i++) + store64(h+8*i,s[i]); - } - --// Generates a secret vector by using --// |scalar_centered_binomial_distribution_eta_2_with_prf|, using the given seed --// appending and incrementing |counter| for entry of the vector. --static void vector_generate_secret_eta_2(vector *out, uint8_t *counter, -- const uint8_t seed[32]) { -- uint8_t input[33]; -- OPENSSL_memcpy(input, seed, 32); -- for (int i = 0; i < RANK; i++) { -- input[32] = (*counter)++; -- scalar_centered_binomial_distribution_eta_2_with_prf(&out->v[i], input); -- } ++} ++ +// +// symmetric-shake.c +// @@ -2727,20 +3097,11 @@ index d3ea02090..ccb5b3d9b 100644 + shake128_absorb_once(state, extseed, sizeof(extseed)); } --// Expands the matrix of a seed for key generation and for encaps-CPA. --static void matrix_expand(matrix *out, const uint8_t rho[32]) { -- uint8_t input[34]; -- OPENSSL_memcpy(input, rho, 32); -- for (int i = 0; i < RANK; i++) { -- for (int j = 0; j < RANK; j++) { -- input[32] = i; -- input[33] = j; -- struct BORINGSSL_keccak_st keccak_ctx; -- BORINGSSL_keccak_init(&keccak_ctx, boringssl_shake128); -- BORINGSSL_keccak_absorb(&keccak_ctx, input, sizeof(input)); -- scalar_from_keccak_vartime(&out->v[i][j], &keccak_ctx); -- } -- } +-int KYBER_parse_public_key(struct KYBER_public_key *public_key, CBS *in) { +- struct public_key *pub = public_key_from_external(public_key); +- CBS orig_in = *in; +- if (!kyber_parse_public_key_no_hash(pub, in) || // +- CBS_len(in) != 0) { +/************************************************* +* Name: kyber_shake256_prf +* @@ -2760,16 +3121,12 @@ index d3ea02090..ccb5b3d9b 100644 + extkey[KYBER_SYMBYTES] = nonce; + + shake256(out, outlen, extkey, sizeof(extkey)); - } - --static const uint8_t kMasks[8] = {0x01, 0x03, 0x07, 0x0f, -- 0x1f, 0x3f, 0x7f, 0xff}; ++} ++ +// +// kem.c +// - --static void scalar_encode(uint8_t *out, const scalar *s, int bits) { -- assert(bits <= (int)sizeof(*s->c) * 8 && bits != 1); ++ +// Modified crypto_kem_keypair to BoringSSL style API +void generate_key(struct public_key *out_pub, struct private_key *out_priv, + const uint8_t seed[KYBER_GENERATE_KEY_BYTES]) @@ -2777,56 +3134,15 @@ index d3ea02090..ccb5b3d9b 100644 + size_t i; + uint8_t* pk = &out_pub->opaque[0]; + uint8_t* sk = &out_priv->opaque[0]; - -- uint8_t out_byte = 0; -- int out_byte_bits = 0; -- -- for (int i = 0; i < DEGREE; i++) { -- uint16_t element = s->c[i]; -- int element_bits_done = 0; -- -- while (element_bits_done < bits) { -- int chunk_bits = bits - element_bits_done; -- int out_bits_remaining = 8 - out_byte_bits; -- if (chunk_bits >= out_bits_remaining) { -- chunk_bits = out_bits_remaining; -- out_byte |= (element & kMasks[chunk_bits - 1]) << out_byte_bits; -- *out = out_byte; -- out++; -- out_byte_bits = 0; -- out_byte = 0; -- } else { -- out_byte |= (element & kMasks[chunk_bits - 1]) << out_byte_bits; -- out_byte_bits += chunk_bits; -- } -- -- element_bits_done += chunk_bits; -- element >>= chunk_bits; -- } -- } -- -- if (out_byte_bits > 0) { -- *out = out_byte; -- } ++ + indcpa_keypair(pk, sk, seed); + for(i=0;ic[i + j] & 1) << j; -- } -- *out = out_byte; -- out++; -- } --} ++} ++ +// Modified crypto_kem_enc to BoringSSL style API +int encap(uint8_t out_ciphertext[KYBER_CIPHERTEXTBYTES], + uint8_t ss[KYBER_KEY_BYTES], @@ -2839,472 +3155,68 @@ index d3ea02090..ccb5b3d9b 100644 + uint8_t buf[2*KYBER_SYMBYTES]; + /* Will contain key, coins */ + uint8_t kr[2*KYBER_SYMBYTES]; - --// Encodes an entire vector into 32*|RANK|*|bits| bytes. Note that since 256 --// (DEGREE) is divisible by 8, the individual vector entries will always fill a --// whole number of bytes, so we do not need to worry about bit packing here. --static void vector_encode(uint8_t *out, const vector *a, int bits) { -- for (int i = 0; i < RANK; i++) { -- scalar_encode(out + i * bits * DEGREE / 8, &a->v[i], bits); -- } --} ++ + memcpy(buf, seed, KYBER_SYMBYTES); - --// scalar_decode parses |DEGREE * bits| bits from |in| into |DEGREE| values in --// |out|. It returns one on success and zero if any parsed value is >= --// |kPrime|. --static int scalar_decode(scalar *out, const uint8_t *in, int bits) { -- assert(bits <= (int)sizeof(*out->c) * 8 && bits != 1); -+ /* Don't release system RNG output */ -+ hash_h(buf, buf, KYBER_SYMBYTES); - -- uint8_t in_byte = 0; -- int in_byte_bits_left = 0; -+ /* Multitarget countermeasure for coins + contributory KEM */ -+ hash_h(buf+KYBER_SYMBYTES, pk, KYBER_PUBLICKEYBYTES); -+ hash_g(kr, buf, 2*KYBER_SYMBYTES); - -- for (int i = 0; i < DEGREE; i++) { -- uint16_t element = 0; -- int element_bits_done = 0; -+ /* coins are in kr+KYBER_SYMBYTES */ -+ if(!indcpa_enc(ct, buf, pk, kr+KYBER_SYMBYTES)) -+ return 0; - -- while (element_bits_done < bits) { -- if (in_byte_bits_left == 0) { -- in_byte = *in; -- in++; -- in_byte_bits_left = 8; -- } -- -- int chunk_bits = bits - element_bits_done; -- if (chunk_bits > in_byte_bits_left) { -- chunk_bits = in_byte_bits_left; -- } -- -- element |= (in_byte & kMasks[chunk_bits - 1]) << element_bits_done; -- in_byte_bits_left -= chunk_bits; -- in_byte >>= chunk_bits; -- -- element_bits_done += chunk_bits; -- } -- -- if (element >= kPrime) { -- return 0; -- } -- out->c[i] = element; -- } -- -- return 1; --} -- --// scalar_decode_1 is |scalar_decode| specialised for |bits| == 1. --static void scalar_decode_1(scalar *out, const uint8_t in[32]) { -- for (int i = 0; i < DEGREE; i += 8) { -- uint8_t in_byte = *in; -- in++; -- for (int j = 0; j < 8; j++) { -- out->c[i + j] = in_byte & 1; -- in_byte >>= 1; -- } -- } --} -- --// Decodes 32*|RANK|*|bits| bytes from |in| into |out|. It returns one on --// success or zero if any parsed value is >= |kPrime|. --static int vector_decode(vector *out, const uint8_t *in, int bits) { -- for (int i = 0; i < RANK; i++) { -- if (!scalar_decode(&out->v[i], in + i * bits * DEGREE / 8, bits)) { -- return 0; -- } -+ if (mlkem == 1) { -+ memcpy(ss, kr, KYBER_SYMBYTES); -+ } else { -+ /* overwrite coins in kr with H(c) */ -+ hash_h(kr+KYBER_SYMBYTES, ct, KYBER_CIPHERTEXTBYTES); -+ /* hash concatenation of pre-k and H(c) to k */ -+ kdf(ss, kr, 2*KYBER_SYMBYTES); - } - return 1; - } - --// Compresses (lossily) an input |x| mod 3329 into |bits| many bits by grouping --// numbers close to each other together. The formula used is --// round(2^|bits|/kPrime*x) mod 2^|bits|. --// Uses Barrett reduction to achieve constant time. Since we need both the --// remainder (for rounding) and the quotient (as the result), we cannot use --// |reduce| here, but need to do the Barrett reduction directly. --static uint16_t compress(uint16_t x, int bits) { -- uint32_t shifted = (uint32_t)x << bits; -- uint64_t product = (uint64_t)shifted * kBarrettMultiplier; -- uint32_t quotient = (uint32_t)(product >> kBarrettShift); -- uint32_t remainder = shifted - quotient * kPrime; -+// Modified crypto_kem_decap to BoringSSL style API -+void decap(uint8_t out_shared_key[KYBER_SSBYTES], -+ const struct private_key *in_priv, -+ const uint8_t *ct, size_t ciphertext_len, int mlkem) -+{ -+ uint8_t *ss = out_shared_key; -+ const uint8_t *sk = &in_priv->opaque[0]; - -- // Adjust the quotient to round correctly: -- // 0 <= remainder <= kHalfPrime round to 0 -- // kHalfPrime < remainder <= kPrime + kHalfPrime round to 1 -- // kPrime + kHalfPrime < remainder < 2 * kPrime round to 2 -- assert(remainder < 2u * kPrime); -- quotient += 1 & constant_time_lt_w(kHalfPrime, remainder); -- quotient += 1 & constant_time_lt_w(kPrime + kHalfPrime, remainder); -- return quotient & ((1 << bits) - 1); --} -+ size_t i; -+ int fail = 1; -+ uint8_t buf[2*KYBER_SYMBYTES]; -+ /* Will contain key, coins */ -+ uint8_t kr[2*KYBER_SYMBYTES]; -+ uint8_t cmp[KYBER_CIPHERTEXTBYTES]; -+ const uint8_t *pk = sk+KYBER_INDCPA_SECRETKEYBYTES; - --// Decompresses |x| by using an equi-distant representative. The formula is --// round(kPrime/2^|bits|*x). Note that 2^|bits| being the divisor allows us to --// implement this logic using only bit operations. --static uint16_t decompress(uint16_t x, int bits) { -- uint32_t product = (uint32_t)x * kPrime; -- uint32_t power = 1 << bits; -- // This is |product| % power, since |power| is a power of 2. -- uint32_t remainder = product & (power - 1); -- // This is |product| / power, since |power| is a power of 2. -- uint32_t lower = product >> bits; -- // The rounding logic works since the first half of numbers mod |power| have a -- // 0 as first bit, and the second half has a 1 as first bit, since |power| is -- // a power of 2. As a 12 bit number, |remainder| is always positive, so we -- // will shift in 0s for a right shift. -- return lower + (remainder >> (bits - 1)); --} -+ if (ciphertext_len == KYBER_CIPHERTEXTBYTES) { -+ indcpa_dec(buf, ct, sk); - --static void scalar_compress(scalar *s, int bits) { -- for (int i = 0; i < DEGREE; i++) { -- s->c[i] = compress(s->c[i], bits); -+ /* Multitarget countermeasure for coins + contributory KEM */ -+ for(i=0;ic[i] = decompress(s->c[i], bits); -- } -+void marshal_public_key(uint8_t out[KYBER_PUBLICKEYBYTES], -+ const struct public_key *in_pub) { -+ memcpy(out, &in_pub->opaque, KYBER_PUBLICKEYBYTES); - } - --static void vector_compress(vector *a, int bits) { -- for (int i = 0; i < RANK; i++) { -- scalar_compress(&a->v[i], bits); -- } --} -- --static void vector_decompress(vector *a, int bits) { -- for (int i = 0; i < RANK; i++) { -- scalar_decompress(&a->v[i], bits); -- } --} -- --struct public_key { -- vector t; -- uint8_t rho[32]; -- uint8_t public_key_hash[32]; -- matrix m; --}; -- --static struct public_key *public_key_from_external( -- const struct KYBER_public_key *external) { -- static_assert(sizeof(struct KYBER_public_key) >= sizeof(struct public_key), -- "Kyber public key is too small"); -- static_assert(alignof(struct KYBER_public_key) >= alignof(struct public_key), -- "Kyber public key align incorrect"); -- return (struct public_key *)external; --} -- --struct private_key { -- struct public_key pub; -- vector s; -- uint8_t fo_failure_secret[32]; --}; -- --static struct private_key *private_key_from_external( -- const struct KYBER_private_key *external) { -- static_assert(sizeof(struct KYBER_private_key) >= sizeof(struct private_key), -- "Kyber private key too small"); -- static_assert( -- alignof(struct KYBER_private_key) >= alignof(struct private_key), -- "Kyber private key align incorrect"); -- return (struct private_key *)external; --} -- --// Calls |KYBER_generate_key_external_entropy| with random bytes from --// |RAND_bytes|. --void KYBER_generate_key(uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], -- struct KYBER_private_key *out_private_key) { -- uint8_t entropy[KYBER_GENERATE_KEY_ENTROPY]; -- RAND_bytes(entropy, sizeof(entropy)); -- KYBER_generate_key_external_entropy(out_encoded_public_key, out_private_key, -- entropy); --} -- --static int kyber_marshal_public_key(CBB *out, const struct public_key *pub) { -- uint8_t *vector_output; -- if (!CBB_add_space(out, &vector_output, kEncodedVectorSize)) { -- return 0; -- } -- vector_encode(vector_output, &pub->t, kLog2Prime); -- if (!CBB_add_bytes(out, pub->rho, sizeof(pub->rho))) { -- return 0; -- } -- return 1; --} -- --// Algorithms 4 and 7 of the Kyber spec. Algorithms are combined since key --// generation is not part of the FO transform, and the spec uses Algorithm 7 to --// specify the actual key format. --void KYBER_generate_key_external_entropy( -- uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], -- struct KYBER_private_key *out_private_key, -- const uint8_t entropy[KYBER_GENERATE_KEY_ENTROPY]) { -- struct private_key *priv = private_key_from_external(out_private_key); -- uint8_t hashed[64]; -- BORINGSSL_keccak(hashed, sizeof(hashed), entropy, 32, boringssl_sha3_512); -- const uint8_t *const rho = hashed; -- const uint8_t *const sigma = hashed + 32; -- OPENSSL_memcpy(priv->pub.rho, hashed, sizeof(priv->pub.rho)); -- matrix_expand(&priv->pub.m, rho); -- uint8_t counter = 0; -- vector_generate_secret_eta_2(&priv->s, &counter, sigma); -- vector_ntt(&priv->s); -- vector error; -- vector_generate_secret_eta_2(&error, &counter, sigma); -- vector_ntt(&error); -- matrix_mult_transpose(&priv->pub.t, &priv->pub.m, &priv->s); -- vector_add(&priv->pub.t, &error); -- -- CBB cbb; -- CBB_init_fixed(&cbb, out_encoded_public_key, KYBER_PUBLIC_KEY_BYTES); -- if (!kyber_marshal_public_key(&cbb, &priv->pub)) { -- abort(); -- } -- -- BORINGSSL_keccak(priv->pub.public_key_hash, sizeof(priv->pub.public_key_hash), -- out_encoded_public_key, KYBER_PUBLIC_KEY_BYTES, -- boringssl_sha3_256); -- OPENSSL_memcpy(priv->fo_failure_secret, entropy + 32, 32); --} -- --void KYBER_public_from_private(struct KYBER_public_key *out_public_key, -- const struct KYBER_private_key *private_key) { -- struct public_key *const pub = public_key_from_external(out_public_key); -- const struct private_key *const priv = private_key_from_external(private_key); -- *pub = priv->pub; --} -- --// Algorithm 5 of the Kyber spec. Encrypts a message with given randomness to --// the ciphertext in |out|. Without applying the Fujisaki-Okamoto transform this --// would not result in a CCA secure scheme, since lattice schemes are vulnerable --// to decryption failure oracles. --static void encrypt_cpa(uint8_t out[KYBER_CIPHERTEXT_BYTES], -- const struct public_key *pub, const uint8_t message[32], -- const uint8_t randomness[32]) { -- uint8_t counter = 0; -- vector secret; -- vector_generate_secret_eta_2(&secret, &counter, randomness); -- vector_ntt(&secret); -- vector error; -- vector_generate_secret_eta_2(&error, &counter, randomness); -- uint8_t input[33]; -- OPENSSL_memcpy(input, randomness, 32); -- input[32] = counter; -- scalar scalar_error; -- scalar_centered_binomial_distribution_eta_2_with_prf(&scalar_error, input); -- vector u; -- matrix_mult(&u, &pub->m, &secret); -- vector_inverse_ntt(&u); -- vector_add(&u, &error); -- scalar v; -- scalar_inner_product(&v, &pub->t, &secret); -- scalar_inverse_ntt(&v); -- scalar_add(&v, &scalar_error); -- scalar expanded_message; -- scalar_decode_1(&expanded_message, message); -- scalar_decompress(&expanded_message, 1); -- scalar_add(&v, &expanded_message); -- vector_compress(&u, kDU); -- vector_encode(out, &u, kDU); -- scalar_compress(&v, kDV); -- scalar_encode(out + kCompressedVectorSize, &v, kDV); --} -- --// Calls KYBER_encap_external_entropy| with random bytes from |RAND_bytes| --void KYBER_encap(uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], -- uint8_t *out_shared_secret, size_t out_shared_secret_len, -- const struct KYBER_public_key *public_key) { -- uint8_t entropy[KYBER_ENCAP_ENTROPY]; -- RAND_bytes(entropy, KYBER_ENCAP_ENTROPY); -- KYBER_encap_external_entropy(out_ciphertext, out_shared_secret, -- out_shared_secret_len, public_key, entropy); --} -- --// Algorithm 8 of the Kyber spec, safe for line 2 of the spec. The spec there --// hashes the output of the system's random number generator, since the FO --// transform will reveal it to the decrypting party. There is no reason to do --// this when a secure random number generator is used. When an insecure random --// number generator is used, the caller should switch to a secure one before --// calling this method. --void KYBER_encap_external_entropy( -- uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], uint8_t *out_shared_secret, -- size_t out_shared_secret_len, const struct KYBER_public_key *public_key, -- const uint8_t entropy[KYBER_ENCAP_ENTROPY]) { -- const struct public_key *pub = public_key_from_external(public_key); -- uint8_t input[64]; -- OPENSSL_memcpy(input, entropy, KYBER_ENCAP_ENTROPY); -- OPENSSL_memcpy(input + KYBER_ENCAP_ENTROPY, pub->public_key_hash, -- sizeof(input) - KYBER_ENCAP_ENTROPY); -- uint8_t prekey_and_randomness[64]; -- BORINGSSL_keccak(prekey_and_randomness, sizeof(prekey_and_randomness), input, -- sizeof(input), boringssl_sha3_512); -- encrypt_cpa(out_ciphertext, pub, entropy, prekey_and_randomness + 32); -- BORINGSSL_keccak(prekey_and_randomness + 32, 32, out_ciphertext, -- KYBER_CIPHERTEXT_BYTES, boringssl_sha3_256); -- BORINGSSL_keccak(out_shared_secret, out_shared_secret_len, -- prekey_and_randomness, sizeof(prekey_and_randomness), -- boringssl_shake256); --} -- --// Algorithm 6 of the Kyber spec. --static void decrypt_cpa(uint8_t out[32], const struct private_key *priv, -- const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES]) { -- vector u; -- vector_decode(&u, ciphertext, kDU); -- vector_decompress(&u, kDU); -- vector_ntt(&u); -- scalar v; -- scalar_decode(&v, ciphertext + kCompressedVectorSize, kDV); -- scalar_decompress(&v, kDV); -- scalar mask; -- scalar_inner_product(&mask, &priv->s, &u); -- scalar_inverse_ntt(&mask); -- scalar_sub(&v, &mask); -- scalar_compress(&v, 1); -- scalar_encode_1(out, &v); --} -- --// Algorithm 9 of the Kyber spec, performing the FO transform by running --// encrypt_cpa on the decrypted message. The spec does not allow the decryption --// failure to be passed on to the caller, and instead returns a result that is --// deterministic but unpredictable to anyone without knowledge of the private --// key. --void KYBER_decap(uint8_t *out_shared_secret, size_t out_shared_secret_len, -- const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES], -- const struct KYBER_private_key *private_key) { -- const struct private_key *priv = private_key_from_external(private_key); -- uint8_t decrypted[64]; -- decrypt_cpa(decrypted, priv, ciphertext); -- OPENSSL_memcpy(decrypted + 32, priv->pub.public_key_hash, -- sizeof(decrypted) - 32); -- uint8_t prekey_and_randomness[64]; -- BORINGSSL_keccak(prekey_and_randomness, sizeof(prekey_and_randomness), -- decrypted, sizeof(decrypted), boringssl_sha3_512); -- uint8_t expected_ciphertext[KYBER_CIPHERTEXT_BYTES]; -- encrypt_cpa(expected_ciphertext, &priv->pub, decrypted, -- prekey_and_randomness + 32); -- uint8_t mask = -- constant_time_eq_int_8(CRYPTO_memcmp(ciphertext, expected_ciphertext, -- sizeof(expected_ciphertext)), -- 0); -- uint8_t input[64]; -- for (int i = 0; i < 32; i++) { -- input[i] = constant_time_select_8(mask, prekey_and_randomness[i], -- priv->fo_failure_secret[i]); -- } -- BORINGSSL_keccak(input + 32, 32, ciphertext, KYBER_CIPHERTEXT_BYTES, -- boringssl_sha3_256); -- BORINGSSL_keccak(out_shared_secret, out_shared_secret_len, input, -- sizeof(input), boringssl_shake256); --} -- --int KYBER_marshal_public_key(CBB *out, -- const struct KYBER_public_key *public_key) { -- return kyber_marshal_public_key(out, public_key_from_external(public_key)); --} -- --// kyber_parse_public_key_no_hash parses |in| into |pub| but doesn't calculate --// the value of |pub->public_key_hash|. --static int kyber_parse_public_key_no_hash(struct public_key *pub, CBS *in) { -- CBS t_bytes; -- if (!CBS_get_bytes(in, &t_bytes, kEncodedVectorSize) || -- !vector_decode(&pub->t, CBS_data(&t_bytes), kLog2Prime) || -- !CBS_copy_bytes(in, pub->rho, sizeof(pub->rho))) { -- return 0; -- } -- matrix_expand(&pub->m, pub->rho); -- return 1; --} -- --int KYBER_parse_public_key(struct KYBER_public_key *public_key, CBS *in) { -- struct public_key *pub = public_key_from_external(public_key); -- CBS orig_in = *in; -- if (!kyber_parse_public_key_no_hash(pub, in) || // -- CBS_len(in) != 0) { -- return 0; -- } ++ ++ /* Don't release system RNG output */ ++ hash_h(buf, buf, KYBER_SYMBYTES); ++ ++ /* Multitarget countermeasure for coins + contributory KEM */ ++ hash_h(buf+KYBER_SYMBYTES, pk, KYBER_PUBLICKEYBYTES); ++ hash_g(kr, buf, 2*KYBER_SYMBYTES); ++ ++ /* coins are in kr+KYBER_SYMBYTES */ ++ if(!indcpa_enc(ct, buf, pk, kr+KYBER_SYMBYTES)) + return 0; ++ ++ if (mlkem == 1) { ++ memcpy(ss, kr, KYBER_SYMBYTES); ++ } else { ++ /* overwrite coins in kr with H(c) */ ++ hash_h(kr+KYBER_SYMBYTES, ct, KYBER_CIPHERTEXTBYTES); ++ /* hash concatenation of pre-k and H(c) to k */ ++ kdf(ss, kr, 2*KYBER_SYMBYTES); + } - BORINGSSL_keccak(pub->public_key_hash, sizeof(pub->public_key_hash), - CBS_data(&orig_in), CBS_len(&orig_in), boringssl_sha3_256); -- return 1; --} -- + return 1; + } + -int KYBER_marshal_private_key(CBB *out, - const struct KYBER_private_key *private_key) { - const struct private_key *const priv = private_key_from_external(private_key); - uint8_t *s_output; - if (!CBB_add_space(out, &s_output, kEncodedVectorSize)) { - return 0; -- } ++// Modified crypto_kem_decap to BoringSSL style API ++void decap(uint8_t out_shared_key[KYBER_SSBYTES], ++ const struct private_key *in_priv, ++ const uint8_t *ct, size_t ciphertext_len, int mlkem) ++{ ++ uint8_t *ss = out_shared_key; ++ const uint8_t *sk = &in_priv->opaque[0]; ++ ++ size_t i; ++ int fail = 1; ++ uint8_t buf[2*KYBER_SYMBYTES]; ++ /* Will contain key, coins */ ++ uint8_t kr[2*KYBER_SYMBYTES]; ++ uint8_t cmp[KYBER_CIPHERTEXTBYTES]; ++ const uint8_t *pk = sk+KYBER_INDCPA_SECRETKEYBYTES; ++ ++ if (ciphertext_len == KYBER_CIPHERTEXTBYTES) { ++ indcpa_dec(buf, ct, sk); ++ ++ /* Multitarget countermeasure for coins + contributory KEM */ ++ for(i=0;is, kLog2Prime); - if (!kyber_marshal_public_key(out, &priv->pub) || - !CBB_add_bytes(out, priv->pub.public_key_hash, @@ -3312,14 +3224,45 @@ index d3ea02090..ccb5b3d9b 100644 - !CBB_add_bytes(out, priv->fo_failure_secret, - sizeof(priv->fo_failure_secret))) { - return 0; -- } ++ ++ if (mlkem == 1) { ++ /* Compute shared secret in case of rejection: ss2 = PRF(z || c). */ ++ uint8_t ss2[KYBER_SYMBYTES]; ++ keccak_state ks; ++ shake256_init(&ks); ++ shake256_absorb( ++ &ks, ++ sk + KYBER_SECRETKEYBYTES - KYBER_SYMBYTES, ++ KYBER_SYMBYTES ++ ); ++ shake256_absorb(&ks, ct, ciphertext_len); ++ shake256_finalize(&ks); ++ shake256_squeeze(ss2, KYBER_SYMBYTES, &ks); ++ ++ /* Set ss2 to the real shared secret if c = c' */ ++ cmov(ss2, kr, KYBER_SYMBYTES, 1-fail); ++ memcpy(ss, ss2, KYBER_SYMBYTES); ++ } else { ++ /* overwrite coins in kr with H(c) */ ++ hash_h(kr+KYBER_SYMBYTES, ct, ciphertext_len); ++ ++ /* Overwrite pre-k with z on re-encryption failure */ ++ cmov(kr, sk+KYBER_SECRETKEYBYTES-KYBER_SYMBYTES, KYBER_SYMBYTES, fail); ++ ++ /* hash concatenation of pre-k and H(c) to k */ ++ kdf(ss, kr, 2*KYBER_SYMBYTES); + } - return 1; --} -- + } + -int KYBER_parse_private_key(struct KYBER_private_key *out_private_key, - CBS *in) { - struct private_key *const priv = private_key_from_external(out_private_key); -- ++void marshal_public_key(uint8_t out[KYBER_PUBLICKEYBYTES], ++ const struct public_key *in_pub) { ++ memcpy(out, &in_pub->opaque, KYBER_PUBLICKEYBYTES); ++} + - CBS s_bytes; - if (!CBS_get_bytes(in, &s_bytes, kEncodedVectorSize) || - !vector_decode(&priv->s, CBS_data(&s_bytes), kLog2Prime) || @@ -3687,7 +3630,15 @@ index cafae9d17..a05eb8957 100644 - } opaque; +struct KYBER512_private_key { + uint8_t opaque[KYBER512_PRIVATE_KEY_BYTES]; -+}; + }; +- +-// KYBER_private_key contains a Kyber768 private key. The contents of this +-// object should never leave the address space since the format is unstable. +-struct KYBER_private_key { +- union { +- uint8_t bytes[512 * (3 + 3 + 9) + 32 + 32 + 32]; +- uint16_t alignment; +- } opaque; +struct KYBER768_private_key { + uint8_t opaque[KYBER768_PRIVATE_KEY_BYTES]; +}; @@ -3698,34 +3649,17 @@ index cafae9d17..a05eb8957 100644 + uint8_t opaque[KYBER768_PUBLIC_KEY_BYTES]; }; --// KYBER_private_key contains a Kyber768 private key. The contents of this --// object should never leave the address space since the format is unstable. --struct KYBER_private_key { -- union { -- uint8_t bytes[512 * (3 + 3 + 9) + 32 + 32 + 32]; -- uint16_t alignment; -- } opaque; --}; -+// KYBER_GENERATE_KEY_BYTES is the number of bytes of entropy needed to -+// generate a keypair. -+#define KYBER_GENERATE_KEY_BYTES 64 - -// KYBER_PUBLIC_KEY_BYTES is the number of bytes in an encoded Kyber768 public -// key. -#define KYBER_PUBLIC_KEY_BYTES 1184 -+// KYBER_ENCAP_BYTES is the number of bytes of entropy needed to encapsulate a -+// session key. -+#define KYBER_ENCAP_BYTES 32 - +- -// KYBER_generate_key generates a random public/private key pair, writes the -// encoded public key to |out_encoded_public_key| and sets |out_private_key| to -// the private key. -OPENSSL_EXPORT void KYBER_generate_key( - uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], - struct KYBER_private_key *out_private_key); -+// KYBER_KEY_BYTES is the number of bytes in a shared key. -+#define KYBER_KEY_BYTES 32 - +- -// KYBER_public_from_private sets |*out_public_key| to the public key that -// corresponds to |private_key|. (This is faster than parsing the output of -// |KYBER_generate_key| if, for some reason, you need to encapsulate to a key @@ -3733,20 +3667,10 @@ index cafae9d17..a05eb8957 100644 -OPENSSL_EXPORT void KYBER_public_from_private( - struct KYBER_public_key *out_public_key, - const struct KYBER_private_key *private_key); -+// KYBER512_generate_key is a deterministic function that outputs a public and -+// private key based on the given entropy. -+OPENSSL_EXPORT void KYBER512_generate_key( -+ struct KYBER512_public_key *out_pub, struct KYBER512_private_key *out_priv, -+ const uint8_t input[KYBER_GENERATE_KEY_BYTES]); - +- -// KYBER_CIPHERTEXT_BYTES is number of bytes in the Kyber768 ciphertext. -#define KYBER_CIPHERTEXT_BYTES 1088 -+// KYBER768_generate_key is a deterministic function that outputs a public and -+// private key based on the given entropy. -+OPENSSL_EXPORT void KYBER768_generate_key( -+ struct KYBER768_public_key *out_pub, struct KYBER768_private_key *out_priv, -+ const uint8_t input[KYBER_GENERATE_KEY_BYTES]); - +- -// KYBER_encap encrypts a random secret key of length |out_shared_secret_len| to -// |public_key|, writes the ciphertext to |ciphertext|, and writes the random -// key to |out_shared_secret|. The party calling |KYBER_decap| must already know @@ -3755,15 +3679,7 @@ index cafae9d17..a05eb8957 100644 - uint8_t *out_shared_secret, - size_t out_shared_secret_len, - const struct KYBER_public_key *public_key); -+// KYBER512_encap is a deterministic function the generates and encrypts a random -+// session key from the given entropy, writing those values to |out_shared_key| -+// and |out_ciphertext|, respectively. If |mlkem| is 1, will use ML-KEM-512. -+OPENSSL_EXPORT int KYBER512_encap(uint8_t out_ciphertext[KYBER512_CIPHERTEXT_BYTES], -+ uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER512_public_key *in_pub, -+ const uint8_t in[KYBER_ENCAP_BYTES], -+ int mlkem); - +- -// KYBER_decap decrypts a key of length |out_shared_secret_len| from -// |ciphertext| using |private_key| and writes it to |out_shared_secret|. If -// |ciphertext| is invalid, |out_shared_secret| is filled with a key that @@ -3776,57 +3692,23 @@ index cafae9d17..a05eb8957 100644 - uint8_t *out_shared_secret, size_t out_shared_secret_len, - const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES], - const struct KYBER_private_key *private_key); -+// KYBER768_encap is a deterministic function the generates and encrypts a random -+// session key from the given entropy, writing those values to |out_shared_key| -+// and |out_ciphertext|, respectively. If |mlkem| is 1, will use ML-KEM-768. -+OPENSSL_EXPORT int KYBER768_encap(uint8_t out_ciphertext[KYBER768_CIPHERTEXT_BYTES], -+ uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER768_public_key *in_pub, -+ const uint8_t in[KYBER_ENCAP_BYTES], -+ int mlkem); - -+// KYBER_decap decrypts a session key from |ciphertext_len| bytes of -+// |ciphertext|. If the ciphertext is valid, the decrypted key is written to -+// |out_shared_key|. Otherwise a key dervied from |ciphertext| and a secret key (kept -+// in |in_priv|) is written. If the ciphertext is the wrong length then it will -+// leak which was done via side-channels. Otherwise it should perform either -+// action in constant-time. If |mlkem| is 1, will use ML-KEM-512. -+OPENSSL_EXPORT void KYBER512_decap(uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER512_private_key *in_priv, -+ const uint8_t *ciphertext, size_t ciphertext_len, -+ int mlkem); - +- +- -// Serialisation of keys. -+// KYBER_decap decrypts a session key from |ciphertext_len| bytes of -+// |ciphertext|. If the ciphertext is valid, the decrypted key is written to -+// |out_shared_key|. Otherwise a key dervied from |ciphertext| and a secret key (kept -+// in |in_priv|) is written. If the ciphertext is the wrong length then it will -+// leak which was done via side-channels. Otherwise it should perform either -+// action in constant-time. If |mlkem| is 1, will use ML-KEM-768. -+OPENSSL_EXPORT void KYBER768_decap(uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER768_private_key *in_priv, -+ const uint8_t *ciphertext, size_t ciphertext_len, -+ int mlkem); - +- -// KYBER_marshal_public_key serializes |public_key| to |out| in the standard -// format for Kyber public keys. It returns one on success or zero on allocation -// error. -OPENSSL_EXPORT int KYBER_marshal_public_key( - CBB *out, const struct KYBER_public_key *public_key); -+// KYBER512_marshal_public_key serialises |in_pub| to |out|. -+OPENSSL_EXPORT void KYBER512_marshal_public_key( -+ uint8_t out[KYBER512_PUBLIC_KEY_BYTES], const struct KYBER512_public_key *in_pub); - +- -// KYBER_parse_public_key parses a public key, in the format generated by -// |KYBER_marshal_public_key|, from |in| and writes the result to -// |out_public_key|. It returns one on success or zero on parse error or if -// there are trailing bytes in |in|. -OPENSSL_EXPORT int KYBER_parse_public_key( - struct KYBER_public_key *out_public_key, CBS *in); -+// KYBER768_marshal_public_key serialises |in_pub| to |out|. -+OPENSSL_EXPORT void KYBER768_marshal_public_key( -+ uint8_t out[KYBER768_PUBLIC_KEY_BYTES], const struct KYBER768_public_key *in_pub); - +- -// KYBER_marshal_private_key serializes |private_key| to |out| in the standard -// format for Kyber private keys. It returns one on success or zero on -// allocation error. @@ -3843,10 +3725,82 @@ index cafae9d17..a05eb8957 100644 -// there are trailing bytes in |in|. -OPENSSL_EXPORT int KYBER_parse_private_key( - struct KYBER_private_key *out_private_key, CBS *in); +- ++// KYBER_GENERATE_KEY_BYTES is the number of bytes of entropy needed to ++// generate a keypair. ++#define KYBER_GENERATE_KEY_BYTES 64 ++ ++// KYBER_ENCAP_BYTES is the number of bytes of entropy needed to encapsulate a ++// session key. ++#define KYBER_ENCAP_BYTES 32 ++ ++// KYBER_KEY_BYTES is the number of bytes in a shared key. ++#define KYBER_KEY_BYTES 32 ++ ++// KYBER512_generate_key is a deterministic function that outputs a public and ++// private key based on the given entropy. ++OPENSSL_EXPORT void KYBER512_generate_key( ++ struct KYBER512_public_key *out_pub, struct KYBER512_private_key *out_priv, ++ const uint8_t input[KYBER_GENERATE_KEY_BYTES]); ++ ++// KYBER768_generate_key is a deterministic function that outputs a public and ++// private key based on the given entropy. ++OPENSSL_EXPORT void KYBER768_generate_key( ++ struct KYBER768_public_key *out_pub, struct KYBER768_private_key *out_priv, ++ const uint8_t input[KYBER_GENERATE_KEY_BYTES]); ++ ++// KYBER512_encap is a deterministic function the generates and encrypts a random ++// session key from the given entropy, writing those values to |out_shared_key| ++// and |out_ciphertext|, respectively. If |mlkem| is 1, will use ML-KEM-512. ++OPENSSL_EXPORT int KYBER512_encap(uint8_t out_ciphertext[KYBER512_CIPHERTEXT_BYTES], ++ uint8_t out_shared_key[KYBER_KEY_BYTES], ++ const struct KYBER512_public_key *in_pub, ++ const uint8_t in[KYBER_ENCAP_BYTES], ++ int mlkem); ++ ++// KYBER768_encap is a deterministic function the generates and encrypts a random ++// session key from the given entropy, writing those values to |out_shared_key| ++// and |out_ciphertext|, respectively. If |mlkem| is 1, will use ML-KEM-768. ++OPENSSL_EXPORT int KYBER768_encap(uint8_t out_ciphertext[KYBER768_CIPHERTEXT_BYTES], ++ uint8_t out_shared_key[KYBER_KEY_BYTES], ++ const struct KYBER768_public_key *in_pub, ++ const uint8_t in[KYBER_ENCAP_BYTES], ++ int mlkem); ++ ++// KYBER_decap decrypts a session key from |ciphertext_len| bytes of ++// |ciphertext|. If the ciphertext is valid, the decrypted key is written to ++// |out_shared_key|. Otherwise a key dervied from |ciphertext| and a secret key (kept ++// in |in_priv|) is written. If the ciphertext is the wrong length then it will ++// leak which was done via side-channels. Otherwise it should perform either ++// action in constant-time. If |mlkem| is 1, will use ML-KEM-512. ++OPENSSL_EXPORT void KYBER512_decap(uint8_t out_shared_key[KYBER_KEY_BYTES], ++ const struct KYBER512_private_key *in_priv, ++ const uint8_t *ciphertext, size_t ciphertext_len, ++ int mlkem); ++ ++// KYBER_decap decrypts a session key from |ciphertext_len| bytes of ++// |ciphertext|. If the ciphertext is valid, the decrypted key is written to ++// |out_shared_key|. Otherwise a key dervied from |ciphertext| and a secret key (kept ++// in |in_priv|) is written. If the ciphertext is the wrong length then it will ++// leak which was done via side-channels. Otherwise it should perform either ++// action in constant-time. If |mlkem| is 1, will use ML-KEM-768. ++OPENSSL_EXPORT void KYBER768_decap(uint8_t out_shared_key[KYBER_KEY_BYTES], ++ const struct KYBER768_private_key *in_priv, ++ const uint8_t *ciphertext, size_t ciphertext_len, ++ int mlkem); ++ ++// KYBER512_marshal_public_key serialises |in_pub| to |out|. ++OPENSSL_EXPORT void KYBER512_marshal_public_key( ++ uint8_t out[KYBER512_PUBLIC_KEY_BYTES], const struct KYBER512_public_key *in_pub); ++ ++// KYBER768_marshal_public_key serialises |in_pub| to |out|. ++OPENSSL_EXPORT void KYBER768_marshal_public_key( ++ uint8_t out[KYBER768_PUBLIC_KEY_BYTES], const struct KYBER768_public_key *in_pub); ++ +// KYBER512_parse_public_key sets |*out| to the public-key encoded in |in|. +OPENSSL_EXPORT void KYBER512_parse_public_key( + struct KYBER512_public_key *out, const uint8_t in[KYBER512_PUBLIC_KEY_BYTES]); - ++ +// KYBER768_parse_public_key sets |*out| to the public-key encoded in |in|. +OPENSSL_EXPORT void KYBER768_parse_public_key( + struct KYBER768_public_key *out, const uint8_t in[KYBER768_PUBLIC_KEY_BYTES]); @@ -3912,7 +3866,7 @@ index ba2f5bc9e..d7ef5153a 100644 crypto/pkcs8/test/no_encryption.p12 crypto/pkcs8/test/nss.p12 diff --git a/ssl/extensions.cc b/ssl/extensions.cc -index b13400097..4655b1881 100644 +index b13400097..894396414 100644 --- a/ssl/extensions.cc +++ b/ssl/extensions.cc @@ -207,6 +207,10 @@ static bool tls1_check_duplicate_extensions(const CBS *cbs) { @@ -3926,6 +3880,14 @@ index b13400097..4655b1881 100644 return true; default: return false; +@@ -307,6 +311,7 @@ bool ssl_client_hello_get_extension(const SSL_CLIENT_HELLO *client_hello, + } + + static const uint16_t kDefaultGroups[] = { ++ SSL_GROUP_X25519_MLKEM768, + SSL_GROUP_X25519, + SSL_GROUP_SECP256R1, + SSL_GROUP_SECP384R1, diff --git a/ssl/ssl_key_share.cc b/ssl/ssl_key_share.cc index 694bec11d..3e4d2e7c4 100644 --- a/ssl/ssl_key_share.cc @@ -3938,7 +3900,7 @@ index 694bec11d..3e4d2e7c4 100644 #include #include #include -@@ -191,63 +192,145 @@ class X25519KeyShare : public SSLKeyShare { +@@ -191,63 +192,292 @@ class X25519KeyShare : public SSLKeyShare { uint8_t private_key_[32]; }; @@ -3947,27 +3909,18 @@ index 694bec11d..3e4d2e7c4 100644 public: - X25519Kyber768KeyShare() {} + P256Kyber768Draft00KeyShare() {} - -- uint16_t GroupID() const override { -- return SSL_GROUP_X25519_KYBER768_DRAFT00; -- } ++ + uint16_t GroupID() const override { return SSL_GROUP_P256_KYBER768_DRAFT00; } - - bool Generate(CBB *out) override { -- uint8_t x25519_public_key[32]; -- X25519_keypair(x25519_public_key, x25519_private_key_); ++ ++ bool Generate(CBB *out) override { + assert(!p256_private_key_); - -- uint8_t kyber_public_key[KYBER_PUBLIC_KEY_BYTES]; -- KYBER_generate_key(kyber_public_key, &kyber_private_key_); ++ + // Set up a shared |BN_CTX| for P-256 operations. + UniquePtr bn_ctx(BN_CTX_new()); + if (!bn_ctx) { + return false; + } - -- if (!CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key)) || -- !CBB_add_bytes(out, kyber_public_key, sizeof(kyber_public_key))) { ++ + BN_CTXScope scope(bn_ctx.get()); + + // Generate a P-256 private key. @@ -3999,58 +3952,33 @@ index 694bec11d..3e4d2e7c4 100644 + + uint8_t kyber_public_key_bytes[KYBER768_PUBLIC_KEY_BYTES]; + KYBER768_marshal_public_key(kyber_public_key_bytes, &kyber_public_key); -+ + +- uint16_t GroupID() const override { +- return SSL_GROUP_X25519_KYBER768_DRAFT00; + if (!CBB_add_bytes(out, kyber_public_key_bytes, + sizeof(kyber_public_key_bytes))) { - return false; - } - - return true; ++ return false; ++ } ++ ++ return true; } -- bool Encap(CBB *out_ciphertext, Array *out_secret, -- uint8_t *out_alert, Span peer_key) override { -- Array secret; -- if (!secret.Init(32 + 32)) { -- return false; -- } + bool Encap(CBB *out_public_key, Array *out_secret, + uint8_t *out_alert, Span peer_key) override { + assert(!p256_private_key_); - -- uint8_t x25519_public_key[32]; -- X25519_keypair(x25519_public_key, x25519_private_key_); -- KYBER_public_key peer_kyber_pub; -- CBS peer_key_cbs; -- CBS peer_x25519_cbs; -- CBS peer_kyber_cbs; -- CBS_init(&peer_key_cbs, peer_key.data(), peer_key.size()); -- if (!CBS_get_bytes(&peer_key_cbs, &peer_x25519_cbs, 32) || -- !CBS_get_bytes(&peer_key_cbs, &peer_kyber_cbs, -- KYBER_PUBLIC_KEY_BYTES) || -- CBS_len(&peer_key_cbs) != 0 || -- !X25519(secret.data(), x25519_private_key_, -- CBS_data(&peer_x25519_cbs)) || -- !KYBER_parse_public_key(&peer_kyber_pub, &peer_kyber_cbs)) { ++ + if (peer_key.size() != 65 + KYBER768_PUBLIC_KEY_BYTES) { - *out_alert = SSL_AD_DECODE_ERROR; - OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); - return false; - } - -- uint8_t kyber_ciphertext[KYBER_CIPHERTEXT_BYTES]; -- KYBER_encap(kyber_ciphertext, secret.data() + 32, secret.size() - 32, -- &peer_kyber_pub); ++ *out_alert = SSL_AD_DECODE_ERROR; ++ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); ++ return false; ++ } ++ + // Set up a shared |BN_CTX| for P-256 operations. + UniquePtr bn_ctx(BN_CTX_new()); + if (!bn_ctx) { + return false; + } - -- if (!CBB_add_bytes(out_ciphertext, x25519_public_key, -- sizeof(x25519_public_key)) || -- !CBB_add_bytes(out_ciphertext, kyber_ciphertext, -- sizeof(kyber_ciphertext))) { ++ + BN_CTXScope scope(bn_ctx.get()); + + UniquePtr group; @@ -4119,35 +4047,30 @@ index 694bec11d..3e4d2e7c4 100644 + return false; + } + if(!CBB_add_bytes(out_public_key, ciphertext, sizeof(ciphertext))) { - return false; - } - -@@ -256,30 +339,380 @@ class X25519Kyber768KeyShare : public SSLKeyShare { - } - - bool Decap(Array *out_secret, uint8_t *out_alert, -- Span ciphertext) override { ++ return false; ++ } ++ ++ *out_secret = std::move(secret); ++ return true; ++ } ++ ++ bool Decap(Array *out_secret, uint8_t *out_alert, + Span peer_key) override { + assert(p256_private_key_); - *out_alert = SSL_AD_INTERNAL_ERROR; - - Array secret; -- if (!secret.Init(32 + 32)) { ++ *out_alert = SSL_AD_INTERNAL_ERROR; ++ ++ Array secret; + if (!secret.Init(32 + KYBER_KEY_BYTES)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); - return false; - } - -- if (ciphertext.size() != 32 + KYBER_CIPHERTEXT_BYTES || -- !X25519(secret.data(), x25519_private_key_, ciphertext.data())) { ++ return false; ++ } ++ + if (peer_key.size() != 65 + KYBER768_CIPHERTEXT_BYTES) { - *out_alert = SSL_AD_DECODE_ERROR; - OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); - return false; - } - -- KYBER_decap(secret.data() + 32, secret.size() - 32, ciphertext.data() + 32, -- &kyber_private_key_); ++ *out_alert = SSL_AD_DECODE_ERROR; ++ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); ++ return false; ++ } ++ + // Set up a shared |BN_CTX| for P-256 operations. + UniquePtr bn_ctx(BN_CTX_new()); + if (!bn_ctx) { @@ -4213,10 +4136,12 @@ index 694bec11d..3e4d2e7c4 100644 + + uint16_t GroupID() const override { return group_id_; } + -+ bool Generate(CBB *out) override { -+ uint8_t x25519_public_key[32]; -+ X25519_keypair(x25519_public_key, x25519_private_key_); -+ + bool Generate(CBB *out) override { + uint8_t x25519_public_key[32]; + X25519_keypair(x25519_public_key, x25519_private_key_); + +- uint8_t kyber_public_key[KYBER_PUBLIC_KEY_BYTES]; +- KYBER_generate_key(kyber_public_key, &kyber_private_key_); + uint8_t kyber_entropy[KYBER_GENERATE_KEY_BYTES]; + KYBER768_public_key kyber_public_key; + RAND_bytes(kyber_entropy, sizeof(kyber_entropy)); @@ -4224,26 +4149,42 @@ index 694bec11d..3e4d2e7c4 100644 + + uint8_t kyber_public_key_bytes[KYBER768_PUBLIC_KEY_BYTES]; + KYBER768_marshal_public_key(kyber_public_key_bytes, &kyber_public_key); -+ -+ if (!CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key)) || + + if (!CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key)) || +- !CBB_add_bytes(out, kyber_public_key, sizeof(kyber_public_key))) { + !CBB_add_bytes(out, kyber_public_key_bytes, + sizeof(kyber_public_key_bytes))) { -+ return false; -+ } -+ -+ return true; -+ } -+ + return false; + } + + return true; + } + +- bool Encap(CBB *out_ciphertext, Array *out_secret, +- uint8_t *out_alert, Span peer_key) override { + bool Encap(CBB *out_public_key, Array *out_secret, + uint8_t *out_alert, Span peer_key) override { -+ Array secret; + Array secret; +- if (!secret.Init(32 + 32)) { + if (!secret.Init(32 + KYBER_KEY_BYTES)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } -+ -+ uint8_t x25519_public_key[32]; -+ X25519_keypair(x25519_public_key, x25519_private_key_); + return false; + } + + uint8_t x25519_public_key[32]; + X25519_keypair(x25519_public_key, x25519_private_key_); +- KYBER_public_key peer_kyber_pub; +- CBS peer_key_cbs; +- CBS peer_x25519_cbs; +- CBS peer_kyber_cbs; +- CBS_init(&peer_key_cbs, peer_key.data(), peer_key.size()); +- if (!CBS_get_bytes(&peer_key_cbs, &peer_x25519_cbs, 32) || +- !CBS_get_bytes(&peer_key_cbs, &peer_kyber_cbs, +- KYBER_PUBLIC_KEY_BYTES) || +- CBS_len(&peer_key_cbs) != 0 || +- !X25519(secret.data(), x25519_private_key_, +- CBS_data(&peer_x25519_cbs)) || +- !KYBER_parse_public_key(&peer_kyber_pub, &peer_kyber_cbs)) { + + KYBER768_public_key peer_public_key; + if (peer_key.size() != 32 + KYBER768_PUBLIC_KEY_BYTES) { @@ -4255,30 +4196,36 @@ index 694bec11d..3e4d2e7c4 100644 + KYBER768_parse_public_key(&peer_public_key, peer_key.data() + 32); + + if (!X25519(secret.data(), x25519_private_key_, peer_key.data())) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } -+ + *out_alert = SSL_AD_DECODE_ERROR; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + +- uint8_t kyber_ciphertext[KYBER_CIPHERTEXT_BYTES]; +- KYBER_encap(kyber_ciphertext, secret.data() + 32, secret.size() - 32, +- &peer_kyber_pub); + uint8_t ciphertext[KYBER768_CIPHERTEXT_BYTES]; + uint8_t entropy[KYBER_ENCAP_BYTES]; + RAND_bytes(entropy, sizeof(entropy)); -+ + +- if (!CBB_add_bytes(out_ciphertext, x25519_public_key, + if(!KYBER768_encap(ciphertext, secret.data() + 32, &peer_public_key, entropy, 0)) { + *out_alert = SSL_AD_ILLEGAL_PARAMETER; + return false; + } + if(!CBB_add_bytes(out_public_key, x25519_public_key, -+ sizeof(x25519_public_key)) || + sizeof(x25519_public_key)) || +- !CBB_add_bytes(out_ciphertext, kyber_ciphertext, +- sizeof(kyber_ciphertext))) { + !CBB_add_bytes(out_public_key, ciphertext, sizeof(ciphertext))) { -+ return false; -+ } -+ -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ bool Decap(Array *out_secret, uint8_t *out_alert, + return false; + } + +@@ -256,30 +486,233 @@ class X25519Kyber768KeyShare : public SSLKeyShare { + } + + bool Decap(Array *out_secret, uint8_t *out_alert, +- Span ciphertext) override { + Span peer_key) override { + *out_alert = SSL_AD_INTERNAL_ERROR; + @@ -4298,13 +4245,12 @@ index 694bec11d..3e4d2e7c4 100644 + KYBER768_decap(secret.data() + 32, &kyber_private_key_, + peer_key.data() + 32, peer_key.size() - 32, 0); + - *out_secret = std::move(secret); - return true; - } - - private: - uint8_t x25519_private_key_[32]; -- KYBER_private_key kyber_private_key_; ++ *out_secret = std::move(secret); ++ return true; ++ } ++ ++ private: ++ uint8_t x25519_private_key_[32]; + KYBER768_private_key kyber_private_key_; + uint16_t group_id_; +}; @@ -4381,22 +4327,27 @@ index 694bec11d..3e4d2e7c4 100644 + + bool Decap(Array *out_secret, uint8_t *out_alert, + Span peer_key) override { -+ *out_alert = SSL_AD_INTERNAL_ERROR; -+ -+ Array secret; + *out_alert = SSL_AD_INTERNAL_ERROR; + + Array secret; +- if (!secret.Init(32 + 32)) { + if (!secret.Init(32 + KYBER_KEY_BYTES)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } -+ + return false; + } + +- if (ciphertext.size() != 32 + KYBER_CIPHERTEXT_BYTES || +- !X25519(secret.data(), x25519_private_key_, ciphertext.data())) { + if (peer_key.size() != KYBER768_CIPHERTEXT_BYTES + 32 || + !X25519(secret.data() + 32, x25519_private_key_, + peer_key.data() + KYBER768_CIPHERTEXT_BYTES )) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } -+ + *out_alert = SSL_AD_DECODE_ERROR; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + +- KYBER_decap(secret.data() + 32, secret.size() - 32, ciphertext.data() + 32, +- &kyber_private_key_); + KYBER768_decap(secret.data(), &kyber_private_key_, + peer_key.data(), peer_key.size() - 32, 1); + @@ -4500,12 +4451,13 @@ index 694bec11d..3e4d2e7c4 100644 + KYBER512_decap(secret.data() + 32, &kyber_private_key_, + peer_key.data() + 32, peer_key.size() - 32, 0); + -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ private: -+ uint8_t x25519_private_key_[32]; + *out_secret = std::move(secret); + return true; + } + + private: + uint8_t x25519_private_key_[32]; +- KYBER_private_key kyber_private_key_; + KYBER512_private_key kyber_private_key_; }; @@ -4788,3 +4740,6 @@ index 942dcade1..f31e9e244 100644 !SpeedSpx(selected) || // !SpeedHashToCurve(selected) || // !SpeedTrustToken("TrustToken-Exp1-Batch1", TRUST_TOKEN_experiment_v1(), 1, +-- +2.50.1 (Apple Git-155) + From 47c33f64284a905bd1c26dc59c5eec6f5f38bf8b Mon Sep 17 00:00:00 2001 From: Bas Westerbaan Date: Fri, 3 Oct 2025 13:48:57 +0200 Subject: [PATCH 033/111] pq patch: also enable P256Kyber768Draft00 by default --- boring-sys/patches/boring-pq.patch | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/boring-sys/patches/boring-pq.patch b/boring-sys/patches/boring-pq.patch index 405a0185b..5c55eb8c4 100644 --- a/boring-sys/patches/boring-pq.patch +++ b/boring-sys/patches/boring-pq.patch @@ -1,4 +1,4 @@ -From 6f1b1e1f451e61cd2bda0922eecaa8387397ac5a Mon Sep 17 00:00:00 2001 +From 969fc4fb866c94b6585c323d6e27571e5286f845 Mon Sep 17 00:00:00 2001 From: Bas Westerbaan Date: Thu, 2 Oct 2025 13:07:05 +0200 Subject: [PATCH] Add additional post-quantum key agreements @@ -20,7 +20,7 @@ This patch adds: 2. Supports for P256Kyber768Draft00 under 0xfe32, which we temporarily need for compliance reasons. (Note that this is not the codepoint allocated for that exchange in the IANA table.) - It also enables it in FIPS mode. + Enables by default and in FIPS mode. 3. Support for X25519Kyber768Draft00 under the old codepoint 0xfe31. @@ -46,12 +46,12 @@ Cf RTG-2076 RTG-2051 RTG-2508 RTG-2707 RTG-2607 RTG-3239 include/openssl/nid.h | 12 + include/openssl/ssl.h | 4 + sources.cmake | 2 - - ssl/extensions.cc | 5 + + ssl/extensions.cc | 6 + ssl/ssl_key_share.cc | 525 ++++++- ssl/ssl_lib.cc | 2 +- ssl/ssl_test.cc | 29 +- tool/speed.cc | 162 +- - 18 files changed, 3082 insertions(+), 1158 deletions(-) + 18 files changed, 3083 insertions(+), 1158 deletions(-) delete mode 100644 crypto/kyber/internal.h create mode 100644 crypto/kyber/kyber512.c create mode 100644 crypto/kyber/kyber768.c @@ -3866,7 +3866,7 @@ index ba2f5bc9e..d7ef5153a 100644 crypto/pkcs8/test/no_encryption.p12 crypto/pkcs8/test/nss.p12 diff --git a/ssl/extensions.cc b/ssl/extensions.cc -index b13400097..894396414 100644 +index b13400097..44a2d0f5c 100644 --- a/ssl/extensions.cc +++ b/ssl/extensions.cc @@ -207,6 +207,10 @@ static bool tls1_check_duplicate_extensions(const CBS *cbs) { @@ -3880,11 +3880,12 @@ index b13400097..894396414 100644 return true; default: return false; -@@ -307,6 +311,7 @@ bool ssl_client_hello_get_extension(const SSL_CLIENT_HELLO *client_hello, +@@ -307,6 +311,8 @@ bool ssl_client_hello_get_extension(const SSL_CLIENT_HELLO *client_hello, } static const uint16_t kDefaultGroups[] = { + SSL_GROUP_X25519_MLKEM768, ++ SSL_GROUP_P256_KYBER768_DRAFT00, SSL_GROUP_X25519, SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1, From a095d95238ea5fef8b72f6526afb8fbdf54c998e Mon Sep 17 00:00:00 2001 From: Arjan Singh Bal <46515553+arjan-bal@users.noreply.github.com> Date: Wed, 12 Nov 2025 17:05:45 +0530 Subject: [PATCH 034/111] Fix peer_cert_chain doc --- boring/src/ssl/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 19688c396..bd864bdae 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -3160,7 +3160,7 @@ impl SslRef { /// /// On the client side, the chain includes the leaf certificate, but on the server side it does /// not. Fun! - #[corresponds(SSL_get_peer_certificate)] + #[corresponds(SSL_get_peer_cert_chain)] #[must_use] pub fn peer_cert_chain(&self) -> Option<&StackRef> { #[cfg(feature = "rpk")] From 0c4062ed54dffd1e450df6ddfcd2f9bb31e42e56 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Thu, 20 Nov 2025 11:37:07 +0100 Subject: [PATCH 035/111] Introduce SslCipherRef::protocol_id --- boring/src/ssl/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index bd864bdae..cf84da6a0 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -2465,6 +2465,13 @@ unsafe impl ForeignTypeRef for SslCipherRef { } impl SslCipherRef { + /// Returns the IANA number of the cipher. + #[corresponds(SSL_CIPHER_get_protocol_id)] + #[must_use] + pub fn protocol_id(&self) -> u16 { + unsafe { ffi::SSL_CIPHER_get_protocol_id(self.as_ptr()) } + } + /// Returns the name of the cipher. #[corresponds(SSL_CIPHER_get_name)] #[must_use] From 230f167b80dea6f8bf55343ec16ae1bf5398ac54 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Mon, 24 Nov 2025 10:03:00 +0100 Subject: [PATCH 036/111] Store a &'static SslCipherRef in SslCipher This lets us implement Send and Sync for SslCipherRef and get it for free on SslCipher. --- boring/src/ssl/mod.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index cf84da6a0..e21a87691 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -2405,7 +2405,8 @@ impl ClientHello<'_> { } /// Information about a cipher. -pub struct SslCipher(*mut ffi::SSL_CIPHER); +#[derive(Clone, Copy)] +pub struct SslCipher(&'static SslCipherRef); impl SslCipher { #[corresponds(SSL_get_cipher_by_value)] @@ -2432,12 +2433,12 @@ unsafe impl ForeignType for SslCipher { #[inline] unsafe fn from_ptr(ptr: *mut ffi::SSL_CIPHER) -> SslCipher { - SslCipher(ptr) + SslCipher(SslCipherRef::from_ptr(ptr)) } #[inline] fn as_ptr(&self) -> *mut ffi::SSL_CIPHER { - self.0 + self.0.as_ptr() } } @@ -2445,13 +2446,13 @@ impl Deref for SslCipher { type Target = SslCipherRef; fn deref(&self) -> &SslCipherRef { - unsafe { SslCipherRef::from_ptr(self.0) } + self.0 } } impl DerefMut for SslCipher { fn deref_mut(&mut self) -> &mut SslCipherRef { - unsafe { SslCipherRef::from_ptr_mut(self.0) } + unsafe { SslCipherRef::from_ptr_mut(self.0.as_ptr()) } } } @@ -2460,6 +2461,9 @@ impl DerefMut for SslCipher { /// [`SslCipher`]: struct.SslCipher.html pub struct SslCipherRef(Opaque); +unsafe impl Send for SslCipherRef {} +unsafe impl Sync for SslCipherRef {} + unsafe impl ForeignTypeRef for SslCipherRef { type CType = ffi::SSL_CIPHER; } From 7a0021e169557e205f4b069bb6575029f4267628 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Tue, 25 Nov 2025 09:53:19 +0100 Subject: [PATCH 037/111] Remove DerefMut for SslCipher Not a breaking change because it's a bug that it existed in the first place. --- boring/src/ssl/mod.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index e21a87691..78851c5f4 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -69,7 +69,7 @@ use std::io; use std::io::prelude::*; use std::marker::PhantomData; use std::mem::{self, ManuallyDrop, MaybeUninit}; -use std::ops::{Deref, DerefMut}; +use std::ops::Deref; use std::panic::resume_unwind; use std::path::Path; use std::ptr::{self, NonNull}; @@ -2450,12 +2450,6 @@ impl Deref for SslCipher { } } -impl DerefMut for SslCipher { - fn deref_mut(&mut self) -> &mut SslCipherRef { - unsafe { SslCipherRef::from_ptr_mut(self.0.as_ptr()) } - } -} - /// Reference to an [`SslCipher`]. /// /// [`SslCipher`]: struct.SslCipher.html From 76f47a794ce1f3a048ac47c2d0f93fe12c5bdb39 Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 10 Dec 2025 15:33:54 +0000 Subject: [PATCH 038/111] Can't build with clang-12 to libc++ mismatch --- .github/workflows/ci.yml | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 741dde228..c1aa430f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -306,45 +306,6 @@ jobs: - name: Build for ${{ matrix.target }} run: cargo build --target ${{ matrix.target }} --all-targets - cross-build-fips: - name: Cross build from macOS to Linux (FIPS) - runs-on: macos-13 # Need an Intel (x86_64) runner for Clang 12.0.0 - strategy: - matrix: - include: - - target: x86_64-unknown-linux-gnu - steps: - - uses: actions/checkout@v4 - with: - submodules: 'recursive' - - name: Install Rust (rustup) - run: rustup update stable --no-self-update && rustup default stable && rustup target add ${{ matrix.target }} - shell: bash - - name: Install golang - uses: actions/setup-go@v5 - with: - go-version: '>=1.22.0' - - name: Install ${{ matrix.target }} toolchain - run: brew tap messense/macos-cross-toolchains && brew install ${{ matrix.target }} && brew link x86_64-unknown-linux-gnu - - name: Install Clang-12 - uses: KyleMayes/install-llvm-action@v1 - with: - version: "12.0.0" - directory: ${{ runner.temp }}/llvm - - name: Add clang++-12 link - working-directory: ${{ runner.temp }}/llvm/bin - run: ln -s clang++ clang++-12 - - name: Set BORING_BSSL_FIPS_COMPILER_EXTERNAL_TOOLCHAIN - run: echo "BORING_BSSL_FIPS_COMPILER_EXTERNAL_TOOLCHAIN=$(brew --prefix ${{ matrix.target }})/toolchain" >> $GITHUB_ENV - shell: bash - - name: Set BORING_BSSL_FIPS_SYSROOT - run: echo "BORING_BSSL_FIPS_SYSROOT=$BORING_BSSL_FIPS_COMPILER_EXTERNAL_TOOLCHAIN/${{ matrix.target }}/sysroot" >> $GITHUB_ENV - shell: bash - - name: Set CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER - run: echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=${{ matrix.target }}-gcc" >> $GITHUB_ENV - - name: Build for ${{ matrix.target }} - run: cargo build --target ${{ matrix.target }} --all-targets --features fips - test-features: name: Test features runs-on: ubuntu-latest From 456836aea1f7319b9e78a1c8a1489d941de36573 Mon Sep 17 00:00:00 2001 From: Antoine Bernardeau Date: Sat, 13 Dec 2025 00:19:46 +0100 Subject: [PATCH 039/111] Add boring specific api set_strict_cipher_list to SslContextBuilder --- boring/src/ssl/mod.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 78851c5f4..ea54a6e25 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -1474,6 +1474,27 @@ impl SslContextBuilder { } } + /// Sets the list of supported ciphers for protocols before TLSv1.3 but do not + /// tolerate anything meaningless in the cipher list. + /// + /// The `set_ciphersuites` method controls the cipher suites for TLSv1.3 in OpenSSL. + /// BoringSSL doesn't implement `set_ciphersuites`. + /// See + /// + /// See [`ciphers`] for details on the format. + /// + /// [`ciphers`]: . + pub fn set_strict_cipher_list(&mut self, cipher_list: &str) -> Result<(), ErrorStack> { + let cipher_list = CString::new(cipher_list).unwrap(); + unsafe { + cvt(ffi::SSL_CTX_set_strict_cipher_list( + self.as_ptr(), + cipher_list.as_ptr() as *const _, + )) + .map(|_| ()) + } + } + /// Gets the list of supported ciphers for protocols before TLSv1.3. /// /// See [`ciphers`] for details on the format From 2c7c9b7672ab2e51ca06261d226d52ce6125b545 Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 5 Jan 2026 17:46:42 +0000 Subject: [PATCH 040/111] Fix leak in set_ex_data --- boring/src/ssl/mod.rs | 5 +---- boring/src/ssl/test/mod.rs | 43 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index ea54a6e25..701a089e9 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -1948,13 +1948,10 @@ impl SslContextBuilder { /// /// This can be used to provide data to callbacks registered with the context. Use the /// `SslContext::new_ex_index` method to create an `Index`. - /// - /// Note that if this method is called multiple times with the same index, any previous - /// value stored in the `SslContextBuilder` will be leaked. #[corresponds(SSL_CTX_set_ex_data)] pub fn set_ex_data(&mut self, index: Index, data: T) { unsafe { - self.ctx.set_ex_data(index, data); + self.ctx.replace_ex_data(index, data); } } diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index aded182d1..e6c61cf85 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -1118,3 +1118,46 @@ fn test_ssl_set_compliance() { ssl.set_compliance_policy(CompliancePolicy::NONE) .expect_err("Testing expect err if set compliance policy to NONE"); } + +#[test] +fn ex_data_drop() { + use crate::ssl::SslContextBuilder; + use std::sync::atomic::AtomicU32; + use std::sync::atomic::Ordering::Relaxed; + use std::sync::Arc; + + struct TrackDrop(Arc); + impl Drop for TrackDrop { + fn drop(&mut self) { + self.0.fetch_add(1, Relaxed); + } + } + + let mut ctx = SslContextBuilder::new(SslMethod::tls()).unwrap(); + let index = SslContext::new_ex_index().unwrap(); + let d1 = Arc::new(AtomicU32::new(100)); + let d2 = Arc::new(AtomicU32::new(200)); + let d3 = Arc::new(AtomicU32::new(300)); + ctx.set_ex_data(index, TrackDrop(d1.clone())); + assert_eq!(100, d1.load(Relaxed)); + assert_eq!(200, d2.load(Relaxed)); + ctx.replace_ex_data(index, TrackDrop(d2.clone())); + assert_eq!(101, d1.load(Relaxed)); + assert_eq!(200, d2.load(Relaxed)); + ctx.replace_ex_data(index, TrackDrop(d3.clone())); + assert_eq!(101, d1.load(Relaxed)); + assert_eq!(201, d2.load(Relaxed)); + assert_eq!(300, d3.load(Relaxed)); + drop(ctx); + assert_eq!(101, d1.load(Relaxed)); + assert_eq!(201, d2.load(Relaxed)); + assert_eq!(301, d3.load(Relaxed)); + + let mut ctx2 = SslContextBuilder::new(SslMethod::tls()).unwrap(); + + ctx2.set_ex_data(index, TrackDrop(d1.clone())); + ctx2.set_ex_data(index, TrackDrop(d2.clone())); + drop(ctx2); + assert_eq!(102, d1.load(Relaxed)); + assert_eq!(202, d2.load(Relaxed)); +} From 794d4d5e2e0f9b299e4aedf070c1d1eac199b3ba Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 5 Jan 2026 21:26:56 +0000 Subject: [PATCH 041/111] Safe clone for X509Store Fixes #362 --- boring/src/ssl/mod.rs | 22 +++++++++------------- boring/src/x509/store.rs | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 701a089e9..41db04ae9 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -1203,16 +1203,18 @@ impl SslContextBuilder { } } - /// Use [`set_cert_store_builder`] or [`set_cert_store_ref`] instead. + /// Replaces the context's certificate store, and keeps it immutable. + /// + /// This method allows sharing the `X509Store`, but calls to `cert_store_mut` will panic. /// - /// Replaces the context's certificate store. + /// Use [`set_cert_store_builder`] to set a mutable cert store + /// (there's no way to have both sharing and mutability). #[corresponds(SSL_CTX_set_cert_store)] - #[deprecated(note = "Use set_cert_store_builder or set_cert_store_ref instead")] pub fn set_cert_store(&mut self, cert_store: X509Store) { #[cfg(feature = "rpk")] assert!(!self.is_rpk, "This API is not supported for RPK"); - self.has_shared_cert_store = false; + self.has_shared_cert_store = true; unsafe { ffi::SSL_CTX_set_cert_store(self.as_ptr(), cert_store.into_ptr()); } @@ -1235,14 +1237,7 @@ impl SslContextBuilder { /// This method allows sharing the `X509Store`, but calls to `cert_store_mut` will panic. #[corresponds(SSL_CTX_set_cert_store)] pub fn set_cert_store_ref(&mut self, cert_store: &X509Store) { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); - - self.has_shared_cert_store = true; - unsafe { - ffi::X509_STORE_up_ref(cert_store.as_ptr()); - ffi::SSL_CTX_set_cert_store(self.as_ptr(), cert_store.as_ptr()); - } + self.set_cert_store(cert_store.to_owned()); } /// Controls read ahead behavior. @@ -1771,7 +1766,8 @@ impl SslContextBuilder { assert!( !self.has_shared_cert_store, - "Shared X509Store can't be mutated. Make a new store" + "Shared X509Store can't be mutated. Use set_cert_store_builder() instead of set_cert_store() + or completely finish building the cert store setting it." ); // OTOH, it's not safe to return a shared &X509Store when the builder owns it exclusively diff --git a/boring/src/x509/store.rs b/boring/src/x509/store.rs index 1f6d839b2..e7621e739 100644 --- a/boring/src/x509/store.rs +++ b/boring/src/x509/store.rs @@ -134,6 +134,23 @@ foreign_type_and_impl_send_sync! { pub struct X509Store; } +impl ToOwned for X509StoreRef { + type Owned = X509Store; + + fn to_owned(&self) -> X509Store { + unsafe { + ffi::X509_STORE_up_ref(self.as_ptr()); + X509Store::from_ptr(self.as_ptr()) + } + } +} + +impl Clone for X509Store { + fn clone(&self) -> X509Store { + (**self).to_owned() + } +} + impl X509StoreRef { /// **Warning: this method is unsound** /// @@ -160,12 +177,15 @@ impl X509StoreRef { } #[test] -#[allow(dead_code)] -// X509Store must not implement Clone because `SslContextBuilder::cert_store_mut` lets -// you get a mutable reference to a store that could have been cloned before being -// passed to `SslContextBuilder::set_cert_store`. -fn no_clone_for_x509store() { - trait MustNotImplementClone {} - impl MustNotImplementClone for T {} - impl MustNotImplementClone for X509Store {} +#[should_panic = "Shared X509Store can't be mutated"] +fn set_cert_store_pevents_mutability() { + use crate::ssl::*; + + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + let store = X509StoreBuilder::new().unwrap().build(); + + ctx.set_cert_store(store.clone()); + + // This is bad. + let _aliased_store = ctx.cert_store_mut(); } From 8dfa471c864e40b11e1ff29b1d95858987fc6a78 Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 5 Jan 2026 21:00:37 +0000 Subject: [PATCH 042/111] Remove deprecated X509CheckFlags flag --- boring/src/x509/tests/trusted_first.rs | 8 ++++---- boring/src/x509/verify.rs | 8 +------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/boring/src/x509/tests/trusted_first.rs b/boring/src/x509/tests/trusted_first.rs index 9f49ffe3c..187a49b0b 100644 --- a/boring/src/x509/tests/trusted_first.rs +++ b/boring/src/x509/tests/trusted_first.rs @@ -2,7 +2,7 @@ use crate::stack::Stack; use crate::x509::store::X509StoreBuilder; -use crate::x509::verify::{X509Flags, X509VerifyParamRef}; +use crate::x509::verify::{X509VerifyFlags, X509VerifyParamRef}; use crate::x509::{X509Ref, X509StoreContext, X509VerifyError, X509VerifyResult, X509}; #[test] @@ -43,7 +43,7 @@ fn test_verify_cert() { &leaf, &[&root1, &root2], &[&intermediate, &root1_cross], - |param| param.set_flags(X509Flags::TRUSTED_FIRST), + |param| param.set_flags(X509VerifyFlags::TRUSTED_FIRST), ) ); @@ -53,14 +53,14 @@ fn test_verify_cert() { &leaf, &[&root1, &root2], &[&intermediate, &root1_cross], - |param| param.clear_flags(X509Flags::TRUSTED_FIRST), + |param| param.clear_flags(X509VerifyFlags::TRUSTED_FIRST), ) ); assert_eq!( Ok(()), verify(&leaf, &[&root1], &[&intermediate, &root1_cross], |param| { - param.clear_flags(X509Flags::TRUSTED_FIRST) + param.clear_flags(X509VerifyFlags::TRUSTED_FIRST) }) ); } diff --git a/boring/src/x509/verify.rs b/boring/src/x509/verify.rs index d89c67c83..249563380 100644 --- a/boring/src/x509/verify.rs +++ b/boring/src/x509/verify.rs @@ -20,20 +20,14 @@ bitflags! { const NEVER_CHECK_SUBJECT = ffi::X509_CHECK_FLAG_NEVER_CHECK_SUBJECT as _; #[cfg(feature = "underscore-wildcards")] const UNDERSCORE_WILDCARDS = ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS as _; - - #[deprecated(since = "0.10.6", note = "renamed to NO_WILDCARDS")] - const FLAG_NO_WILDCARDS = ffi::X509_CHECK_FLAG_NO_WILDCARDS as _; } } -#[doc(hidden)] -#[deprecated(note = "X509Flags renamed to X509VerifyFlags")] -pub use X509VerifyFlags as X509Flags; - bitflags! { /// Flags used to check an `X509` certificate. #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)] #[repr(transparent)] + #[doc(alias = "X509Flags")] pub struct X509VerifyFlags: c_ulong { const CB_ISSUER_CHECK = ffi::X509_V_FLAG_CB_ISSUER_CHECK as _; const USE_CHECK_TIME = ffi::X509_V_FLAG_USE_CHECK_TIME as _; From 1999540f7de63ee6d00b75a7a2f84814ff1fcad8 Mon Sep 17 00:00:00 2001 From: southorange0929 Date: Mon, 1 Apr 2024 16:38:46 +0800 Subject: [PATCH 043/111] feat: support openharmony platform --- boring-sys/build/config.rs | 3 +++ boring-sys/build/main.rs | 11 +++++++++++ boring/src/asn1.rs | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/boring-sys/build/config.rs b/boring-sys/build/config.rs index 25aaabf40..d3a1c0e35 100644 --- a/boring-sys/build/config.rs +++ b/boring-sys/build/config.rs @@ -10,6 +10,7 @@ pub(crate) struct Config { pub(crate) target: String, pub(crate) target_arch: String, pub(crate) target_os: String, + pub(crate) target_env: String, pub(crate) features: Features, pub(crate) env: Env, } @@ -46,6 +47,7 @@ impl Config { let target = env::var("TARGET").unwrap(); let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); + let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap(); let features = Features::from_env(); let env = Env::from_env(&host, &target, features.is_fips_like()); @@ -63,6 +65,7 @@ impl Config { target, target_arch, target_os, + target_env, features, env, }; diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 41789cee3..174348eba 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -662,6 +662,17 @@ fn generate_bindings(config: &Config) { builder = builder .clang_arg("--sysroot") .clang_arg(sysroot.display().to_string()); + + let c_target = format!( + "{}-{}-{}", + &config.target_arch, &config.target_os, &config.target_env + ); + + // we need to add special platform header file with env for support cross building + let header = format!("{}/usr/include/{}", sysroot.display().to_string(), c_target); + if PathBuf::from(&header).is_dir() { + builder = builder.clang_arg("-I").clang_arg(&header); + } } let headers = [ diff --git a/boring/src/asn1.rs b/boring/src/asn1.rs index d9929ef72..473f72958 100644 --- a/boring/src/asn1.rs +++ b/boring/src/asn1.rs @@ -315,7 +315,7 @@ impl Asn1Time { ffi::init(); unsafe { - let handle = cvt_p(ffi::ASN1_TIME_set(ptr::null_mut(), time))?; + let handle = cvt_p(ffi::ASN1_TIME_set(ptr::null_mut(), time.into()))?; Ok(Asn1Time::from_ptr(handle)) } } From 9fcdba89f7a50a161e0debe43e9c11814e2ba90b Mon Sep 17 00:00:00 2001 From: southorange0929 Date: Mon, 1 Apr 2024 16:44:29 +0800 Subject: [PATCH 044/111] docs: add docs --- boring/src/asn1.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/boring/src/asn1.rs b/boring/src/asn1.rs index 473f72958..dabe29b6e 100644 --- a/boring/src/asn1.rs +++ b/boring/src/asn1.rs @@ -315,6 +315,8 @@ impl Asn1Time { ffi::init(); unsafe { + // for higher musl version, need to convert i32 to i64 + // https://github.com/rust-lang/libc/issues/1848 let handle = cvt_p(ffi::ASN1_TIME_set(ptr::null_mut(), time.into()))?; Ok(Asn1Time::from_ptr(handle)) } From 2f531531280482f64706230dc20347b3bfb5fad8 Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 10 Dec 2025 17:30:03 +0000 Subject: [PATCH 045/111] Smaller cache, quicker rustup --- .github/workflows/ci.yml | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1aa430f2..d9df00e40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,8 @@ on: env: RUSTFLAGS: -Dwarnings RUST_BACKTRACE: 1 + CARGO_INCREMENTAL: 0 + CARGO_PROFILE_DEV_DEBUG: 0 jobs: rustfmt: @@ -31,7 +33,7 @@ jobs: with: submodules: 'recursive' - name: Install Rust - run: rustup update --no-self-update stable && rustup default stable && rustup component add clippy + run: rustup toolchain add stable --no-self-update --component clippy && rustup default stable - name: Get rust version id: rust-version run: | @@ -39,24 +41,17 @@ jobs: - name: Cache cargo index uses: actions/cache@v4 with: - path: ~/.cargo/registry/index - key: index-${{ runner.os }}-${{ github.run_number }} - restore-keys: | - index-${{ runner.os }}- - - name: Create lockfile - run: cargo generate-lockfile - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry/cache - key: registry-${{ runner.os }}-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }} + path: | + ~/.cargo/registry/index + ~/.cargo/registry/cache + key: index-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.toml') }} - name: Fetch dependencies run: cargo fetch - name: Cache target directory uses: actions/cache@v4 with: path: target - key: clippy-target-${{ runner.os }}-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }} + key: clippy-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }} - name: Run clippy run: cargo clippy --all --all-targets - name: Check docs @@ -290,7 +285,7 @@ jobs: with: submodules: 'recursive' - name: Install Rust (rustup) - run: rustup update stable --no-self-update && rustup default stable && rustup target add ${{ matrix.target }} + run: rustup toolchain install stable --no-self-update --profile minimal --target ${{ matrix.target }} && rustup default stable shell: bash - name: Install golang uses: actions/setup-go@v5 @@ -303,12 +298,15 @@ jobs: shell: bash - name: Set CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER run: echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=${{ matrix.target }}-gcc" >> $GITHUB_ENV + shell: bash - name: Build for ${{ matrix.target }} run: cargo build --target ${{ matrix.target }} --all-targets test-features: name: Test features runs-on: ubuntu-latest + env: + CARGO_INCREMENTAL: 1 steps: - uses: actions/checkout@v4 with: From e98f4289e2fc5a2fcfdf8f5373b242dc1e085222 Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 10 Dec 2025 17:39:44 +0000 Subject: [PATCH 046/111] Cached index in tests --- .github/workflows/ci.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9df00e40..77c85a4d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -221,12 +221,23 @@ jobs: - name: Set Android Linker path if: endsWith(matrix.thing, '-android') run: echo "CARGO_TARGET_$(echo ${{ matrix.target }} | tr \\-a-z _A-Z)_LINKER=$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/$(echo ${{ matrix.target }} | sed s/armv7/armv7a/)21-clang++" >> "$GITHUB_ENV" + - name: Get rust version + id: rust-version + run: | + echo "version=$(rustc --version)" >> $GITHUB_OUTPUT + - name: Prepopulate cargo index + uses: actions/cache/restore@v4 + with: + path: | + ~/.cargo/registry/index + ~/.cargo/registry/cache + key: index-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.toml') }} - name: Build tests # We `build` because we want the linker to verify we are cross-compiling correctly for check-only targets. run: cargo build --target ${{ matrix.target }} --tests ${{ matrix.extra_test_args }} shell: bash env: ${{ matrix.custom_env }} - - name: Run tests + - name: Run tests (skip=${{ matrix.check_only }}) if: "!matrix.check_only" run: cargo test --target ${{ matrix.target }} ${{ matrix.extra_test_args }} shell: bash From 2d2052ee3243bdfdeaab3e92ad2d9adda7b7ea0c Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 29 Dec 2025 17:03:28 +0000 Subject: [PATCH 047/111] set_strict_cipher_list docs --- boring/src/ssl/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 41db04ae9..76f036ade 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -1448,7 +1448,9 @@ impl SslContextBuilder { unsafe { cvt(ffi::SSL_CTX_use_PrivateKey(self.as_ptr(), key.as_ptr())).map(|_| ()) } } - /// Sets the list of supported ciphers for protocols before TLSv1.3. + /// Sets the list of supported ciphers for protocols before TLSv1.3, ignoring meaningless entries. + /// + /// See [`SslContextBuilder::set_strict_cipher_list()`]. /// /// The `set_ciphersuites` method controls the cipher suites for TLSv1.3 in OpenSSL. /// BoringSSL doesn't implement `set_ciphersuites`. @@ -1479,6 +1481,7 @@ impl SslContextBuilder { /// See [`ciphers`] for details on the format. /// /// [`ciphers`]: . + #[corresponds(SSL_CTX_set_strict_cipher_list)] pub fn set_strict_cipher_list(&mut self, cipher_list: &str) -> Result<(), ErrorStack> { let cipher_list = CString::new(cipher_list).unwrap(); unsafe { From 04114a8868af5170c7126bec98e7b51a6ec113b3 Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 29 Dec 2025 17:01:46 +0000 Subject: [PATCH 048/111] Fewer unwrap()s --- boring-sys/build/main.rs | 55 +++++++++++++++++----------------------- boring/src/macros.rs | 3 ++- boring/src/ssl/mod.rs | 4 +-- 3 files changed, 27 insertions(+), 35 deletions(-) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 174348eba..1e4ed2fb0 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -135,7 +135,8 @@ fn get_boringssl_source_path(config: &Config) -> &PathBuf { } let _ = fs::remove_dir_all(&src_path); - fs_extra::dir::copy(submodule_path, &config.out_dir, &Default::default()).unwrap(); + fs_extra::dir::copy(submodule_path, &config.out_dir, &Default::default()) + .expect("out dir copy"); // NOTE: .git can be both file and dir, depening on whether it was copied from a submodule // or created by the patches code. @@ -370,31 +371,23 @@ fn get_extra_clang_args_for_bindgen(config: &Config) -> Vec { let mut params = Vec::new(); // Add platform-specific parameters. - #[allow(clippy::single_match)] match &*config.target_os { "ios" | "macos" => { // When cross-compiling for Apple targets, tell bindgen to use SDK sysroot, // and *don't* use system headers of the host macOS. let sdk = get_apple_sdk_name(config); - let output = std::process::Command::new("xcrun") - .args(["--show-sdk-path", "--sdk", sdk]) - .output() - .unwrap(); - if !output.status.success() { - if let Some(exit_code) = output.status.code() { - println!("cargo:warning=xcrun failed: exit code {exit_code}"); - } else { - println!("cargo:warning=xcrun failed: killed"); + match run_command(Command::new("xcrun").args(["--show-sdk-path", "--sdk", sdk])) { + Ok(output) => { + let sysroot = std::str::from_utf8(&output.stdout).expect("xcrun output"); + params.push("-isysroot".to_string()); + // There is typically a newline at the end which confuses clang. + params.push(sysroot.trim_end().to_string()); + } + Err(e) => { + println!("cargo:warning={e}"); + // Uh... let's try anyway, I guess? } - std::io::stderr().write_all(&output.stderr).unwrap(); - // Uh... let's try anyway, I guess? - return params; } - let mut sysroot = String::from_utf8(output.stdout).unwrap(); - // There is typically a newline at the end which confuses clang. - sysroot.truncate(sysroot.trim_end().len()); - params.push("-isysroot".to_string()); - params.push(sysroot); } "android" => { let mut android_sysroot = config @@ -405,20 +398,18 @@ fn get_extra_clang_args_for_bindgen(config: &Config) -> Vec { android_sysroot.extend(["toolchains", "llvm", "prebuilt"]); - let toolchain = match pick_best_android_ndk_toolchain(&android_sysroot) { - Ok(toolchain) => toolchain, + match pick_best_android_ndk_toolchain(&android_sysroot) { + Ok(toolchain) => { + android_sysroot.push(toolchain); + android_sysroot.push("sysroot"); + params.push("--sysroot".to_string()); + params.push(android_sysroot.into_os_string().into_string().unwrap()); + } Err(e) => { - println!( - "cargo:warning=failed to find prebuilt Android NDK toolchain for bindgen: {e}" - ); + println!("cargo:warning=failed to find prebuilt Android NDK toolchain for bindgen: {e}"); // Uh... let's try anyway, I guess? - return params; } - }; - android_sysroot.push(toolchain); - android_sysroot.push("sysroot"); - params.push("--sysroot".to_string()); - params.push(android_sysroot.into_os_string().into_string().unwrap()); + } } _ => {} } @@ -502,8 +493,8 @@ fn apply_patch(config: &Config, patch_name: &str) -> io::Result<()> { fn run_command(command: &mut Command) -> io::Result { let out = command.output()?; - println!("{}", std::str::from_utf8(&out.stdout).unwrap()); - eprintln!("{}", std::str::from_utf8(&out.stderr).unwrap()); + std::io::stderr().write_all(&out.stderr)?; + std::io::stdout().write_all(&out.stdout)?; if !out.status.success() { let err = match out.status.code() { diff --git a/boring/src/macros.rs b/boring/src/macros.rs index b6cbeb8fc..f0511b178 100644 --- a/boring/src/macros.rs +++ b/boring/src/macros.rs @@ -7,7 +7,8 @@ macro_rules! private_key_from_pem { unsafe { ffi::init(); let bio = crate::bio::MemBioSlice::new(pem)?; - let passphrase = ::std::ffi::CString::new(passphrase).unwrap(); + let passphrase = ::std::ffi::CString::new(passphrase) + .map_err(crate::error::ErrorStack::internal_error)?; cvt_p($f(bio.as_ptr(), ptr::null_mut(), None, diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 76f036ade..1f2026504 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -1461,7 +1461,7 @@ impl SslContextBuilder { /// [`ciphers`]: https://www.openssl.org/docs/manmaster/apps/ciphers.html #[corresponds(SSL_CTX_set_cipher_list)] pub fn set_cipher_list(&mut self, cipher_list: &str) -> Result<(), ErrorStack> { - let cipher_list = CString::new(cipher_list).unwrap(); + let cipher_list = CString::new(cipher_list).map_err(ErrorStack::internal_error)?; unsafe { cvt(ffi::SSL_CTX_set_cipher_list( self.as_ptr(), @@ -1483,7 +1483,7 @@ impl SslContextBuilder { /// [`ciphers`]: . #[corresponds(SSL_CTX_set_strict_cipher_list)] pub fn set_strict_cipher_list(&mut self, cipher_list: &str) -> Result<(), ErrorStack> { - let cipher_list = CString::new(cipher_list).unwrap(); + let cipher_list = CString::new(cipher_list).map_err(ErrorStack::internal_error)?; unsafe { cvt(ffi::SSL_CTX_set_strict_cipher_list( self.as_ptr(), From fc4ccbee1d68bd1f2c878b8fb893a49841cdda42 Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 7 Jan 2026 19:27:04 +0000 Subject: [PATCH 049/111] Clippy CI blocker --- boring-sys/build/main.rs | 2 +- boring/src/asn1.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 1e4ed2fb0..798d5984b 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -660,7 +660,7 @@ fn generate_bindings(config: &Config) { ); // we need to add special platform header file with env for support cross building - let header = format!("{}/usr/include/{}", sysroot.display().to_string(), c_target); + let header = format!("{}/usr/include/{}", sysroot.display(), c_target); if PathBuf::from(&header).is_dir() { builder = builder.clang_arg("-I").clang_arg(&header); } diff --git a/boring/src/asn1.rs b/boring/src/asn1.rs index dabe29b6e..21fbc48c4 100644 --- a/boring/src/asn1.rs +++ b/boring/src/asn1.rs @@ -317,6 +317,7 @@ impl Asn1Time { unsafe { // for higher musl version, need to convert i32 to i64 // https://github.com/rust-lang/libc/issues/1848 + #[allow(clippy::useless_conversion)] let handle = cvt_p(ffi::ASN1_TIME_set(ptr::null_mut(), time.into()))?; Ok(Asn1Time::from_ptr(handle)) } From 41b4d6b77ebd9666f1e34d2324ef86560e53479e Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 7 Jan 2026 19:17:16 +0000 Subject: [PATCH 050/111] Warn about BORING_BSSL_FIPS_PATH vs BORING_BSSL_PATH --- boring-sys/build/config.rs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/boring-sys/build/config.rs b/boring-sys/build/config.rs index d3a1c0e35..09c188c8a 100644 --- a/boring-sys/build/config.rs +++ b/boring-sys/build/config.rs @@ -123,9 +123,6 @@ impl Features { impl Env { fn from_env(host: &str, target: &str, is_fips_like: bool) -> Self { - const NORMAL_PREFIX: &str = "BORING_BSSL"; - const FIPS_PREFIX: &str = "BORING_BSSL_FIPS"; - let var_prefix = if host == target { "HOST" } else { "TARGET" }; let target_with_underscores = target.replace('-', "_"); @@ -137,25 +134,34 @@ impl Env { let target_var = |name: &str| target_only_var(name).or_else(|| var(name)); let boringssl_var = |name: &str| { + const BORING_BSSL_PREFIX: &str = "BORING_BSSL_"; + const BORING_BSSL_FIPS_PREFIX: &str = "BORING_BSSL_FIPS_"; + // The passed name is the non-fips version of the environment variable, // to help look for them in the repository. - assert!(name.starts_with(NORMAL_PREFIX)); + assert!(name.starts_with(BORING_BSSL_PREFIX)); + let non_fips = target_var(name); if is_fips_like { - target_var(&name.replace(NORMAL_PREFIX, FIPS_PREFIX)) + let fips_name = name.replace(BORING_BSSL_PREFIX, BORING_BSSL_FIPS_PREFIX); + let fips = target_var(&fips_name); + if fips.is_none() && non_fips.is_some() { + println!("cargo:warning=env var {name} ignored, because FIPS is enabled. Set {fips_name} instead."); + } + fips } else { - target_var(name) + non_fips } }; Self { - path: boringssl_var("BORING_BSSL_PATH").map(PathBuf::from), - include_path: boringssl_var("BORING_BSSL_INCLUDE_PATH").map(PathBuf::from), - source_path: boringssl_var("BORING_BSSL_SOURCE_PATH").map(PathBuf::from), - assume_patched: boringssl_var("BORING_BSSL_ASSUME_PATCHED") + path: boringssl_var("BORING_BSSL_PATH").map(PathBuf::from), // gets BORING_BSSL_FIPS_PATH if fips is enabled + include_path: boringssl_var("BORING_BSSL_INCLUDE_PATH").map(PathBuf::from), // gets BORING_BSSL_FIPS_INCLUDE_PATH if fips is enabled + source_path: boringssl_var("BORING_BSSL_SOURCE_PATH").map(PathBuf::from), // gets BORING_BSSL_FIPS_SOURCE_PATH if fips is enabled + assume_patched: boringssl_var("BORING_BSSL_ASSUME_PATCHED") // gets BORING_BSSL_FIPS_ASSUME_PATCHED if fips is enabled .is_some_and(|v| !v.is_empty()), - sysroot: boringssl_var("BORING_BSSL_SYSROOT").map(PathBuf::from), - compiler_external_toolchain: boringssl_var("BORING_BSSL_COMPILER_EXTERNAL_TOOLCHAIN") + sysroot: boringssl_var("BORING_BSSL_SYSROOT").map(PathBuf::from), // gets BORING_BSSL_FIPS_SYSROOT if fips is enabled + compiler_external_toolchain: boringssl_var("BORING_BSSL_COMPILER_EXTERNAL_TOOLCHAIN") // gets BORING_BSSL_FIPS_COMPILER_EXTERNAL_TOOLCHAIN if fips is enabled .map(PathBuf::from), debug: target_var("DEBUG"), opt_level: target_var("OPT_LEVEL"), From 9bb20132eda797afca0751947ad4c1590936c5bc Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 7 Jan 2026 20:42:05 +0000 Subject: [PATCH 051/111] Cross-platform Cargo registry cache --- .gitattributes | 1 + .github/workflows/ci.yml | 38 ++++++++++++++++++++++---------------- 2 files changed, 23 insertions(+), 16 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..a7bce3105 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.toml text eol=lf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77c85a4d2..ea6a151e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,8 @@ jobs: clippy: name: clippy runs-on: ubuntu-latest + env: + CARGO_HOME: ${{ github.workspace }}/.cache/cargo steps: - uses: actions/checkout@v4 with: @@ -36,15 +38,17 @@ jobs: run: rustup toolchain add stable --no-self-update --component clippy && rustup default stable - name: Get rust version id: rust-version + shell: bash run: | echo "version=$(rustc --version)" >> $GITHUB_OUTPUT - name: Cache cargo index uses: actions/cache@v4 with: path: | - ~/.cargo/registry/index - ~/.cargo/registry/cache + .cache/cargo/registry/index + .cache/cargo/registry/cache key: index-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.toml') }} + enableCrossOsArchive: true - name: Fetch dependencies run: cargo fetch - name: Cache target directory @@ -188,15 +192,28 @@ jobs: os: windows-latest # CI's Windows doesn't have required root certs extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring - + env: + CARGO_HOME: ${{ github.workspace }}/.cache/cargo steps: - uses: actions/checkout@v4 with: submodules: 'recursive' - name: Install Rust (rustup) - run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} + run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} && rustup target add ${{ matrix.target }} + shell: bash + - name: Get rust version + id: rust-version shell: bash - - run: rustup target add ${{ matrix.target }} + run: | + echo "version=$(rustc --version)" >> $GITHUB_OUTPUT + - name: Prepopulate cargo index + uses: actions/cache/restore@v4 + with: + path: | + .cache/cargo/registry/index + .cache/cargo/registry/cache + key: index-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.toml') }} + enableCrossOsArchive: true - name: Install golang uses: actions/setup-go@v5 with: @@ -221,17 +238,6 @@ jobs: - name: Set Android Linker path if: endsWith(matrix.thing, '-android') run: echo "CARGO_TARGET_$(echo ${{ matrix.target }} | tr \\-a-z _A-Z)_LINKER=$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/$(echo ${{ matrix.target }} | sed s/armv7/armv7a/)21-clang++" >> "$GITHUB_ENV" - - name: Get rust version - id: rust-version - run: | - echo "version=$(rustc --version)" >> $GITHUB_OUTPUT - - name: Prepopulate cargo index - uses: actions/cache/restore@v4 - with: - path: | - ~/.cargo/registry/index - ~/.cargo/registry/cache - key: index-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.toml') }} - name: Build tests # We `build` because we want the linker to verify we are cross-compiling correctly for check-only targets. run: cargo build --target ${{ matrix.target }} --tests ${{ matrix.extra_test_args }} From a88541930f491668807342032fa12a6f630751bf Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 16 Jan 2026 17:12:32 +0000 Subject: [PATCH 052/111] cargo publish is target-specific --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea6a151e9..b93891d90 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -259,7 +259,9 @@ jobs: # # Both of these may no longer be the case after updating the BoringSSL # submodules to a new revision, so it's important to test this on CI. - run: cargo publish --dry-run -p boring-sys + run: cargo publish --dry-run --target ${{ matrix.target }} -p boring-sys + shell: bash + env: ${{ matrix.custom_env }} test-fips: name: Test FIPS integration From 7cb075cc6f8241a25173dc056d6acbf572226672 Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 16 Jan 2026 17:28:34 +0000 Subject: [PATCH 053/111] Include err.h in FFI bindings --- boring-sys/build/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 798d5984b..6f53cd827 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -679,6 +679,7 @@ fn generate_bindings(config: &Config) { "curve25519.h", "des.h", "dtls1.h", + "err.h", "hkdf.h", "hpke.h", "hmac.h", From 3cf9b4f94354e22a96ecca2281acdeec726162f1 Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 19 Jan 2026 22:41:46 +0000 Subject: [PATCH 054/111] Use fips-build-compatible ERR_add_error_data --- boring/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boring/src/error.rs b/boring/src/error.rs index 1e8f79467..5087d10fb 100644 --- a/boring/src/error.rs +++ b/boring/src/error.rs @@ -193,7 +193,7 @@ impl Error { self.line, ); if let Some(cstr) = self.data_cstr() { - ffi::ERR_set_error_data(cstr.as_ptr().cast_mut(), ffi::ERR_FLAG_STRING); + ffi::ERR_add_error_data(1, cstr.as_ptr().cast_mut()); } } } From c5ed1d1319a5606c9d707ae700204be58adb2d14 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Sat, 20 Dec 2025 12:56:09 +0100 Subject: [PATCH 055/111] Define __STDC_FORMAT_MACROS when cross-building from macOS to Linux --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b93891d90..990b18fbc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -318,6 +318,8 @@ jobs: - name: Set CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER run: echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=${{ matrix.target }}-gcc" >> $GITHUB_ENV shell: bash + - name: Set CXXFLAGS + run: echo "CXXFLAGS=-D__STDC_FORMAT_MACROS" >> $GITHUB_ENV - name: Build for ${{ matrix.target }} run: cargo build --target ${{ matrix.target }} --all-targets From e3483004e5242b510dc011e64bcdd0699aa0e59f Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Sat, 20 Dec 2025 13:06:05 +0100 Subject: [PATCH 056/111] Pass -msse2 to i686 platforms on CI In upstream commit 56d3ad9d23bc130aa9404bfdd1957fe81b3ba498, BoringSSL stopped assuming SSE2 support for i688 platforms, requiring users to explicitly pass -msse2. ``` target/i686-unknown-linux-gnu/debug/build/boring-sys-3edff0f2746d7cbb/out/boringssl/crypto/fipsmodule/../internal.h:120:2: error: #error "x86 assembly requires SSE2. Build with -msse2 (recommended), or disable assembly optimizations with -DOPENSSL_NO_ASM." ``` --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 990b18fbc..4d2ffaee2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -108,6 +108,8 @@ jobs: rust: stable os: ubuntu-latest check_only: true + custom_env: + CXXFLAGS: -msse2 - thing: x86_64-android target: x86_64-linux-android rust: stable @@ -138,6 +140,8 @@ jobs: rust: stable os: ubuntu-latest apt_packages: gcc-multilib g++-multilib + custom_env: + CXXFLAGS: -msse2 - thing: arm-linux target: arm-unknown-linux-gnueabi rust: stable @@ -184,6 +188,8 @@ jobs: target: i686-pc-windows-msvc rust: stable-x86_64-msvc os: windows-latest + custom_env: + CXXFLAGS: -msse2 # CI's Windows doesn't have required root certs extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring - thing: x86_64-msvc From c299b1476becf964459e4b8b2de86a09276e5bbf Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Sat, 20 Dec 2025 15:53:35 +0100 Subject: [PATCH 057/111] Never use the debug CRT on Windows See https://github.com/rust-lang/cmake-rs/pull/30#issuecomment-2968661195. --- boring-sys/build/main.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 6f53cd827..a3e0f6db6 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -200,11 +200,19 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { let src_path = get_boringssl_source_path(config); let mut boringssl_cmake = cmake::Config::new(src_path); - if config.host == config.target { + if config.env.cmake_toolchain_file.is_some() { return boringssl_cmake; } - if config.env.cmake_toolchain_file.is_some() { + if config.target_os == "windows" { + // Explicitly use the non-debug CRT. + // This is required now because newest BoringSSL requires CMake 3.22 which + // uses the new logic with CMAKE_MSVC_RUNTIME_LIBRARY introduced in CMake 3.15. + // https://github.com/rust-lang/cmake-rs/pull/30#issuecomment-2969758499 + boringssl_cmake.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreadedDLL"); + } + + if config.host == config.target { return boringssl_cmake; } From 3ac364abc465890c0eb1cb9d841aa59f18bec791 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Mon, 22 Dec 2025 10:55:01 +0100 Subject: [PATCH 058/111] Fix Android builds For starters, they should link against libc++, as they have always intended to use STL "c++_shared". https://github.com/Kitware/CMake/blob/824f2a7a200f9d3e41a2b68be2d5e1cc678afffa/Modules/Platform/Android-Common.cmake#L70-L75 Also, fix the variable names we define, as far as I know cmake never cared about ANDROID_NATIVE_API_LEVEL nor ANDROID_STL. --- boring-sys/build/config.rs | 3 +++ boring-sys/build/main.rs | 18 +++++++----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/boring-sys/build/config.rs b/boring-sys/build/config.rs index 09c188c8a..e344f9acb 100644 --- a/boring-sys/build/config.rs +++ b/boring-sys/build/config.rs @@ -10,6 +10,7 @@ pub(crate) struct Config { pub(crate) target: String, pub(crate) target_arch: String, pub(crate) target_os: String, + pub(crate) unix: bool, pub(crate) target_env: String, pub(crate) features: Features, pub(crate) env: Env, @@ -48,6 +49,7 @@ impl Config { let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap(); + let unix = env::var("CARGO_CFG_UNIX").is_ok(); let features = Features::from_env(); let env = Env::from_env(&host, &target, features.is_fips_like()); @@ -65,6 +67,7 @@ impl Config { target, target_arch, target_os, + unix, target_env, features, env, diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index a3e0f6db6..4fbf2d53b 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -1,5 +1,4 @@ use fslock::LockFile; -use std::env; use std::ffi::OsString; use std::fs; use std::io; @@ -263,8 +262,8 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { boringssl_cmake.define("CMAKE_TOOLCHAIN_FILE", toolchain_file); // 21 is the minimum level tested. You can give higher value. - boringssl_cmake.define("ANDROID_NATIVE_API_LEVEL", "21"); - boringssl_cmake.define("ANDROID_STL", "c++_shared"); + boringssl_cmake.define("CMAKE_SYSTEM_VERSION", "21"); + boringssl_cmake.define("CMAKE_ANDROID_STL_TYPE", "c++_shared"); } "macos" => { @@ -552,14 +551,11 @@ fn get_cpp_runtime_lib(config: &Config) -> Option { return cpp_lib.clone().into_string().ok(); } - // TODO(rmehra): figure out how to do this for windows - if env::var_os("CARGO_CFG_UNIX").is_some() { - match env::var("CARGO_CFG_TARGET_OS").unwrap().as_ref() { - "macos" | "ios" | "freebsd" => Some("c++".into()), - _ => Some("stdc++".into()), - } - } else { - None + match &*config.target_os { + "macos" | "ios" | "freebsd" | "android" => Some("c++".into()), + _ if config.unix => Some("stdc++".into()), + // TODO(rmehra): figure out how to do this for windows + _ => None, } } From acd8cbaf028314513165dc643b26fa87de99fc4c Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Mon, 22 Dec 2025 11:26:14 +0100 Subject: [PATCH 059/111] Fix MinGW builds Those need to link against libstdc++. --- boring-sys/build/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 4fbf2d53b..0f7053cb8 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -553,7 +553,7 @@ fn get_cpp_runtime_lib(config: &Config) -> Option { match &*config.target_os { "macos" | "ios" | "freebsd" | "android" => Some("c++".into()), - _ if config.unix => Some("stdc++".into()), + _ if config.unix || config.target_env == "gnu" => Some("stdc++".into()), // TODO(rmehra): figure out how to do this for windows _ => None, } From 11fec56d55428ccee536f7176c5411602a1663e6 Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 20 Jan 2026 00:16:48 +0000 Subject: [PATCH 060/111] Flip is_rpk to has_x509_support --- boring/src/ssl/connector.rs | 2 +- boring/src/ssl/error.rs | 2 +- boring/src/ssl/mod.rs | 156 +++++++++++------------------------- 3 files changed, 51 insertions(+), 109 deletions(-) diff --git a/boring/src/ssl/connector.rs b/boring/src/ssl/connector.rs index 111b45c2a..dc9c35e6b 100644 --- a/boring/src/ssl/connector.rs +++ b/boring/src/ssl/connector.rs @@ -225,7 +225,7 @@ impl ConnectConfiguration { } #[cfg(feature = "rpk")] - let verify_hostname = !self.ssl.ssl_context().is_rpk() && self.verify_hostname; + let verify_hostname = self.ssl.ssl_context().has_x509_support() && self.verify_hostname; #[cfg(not(feature = "rpk"))] let verify_hostname = self.verify_hostname; diff --git a/boring/src/ssl/error.rs b/boring/src/ssl/error.rs index 5acad8200..1289c7484 100644 --- a/boring/src/ssl/error.rs +++ b/boring/src/ssl/error.rs @@ -250,7 +250,7 @@ fn fmt_mid_handshake_error( prefix: &str, ) -> fmt::Result { #[cfg(feature = "rpk")] - if s.ssl().ssl_context().is_rpk() { + if !s.ssl().ssl_context().has_x509_support() { write!(f, "{}", prefix)?; return write!(f, " {}", s.error()); } diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 1f2026504..1509d724a 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -969,7 +969,7 @@ impl SslContextBuilder { let ctx = SslContext::from_ptr(ctx); SslContextBuilder { #[cfg(feature = "rpk")] - is_rpk: ctx.is_rpk(), + is_rpk: !ctx.has_x509_support(), has_shared_cert_store: false, ctx, } @@ -1005,8 +1005,7 @@ impl SslContextBuilder { where F: Fn(&mut X509StoreContextRef) -> bool + 'static + Sync + Send, { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); // NOTE(jlarisch): Q: Why don't we wrap the callback in an Arc, since // `set_verify_callback` does? @@ -1027,8 +1026,7 @@ impl SslContextBuilder { /// Configures the certificate verification method for new connections. #[corresponds(SSL_CTX_set_verify)] pub fn set_verify(&mut self, mode: SslVerifyMode) { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { ffi::SSL_CTX_set_verify(self.as_ptr(), mode.bits() as c_int, None); @@ -1056,8 +1054,7 @@ impl SslContextBuilder { where F: Fn(bool, &mut X509StoreContextRef) -> bool + 'static + Sync + Send, { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { self.replace_ex_data(SslContext::cached_ex_index::(), callback); @@ -1084,8 +1081,7 @@ impl SslContextBuilder { where F: Fn(&mut SslRef) -> Result<(), SslVerifyError> + 'static + Sync + Send, { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { self.replace_ex_data(SslContext::cached_ex_index::(), callback); @@ -1166,8 +1162,7 @@ impl SslContextBuilder { + Sync + Send, { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { self.replace_ex_data(SslContext::cached_ex_index::(), callback); @@ -1180,8 +1175,7 @@ impl SslContextBuilder { /// If the peer's certificate chain is longer than this value, verification will fail. #[corresponds(SSL_CTX_set_verify_depth)] pub fn set_verify_depth(&mut self, depth: u32) { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { ffi::SSL_CTX_set_verify_depth(self.as_ptr(), depth as c_int); @@ -1191,8 +1185,7 @@ impl SslContextBuilder { /// Sets a custom certificate store for verifying peer certificates. #[corresponds(SSL_CTX_set0_verify_cert_store)] pub fn set_verify_cert_store(&mut self, cert_store: X509Store) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { cvt( @@ -1211,8 +1204,7 @@ impl SslContextBuilder { /// (there's no way to have both sharing and mutability). #[corresponds(SSL_CTX_set_cert_store)] pub fn set_cert_store(&mut self, cert_store: X509Store) { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); self.has_shared_cert_store = true; unsafe { @@ -1223,8 +1215,7 @@ impl SslContextBuilder { /// Replaces the context's certificate store, and allows mutating the store afterwards. #[corresponds(SSL_CTX_set_cert_store)] pub fn set_cert_store_builder(&mut self, cert_store: X509StoreBuilder) { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); self.has_shared_cert_store = false; unsafe { @@ -1278,8 +1269,7 @@ impl SslContextBuilder { /// if present, or defaults specified at OpenSSL build time otherwise. #[corresponds(SSL_CTX_set_default_verify_paths)] pub fn set_default_verify_paths(&mut self) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { cvt(ffi::SSL_CTX_set_default_verify_paths(self.as_ptr())).map(|_| ()) } } @@ -1289,8 +1279,7 @@ impl SslContextBuilder { /// The file should contain a sequence of PEM-formatted CA certificates. #[corresponds(SSL_CTX_load_verify_locations)] pub fn set_ca_file>(&mut self, file: P) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); let file = CString::new(file.as_ref().as_os_str().as_encoded_bytes()) .map_err(ErrorStack::internal_error)?; @@ -1310,8 +1299,7 @@ impl SslContextBuilder { /// as trusted by this method. #[corresponds(SSL_CTX_set_client_CA_list)] pub fn set_client_ca_list(&mut self, list: Stack) { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { ffi::SSL_CTX_set_client_CA_list(self.as_ptr(), list.as_ptr()); @@ -1323,8 +1311,7 @@ impl SslContextBuilder { /// requesting client-side TLS authentication. #[corresponds(SSL_CTX_add_client_CA)] pub fn add_client_ca(&mut self, cacert: &X509Ref) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { cvt(ffi::SSL_CTX_add_client_CA(self.as_ptr(), cacert.as_ptr())).map(|_| ()) } } @@ -1361,8 +1348,7 @@ impl SslContextBuilder { file: P, file_type: SslFiletype, ) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); let file = CString::new(file.as_ref().as_os_str().as_encoded_bytes()) .map_err(ErrorStack::internal_error)?; @@ -1411,8 +1397,7 @@ impl SslContextBuilder { /// `set_certificate` to a trusted root. #[corresponds(SSL_CTX_add_extra_chain_cert)] pub fn add_extra_chain_cert(&mut self, cert: X509) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { cvt(ffi::SSL_CTX_add_extra_chain_cert(self.as_ptr(), cert.into_ptr()) as c_int)?; @@ -1747,8 +1732,7 @@ impl SslContextBuilder { #[corresponds(SSL_CTX_get_cert_store)] #[must_use] pub fn cert_store(&self) -> &X509StoreBuilderRef { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); unsafe { X509StoreBuilderRef::from_ptr(ffi::SSL_CTX_get_cert_store(self.as_ptr())) } } @@ -1764,8 +1748,7 @@ impl SslContextBuilder { /// #[corresponds(SSL_CTX_get_cert_store)] pub fn cert_store_mut(&mut self) -> &mut X509StoreBuilderRef { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk, "This API is not supported for RPK"); + self.ctx.check_x509(); assert!( !self.has_shared_cert_store, @@ -2169,8 +2152,7 @@ impl SslContextRef { #[corresponds(SSL_CTX_get0_certificate)] #[must_use] pub fn certificate(&self) -> Option<&X509Ref> { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk(), "This API is not supported for RPK"); + self.check_x509(); unsafe { let ptr = ffi::SSL_CTX_get0_certificate(self.as_ptr()); @@ -2200,8 +2182,7 @@ impl SslContextRef { #[corresponds(SSL_CTX_get_cert_store)] #[must_use] pub fn cert_store(&self) -> &X509StoreRef { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk(), "This API is not supported for RPK"); + self.check_x509(); unsafe { X509StoreRef::from_ptr(ffi::SSL_CTX_get_cert_store(self.as_ptr())) } } @@ -2311,17 +2292,26 @@ impl SslContextRef { #[corresponds(SSL_CTX_get_verify_mode)] #[must_use] pub fn verify_mode(&self) -> SslVerifyMode { - #[cfg(feature = "rpk")] - assert!(!self.is_rpk(), "This API is not supported for RPK"); + self.check_x509(); let mode = unsafe { ffi::SSL_CTX_get_verify_mode(self.as_ptr()) }; SslVerifyMode::from_bits(mode).expect("SSL_CTX_get_verify_mode returned invalid mode") } - /// Returns `true` if context was created for Raw Public Key verification - #[cfg(feature = "rpk")] - pub fn is_rpk(&self) -> bool { - self.ex_data(*RPK_FLAG_INDEX).copied().unwrap_or_default() + /// Returns `true` if context was NOT created for Raw Public Key verification + pub fn has_x509_support(&self) -> bool { + #[cfg(feature = "rpk")] + return !self.ex_data(*RPK_FLAG_INDEX).copied().unwrap_or_default(); + #[cfg(not(feature = "rpk"))] + return true; + } + + #[track_caller] + fn check_x509(&self) { + assert!( + self.has_x509_support(), + "This context is not configured for X.509 certificates" + ); } /// Registers a list of ECH keys on the context. This list should contain new and old @@ -2799,7 +2789,7 @@ impl Ssl { { let ctx = self.ssl_context(); - if ctx.is_rpk() { + if !ctx.has_x509_support() { unsafe { ffi::SSL_CTX_set_custom_verify( ctx.as_ptr(), @@ -2839,7 +2829,7 @@ impl fmt::Debug for SslRef { builder.field("state", &self.state_string_long()); #[cfg(feature = "rpk")] - if !self.ssl_context().is_rpk() { + if self.ssl_context().has_x509_support() { builder.field("verify_result", &self.verify_result()); } @@ -2925,11 +2915,7 @@ impl SslRef { /// [`SslContextBuilder::set_verify`]: struct.SslContextBuilder.html#method.set_verify #[corresponds(SSL_set_verify)] pub fn set_verify(&mut self, mode: SslVerifyMode) { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { ffi::SSL_set_verify(self.as_ptr(), mode.bits() as c_int, None) } } @@ -2939,11 +2925,7 @@ impl SslRef { /// If the peer's certificate chain is longer than this value, verification will fail. #[corresponds(SSL_set_verify_depth)] pub fn set_verify_depth(&mut self, depth: u32) { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { ffi::SSL_set_verify_depth(self.as_ptr(), depth as c_int); @@ -2954,11 +2936,7 @@ impl SslRef { #[corresponds(SSL_get_verify_mode)] #[must_use] pub fn verify_mode(&self) -> SslVerifyMode { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); let mode = unsafe { ffi::SSL_get_verify_mode(self.as_ptr()) }; SslVerifyMode::from_bits(mode).expect("SSL_get_verify_mode returned invalid mode") @@ -2985,11 +2963,7 @@ impl SslRef { where F: Fn(bool, &mut X509StoreContextRef) -> bool + 'static + Sync + Send, { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { // this needs to be in an Arc since the callback can register a new callback! @@ -3005,11 +2979,7 @@ impl SslRef { /// Sets a custom certificate store for verifying peer certificates. #[corresponds(SSL_set0_verify_cert_store)] pub fn set_verify_cert_store(&mut self, cert_store: X509Store) -> Result<(), ErrorStack> { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { cvt(ffi::SSL_set0_verify_cert_store(self.as_ptr(), cert_store.into_ptr()) as c_int)?; @@ -3027,11 +2997,7 @@ impl SslRef { where F: Fn(&mut SslRef) -> Result<(), SslVerifyError> + 'static + Sync + Send, { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { // this needs to be in an Arc since the callback can register a new callback! @@ -3162,11 +3128,7 @@ impl SslRef { #[corresponds(SSL_get_peer_certificate)] #[must_use] pub fn peer_certificate(&self) -> Option { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { let ptr = ffi::SSL_get_peer_certificate(self.as_ptr()); @@ -3185,11 +3147,7 @@ impl SslRef { #[corresponds(SSL_get_peer_cert_chain)] #[must_use] pub fn peer_cert_chain(&self) -> Option<&StackRef> { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { let ptr = ffi::SSL_get_peer_cert_chain(self.as_ptr()); @@ -3205,11 +3163,7 @@ impl SslRef { #[corresponds(SSL_get_certificate)] #[must_use] pub fn certificate(&self) -> Option<&X509Ref> { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { let ptr = ffi::SSL_get_certificate(self.as_ptr()); @@ -3464,11 +3418,7 @@ impl SslRef { /// Returns a mutable reference to the X509 verification configuration. #[corresponds(SSL_get0_param)] pub fn verify_param_mut(&mut self) -> &mut X509VerifyParamRef { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { X509VerifyParamRef::from_ptr_mut(ffi::SSL_get0_param(self.as_ptr())) } } @@ -3481,11 +3431,7 @@ impl SslRef { /// Returns the certificate verification result. #[corresponds(SSL_get_verify_result)] pub fn verify_result(&self) -> X509VerifyResult { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { X509VerifyError::from_raw(ffi::SSL_get_verify_result(self.as_ptr()) as c_int) } } @@ -3741,11 +3687,7 @@ impl SslRef { /// as trusted by this method. #[corresponds(SSL_set_client_CA_list)] pub fn set_client_ca_list(&mut self, list: Stack) { - #[cfg(feature = "rpk")] - assert!( - !self.ssl_context().is_rpk(), - "This API is not supported for RPK" - ); + self.ssl_context().check_x509(); unsafe { ffi::SSL_set_client_CA_list(self.as_ptr(), list.as_ptr()) } mem::forget(list); From 93d9018774504e33a8bc7be4471232dd7e40665f Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Fri, 19 Dec 2025 18:42:25 +0100 Subject: [PATCH 061/111] Update boring to a newer version RPK support has changed completely, it uses SSL_CREDENTIAL now. Have fun reviewing this! --- boring-sys/Cargo.toml | 1 + boring-sys/deps/boringssl | 2 +- boring-sys/patches/boring-pq.patch | 5262 ++--------------- boring-sys/patches/rpk.patch | 1909 ++++-- boring-sys/patches/underscore-wildcards.patch | 71 +- boring/src/ssl/async_callbacks.rs | 17 + boring/src/ssl/mod.rs | 422 +- tokio-boring/tests/rpk.rs | 210 +- 8 files changed, 2482 insertions(+), 5412 deletions(-) diff --git a/boring-sys/Cargo.toml b/boring-sys/Cargo.toml index ce49709d2..6def757f8 100644 --- a/boring-sys/Cargo.toml +++ b/boring-sys/Cargo.toml @@ -20,6 +20,7 @@ include = [ "/LICENSE-MIT", "/cmake/*.cmake", "/deps/boringssl/**/*.[chS]", + "/deps/boringssl/**/*.inc", "/deps/boringssl/**/*.asm", "/deps/boringssl/**/*.pl", "/deps/boringssl/**/*.go", diff --git a/boring-sys/deps/boringssl b/boring-sys/deps/boringssl index 478b28ab1..91a66a59b 160000 --- a/boring-sys/deps/boringssl +++ b/boring-sys/deps/boringssl @@ -1 +1 @@ -Subproject commit 478b28ab12f2001a03261624261fd041f5439706 +Subproject commit 91a66a59b6c1435120ff83e245d7719411294386 diff --git a/boring-sys/patches/boring-pq.patch b/boring-sys/patches/boring-pq.patch index 5c55eb8c4..f0aa1ea26 100644 --- a/boring-sys/patches/boring-pq.patch +++ b/boring-sys/patches/boring-pq.patch @@ -1,4746 +1,706 @@ -From 969fc4fb866c94b6585c323d6e27571e5286f845 Mon Sep 17 00:00:00 2001 -From: Bas Westerbaan -Date: Thu, 2 Oct 2025 13:07:05 +0200 +From cb5689e091f515fc8a42ceaff08d702333e505ed Mon Sep 17 00:00:00 2001 +From: Anthony Ramine +Date: Wed, 3 Dec 2025 11:10:16 +0100 Subject: [PATCH] Add additional post-quantum key agreements -BoringSSL upstream has supported the temporary post-quantum -key agreement X25519Kyber768Draft00 (0x6399) for a while. -At the time of writing X25519Kyber768Draft00 is widely deployed by browsers. - -Recent BoringSSL adds support for X25519MLKEM768 (0x11ec), -which will be the long term post-quantum key agreement of choice, -and many browsers are expected to switch to it before the end of 2024. - -This patch adds: - -1. Support for X25519MLKEM768 under the codepoint 0x11ec. The version - of BoringSSL we patch against did not support it yet. Like recent - upstream, enable by default. - -2. Supports for P256Kyber768Draft00 under 0xfe32, which we temporarily - need for compliance reasons. (Note that this is not the codepoint - allocated for that exchange in the IANA table.) - Enables by default and in FIPS mode. - -3. Support for X25519Kyber768Draft00 under the old codepoint 0xfe31. - -4. Support for X25519Kyber512Draft00 under the codepoint 0xfe30. This - key agreement should only be used for testing: to see if the smaller - keyshare makes a difference. - -The patch also replaces Google's implementation of Kyber, by the -portable reference implementation, so as to support Kyber512. - -Cf RTG-2076 RTG-2051 RTG-2508 RTG-2707 RTG-2607 RTG-3239 ---- - crypto/CMakeLists.txt | 3 +- - crypto/kyber/internal.h | 60 - - crypto/kyber/kyber.c | 3013 +++++++++++++++++++++++++++--------- - crypto/kyber/kyber512.c | 5 + - crypto/kyber/kyber768.c | 4 + - crypto/kyber/kyber_test.cc | 184 --- - crypto/obj/obj_dat.h | 17 +- - crypto/obj/obj_mac.num | 4 + - crypto/obj/objects.txt | 6 +- - include/openssl/kyber.h | 203 ++- - include/openssl/nid.h | 12 + - include/openssl/ssl.h | 4 + - sources.cmake | 2 - - ssl/extensions.cc | 6 + - ssl/ssl_key_share.cc | 525 ++++++- - ssl/ssl_lib.cc | 2 +- - ssl/ssl_test.cc | 29 +- - tool/speed.cc | 162 +- - 18 files changed, 3083 insertions(+), 1158 deletions(-) - delete mode 100644 crypto/kyber/internal.h - create mode 100644 crypto/kyber/kyber512.c - create mode 100644 crypto/kyber/kyber768.c - delete mode 100644 crypto/kyber/kyber_test.cc - -diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt -index a594b9e9d..ed468237f 100644 ---- a/crypto/CMakeLists.txt -+++ b/crypto/CMakeLists.txt -@@ -176,7 +176,8 @@ add_library( - hpke/hpke.c - hrss/hrss.c - keccak/keccak.c -- kyber/kyber.c -+ kyber/kyber512.c -+ kyber/kyber768.c - lhash/lhash.c - mem.c - obj/obj.c -diff --git a/crypto/kyber/internal.h b/crypto/kyber/internal.h -deleted file mode 100644 -index b11211726..000000000 ---- a/crypto/kyber/internal.h -+++ /dev/null -@@ -1,60 +0,0 @@ --/* Copyright (c) 2023, Google Inc. -- * -- * Permission to use, copy, modify, and/or distribute this software for any -- * purpose with or without fee is hereby granted, provided that the above -- * copyright notice and this permission notice appear in all copies. -- * -- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -- --#ifndef OPENSSL_HEADER_CRYPTO_KYBER_INTERNAL_H --#define OPENSSL_HEADER_CRYPTO_KYBER_INTERNAL_H -- --#include --#include -- --#if defined(__cplusplus) --extern "C" { --#endif -- -- --// KYBER_ENCAP_ENTROPY is the number of bytes of uniformly random entropy --// necessary to encapsulate a secret. The entropy will be leaked to the --// decapsulating party. --#define KYBER_ENCAP_ENTROPY 32 -- --// KYBER_GENERATE_KEY_ENTROPY is the number of bytes of uniformly random entropy --// necessary to generate a key. --#define KYBER_GENERATE_KEY_ENTROPY 64 -- --// KYBER_generate_key_external_entropy is a deterministic function to create a --// pair of Kyber768 keys, using the supplied entropy. The entropy needs to be --// uniformly random generated. This function is should only be used for tests, --// regular callers should use the non-deterministic |KYBER_generate_key| --// directly. --OPENSSL_EXPORT void KYBER_generate_key_external_entropy( -- uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], -- struct KYBER_private_key *out_private_key, -- const uint8_t entropy[KYBER_GENERATE_KEY_ENTROPY]); -- --// KYBER_encap_external_entropy is a deterministic function to encapsulate --// |out_shared_secret_len| bytes of |out_shared_secret| to |ciphertext|, using --// |KYBER_ENCAP_ENTROPY| bytes of |entropy| for randomization. The --// decapsulating side will be able to recover |entropy| in full. This --// function is should only be used for tests, regular callers should use the --// non-deterministic |KYBER_encap| directly. --OPENSSL_EXPORT void KYBER_encap_external_entropy( -- uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], uint8_t *out_shared_secret, -- size_t out_shared_secret_len, const struct KYBER_public_key *public_key, -- const uint8_t entropy[KYBER_ENCAP_ENTROPY]); -- --#if defined(__cplusplus) --} --#endif -- --#endif // OPENSSL_HEADER_CRYPTO_KYBER_INTERNAL_H -diff --git a/crypto/kyber/kyber.c b/crypto/kyber/kyber.c -index d3ea02090..74d092907 100644 ---- a/crypto/kyber/kyber.c -+++ b/crypto/kyber/kyber.c -@@ -1,835 +1,2426 @@ --/* Copyright (c) 2023, Google Inc. -- * -- * Permission to use, copy, modify, and/or distribute this software for any -- * purpose with or without fee is hereby granted, provided that the above -- * copyright notice and this permission notice appear in all copies. -- * -- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -+// Taken from round 3 public domain reference implementation -+// -+// https://github.com/pq-crystals/kyber -+// 8e00ec73035147d18b27d06048dff322f8de1f29 -+// -+// with some small modifications: -+// -+// - Merged into one file. -+// - Removed 90s version. -+// - Seeds are passed as paramters. -+// - Changed the API to be more BoringSSL-like -+// - Mitigated timing sidechannels (Kyberslash 1 and 2). -+// (Note that these do not affect ephemeral usage as in TLS.) -+// -+// TODO -+// -+// - Optimizations -+// -+// The majority of Kyber's time is spent in keccak: generating the matrix -+// A, hashing the public key, et cetera. This can be sped up dramatically -+// by using a multiway keccak implementation such as f1600x4 on AVX2. -+// -+// Also the NTT and other operations can be sped up with SIMD. This is -+// more complex and the gains are more modest. See the avx2 reference -+// implementation or https://github.com/cloudflare/circl/tree/main/pke/kyber -+// -+// - Option to keep A stored in private key. - --#include -+#ifndef KYBER_K -+#error "Don't compile this file direcly" -+#endif - --#include --#include -+#include -+#include - --#include --#include -+#include -+#include -+#include - - #include "../internal.h" --#include "../keccak/internal.h" --#include "./internal.h" -- -- --// See --// https://pq-crystals.org/kyber/data/kyber-specification-round3-20210804.pdf -- --#define DEGREE 256 --#define RANK 3 -- --static const size_t kBarrettMultiplier = 5039; --static const unsigned kBarrettShift = 24; --static const uint16_t kPrime = 3329; --static const int kLog2Prime = 12; --static const uint16_t kHalfPrime = (/*kPrime=*/3329 - 1) / 2; --static const int kDU = 10; --static const int kDV = 4; --// kInverseDegree is 128^-1 mod 3329; 128 because kPrime does not have a 512th --// root of unity. --static const uint16_t kInverseDegree = 3303; --static const size_t kEncodedVectorSize = -- (/*kLog2Prime=*/12 * DEGREE / 8) * RANK; --static const size_t kCompressedVectorSize = /*kDU=*/10 * RANK * DEGREE / 8; -- --typedef struct scalar { -- // On every function entry and exit, 0 <= c < kPrime. -- uint16_t c[DEGREE]; --} scalar; -- --typedef struct vector { -- scalar v[RANK]; --} vector; -- --typedef struct matrix { -- scalar v[RANK][RANK]; --} matrix; -- --// This bit of Python will be referenced in some of the following comments: --// --// p = 3329 --// --// def bitreverse(i): --// ret = 0 --// for n in range(7): --// bit = i & 1 --// ret <<= 1 --// ret |= bit --// i >>= 1 --// return ret -- --// kNTTRoots = [pow(17, bitreverse(i), p) for i in range(128)] --static const uint16_t kNTTRoots[128] = { -- 1, 1729, 2580, 3289, 2642, 630, 1897, 848, 1062, 1919, 193, 797, -- 2786, 3260, 569, 1746, 296, 2447, 1339, 1476, 3046, 56, 2240, 1333, -- 1426, 2094, 535, 2882, 2393, 2879, 1974, 821, 289, 331, 3253, 1756, -- 1197, 2304, 2277, 2055, 650, 1977, 2513, 632, 2865, 33, 1320, 1915, -- 2319, 1435, 807, 452, 1438, 2868, 1534, 2402, 2647, 2617, 1481, 648, -- 2474, 3110, 1227, 910, 17, 2761, 583, 2649, 1637, 723, 2288, 1100, -- 1409, 2662, 3281, 233, 756, 2156, 3015, 3050, 1703, 1651, 2789, 1789, -- 1847, 952, 1461, 2687, 939, 2308, 2437, 2388, 733, 2337, 268, 641, -- 1584, 2298, 2037, 3220, 375, 2549, 2090, 1645, 1063, 319, 2773, 757, -- 2099, 561, 2466, 2594, 2804, 1092, 403, 1026, 1143, 2150, 2775, 886, -- 1722, 1212, 1874, 1029, 2110, 2935, 885, 2154, --}; - --// kInverseNTTRoots = [pow(17, -bitreverse(i), p) for i in range(128)] --static const uint16_t kInverseNTTRoots[128] = { -- 1, 1600, 40, 749, 2481, 1432, 2699, 687, 1583, 2760, 69, 543, -- 2532, 3136, 1410, 2267, 2508, 1355, 450, 936, 447, 2794, 1235, 1903, -- 1996, 1089, 3273, 283, 1853, 1990, 882, 3033, 2419, 2102, 219, 855, -- 2681, 1848, 712, 682, 927, 1795, 461, 1891, 2877, 2522, 1894, 1010, -- 1414, 2009, 3296, 464, 2697, 816, 1352, 2679, 1274, 1052, 1025, 2132, -- 1573, 76, 2998, 3040, 1175, 2444, 394, 1219, 2300, 1455, 2117, 1607, -- 2443, 554, 1179, 2186, 2303, 2926, 2237, 525, 735, 863, 2768, 1230, -- 2572, 556, 3010, 2266, 1684, 1239, 780, 2954, 109, 1292, 1031, 1745, -- 2688, 3061, 992, 2596, 941, 892, 1021, 2390, 642, 1868, 2377, 1482, -- 1540, 540, 1678, 1626, 279, 314, 1173, 2573, 3096, 48, 667, 1920, -- 2229, 1041, 2606, 1692, 680, 2746, 568, 3312, --}; -+#if (KYBER_K == 2) -+#define KYBER_NAMESPACE(s) KYBER512_##s -+#elif (KYBER_K == 3) -+#define KYBER_NAMESPACE(s) KYBER768_##s -+#elif (KYBER_K == 4) -+#define KYBER_NAMESPACE(s) KYBER1024_##s -+#else -+#error "KYBER_K must be in {2,3,4}" -+#endif - --// kModRoots = [pow(17, 2*bitreverse(i) + 1, p) for i in range(128)] --static const uint16_t kModRoots[128] = { -- 17, 3312, 2761, 568, 583, 2746, 2649, 680, 1637, 1692, 723, 2606, -- 2288, 1041, 1100, 2229, 1409, 1920, 2662, 667, 3281, 48, 233, 3096, -- 756, 2573, 2156, 1173, 3015, 314, 3050, 279, 1703, 1626, 1651, 1678, -- 2789, 540, 1789, 1540, 1847, 1482, 952, 2377, 1461, 1868, 2687, 642, -- 939, 2390, 2308, 1021, 2437, 892, 2388, 941, 733, 2596, 2337, 992, -- 268, 3061, 641, 2688, 1584, 1745, 2298, 1031, 2037, 1292, 3220, 109, -- 375, 2954, 2549, 780, 2090, 1239, 1645, 1684, 1063, 2266, 319, 3010, -- 2773, 556, 757, 2572, 2099, 1230, 561, 2768, 2466, 863, 2594, 735, -- 2804, 525, 1092, 2237, 403, 2926, 1026, 2303, 1143, 2186, 2150, 1179, -- 2775, 554, 886, 2443, 1722, 1607, 1212, 2117, 1874, 1455, 1029, 2300, -- 2110, 1219, 2935, 394, 885, 2444, 2154, 1175, --}; -+#define public_key KYBER_NAMESPACE(public_key) -+#define private_key KYBER_NAMESPACE(private_key) - --// reduce_once reduces 0 <= x < 2*kPrime, mod kPrime. --static uint16_t reduce_once(uint16_t x) { -- assert(x < 2 * kPrime); -- const uint16_t subtracted = x - kPrime; -- uint16_t mask = 0u - (subtracted >> 15); -- // On Aarch64, omitting a |value_barrier_u16| results in a 2x speedup of Kyber -- // overall and Clang still produces constant-time code using `csel`. On other -- // platforms & compilers on godbolt that we care about, this code also -- // produces constant-time output. -- return (mask & x) | (~mask & subtracted); --} -- --// constant time reduce x mod kPrime using Barrett reduction. x must be less --// than kPrime + 2×kPrime². --static uint16_t reduce(uint32_t x) { -- assert(x < kPrime + 2u * kPrime * kPrime); -- uint64_t product = (uint64_t)x * kBarrettMultiplier; -- uint32_t quotient = (uint32_t)(product >> kBarrettShift); -- uint32_t remainder = x - quotient * kPrime; -- return reduce_once(remainder); --} -- --static void scalar_zero(scalar *out) { OPENSSL_memset(out, 0, sizeof(*out)); } -- --static void vector_zero(vector *out) { OPENSSL_memset(out, 0, sizeof(*out)); } -- --// In place number theoretic transform of a given scalar. --// Note that Kyber's kPrime 3329 does not have a 512th root of unity, so this --// transform leaves off the last iteration of the usual FFT code, with the 128 --// relevant roots of unity being stored in |kNTTRoots|. This means the output --// should be seen as 128 elements in GF(3329^2), with the coefficients of the --// elements being consecutive entries in |s->c|. --static void scalar_ntt(scalar *s) { -- int offset = DEGREE; -- // `int` is used here because using `size_t` throughout caused a ~5% slowdown -- // with Clang 14 on Aarch64. -- for (int step = 1; step < DEGREE / 2; step <<= 1) { -- offset >>= 1; -- int k = 0; -- for (int i = 0; i < step; i++) { -- const uint32_t step_root = kNTTRoots[i + step]; -- for (int j = k; j < k + offset; j++) { -- uint16_t odd = reduce(step_root * s->c[j + offset]); -- uint16_t even = s->c[j]; -- s->c[j] = reduce_once(odd + even); -- s->c[j + offset] = reduce_once(even - odd + kPrime); -- } -- k += 2 * offset; -+#define generate_key KYBER_NAMESPACE(generate_key) -+#define encap KYBER_NAMESPACE(encap) -+#define decap KYBER_NAMESPACE(decap) -+#define marshal_public_key KYBER_NAMESPACE(marshal_public_key) -+#define parse_public_key KYBER_NAMESPACE(parse_public_key) -+ -+ -+// -+// params.h -+// -+#define KYBER_N 256 -+#define KYBER_Q 3329 -+ -+#define KYBER_SYMBYTES 32 /* size in bytes of hashes, and seeds */ -+#define KYBER_SSBYTES 32 /* size in bytes of shared key */ -+ -+#define KYBER_POLYBYTES 384 -+#define KYBER_POLYVECBYTES (KYBER_K * KYBER_POLYBYTES) -+ -+#if KYBER_K == 2 -+#define KYBER_ETA1 3 -+#define KYBER_POLYCOMPRESSEDBYTES 128 -+#define KYBER_POLYVECCOMPRESSEDBYTES (KYBER_K * 320) -+#elif KYBER_K == 3 -+#define KYBER_ETA1 2 -+#define KYBER_POLYCOMPRESSEDBYTES 128 -+#define KYBER_POLYVECCOMPRESSEDBYTES (KYBER_K * 320) -+#elif KYBER_K == 4 -+#define KYBER_ETA1 2 -+#define KYBER_POLYCOMPRESSEDBYTES 160 -+#define KYBER_POLYVECCOMPRESSEDBYTES (KYBER_K * 352) -+#endif -+ -+#define KYBER_ETA2 2 -+ -+#define KYBER_INDCPA_MSGBYTES (KYBER_SYMBYTES) -+#define KYBER_INDCPA_PUBLICKEYBYTES (KYBER_POLYVECBYTES + KYBER_SYMBYTES) -+#define KYBER_INDCPA_SECRETKEYBYTES (KYBER_POLYVECBYTES) -+#define KYBER_INDCPA_BYTES (KYBER_POLYVECCOMPRESSEDBYTES + KYBER_POLYCOMPRESSEDBYTES) -+ -+#define KYBER_PUBLICKEYBYTES (KYBER_INDCPA_PUBLICKEYBYTES) -+/* 32 bytes of additional space to save H(pk) */ -+#define KYBER_SECRETKEYBYTES (KYBER_INDCPA_SECRETKEYBYTES + KYBER_INDCPA_PUBLICKEYBYTES + 2*KYBER_SYMBYTES) -+#define KYBER_CIPHERTEXTBYTES (KYBER_INDCPA_BYTES) -+ -+// -+// verify.h -+// -+static int verify(const uint8_t *a, const uint8_t *b, size_t len); -+static void cmov(uint8_t *r, const uint8_t *x, size_t len, uint8_t b); -+ -+// -+// reduce.h -+// -+#define MONT -1044 // 2^16 mod q -+#define QINV -3327 // q^-1 mod 2^16 -+ -+static int16_t montgomery_reduce(int32_t a); -+static int16_t barrett_reduce(int16_t a); -+ -+// -+// ntt.h -+// -+static void ntt(int16_t poly[256]); -+static void invntt(int16_t poly[256]); -+static void basemul(int16_t r[2], const int16_t a[2], const int16_t b[2], int16_t zeta); -+ -+// -+// poly.h -+// -+ -+/* -+ * Elements of R_q = Z_q[X]/(X^n + 1). Represents polynomial -+ * coeffs[0] + X*coeffs[1] + X^2*xoeffs[2] + ... + X^{n-1}*coeffs[n-1] -+ */ -+typedef struct{ -+ int16_t coeffs[KYBER_N]; -+} poly; -+ -+static void poly_compress(uint8_t r[KYBER_POLYCOMPRESSEDBYTES], const poly *a); -+static void poly_decompress(poly *r, const uint8_t a[KYBER_POLYCOMPRESSEDBYTES]); -+ -+static void poly_tobytes(uint8_t r[KYBER_POLYBYTES], const poly *a); -+static void poly_frombytes(poly *r, const uint8_t a[KYBER_POLYBYTES]); -+ -+static void poly_frommsg(poly *r, const uint8_t msg[KYBER_INDCPA_MSGBYTES]); -+static void poly_tomsg(uint8_t msg[KYBER_INDCPA_MSGBYTES], const poly *r); -+ -+static void poly_getnoise_eta1(poly *r, const uint8_t seed[KYBER_SYMBYTES], uint8_t nonce); -+static void poly_getnoise_eta2(poly *r, const uint8_t seed[KYBER_SYMBYTES], uint8_t nonce); -+ -+static void poly_ntt(poly *r); -+static void poly_invntt_tomont(poly *r); -+static void poly_basemul_montgomery(poly *r, const poly *a, const poly *b); -+static void poly_tomont(poly *r); -+ -+static void poly_reduce(poly *r); -+ -+static void poly_add(poly *r, const poly *a, const poly *b); -+static void poly_sub(poly *r, const poly *a, const poly *b); -+ -+// -+// cbd.h -+// -+static void poly_cbd_eta1(poly *r, const uint8_t buf[KYBER_ETA1*KYBER_N/4]); -+static void poly_cbd_eta2(poly *r, const uint8_t buf[KYBER_ETA2*KYBER_N/4]); -+ -+// -+// polyvec.h -+// -+ -+typedef struct{ -+ poly vec[KYBER_K]; -+} polyvec; -+ -+static void polyvec_compress(uint8_t r[KYBER_POLYVECCOMPRESSEDBYTES], const polyvec *a); -+static void polyvec_decompress(polyvec *r, const uint8_t a[KYBER_POLYVECCOMPRESSEDBYTES]); -+ -+static void polyvec_tobytes(uint8_t r[KYBER_POLYVECBYTES], const polyvec *a); -+static void polyvec_frombytes(polyvec *r, const uint8_t a[KYBER_POLYVECBYTES]); -+ -+static void polyvec_ntt(polyvec *r); -+static void polyvec_invntt_tomont(polyvec *r); -+ -+static void polyvec_basemul_acc_montgomery(poly *r, const polyvec *a, const polyvec *b); -+ -+static void polyvec_reduce(polyvec *r); -+ -+static void polyvec_add(polyvec *r, const polyvec *a, const polyvec *b); -+ -+// -+// indcpa.h -+// -+ -+static void gen_matrix(polyvec *a, const uint8_t seed[KYBER_SYMBYTES], int transposed); -+static void indcpa_keypair(uint8_t pk[KYBER_INDCPA_PUBLICKEYBYTES], -+ uint8_t sk[KYBER_INDCPA_SECRETKEYBYTES], -+ const uint8_t seed[KYBER_SYMBYTES]); -+ -+static int indcpa_enc(uint8_t c[KYBER_INDCPA_BYTES], -+ const uint8_t m[KYBER_INDCPA_MSGBYTES], -+ const uint8_t pk[KYBER_INDCPA_PUBLICKEYBYTES], -+ const uint8_t coins[KYBER_SYMBYTES]); -+ -+static void indcpa_dec(uint8_t m[KYBER_INDCPA_MSGBYTES], -+ const uint8_t c[KYBER_INDCPA_BYTES], -+ const uint8_t sk[KYBER_INDCPA_SECRETKEYBYTES]); -+ -+// -+// fips202.h -+// -+ -+#define SHAKE128_RATE 168 -+#define SHAKE256_RATE 136 -+#define SHA3_256_RATE 136 -+#define SHA3_512_RATE 72 -+ -+typedef struct { -+ uint64_t s[25]; -+ unsigned int pos; -+} keccak_state; -+ -+static void shake128_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen); -+static void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state); -+ -+static void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state); -+static void shake256_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen); -+static void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state); -+static void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen); -+static void shake256_finalize(keccak_state *state); -+static void shake256_init(keccak_state *state); -+ -+static void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen); -+static void sha3_256(uint8_t h[32], const uint8_t *in, size_t inlen); -+static void sha3_512(uint8_t h[64], const uint8_t *in, size_t inlen); -+ -+// -+// symmetric.h -+// -+ -+typedef keccak_state xof_state; -+ -+static void kyber_shake128_absorb(keccak_state *s, -+ const uint8_t seed[KYBER_SYMBYTES], -+ uint8_t x, -+ uint8_t y); -+ -+static void kyber_shake256_prf(uint8_t *out, size_t outlen, const uint8_t key[KYBER_SYMBYTES], uint8_t nonce); -+ -+#define XOF_BLOCKBYTES SHAKE128_RATE -+ -+#define hash_h(OUT, IN, INBYTES) sha3_256(OUT, IN, INBYTES) -+#define hash_g(OUT, IN, INBYTES) sha3_512(OUT, IN, INBYTES) -+#define xof_absorb(STATE, SEED, X, Y) kyber_shake128_absorb(STATE, SEED, X, Y) -+#define xof_squeezeblocks(OUT, OUTBLOCKS, STATE) shake128_squeezeblocks(OUT, OUTBLOCKS, STATE) -+#define prf(OUT, OUTBYTES, KEY, NONCE) kyber_shake256_prf(OUT, OUTBYTES, KEY, NONCE) -+#define kdf(OUT, IN, INBYTES) shake256(OUT, KYBER_SSBYTES, IN, INBYTES) -+ -+ -+// -+// verify.c -+// -+ -+/************************************************* -+* Name: verify -+* -+* Description: Compare two arrays for equality in constant time. -+* -+* Arguments: const uint8_t *a: pointer to first byte array -+* const uint8_t *b: pointer to second byte array -+* size_t len: length of the byte arrays -+* -+* Returns 0 if the byte arrays are equal, 1 otherwise -+**************************************************/ -+static int verify(const uint8_t *a, const uint8_t *b, size_t len) -+{ -+ size_t i; -+ uint8_t r = 0; -+ -+ for(i=0;i> 63; -+} -+ -+/************************************************* -+* Name: cmov -+* -+* Description: Copy len bytes from x to r if b is 1; -+* don't modify x if b is 0. Requires b to be in {0,1}; -+* assumes two's complement representation of negative integers. -+* Runs in constant time. -+* -+* Arguments: uint8_t *r: pointer to output byte array -+* const uint8_t *x: pointer to input byte array -+* size_t len: Amount of bytes to be copied -+* uint8_t b: Condition bit; has to be in {0,1} -+**************************************************/ -+static void cmov(uint8_t *r, const uint8_t *x, size_t len, uint8_t b) -+{ -+ size_t i; -+ -+ b = -b; -+ for(i=0;i> 16; -+ return t; -+} -+ -+/************************************************* -+* Name: barrett_reduce -+* -+* Description: Barrett reduction; given a 16-bit integer a, computes -+* centered representative congruent to a mod q in {-(q-1)/2,...,(q-1)/2} -+* -+* Arguments: - int16_t a: input integer to be reduced -+* -+* Returns: integer in {-(q-1)/2,...,(q-1)/2} congruent to a modulo q. -+**************************************************/ -+static int16_t barrett_reduce(int16_t a) { -+ int16_t t; -+ const int16_t v = ((1<<26) + KYBER_Q/2)/KYBER_Q; -+ -+ t = ((int32_t)v*a + (1<<25)) >> 26; -+ t *= KYBER_Q; -+ return a - t; -+} -+ -+// -+// cbd.c -+// -+ -+/************************************************* -+* Name: load32_littleendian -+* -+* Description: load 4 bytes into a 32-bit integer -+* in little-endian order -+* -+* Arguments: - const uint8_t *x: pointer to input byte array -+* -+* Returns 32-bit unsigned integer loaded from x -+**************************************************/ -+static uint32_t load32_littleendian(const uint8_t x[4]) -+{ -+ uint32_t r; -+ r = (uint32_t)x[0]; -+ r |= (uint32_t)x[1] << 8; -+ r |= (uint32_t)x[2] << 16; -+ r |= (uint32_t)x[3] << 24; -+ return r; -+} -+ -+/************************************************* -+* Name: load24_littleendian -+* -+* Description: load 3 bytes into a 32-bit integer -+* in little-endian order. -+* This function is only needed for Kyber-512 -+* -+* Arguments: - const uint8_t *x: pointer to input byte array -+* -+* Returns 32-bit unsigned integer loaded from x (most significant byte is zero) -+**************************************************/ -+#if KYBER_ETA1 == 3 -+static uint32_t load24_littleendian(const uint8_t x[3]) -+{ -+ uint32_t r; -+ r = (uint32_t)x[0]; -+ r |= (uint32_t)x[1] << 8; -+ r |= (uint32_t)x[2] << 16; -+ return r; -+} -+#endif -+ -+ -+/************************************************* -+* Name: cbd2 -+* -+* Description: Given an array of uniformly random bytes, compute -+* polynomial with coefficients distributed according to -+* a centered binomial distribution with parameter eta=2 -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *buf: pointer to input byte array -+**************************************************/ -+static void cbd2(poly *r, const uint8_t buf[2*KYBER_N/4]) -+{ -+ unsigned int i,j; -+ uint32_t t,d; -+ int16_t a,b; -+ -+ for(i=0;i>1) & 0x55555555; -+ -+ for(j=0;j<8;j++) { -+ a = (d >> (4*j+0)) & 0x3; -+ b = (d >> (4*j+2)) & 0x3; -+ r->coeffs[8*i+j] = a - b; -+ } -+ } -+} -+ -+/************************************************* -+* Name: cbd3 -+* -+* Description: Given an array of uniformly random bytes, compute -+* polynomial with coefficients distributed according to -+* a centered binomial distribution with parameter eta=3. -+* This function is only needed for Kyber-512 -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *buf: pointer to input byte array -+**************************************************/ -+#if KYBER_ETA1 == 3 -+static void cbd3(poly *r, const uint8_t buf[3*KYBER_N/4]) -+{ -+ unsigned int i,j; -+ uint32_t t,d; -+ int16_t a,b; -+ -+ for(i=0;i>1) & 0x00249249; -+ d += (t>>2) & 0x00249249; -+ -+ for(j=0;j<4;j++) { -+ a = (d >> (6*j+0)) & 0x7; -+ b = (d >> (6*j+3)) & 0x7; -+ r->coeffs[4*i+j] = a - b; - } - } - } -+#endif -+ -+static void poly_cbd_eta1(poly *r, const uint8_t buf[KYBER_ETA1*KYBER_N/4]) -+{ -+#if KYBER_ETA1 == 2 -+ cbd2(r, buf); -+#elif KYBER_ETA1 == 3 -+ cbd3(r, buf); -+#else -+#error "This implementation requires eta1 in {2,3}" -+#endif -+} - --static void vector_ntt(vector *a) { -- for (int i = 0; i < RANK; i++) { -- scalar_ntt(&a->v[i]); -+static void poly_cbd_eta2(poly *r, const uint8_t buf[KYBER_ETA2*KYBER_N/4]) -+{ -+#if KYBER_ETA2 == 2 -+ cbd2(r, buf); -+#else -+#error "This implementation requires eta2 = 2" -+#endif -+} -+ -+// -+// ntt.c -+// -+ -+/* Code to generate zetas and zetas_inv used in the number-theoretic transform: -+ -+#define KYBER_ROOT_OF_UNITY 17 -+ -+static const uint8_t tree[128] = { -+ 0, 64, 32, 96, 16, 80, 48, 112, 8, 72, 40, 104, 24, 88, 56, 120, -+ 4, 68, 36, 100, 20, 84, 52, 116, 12, 76, 44, 108, 28, 92, 60, 124, -+ 2, 66, 34, 98, 18, 82, 50, 114, 10, 74, 42, 106, 26, 90, 58, 122, -+ 6, 70, 38, 102, 22, 86, 54, 118, 14, 78, 46, 110, 30, 94, 62, 126, -+ 1, 65, 33, 97, 17, 81, 49, 113, 9, 73, 41, 105, 25, 89, 57, 121, -+ 5, 69, 37, 101, 21, 85, 53, 117, 13, 77, 45, 109, 29, 93, 61, 125, -+ 3, 67, 35, 99, 19, 83, 51, 115, 11, 75, 43, 107, 27, 91, 59, 123, -+ 7, 71, 39, 103, 23, 87, 55, 119, 15, 79, 47, 111, 31, 95, 63, 127 -+}; -+ -+void init_ntt() { -+ unsigned int i; -+ int16_t tmp[128]; -+ -+ tmp[0] = MONT; -+ for(i=1;i<128;i++) -+ tmp[i] = fqmul(tmp[i-1],MONT*KYBER_ROOT_OF_UNITY % KYBER_Q); -+ -+ for(i=0;i<128;i++) { -+ zetas[i] = tmp[tree[i]]; -+ if(zetas[i] > KYBER_Q/2) -+ zetas[i] -= KYBER_Q; -+ if(zetas[i] < -KYBER_Q/2) -+ zetas[i] += KYBER_Q; - } - } -+*/ -+ -+static const int16_t zetas[128] = { -+ -1044, -758, -359, -1517, 1493, 1422, 287, 202, -+ -171, 622, 1577, 182, 962, -1202, -1474, 1468, -+ 573, -1325, 264, 383, -829, 1458, -1602, -130, -+ -681, 1017, 732, 608, -1542, 411, -205, -1571, -+ 1223, 652, -552, 1015, -1293, 1491, -282, -1544, -+ 516, -8, -320, -666, -1618, -1162, 126, 1469, -+ -853, -90, -271, 830, 107, -1421, -247, -951, -+ -398, 961, -1508, -725, 448, -1065, 677, -1275, -+ -1103, 430, 555, 843, -1251, 871, 1550, 105, -+ 422, 587, 177, -235, -291, -460, 1574, 1653, -+ -246, 778, 1159, -147, -777, 1483, -602, 1119, -+ -1590, 644, -872, 349, 418, 329, -156, -75, -+ 817, 1097, 603, 610, 1322, -1285, -1465, 384, -+ -1215, -136, 1218, -1335, -874, 220, -1187, -1659, -+ -1185, -1530, -1278, 794, -1510, -854, -870, 478, -+ -108, -308, 996, 991, 958, -1460, 1522, 1628 -+}; -+ -+/************************************************* -+* Name: fqmul -+* -+* Description: Multiplication followed by Montgomery reduction -+* -+* Arguments: - int16_t a: first factor -+* - int16_t b: second factor -+* -+* Returns 16-bit integer congruent to a*b*R^{-1} mod q -+**************************************************/ -+static int16_t fqmul(int16_t a, int16_t b) { -+ return montgomery_reduce((int32_t)a*b); -+} - --// In place inverse number theoretic transform of a given scalar, with pairs of --// entries of s->v being interpreted as elements of GF(3329^2). Just as with the --// number theoretic transform, this leaves off the first step of the normal iFFT --// to account for the fact that 3329 does not have a 512th root of unity, using --// the precomputed 128 roots of unity stored in |kInverseNTTRoots|. --static void scalar_inverse_ntt(scalar *s) { -- int step = DEGREE / 2; -- // `int` is used here because using `size_t` throughout caused a ~5% slowdown -- // with Clang 14 on Aarch64. -- for (int offset = 2; offset < DEGREE; offset <<= 1) { -- step >>= 1; -- int k = 0; -- for (int i = 0; i < step; i++) { -- uint32_t step_root = kInverseNTTRoots[i + step]; -- for (int j = k; j < k + offset; j++) { -- uint16_t odd = s->c[j + offset]; -- uint16_t even = s->c[j]; -- s->c[j] = reduce_once(odd + even); -- s->c[j + offset] = reduce(step_root * (even - odd + kPrime)); -+/************************************************* -+* Name: ntt -+* -+* Description: Inplace number-theoretic transform (NTT) in Rq. -+* input is in standard order, output is in bitreversed order -+* -+* Arguments: - int16_t r[256]: pointer to input/output vector of elements of Zq -+**************************************************/ -+static void ntt(int16_t r[256]) { -+ unsigned int len, start, j, k; -+ int16_t t, zeta; -+ -+ k = 1; -+ for(len = 128; len >= 2; len >>= 1) { -+ for(start = 0; start < 256; start = j + len) { -+ zeta = zetas[k++]; -+ for(j = start; j < start + len; j++) { -+ t = fqmul(zeta, r[j + len]); -+ r[j + len] = r[j] - t; -+ r[j] = r[j] + t; - } -- k += 2 * offset; - } - } -- for (int i = 0; i < DEGREE; i++) { -- s->c[i] = reduce(s->c[i] * kInverseDegree); -- } - } - --static void vector_inverse_ntt(vector *a) { -- for (int i = 0; i < RANK; i++) { -- scalar_inverse_ntt(&a->v[i]); -+/************************************************* -+* Name: invntt_tomont -+* -+* Description: Inplace inverse number-theoretic transform in Rq and -+* multiplication by Montgomery factor 2^16. -+* Input is in bitreversed order, output is in standard order -+* -+* Arguments: - int16_t r[256]: pointer to input/output vector of elements of Zq -+**************************************************/ -+static void invntt(int16_t r[256]) { -+ unsigned int start, len, j, k; -+ int16_t t, zeta; -+ const int16_t f = 1441; // mont^2/128 -+ -+ k = 127; -+ for(len = 2; len <= 128; len <<= 1) { -+ for(start = 0; start < 256; start = j + len) { -+ zeta = zetas[k--]; -+ for(j = start; j < start + len; j++) { -+ t = r[j]; -+ r[j] = barrett_reduce(t + r[j + len]); -+ r[j + len] = r[j + len] - t; -+ r[j + len] = fqmul(zeta, r[j + len]); -+ } -+ } - } -+ -+ for(j = 0; j < 256; j++) -+ r[j] = fqmul(r[j], f); -+} -+ -+/************************************************* -+* Name: basemul -+* -+* Description: Multiplication of polynomials in Zq[X]/(X^2-zeta) -+* used for multiplication of elements in Rq in NTT domain -+* -+* Arguments: - int16_t r[2]: pointer to the output polynomial -+* - const int16_t a[2]: pointer to the first factor -+* - const int16_t b[2]: pointer to the second factor -+* - int16_t zeta: integer defining the reduction polynomial -+**************************************************/ -+static void basemul(int16_t r[2], const int16_t a[2], const int16_t b[2], int16_t zeta) -+{ -+ r[0] = fqmul(a[1], b[1]); -+ r[0] = fqmul(r[0], zeta); -+ r[0] += fqmul(a[0], b[0]); -+ r[1] = fqmul(a[0], b[1]); -+ r[1] += fqmul(a[1], b[0]); - } - --static void scalar_add(scalar *lhs, const scalar *rhs) { -- for (int i = 0; i < DEGREE; i++) { -- lhs->c[i] = reduce_once(lhs->c[i] + rhs->c[i]); -+// -+// poly.c -+// -+ -+/************************************************* -+* Name: poly_compress -+* -+* Description: Compression and subsequent serialization of a polynomial -+* -+* Arguments: - uint8_t *r: pointer to output byte array -+* (of length KYBER_POLYCOMPRESSEDBYTES) -+* - const poly *a: pointer to input polynomial -+**************************************************/ -+static void poly_compress(uint8_t r[KYBER_POLYCOMPRESSEDBYTES], const poly *a) -+{ -+ unsigned int i,j; -+ int16_t u; -+ uint32_t d0; -+ uint8_t t[8]; -+ -+#if (KYBER_POLYCOMPRESSEDBYTES == 128) -+ for(i=0;icoeffs[8*i+j]; -+ u += (u >> 15) & KYBER_Q; -+ d0 = u << 4; -+ d0 += 1665; -+ d0 *= 80635; -+ d0 >>= 28; -+ t[j] = d0 & 0xf; -+ } -+ -+ r[0] = t[0] | (t[1] << 4); -+ r[1] = t[2] | (t[3] << 4); -+ r[2] = t[4] | (t[5] << 4); -+ r[3] = t[6] | (t[7] << 4); -+ r += 4; - } -+#elif (KYBER_POLYCOMPRESSEDBYTES == 160) -+ for(i=0;icoeffs[8*i+j]; -+ u += (u >> 15) & KYBER_Q; -+ d0 = u << 5; -+ d0 += 1664; -+ d0 *= 40318; -+ d0 >>= 27; -+ t[j] = d0 & 0x1f; -+ } -+ -+ r[0] = (t[0] >> 0) | (t[1] << 5); -+ r[1] = (t[1] >> 3) | (t[2] << 2) | (t[3] << 7); -+ r[2] = (t[3] >> 1) | (t[4] << 4); -+ r[3] = (t[4] >> 4) | (t[5] << 1) | (t[6] << 6); -+ r[4] = (t[6] >> 2) | (t[7] << 3); -+ r += 5; -+ } -+#else -+#error "KYBER_POLYCOMPRESSEDBYTES needs to be in {128, 160}" -+#endif - } - --static void scalar_sub(scalar *lhs, const scalar *rhs) { -- for (int i = 0; i < DEGREE; i++) { -- lhs->c[i] = reduce_once(lhs->c[i] - rhs->c[i] + kPrime); -+/************************************************* -+* Name: poly_decompress -+* -+* Description: De-serialization and subsequent decompression of a polynomial; -+* approximate inverse of poly_compress -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *a: pointer to input byte array -+* (of length KYBER_POLYCOMPRESSEDBYTES bytes) -+**************************************************/ -+static void poly_decompress(poly *r, const uint8_t a[KYBER_POLYCOMPRESSEDBYTES]) -+{ -+ unsigned int i; -+ -+#if (KYBER_POLYCOMPRESSEDBYTES == 128) -+ for(i=0;icoeffs[2*i+0] = (((uint16_t)(a[0] & 15)*KYBER_Q) + 8) >> 4; -+ r->coeffs[2*i+1] = (((uint16_t)(a[0] >> 4)*KYBER_Q) + 8) >> 4; -+ a += 1; -+ } -+#elif (KYBER_POLYCOMPRESSEDBYTES == 160) -+ unsigned int j; -+ uint8_t t[8]; -+ for(i=0;i> 0); -+ t[1] = (a[0] >> 5) | (a[1] << 3); -+ t[2] = (a[1] >> 2); -+ t[3] = (a[1] >> 7) | (a[2] << 1); -+ t[4] = (a[2] >> 4) | (a[3] << 4); -+ t[5] = (a[3] >> 1); -+ t[6] = (a[3] >> 6) | (a[4] << 2); -+ t[7] = (a[4] >> 3); -+ a += 5; -+ -+ for(j=0;j<8;j++) -+ r->coeffs[8*i+j] = ((uint32_t)(t[j] & 31)*KYBER_Q + 16) >> 5; - } -+#else -+#error "KYBER_POLYCOMPRESSEDBYTES needs to be in {128, 160}" -+#endif - } - --// Multiplying two scalars in the number theoretically transformed state. Since --// 3329 does not have a 512th root of unity, this means we have to interpret --// the 2*ith and (2*i+1)th entries of the scalar as elements of GF(3329)[X]/(X^2 --// - 17^(2*bitreverse(i)+1)) The value of 17^(2*bitreverse(i)+1) mod 3329 is --// stored in the precomputed |kModRoots| table. Note that our Barrett transform --// only allows us to multipy two reduced numbers together, so we need some --// intermediate reduction steps, even if an uint64_t could hold 3 multiplied --// numbers. --static void scalar_mult(scalar *out, const scalar *lhs, const scalar *rhs) { -- for (int i = 0; i < DEGREE / 2; i++) { -- uint32_t real_real = (uint32_t)lhs->c[2 * i] * rhs->c[2 * i]; -- uint32_t img_img = (uint32_t)lhs->c[2 * i + 1] * rhs->c[2 * i + 1]; -- uint32_t real_img = (uint32_t)lhs->c[2 * i] * rhs->c[2 * i + 1]; -- uint32_t img_real = (uint32_t)lhs->c[2 * i + 1] * rhs->c[2 * i]; -- out->c[2 * i] = -- reduce(real_real + (uint32_t)reduce(img_img) * kModRoots[i]); -- out->c[2 * i + 1] = reduce(img_real + real_img); -+/************************************************* -+* Name: poly_tobytes -+* -+* Description: Serialization of a polynomial -+* -+* Arguments: - uint8_t *r: pointer to output byte array -+* (needs space for KYBER_POLYBYTES bytes) -+* - const poly *a: pointer to input polynomial -+**************************************************/ -+static void poly_tobytes(uint8_t r[KYBER_POLYBYTES], const poly *a) -+{ -+ unsigned int i; -+ uint16_t t0, t1; -+ -+ for(i=0;icoeffs[2*i]; -+ t0 += ((int16_t)t0 >> 15) & KYBER_Q; -+ t1 = a->coeffs[2*i+1]; -+ t1 += ((int16_t)t1 >> 15) & KYBER_Q; -+ r[3*i+0] = (t0 >> 0); -+ r[3*i+1] = (t0 >> 8) | (t1 << 4); -+ r[3*i+2] = (t1 >> 4); - } - } - --static void vector_add(vector *lhs, const vector *rhs) { -- for (int i = 0; i < RANK; i++) { -- scalar_add(&lhs->v[i], &rhs->v[i]); -+/************************************************* -+* Name: poly_frombytes -+* -+* Description: De-serialization of a polynomial; -+* inverse of poly_tobytes -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *a: pointer to input byte array -+* (of KYBER_POLYBYTES bytes) -+**************************************************/ -+static void poly_frombytes(poly *r, const uint8_t a[KYBER_POLYBYTES]) -+{ -+ unsigned int i; -+ for(i=0;icoeffs[2*i] = ((a[3*i+0] >> 0) | ((uint16_t)a[3*i+1] << 8)) & 0xFFF; -+ r->coeffs[2*i+1] = ((a[3*i+1] >> 4) | ((uint16_t)a[3*i+2] << 4)) & 0xFFF; - } - } - --static void matrix_mult(vector *out, const matrix *m, const vector *a) { -- vector_zero(out); -- for (int i = 0; i < RANK; i++) { -- for (int j = 0; j < RANK; j++) { -- scalar product; -- scalar_mult(&product, &m->v[i][j], &a->v[j]); -- scalar_add(&out->v[i], &product); -+/************************************************* -+* Name: poly_frommsg -+* -+* Description: Convert 32-byte message to polynomial -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *msg: pointer to input message -+**************************************************/ -+static void poly_frommsg(poly *r, const uint8_t msg[KYBER_INDCPA_MSGBYTES]) -+{ -+ unsigned int i,j; -+ int16_t mask; -+ -+#if (KYBER_INDCPA_MSGBYTES != KYBER_N/8) -+#error "KYBER_INDCPA_MSGBYTES must be equal to KYBER_N/8 bytes!" -+#endif -+ -+ for(i=0;i> j)&1); -+ r->coeffs[8*i+j] = mask & ((KYBER_Q+1)/2); - } - } - } - --static void matrix_mult_transpose(vector *out, const matrix *m, -- const vector *a) { -- vector_zero(out); -- for (int i = 0; i < RANK; i++) { -- for (int j = 0; j < RANK; j++) { -- scalar product; -- scalar_mult(&product, &m->v[j][i], &a->v[j]); -- scalar_add(&out->v[i], &product); -+/************************************************* -+* Name: poly_tomsg -+* -+* Description: Convert polynomial to 32-byte message -+* -+* Arguments: - uint8_t *msg: pointer to output message -+* - const poly *a: pointer to input polynomial -+**************************************************/ -+static void poly_tomsg(uint8_t msg[KYBER_INDCPA_MSGBYTES], const poly *a) -+{ -+ unsigned int i,j; -+ uint32_t t; -+ -+ for(i=0;icoeffs[8*i+j]; -+ t <<= 1; -+ t += 1665; -+ t *= 80635; -+ t >>= 28; -+ t &= 1; -+ msg[i] |= t << j; - } - } - } - --static void scalar_inner_product(scalar *out, const vector *lhs, -- const vector *rhs) { -- scalar_zero(out); -- for (int i = 0; i < RANK; i++) { -- scalar product; -- scalar_mult(&product, &lhs->v[i], &rhs->v[i]); -- scalar_add(out, &product); -- } -+/************************************************* -+* Name: poly_getnoise_eta1 -+* -+* Description: Sample a polynomial deterministically from a seed and a nonce, -+* with output polynomial close to centered binomial distribution -+* with parameter KYBER_ETA1 -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *seed: pointer to input seed -+* (of length KYBER_SYMBYTES bytes) -+* - uint8_t nonce: one-byte input nonce -+**************************************************/ -+static void poly_getnoise_eta1(poly *r, const uint8_t seed[KYBER_SYMBYTES], uint8_t nonce) -+{ -+ uint8_t buf[KYBER_ETA1*KYBER_N/4]; -+ prf(buf, sizeof(buf), seed, nonce); -+ poly_cbd_eta1(r, buf); - } - --// Algorithm 1 of the Kyber spec. Rejection samples a Keccak stream to get --// uniformly distributed elements. This is used for matrix expansion and only --// operates on public inputs. --static void scalar_from_keccak_vartime(scalar *out, -- struct BORINGSSL_keccak_st *keccak_ctx) { -- assert(keccak_ctx->squeeze_offset == 0); -- assert(keccak_ctx->rate_bytes == 168); -- static_assert(168 % 3 == 0, "block and coefficient boundaries do not align"); -- -- int done = 0; -- while (done < DEGREE) { -- uint8_t block[168]; -- BORINGSSL_keccak_squeeze(keccak_ctx, block, sizeof(block)); -- for (size_t i = 0; i < sizeof(block) && done < DEGREE; i += 3) { -- uint16_t d1 = block[i] + 256 * (block[i + 1] % 16); -- uint16_t d2 = block[i + 1] / 16 + 16 * block[i + 2]; -- if (d1 < kPrime) { -- out->c[done++] = d1; -- } -- if (d2 < kPrime && done < DEGREE) { -- out->c[done++] = d2; -- } -- } -- } -+/************************************************* -+* Name: poly_getnoise_eta2 -+* -+* Description: Sample a polynomial deterministically from a seed and a nonce, -+* with output polynomial close to centered binomial distribution -+* with parameter KYBER_ETA2 -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const uint8_t *seed: pointer to input seed -+* (of length KYBER_SYMBYTES bytes) -+* - uint8_t nonce: one-byte input nonce -+**************************************************/ -+static void poly_getnoise_eta2(poly *r, const uint8_t seed[KYBER_SYMBYTES], uint8_t nonce) -+{ -+ uint8_t buf[KYBER_ETA2*KYBER_N/4]; -+ prf(buf, sizeof(buf), seed, nonce); -+ poly_cbd_eta2(r, buf); - } - --// Algorithm 2 of the Kyber spec, with eta fixed to two and the PRF call --// included. Creates binominally distributed elements by sampling 2*|eta| bits, --// and setting the coefficient to the count of the first bits minus the count of --// the second bits, resulting in a centered binomial distribution. Since eta is --// two this gives -2/2 with a probability of 1/16, -1/1 with probability 1/4, --// and 0 with probability 3/8. --static void scalar_centered_binomial_distribution_eta_2_with_prf( -- scalar *out, const uint8_t input[33]) { -- uint8_t entropy[128]; -- static_assert(sizeof(entropy) == 2 * /*kEta=*/2 * DEGREE / 8, ""); -- BORINGSSL_keccak(entropy, sizeof(entropy), input, 33, boringssl_shake256); -- -- for (int i = 0; i < DEGREE; i += 2) { -- uint8_t byte = entropy[i / 2]; -- -- uint16_t value = kPrime; -- value += (byte & 1) + ((byte >> 1) & 1); -- value -= ((byte >> 2) & 1) + ((byte >> 3) & 1); -- out->c[i] = reduce_once(value); -- -- byte >>= 4; -- value = kPrime; -- value += (byte & 1) + ((byte >> 1) & 1); -- value -= ((byte >> 2) & 1) + ((byte >> 3) & 1); -- out->c[i + 1] = reduce_once(value); -- } -+ -+/************************************************* -+* Name: poly_ntt -+* -+* Description: Computes negacyclic number-theoretic transform (NTT) of -+* a polynomial in place; -+* inputs assumed to be in normal order, output in bitreversed order -+* -+* Arguments: - uint16_t *r: pointer to in/output polynomial -+**************************************************/ -+static void poly_ntt(poly *r) -+{ -+ ntt(r->coeffs); -+ poly_reduce(r); - } - --// Generates a secret vector by using --// |scalar_centered_binomial_distribution_eta_2_with_prf|, using the given seed --// appending and incrementing |counter| for entry of the vector. --static void vector_generate_secret_eta_2(vector *out, uint8_t *counter, -- const uint8_t seed[32]) { -- uint8_t input[33]; -- OPENSSL_memcpy(input, seed, 32); -- for (int i = 0; i < RANK; i++) { -- input[32] = (*counter)++; -- scalar_centered_binomial_distribution_eta_2_with_prf(&out->v[i], input); -- } -+/************************************************* -+* Name: poly_invntt_tomont -+* -+* Description: Computes inverse of negacyclic number-theoretic transform (NTT) -+* of a polynomial in place; -+* inputs assumed to be in bitreversed order, output in normal order -+* -+* Arguments: - uint16_t *a: pointer to in/output polynomial -+**************************************************/ -+static void poly_invntt_tomont(poly *r) -+{ -+ invntt(r->coeffs); - } - --// Expands the matrix of a seed for key generation and for encaps-CPA. --static void matrix_expand(matrix *out, const uint8_t rho[32]) { -- uint8_t input[34]; -- OPENSSL_memcpy(input, rho, 32); -- for (int i = 0; i < RANK; i++) { -- for (int j = 0; j < RANK; j++) { -- input[32] = i; -- input[33] = j; -- struct BORINGSSL_keccak_st keccak_ctx; -- BORINGSSL_keccak_init(&keccak_ctx, boringssl_shake128); -- BORINGSSL_keccak_absorb(&keccak_ctx, input, sizeof(input)); -- scalar_from_keccak_vartime(&out->v[i][j], &keccak_ctx); -- } -+/************************************************* -+* Name: poly_basemul_montgomery -+* -+* Description: Multiplication of two polynomials in NTT domain -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const poly *a: pointer to first input polynomial -+* - const poly *b: pointer to second input polynomial -+**************************************************/ -+static void poly_basemul_montgomery(poly *r, const poly *a, const poly *b) -+{ -+ unsigned int i; -+ for(i=0;icoeffs[4*i], &a->coeffs[4*i], &b->coeffs[4*i], zetas[64+i]); -+ basemul(&r->coeffs[4*i+2], &a->coeffs[4*i+2], &b->coeffs[4*i+2], -zetas[64+i]); - } - } - --static const uint8_t kMasks[8] = {0x01, 0x03, 0x07, 0x0f, -- 0x1f, 0x3f, 0x7f, 0xff}; -- --static void scalar_encode(uint8_t *out, const scalar *s, int bits) { -- assert(bits <= (int)sizeof(*s->c) * 8 && bits != 1); -- -- uint8_t out_byte = 0; -- int out_byte_bits = 0; -- -- for (int i = 0; i < DEGREE; i++) { -- uint16_t element = s->c[i]; -- int element_bits_done = 0; -- -- while (element_bits_done < bits) { -- int chunk_bits = bits - element_bits_done; -- int out_bits_remaining = 8 - out_byte_bits; -- if (chunk_bits >= out_bits_remaining) { -- chunk_bits = out_bits_remaining; -- out_byte |= (element & kMasks[chunk_bits - 1]) << out_byte_bits; -- *out = out_byte; -- out++; -- out_byte_bits = 0; -- out_byte = 0; -- } else { -- out_byte |= (element & kMasks[chunk_bits - 1]) << out_byte_bits; -- out_byte_bits += chunk_bits; -+/************************************************* -+* Name: poly_tomont -+* -+* Description: Inplace conversion of all coefficients of a polynomial -+* from normal domain to Montgomery domain -+* -+* Arguments: - poly *r: pointer to input/output polynomial -+**************************************************/ -+static void poly_tomont(poly *r) -+{ -+ unsigned int i; -+ const int16_t f = (1ULL << 32) % KYBER_Q; -+ for(i=0;icoeffs[i] = montgomery_reduce((int32_t)r->coeffs[i]*f); -+} -+ -+/************************************************* -+* Name: poly_reduce -+* -+* Description: Applies Barrett reduction to all coefficients of a polynomial -+* for details of the Barrett reduction see comments in reduce.c -+* -+* Arguments: - poly *r: pointer to input/output polynomial -+**************************************************/ -+static void poly_reduce(poly *r) -+{ -+ unsigned int i; -+ for(i=0;icoeffs[i] = barrett_reduce(r->coeffs[i]); -+} -+ -+/************************************************* -+* Name: poly_add -+* -+* Description: Add two polynomials; no modular reduction is performed -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const poly *a: pointer to first input polynomial -+* - const poly *b: pointer to second input polynomial -+**************************************************/ -+static void poly_add(poly *r, const poly *a, const poly *b) -+{ -+ unsigned int i; -+ for(i=0;icoeffs[i] = a->coeffs[i] + b->coeffs[i]; -+} -+ -+/************************************************* -+* Name: poly_sub -+* -+* Description: Subtract two polynomials; no modular reduction is performed -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const poly *a: pointer to first input polynomial -+* - const poly *b: pointer to second input polynomial -+**************************************************/ -+static void poly_sub(poly *r, const poly *a, const poly *b) -+{ -+ unsigned int i; -+ for(i=0;icoeffs[i] = a->coeffs[i] - b->coeffs[i]; -+} -+ -+// -+// polyvec.c -+// -+ -+/************************************************* -+* Name: polyvec_compress -+* -+* Description: Compress and serialize vector of polynomials -+* -+* Arguments: - uint8_t *r: pointer to output byte array -+* (needs space for KYBER_POLYVECCOMPRESSEDBYTES) -+* - const polyvec *a: pointer to input vector of polynomials -+**************************************************/ -+static void polyvec_compress(uint8_t r[KYBER_POLYVECCOMPRESSEDBYTES], const polyvec *a) -+{ -+ unsigned int i,j,k; -+ uint64_t d0; -+ -+#if (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 352)) -+ uint16_t t[8]; -+ for(i=0;ivec[i].coeffs[8*j+k]; -+ t[k] += ((int16_t)t[k] >> 15) & KYBER_Q; -+ d0 = t[k]; -+ d0 <<= 11; -+ d0 += 1664; -+ d0 *= 645084; -+ d0 >>= 31; -+ t[k] = d0 & 0x7ff; - } - -- element_bits_done += chunk_bits; -- element >>= chunk_bits; -+ r[ 0] = (t[0] >> 0); -+ r[ 1] = (t[0] >> 8) | (t[1] << 3); -+ r[ 2] = (t[1] >> 5) | (t[2] << 6); -+ r[ 3] = (t[2] >> 2); -+ r[ 4] = (t[2] >> 10) | (t[3] << 1); -+ r[ 5] = (t[3] >> 7) | (t[4] << 4); -+ r[ 6] = (t[4] >> 4) | (t[5] << 7); -+ r[ 7] = (t[5] >> 1); -+ r[ 8] = (t[5] >> 9) | (t[6] << 2); -+ r[ 9] = (t[6] >> 6) | (t[7] << 5); -+ r[10] = (t[7] >> 3); -+ r += 11; - } - } -+#elif (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 320)) -+ uint16_t t[4]; -+ for(i=0;ivec[i].coeffs[4*j+k]; -+ t[k] += ((int16_t)t[k] >> 15) & KYBER_Q; -+ d0 = t[k]; -+ d0 <<= 10; -+ d0 += 1665; -+ d0 *= 1290167; -+ d0 >>= 32; -+ t[k] = d0 & 0x3ff; -+ } - -- if (out_byte_bits > 0) { -- *out = out_byte; -+ r[0] = (t[0] >> 0); -+ r[1] = (t[0] >> 8) | (t[1] << 2); -+ r[2] = (t[1] >> 6) | (t[2] << 4); -+ r[3] = (t[2] >> 4) | (t[3] << 6); -+ r[4] = (t[3] >> 2); -+ r += 5; -+ } - } -+#else -+#error "KYBER_POLYVECCOMPRESSEDBYTES needs to be in {320*KYBER_K, 352*KYBER_K}" -+#endif - } - --// scalar_encode_1 is |scalar_encode| specialised for |bits| == 1. --static void scalar_encode_1(uint8_t out[32], const scalar *s) { -- for (int i = 0; i < DEGREE; i += 8) { -- uint8_t out_byte = 0; -- for (int j = 0; j < 8; j++) { -- out_byte |= (s->c[i + j] & 1) << j; -+/************************************************* -+* Name: polyvec_decompress -+* -+* Description: De-serialize and decompress vector of polynomials; -+* approximate inverse of polyvec_compress -+* -+* Arguments: - polyvec *r: pointer to output vector of polynomials -+* - const uint8_t *a: pointer to input byte array -+* (of length KYBER_POLYVECCOMPRESSEDBYTES) -+**************************************************/ -+static void polyvec_decompress(polyvec *r, const uint8_t a[KYBER_POLYVECCOMPRESSEDBYTES]) -+{ -+ unsigned int i,j,k; -+ -+#if (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 352)) -+ uint16_t t[8]; -+ for(i=0;i> 0) | ((uint16_t)a[ 1] << 8); -+ t[1] = (a[1] >> 3) | ((uint16_t)a[ 2] << 5); -+ t[2] = (a[2] >> 6) | ((uint16_t)a[ 3] << 2) | ((uint16_t)a[4] << 10); -+ t[3] = (a[4] >> 1) | ((uint16_t)a[ 5] << 7); -+ t[4] = (a[5] >> 4) | ((uint16_t)a[ 6] << 4); -+ t[5] = (a[6] >> 7) | ((uint16_t)a[ 7] << 1) | ((uint16_t)a[8] << 9); -+ t[6] = (a[8] >> 2) | ((uint16_t)a[ 9] << 6); -+ t[7] = (a[9] >> 5) | ((uint16_t)a[10] << 3); -+ a += 11; -+ -+ for(k=0;k<8;k++) -+ r->vec[i].coeffs[8*j+k] = ((uint32_t)(t[k] & 0x7FF)*KYBER_Q + 1024) >> 11; -+ } -+ } -+#elif (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 320)) -+ uint16_t t[4]; -+ for(i=0;i> 0) | ((uint16_t)a[1] << 8); -+ t[1] = (a[1] >> 2) | ((uint16_t)a[2] << 6); -+ t[2] = (a[2] >> 4) | ((uint16_t)a[3] << 4); -+ t[3] = (a[3] >> 6) | ((uint16_t)a[4] << 2); -+ a += 5; -+ -+ for(k=0;k<4;k++) -+ r->vec[i].coeffs[4*j+k] = ((uint32_t)(t[k] & 0x3FF)*KYBER_Q + 512) >> 10; - } -- *out = out_byte; -- out++; - } -+#else -+#error "KYBER_POLYVECCOMPRESSEDBYTES needs to be in {320*KYBER_K, 352*KYBER_K}" -+#endif - } - --// Encodes an entire vector into 32*|RANK|*|bits| bytes. Note that since 256 --// (DEGREE) is divisible by 8, the individual vector entries will always fill a --// whole number of bytes, so we do not need to worry about bit packing here. --static void vector_encode(uint8_t *out, const vector *a, int bits) { -- for (int i = 0; i < RANK; i++) { -- scalar_encode(out + i * bits * DEGREE / 8, &a->v[i], bits); -- } -+/************************************************* -+* Name: polyvec_tobytes -+* -+* Description: Serialize vector of polynomials -+* -+* Arguments: - uint8_t *r: pointer to output byte array -+* (needs space for KYBER_POLYVECBYTES) -+* - const polyvec *a: pointer to input vector of polynomials -+**************************************************/ -+static void polyvec_tobytes(uint8_t r[KYBER_POLYVECBYTES], const polyvec *a) -+{ -+ unsigned int i; -+ for(i=0;ivec[i]); - } - --// scalar_decode parses |DEGREE * bits| bits from |in| into |DEGREE| values in --// |out|. It returns one on success and zero if any parsed value is >= --// |kPrime|. --static int scalar_decode(scalar *out, const uint8_t *in, int bits) { -- assert(bits <= (int)sizeof(*out->c) * 8 && bits != 1); -+/************************************************* -+* Name: polyvec_frombytes -+* -+* Description: De-serialize vector of polynomials; -+* inverse of polyvec_tobytes -+* -+* Arguments: - uint8_t *r: pointer to output byte array -+* - const polyvec *a: pointer to input vector of polynomials -+* (of length KYBER_POLYVECBYTES) -+**************************************************/ -+static void polyvec_frombytes(polyvec *r, const uint8_t a[KYBER_POLYVECBYTES]) -+{ -+ unsigned int i; -+ for(i=0;ivec[i], a+i*KYBER_POLYBYTES); -+} - -- uint8_t in_byte = 0; -- int in_byte_bits_left = 0; -+/************************************************* -+* Name: polyvec_ntt -+* -+* Description: Apply forward NTT to all elements of a vector of polynomials -+* -+* Arguments: - polyvec *r: pointer to in/output vector of polynomials -+**************************************************/ -+static void polyvec_ntt(polyvec *r) -+{ -+ unsigned int i; -+ for(i=0;ivec[i]); -+} - -- for (int i = 0; i < DEGREE; i++) { -- uint16_t element = 0; -- int element_bits_done = 0; -+/************************************************* -+* Name: polyvec_invntt_tomont -+* -+* Description: Apply inverse NTT to all elements of a vector of polynomials -+* and multiply by Montgomery factor 2^16 -+* -+* Arguments: - polyvec *r: pointer to in/output vector of polynomials -+**************************************************/ -+static void polyvec_invntt_tomont(polyvec *r) -+{ -+ unsigned int i; -+ for(i=0;ivec[i]); -+} - -- while (element_bits_done < bits) { -- if (in_byte_bits_left == 0) { -- in_byte = *in; -- in++; -- in_byte_bits_left = 8; -- } -+/************************************************* -+* Name: polyvec_basemul_acc_montgomery -+* -+* Description: Multiply elements of a and b in NTT domain, accumulate into r, -+* and multiply by 2^-16. -+* -+* Arguments: - poly *r: pointer to output polynomial -+* - const polyvec *a: pointer to first input vector of polynomials -+* - const polyvec *b: pointer to second input vector of polynomials -+**************************************************/ -+static void polyvec_basemul_acc_montgomery(poly *r, const polyvec *a, const polyvec *b) -+{ -+ unsigned int i; -+ poly t; -+ -+ poly_basemul_montgomery(r, &a->vec[0], &b->vec[0]); -+ for(i=1;ivec[i], &b->vec[i]); -+ poly_add(r, r, &t); -+ } - -- int chunk_bits = bits - element_bits_done; -- if (chunk_bits > in_byte_bits_left) { -- chunk_bits = in_byte_bits_left; -- } -+ poly_reduce(r); -+} - -- element |= (in_byte & kMasks[chunk_bits - 1]) << element_bits_done; -- in_byte_bits_left -= chunk_bits; -- in_byte >>= chunk_bits; -+/************************************************* -+* Name: polyvec_reduce -+* -+* Description: Applies Barrett reduction to each coefficient -+* of each element of a vector of polynomials; -+* for details of the Barrett reduction see comments in reduce.c -+* -+* Arguments: - polyvec *r: pointer to input/output polynomial -+**************************************************/ -+static void polyvec_reduce(polyvec *r) -+{ -+ unsigned int i; -+ for(i=0;ivec[i]); -+} - -- element_bits_done += chunk_bits; -- } -+/************************************************* -+* Name: polyvec_add -+* -+* Description: Add vectors of polynomials -+* -+* Arguments: - polyvec *r: pointer to output vector of polynomials -+* - const polyvec *a: pointer to first input vector of polynomials -+* - const polyvec *b: pointer to second input vector of polynomials -+**************************************************/ -+static void polyvec_add(polyvec *r, const polyvec *a, const polyvec *b) -+{ -+ unsigned int i; -+ for(i=0;ivec[i], &a->vec[i], &b->vec[i]); -+} - -- if (element >= kPrime) { -- return 0; -- } -- out->c[i] = element; -- } -+// -+// indcpa.c -+// -+ -+/************************************************* -+* Name: pack_pk -+* -+* Description: Serialize the public key as concatenation of the -+* serialized vector of polynomials pk -+* and the public seed used to generate the matrix A. -+* -+* Arguments: uint8_t *r: pointer to the output serialized public key -+* polyvec *pk: pointer to the input public-key polyvec -+* const uint8_t *seed: pointer to the input public seed -+**************************************************/ -+static void pack_pk(uint8_t r[KYBER_INDCPA_PUBLICKEYBYTES], -+ polyvec *pk, -+ const uint8_t seed[KYBER_SYMBYTES]) -+{ -+ size_t i; -+ polyvec_tobytes(r, pk); -+ for(i=0;ic[i + j] = in_byte & 1; -- in_byte >>= 1; -- } -+/************************************************* -+* Name: pack_sk -+* -+* Description: Serialize the secret key -+* -+* Arguments: - uint8_t *r: pointer to output serialized secret key -+* - polyvec *sk: pointer to input vector of polynomials (secret key) -+**************************************************/ -+static void pack_sk(uint8_t r[KYBER_INDCPA_SECRETKEYBYTES], polyvec *sk) -+{ -+ polyvec_tobytes(r, sk); -+} -+ -+/************************************************* -+* Name: unpack_sk -+* -+* Description: De-serialize the secret key; inverse of pack_sk -+* -+* Arguments: - polyvec *sk: pointer to output vector of polynomials (secret key) -+* - const uint8_t *packedsk: pointer to input serialized secret key -+**************************************************/ -+static void unpack_sk(polyvec *sk, const uint8_t packedsk[KYBER_INDCPA_SECRETKEYBYTES]) -+{ -+ polyvec_frombytes(sk, packedsk); -+} -+ -+/************************************************* -+* Name: pack_ciphertext -+* -+* Description: Serialize the ciphertext as concatenation of the -+* compressed and serialized vector of polynomials b -+* and the compressed and serialized polynomial v -+* -+* Arguments: uint8_t *r: pointer to the output serialized ciphertext -+* poly *pk: pointer to the input vector of polynomials b -+* poly *v: pointer to the input polynomial v -+**************************************************/ -+static void pack_ciphertext(uint8_t r[KYBER_INDCPA_BYTES], polyvec *b, poly *v) -+{ -+ polyvec_compress(r, b); -+ poly_compress(r+KYBER_POLYVECCOMPRESSEDBYTES, v); -+} -+ -+/************************************************* -+* Name: unpack_ciphertext -+* -+* Description: De-serialize and decompress ciphertext from a byte array; -+* approximate inverse of pack_ciphertext -+* -+* Arguments: - polyvec *b: pointer to the output vector of polynomials b -+* - poly *v: pointer to the output polynomial v -+* - const uint8_t *c: pointer to the input serialized ciphertext -+**************************************************/ -+static void unpack_ciphertext(polyvec *b, poly *v, const uint8_t c[KYBER_INDCPA_BYTES]) -+{ -+ polyvec_decompress(b, c); -+ poly_decompress(v, c+KYBER_POLYVECCOMPRESSEDBYTES); -+} -+ -+/************************************************* -+* Name: rej_uniform -+* -+* Description: Run rejection sampling on uniform random bytes to generate -+* uniform random integers mod q -+* -+* Arguments: - int16_t *r: pointer to output buffer -+* - unsigned int len: requested number of 16-bit integers (uniform mod q) -+* - const uint8_t *buf: pointer to input buffer (assumed to be uniformly random bytes) -+* - unsigned int buflen: length of input buffer in bytes -+* -+* Returns number of sampled 16-bit integers (at most len) -+**************************************************/ -+static unsigned int rej_uniform(int16_t *r, -+ unsigned int len, -+ const uint8_t *buf, -+ unsigned int buflen) -+{ -+ unsigned int ctr, pos; -+ uint16_t val0, val1; -+ -+ ctr = pos = 0; -+ while(ctr < len && pos + 3 <= buflen) { -+ val0 = ((buf[pos+0] >> 0) | ((uint16_t)buf[pos+1] << 8)) & 0xFFF; -+ val1 = ((buf[pos+1] >> 4) | ((uint16_t)buf[pos+2] << 4)) & 0xFFF; -+ pos += 3; -+ -+ if(val0 < KYBER_Q) -+ r[ctr++] = val0; -+ if(ctr < len && val1 < KYBER_Q) -+ r[ctr++] = val1; - } -+ -+ return ctr; - } - --// Decodes 32*|RANK|*|bits| bytes from |in| into |out|. It returns one on --// success or zero if any parsed value is >= |kPrime|. --static int vector_decode(vector *out, const uint8_t *in, int bits) { -- for (int i = 0; i < RANK; i++) { -- if (!scalar_decode(&out->v[i], in + i * bits * DEGREE / 8, bits)) { -- return 0; -+#define gen_a(A,B) gen_matrix(A,B,0) -+#define gen_at(A,B) gen_matrix(A,B,1) -+ -+/************************************************* -+* Name: gen_matrix -+* -+* Description: Deterministically generate matrix A (or the transpose of A) -+* from a seed. Entries of the matrix are polynomials that look -+* uniformly random. Performs rejection sampling on output of -+* a XOF -+* -+* Arguments: - polyvec *a: pointer to ouptput matrix A -+* - const uint8_t *seed: pointer to input seed -+* - int transposed: boolean deciding whether A or A^T is generated -+**************************************************/ -+#define GEN_MATRIX_NBLOCKS ((12*KYBER_N/8*(1 << 12)/KYBER_Q + XOF_BLOCKBYTES)/XOF_BLOCKBYTES) -+// Not static for benchmarking -+static void gen_matrix(polyvec *a, const uint8_t seed[KYBER_SYMBYTES], int transposed) -+{ -+ unsigned int ctr, i, j, k; -+ unsigned int buflen, off; -+ uint8_t buf[GEN_MATRIX_NBLOCKS*XOF_BLOCKBYTES+2]; -+ xof_state state; -+ -+ for(i=0;i> kBarrettShift); -- uint32_t remainder = shifted - quotient * kPrime; -- -- // Adjust the quotient to round correctly: -- // 0 <= remainder <= kHalfPrime round to 0 -- // kHalfPrime < remainder <= kPrime + kHalfPrime round to 1 -- // kPrime + kHalfPrime < remainder < 2 * kPrime round to 2 -- assert(remainder < 2u * kPrime); -- quotient += 1 & constant_time_lt_w(kHalfPrime, remainder); -- quotient += 1 & constant_time_lt_w(kPrime + kHalfPrime, remainder); -- return quotient & ((1 << bits) - 1); --} -- --// Decompresses |x| by using an equi-distant representative. The formula is --// round(kPrime/2^|bits|*x). Note that 2^|bits| being the divisor allows us to --// implement this logic using only bit operations. --static uint16_t decompress(uint16_t x, int bits) { -- uint32_t product = (uint32_t)x * kPrime; -- uint32_t power = 1 << bits; -- // This is |product| % power, since |power| is a power of 2. -- uint32_t remainder = product & (power - 1); -- // This is |product| / power, since |power| is a power of 2. -- uint32_t lower = product >> bits; -- // The rounding logic works since the first half of numbers mod |power| have a -- // 0 as first bit, and the second half has a 1 as first bit, since |power| is -- // a power of 2. As a 12 bit number, |remainder| is always positive, so we -- // will shift in 0s for a right shift. -- return lower + (remainder >> (bits - 1)); --} -- --static void scalar_compress(scalar *s, int bits) { -- for (int i = 0; i < DEGREE; i++) { -- s->c[i] = compress(s->c[i], bits); -+/************************************************* -+* Name: indcpa_keypair -+* -+* Description: Generates public and private key for the CPA-secure -+* public-key encryption scheme underlying Kyber -+* -+* Arguments: - uint8_t *pk: pointer to output public key -+* (of length KYBER_INDCPA_PUBLICKEYBYTES bytes) -+* - uint8_t *sk: pointer to output private key -+ (of length KYBER_INDCPA_SECRETKEYBYTES bytes) -+**************************************************/ -+static void indcpa_keypair(uint8_t pk[KYBER_INDCPA_PUBLICKEYBYTES], -+ uint8_t sk[KYBER_INDCPA_SECRETKEYBYTES], -+ const uint8_t seed[KYBER_SYMBYTES]) -+{ -+ unsigned int i; -+ uint8_t buf[2*KYBER_SYMBYTES]; -+ const uint8_t *publicseed = buf; -+ const uint8_t *noiseseed = buf+KYBER_SYMBYTES; -+ uint8_t nonce = 0; -+ polyvec a[KYBER_K], e, pkpv, skpv; -+ -+ memcpy(buf, seed, KYBER_SYMBYTES); -+ hash_g(buf, buf, KYBER_SYMBYTES); -+ -+ gen_a(a, publicseed); -+ -+ for(i=0;ic[i] = decompress(s->c[i], bits); -- } -+/************************************************* -+* Name: indcpa_enc -+* -+* Description: Encryption function of the CPA-secure -+* public-key encryption scheme underlying Kyber. -+* -+* Arguments: - uint8_t *c: pointer to output ciphertext -+* (of length KYBER_INDCPA_BYTES bytes) -+* - const uint8_t *m: pointer to input message -+* (of length KYBER_INDCPA_MSGBYTES bytes) -+* - const uint8_t *pk: pointer to input public key -+* (of length KYBER_INDCPA_PUBLICKEYBYTES) -+* - const uint8_t *coins: pointer to input random coins used as seed -+* (of length KYBER_SYMBYTES) to deterministically -+* generate all randomness -+**************************************************/ -+static int indcpa_enc(uint8_t c[KYBER_INDCPA_BYTES], -+ const uint8_t m[KYBER_INDCPA_MSGBYTES], -+ const uint8_t pk[KYBER_INDCPA_PUBLICKEYBYTES], -+ const uint8_t coins[KYBER_SYMBYTES]) -+{ -+ unsigned int i; -+ uint8_t seed[KYBER_SYMBYTES]; -+ uint8_t nonce = 0; -+ polyvec sp, pkpv, ep, at[KYBER_K], b; -+ poly v, k, epp; -+ -+ if (!unpack_pk(&pkpv, seed, pk)) -+ return 0; -+ -+ poly_frommsg(&k, m); -+ gen_at(at, seed); -+ -+ for(i=0;iv[i], bits); -- } -+/************************************************* -+* Name: indcpa_dec -+* -+* Description: Decryption function of the CPA-secure -+* public-key encryption scheme underlying Kyber. -+* -+* Arguments: - uint8_t *m: pointer to output decrypted message -+* (of length KYBER_INDCPA_MSGBYTES) -+* - const uint8_t *c: pointer to input ciphertext -+* (of length KYBER_INDCPA_BYTES) -+* - const uint8_t *sk: pointer to input secret key -+* (of length KYBER_INDCPA_SECRETKEYBYTES) -+**************************************************/ -+static void indcpa_dec(uint8_t m[KYBER_INDCPA_MSGBYTES], -+ const uint8_t c[KYBER_INDCPA_BYTES], -+ const uint8_t sk[KYBER_INDCPA_SECRETKEYBYTES]) -+{ -+ polyvec b, skpv; -+ poly v, mp; -+ -+ unpack_ciphertext(&b, &v, c); -+ unpack_sk(&skpv, sk); -+ -+ polyvec_ntt(&b); -+ polyvec_basemul_acc_montgomery(&mp, &skpv, &b); -+ poly_invntt_tomont(&mp); -+ -+ poly_sub(&mp, &v, &mp); -+ poly_reduce(&mp); -+ -+ poly_tomsg(m, &mp); - } - --static void vector_decompress(vector *a, int bits) { -- for (int i = 0; i < RANK; i++) { -- scalar_decompress(&a->v[i], bits); -- } -+// -+// fips202.c -+// -+ -+/* Based on the public domain implementation in crypto_hash/keccakc512/simple/ from -+ * http://bench.cr.yp.to/supercop.html by Ronny Van Keer and the public domain "TweetFips202" -+ * implementation from https://twitter.com/tweetfips202 by Gilles Van Assche, Daniel J. Bernstein, -+ * and Peter Schwabe */ -+ -+#define NROUNDS 24 -+#define ROL(a, offset) ((a << offset) ^ (a >> (64-offset))) -+ -+/************************************************* -+* Name: load64 -+* -+* Description: Load 8 bytes into uint64_t in little-endian order -+* -+* Arguments: - const uint8_t *x: pointer to input byte array -+* -+* Returns the loaded 64-bit unsigned integer -+**************************************************/ -+static uint64_t load64(const uint8_t x[8]) { -+ unsigned int i; -+ uint64_t r = 0; -+ -+ for(i=0;i<8;i++) -+ r |= (uint64_t)x[i] << 8*i; -+ -+ return r; - } - --struct public_key { -- vector t; -- uint8_t rho[32]; -- uint8_t public_key_hash[32]; -- matrix m; -+/************************************************* -+* Name: store64 -+* -+* Description: Store a 64-bit integer to array of 8 bytes in little-endian order -+* -+* Arguments: - uint8_t *x: pointer to the output byte array (allocated) -+* - uint64_t u: input 64-bit unsigned integer -+**************************************************/ -+static void store64(uint8_t x[8], uint64_t u) { -+ unsigned int i; -+ -+ for(i=0;i<8;i++) -+ x[i] = u >> 8*i; -+} -+ -+/* Keccak round constants */ -+static const uint64_t KeccakF_RoundConstants[NROUNDS] = { -+ (uint64_t)0x0000000000000001ULL, -+ (uint64_t)0x0000000000008082ULL, -+ (uint64_t)0x800000000000808aULL, -+ (uint64_t)0x8000000080008000ULL, -+ (uint64_t)0x000000000000808bULL, -+ (uint64_t)0x0000000080000001ULL, -+ (uint64_t)0x8000000080008081ULL, -+ (uint64_t)0x8000000000008009ULL, -+ (uint64_t)0x000000000000008aULL, -+ (uint64_t)0x0000000000000088ULL, -+ (uint64_t)0x0000000080008009ULL, -+ (uint64_t)0x000000008000000aULL, -+ (uint64_t)0x000000008000808bULL, -+ (uint64_t)0x800000000000008bULL, -+ (uint64_t)0x8000000000008089ULL, -+ (uint64_t)0x8000000000008003ULL, -+ (uint64_t)0x8000000000008002ULL, -+ (uint64_t)0x8000000000000080ULL, -+ (uint64_t)0x000000000000800aULL, -+ (uint64_t)0x800000008000000aULL, -+ (uint64_t)0x8000000080008081ULL, -+ (uint64_t)0x8000000000008080ULL, -+ (uint64_t)0x0000000080000001ULL, -+ (uint64_t)0x8000000080008008ULL - }; - --static struct public_key *public_key_from_external( -- const struct KYBER_public_key *external) { -- static_assert(sizeof(struct KYBER_public_key) >= sizeof(struct public_key), -- "Kyber public key is too small"); -- static_assert(alignof(struct KYBER_public_key) >= alignof(struct public_key), -- "Kyber public key align incorrect"); -- return (struct public_key *)external; -+/************************************************* -+* Name: KeccakF1600_StatePermute -+* -+* Description: The Keccak F1600 Permutation -+* -+* Arguments: - uint64_t *state: pointer to input/output Keccak state -+**************************************************/ -+static void KeccakF1600_StatePermute(uint64_t state[25]) -+{ -+ int round; -+ -+ uint64_t Aba, Abe, Abi, Abo, Abu; -+ uint64_t Aga, Age, Agi, Ago, Agu; -+ uint64_t Aka, Ake, Aki, Ako, Aku; -+ uint64_t Ama, Ame, Ami, Amo, Amu; -+ uint64_t Asa, Ase, Asi, Aso, Asu; -+ uint64_t BCa, BCe, BCi, BCo, BCu; -+ uint64_t Da, De, Di, Do, Du; -+ uint64_t Eba, Ebe, Ebi, Ebo, Ebu; -+ uint64_t Ega, Ege, Egi, Ego, Egu; -+ uint64_t Eka, Eke, Eki, Eko, Eku; -+ uint64_t Ema, Eme, Emi, Emo, Emu; -+ uint64_t Esa, Ese, Esi, Eso, Esu; -+ -+ //copyFromState(A, state) -+ Aba = state[ 0]; -+ Abe = state[ 1]; -+ Abi = state[ 2]; -+ Abo = state[ 3]; -+ Abu = state[ 4]; -+ Aga = state[ 5]; -+ Age = state[ 6]; -+ Agi = state[ 7]; -+ Ago = state[ 8]; -+ Agu = state[ 9]; -+ Aka = state[10]; -+ Ake = state[11]; -+ Aki = state[12]; -+ Ako = state[13]; -+ Aku = state[14]; -+ Ama = state[15]; -+ Ame = state[16]; -+ Ami = state[17]; -+ Amo = state[18]; -+ Amu = state[19]; -+ Asa = state[20]; -+ Ase = state[21]; -+ Asi = state[22]; -+ Aso = state[23]; -+ Asu = state[24]; -+ -+ for(round = 0; round < NROUNDS; round += 2) { -+ // prepareTheta -+ BCa = Aba^Aga^Aka^Ama^Asa; -+ BCe = Abe^Age^Ake^Ame^Ase; -+ BCi = Abi^Agi^Aki^Ami^Asi; -+ BCo = Abo^Ago^Ako^Amo^Aso; -+ BCu = Abu^Agu^Aku^Amu^Asu; -+ -+ //thetaRhoPiChiIotaPrepareTheta(round, A, E) -+ Da = BCu^ROL(BCe, 1); -+ De = BCa^ROL(BCi, 1); -+ Di = BCe^ROL(BCo, 1); -+ Do = BCi^ROL(BCu, 1); -+ Du = BCo^ROL(BCa, 1); -+ -+ Aba ^= Da; -+ BCa = Aba; -+ Age ^= De; -+ BCe = ROL(Age, 44); -+ Aki ^= Di; -+ BCi = ROL(Aki, 43); -+ Amo ^= Do; -+ BCo = ROL(Amo, 21); -+ Asu ^= Du; -+ BCu = ROL(Asu, 14); -+ Eba = BCa ^((~BCe)& BCi ); -+ Eba ^= (uint64_t)KeccakF_RoundConstants[round]; -+ Ebe = BCe ^((~BCi)& BCo ); -+ Ebi = BCi ^((~BCo)& BCu ); -+ Ebo = BCo ^((~BCu)& BCa ); -+ Ebu = BCu ^((~BCa)& BCe ); -+ -+ Abo ^= Do; -+ BCa = ROL(Abo, 28); -+ Agu ^= Du; -+ BCe = ROL(Agu, 20); -+ Aka ^= Da; -+ BCi = ROL(Aka, 3); -+ Ame ^= De; -+ BCo = ROL(Ame, 45); -+ Asi ^= Di; -+ BCu = ROL(Asi, 61); -+ Ega = BCa ^((~BCe)& BCi ); -+ Ege = BCe ^((~BCi)& BCo ); -+ Egi = BCi ^((~BCo)& BCu ); -+ Ego = BCo ^((~BCu)& BCa ); -+ Egu = BCu ^((~BCa)& BCe ); -+ -+ Abe ^= De; -+ BCa = ROL(Abe, 1); -+ Agi ^= Di; -+ BCe = ROL(Agi, 6); -+ Ako ^= Do; -+ BCi = ROL(Ako, 25); -+ Amu ^= Du; -+ BCo = ROL(Amu, 8); -+ Asa ^= Da; -+ BCu = ROL(Asa, 18); -+ Eka = BCa ^((~BCe)& BCi ); -+ Eke = BCe ^((~BCi)& BCo ); -+ Eki = BCi ^((~BCo)& BCu ); -+ Eko = BCo ^((~BCu)& BCa ); -+ Eku = BCu ^((~BCa)& BCe ); -+ -+ Abu ^= Du; -+ BCa = ROL(Abu, 27); -+ Aga ^= Da; -+ BCe = ROL(Aga, 36); -+ Ake ^= De; -+ BCi = ROL(Ake, 10); -+ Ami ^= Di; -+ BCo = ROL(Ami, 15); -+ Aso ^= Do; -+ BCu = ROL(Aso, 56); -+ Ema = BCa ^((~BCe)& BCi ); -+ Eme = BCe ^((~BCi)& BCo ); -+ Emi = BCi ^((~BCo)& BCu ); -+ Emo = BCo ^((~BCu)& BCa ); -+ Emu = BCu ^((~BCa)& BCe ); -+ -+ Abi ^= Di; -+ BCa = ROL(Abi, 62); -+ Ago ^= Do; -+ BCe = ROL(Ago, 55); -+ Aku ^= Du; -+ BCi = ROL(Aku, 39); -+ Ama ^= Da; -+ BCo = ROL(Ama, 41); -+ Ase ^= De; -+ BCu = ROL(Ase, 2); -+ Esa = BCa ^((~BCe)& BCi ); -+ Ese = BCe ^((~BCi)& BCo ); -+ Esi = BCi ^((~BCo)& BCu ); -+ Eso = BCo ^((~BCu)& BCa ); -+ Esu = BCu ^((~BCa)& BCe ); -+ -+ // prepareTheta -+ BCa = Eba^Ega^Eka^Ema^Esa; -+ BCe = Ebe^Ege^Eke^Eme^Ese; -+ BCi = Ebi^Egi^Eki^Emi^Esi; -+ BCo = Ebo^Ego^Eko^Emo^Eso; -+ BCu = Ebu^Egu^Eku^Emu^Esu; -+ -+ //thetaRhoPiChiIotaPrepareTheta(round+1, E, A) -+ Da = BCu^ROL(BCe, 1); -+ De = BCa^ROL(BCi, 1); -+ Di = BCe^ROL(BCo, 1); -+ Do = BCi^ROL(BCu, 1); -+ Du = BCo^ROL(BCa, 1); -+ -+ Eba ^= Da; -+ BCa = Eba; -+ Ege ^= De; -+ BCe = ROL(Ege, 44); -+ Eki ^= Di; -+ BCi = ROL(Eki, 43); -+ Emo ^= Do; -+ BCo = ROL(Emo, 21); -+ Esu ^= Du; -+ BCu = ROL(Esu, 14); -+ Aba = BCa ^((~BCe)& BCi ); -+ Aba ^= (uint64_t)KeccakF_RoundConstants[round+1]; -+ Abe = BCe ^((~BCi)& BCo ); -+ Abi = BCi ^((~BCo)& BCu ); -+ Abo = BCo ^((~BCu)& BCa ); -+ Abu = BCu ^((~BCa)& BCe ); -+ -+ Ebo ^= Do; -+ BCa = ROL(Ebo, 28); -+ Egu ^= Du; -+ BCe = ROL(Egu, 20); -+ Eka ^= Da; -+ BCi = ROL(Eka, 3); -+ Eme ^= De; -+ BCo = ROL(Eme, 45); -+ Esi ^= Di; -+ BCu = ROL(Esi, 61); -+ Aga = BCa ^((~BCe)& BCi ); -+ Age = BCe ^((~BCi)& BCo ); -+ Agi = BCi ^((~BCo)& BCu ); -+ Ago = BCo ^((~BCu)& BCa ); -+ Agu = BCu ^((~BCa)& BCe ); -+ -+ Ebe ^= De; -+ BCa = ROL(Ebe, 1); -+ Egi ^= Di; -+ BCe = ROL(Egi, 6); -+ Eko ^= Do; -+ BCi = ROL(Eko, 25); -+ Emu ^= Du; -+ BCo = ROL(Emu, 8); -+ Esa ^= Da; -+ BCu = ROL(Esa, 18); -+ Aka = BCa ^((~BCe)& BCi ); -+ Ake = BCe ^((~BCi)& BCo ); -+ Aki = BCi ^((~BCo)& BCu ); -+ Ako = BCo ^((~BCu)& BCa ); -+ Aku = BCu ^((~BCa)& BCe ); -+ -+ Ebu ^= Du; -+ BCa = ROL(Ebu, 27); -+ Ega ^= Da; -+ BCe = ROL(Ega, 36); -+ Eke ^= De; -+ BCi = ROL(Eke, 10); -+ Emi ^= Di; -+ BCo = ROL(Emi, 15); -+ Eso ^= Do; -+ BCu = ROL(Eso, 56); -+ Ama = BCa ^((~BCe)& BCi ); -+ Ame = BCe ^((~BCi)& BCo ); -+ Ami = BCi ^((~BCo)& BCu ); -+ Amo = BCo ^((~BCu)& BCa ); -+ Amu = BCu ^((~BCa)& BCe ); -+ -+ Ebi ^= Di; -+ BCa = ROL(Ebi, 62); -+ Ego ^= Do; -+ BCe = ROL(Ego, 55); -+ Eku ^= Du; -+ BCi = ROL(Eku, 39); -+ Ema ^= Da; -+ BCo = ROL(Ema, 41); -+ Ese ^= De; -+ BCu = ROL(Ese, 2); -+ Asa = BCa ^((~BCe)& BCi ); -+ Ase = BCe ^((~BCi)& BCo ); -+ Asi = BCi ^((~BCo)& BCu ); -+ Aso = BCo ^((~BCu)& BCa ); -+ Asu = BCu ^((~BCa)& BCe ); -+ } -+ -+ //copyToState(state, A) -+ state[ 0] = Aba; -+ state[ 1] = Abe; -+ state[ 2] = Abi; -+ state[ 3] = Abo; -+ state[ 4] = Abu; -+ state[ 5] = Aga; -+ state[ 6] = Age; -+ state[ 7] = Agi; -+ state[ 8] = Ago; -+ state[ 9] = Agu; -+ state[10] = Aka; -+ state[11] = Ake; -+ state[12] = Aki; -+ state[13] = Ako; -+ state[14] = Aku; -+ state[15] = Ama; -+ state[16] = Ame; -+ state[17] = Ami; -+ state[18] = Amo; -+ state[19] = Amu; -+ state[20] = Asa; -+ state[21] = Ase; -+ state[22] = Asi; -+ state[23] = Aso; -+ state[24] = Asu; - } - --struct private_key { -- struct public_key pub; -- vector s; -- uint8_t fo_failure_secret[32]; --}; - --static struct private_key *private_key_from_external( -- const struct KYBER_private_key *external) { -- static_assert(sizeof(struct KYBER_private_key) >= sizeof(struct private_key), -- "Kyber private key too small"); -- static_assert( -- alignof(struct KYBER_private_key) >= alignof(struct private_key), -- "Kyber private key align incorrect"); -- return (struct private_key *)external; --} -- --// Calls |KYBER_generate_key_external_entropy| with random bytes from --// |RAND_bytes|. --void KYBER_generate_key(uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], -- struct KYBER_private_key *out_private_key) { -- uint8_t entropy[KYBER_GENERATE_KEY_ENTROPY]; -- RAND_bytes(entropy, sizeof(entropy)); -- KYBER_generate_key_external_entropy(out_encoded_public_key, out_private_key, -- entropy); --} -- --static int kyber_marshal_public_key(CBB *out, const struct public_key *pub) { -- uint8_t *vector_output; -- if (!CBB_add_space(out, &vector_output, kEncodedVectorSize)) { -- return 0; -+/************************************************* -+* Name: keccak_squeeze -+* -+* Description: Squeeze step of Keccak. Squeezes arbitratrily many bytes. -+* Modifies the state. Can be called multiple times to keep -+* squeezing, i.e., is incremental. -+* -+* Arguments: - uint8_t *out: pointer to output -+* - size_t outlen: number of bytes to be squeezed (written to out) -+* - uint64_t *s: pointer to input/output Keccak state -+* - unsigned int pos: number of bytes in current block already squeezed -+* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128) -+* -+* Returns new position pos in current block -+**************************************************/ -+static unsigned int keccak_squeeze(uint8_t *out, -+ size_t outlen, -+ uint64_t s[25], -+ unsigned int pos, -+ unsigned int r) -+{ -+ unsigned int i; -+ -+ while(outlen) { -+ if(pos == r) { -+ KeccakF1600_StatePermute(s); -+ pos = 0; -+ } -+ for(i=pos;i < r && i < pos+outlen; i++) -+ *out++ = s[i/8] >> 8*(i%8); -+ outlen -= i-pos; -+ pos = i; - } -- vector_encode(vector_output, &pub->t, kLog2Prime); -- if (!CBB_add_bytes(out, pub->rho, sizeof(pub->rho))) { -- return 0; -+ -+ return pos; -+} -+ -+/************************************************* -+* Name: keccak_absorb -+* -+* Description: Absorb step of Keccak; incremental. -+* -+* Arguments: - uint64_t *s: pointer to Keccak state -+* - unsigned int pos: position in current block to be absorbed -+* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128) -+* - const uint8_t *in: pointer to input to be absorbed into s -+* - size_t inlen: length of input in bytes -+* -+* Returns new position pos in current block -+**************************************************/ -+static unsigned int keccak_absorb(uint64_t s[25], -+ unsigned int pos, -+ unsigned int r, -+ const uint8_t *in, -+ size_t inlen) -+{ -+ unsigned int i; -+ -+ while(pos+inlen >= r) { -+ for(i=pos;ipub.rho, hashed, sizeof(priv->pub.rho)); -- matrix_expand(&priv->pub.m, rho); -- uint8_t counter = 0; -- vector_generate_secret_eta_2(&priv->s, &counter, sigma); -- vector_ntt(&priv->s); -- vector error; -- vector_generate_secret_eta_2(&error, &counter, sigma); -- vector_ntt(&error); -- matrix_mult_transpose(&priv->pub.t, &priv->pub.m, &priv->s); -- vector_add(&priv->pub.t, &error); -- -- CBB cbb; -- CBB_init_fixed(&cbb, out_encoded_public_key, KYBER_PUBLIC_KEY_BYTES); -- if (!kyber_marshal_public_key(&cbb, &priv->pub)) { -- abort(); -+ -+/************************************************* -+* Name: keccak_absorb_once -+* -+* Description: Absorb step of Keccak; -+* non-incremental, starts by zeroeing the state. -+* -+* Arguments: - uint64_t *s: pointer to (uninitialized) output Keccak state -+* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128) -+* - const uint8_t *in: pointer to input to be absorbed into s -+* - size_t inlen: length of input in bytes -+* - uint8_t p: domain-separation byte for different Keccak-derived functions -+**************************************************/ -+static void keccak_absorb_once(uint64_t s[25], -+ unsigned int r, -+ const uint8_t *in, -+ size_t inlen, -+ uint8_t p) -+{ -+ unsigned int i; -+ -+ for(i=0;i<25;i++) -+ s[i] = 0; -+ -+ while(inlen >= r) { -+ for(i=0;ipub.public_key_hash, sizeof(priv->pub.public_key_hash), -- out_encoded_public_key, KYBER_PUBLIC_KEY_BYTES, -- boringssl_sha3_256); -- OPENSSL_memcpy(priv->fo_failure_secret, entropy + 32, 32); --} -- --void KYBER_public_from_private(struct KYBER_public_key *out_public_key, -- const struct KYBER_private_key *private_key) { -- struct public_key *const pub = public_key_from_external(out_public_key); -- const struct private_key *const priv = private_key_from_external(private_key); -- *pub = priv->pub; --} -- --// Algorithm 5 of the Kyber spec. Encrypts a message with given randomness to --// the ciphertext in |out|. Without applying the Fujisaki-Okamoto transform this --// would not result in a CCA secure scheme, since lattice schemes are vulnerable --// to decryption failure oracles. --static void encrypt_cpa(uint8_t out[KYBER_CIPHERTEXT_BYTES], -- const struct public_key *pub, const uint8_t message[32], -- const uint8_t randomness[32]) { -- uint8_t counter = 0; -- vector secret; -- vector_generate_secret_eta_2(&secret, &counter, randomness); -- vector_ntt(&secret); -- vector error; -- vector_generate_secret_eta_2(&error, &counter, randomness); -- uint8_t input[33]; -- OPENSSL_memcpy(input, randomness, 32); -- input[32] = counter; -- scalar scalar_error; -- scalar_centered_binomial_distribution_eta_2_with_prf(&scalar_error, input); -- vector u; -- matrix_mult(&u, &pub->m, &secret); -- vector_inverse_ntt(&u); -- vector_add(&u, &error); -- scalar v; -- scalar_inner_product(&v, &pub->t, &secret); -- scalar_inverse_ntt(&v); -- scalar_add(&v, &scalar_error); -- scalar expanded_message; -- scalar_decode_1(&expanded_message, message); -- scalar_decompress(&expanded_message, 1); -- scalar_add(&v, &expanded_message); -- vector_compress(&u, kDU); -- vector_encode(out, &u, kDU); -- scalar_compress(&v, kDV); -- scalar_encode(out + kCompressedVectorSize, &v, kDV); --} -- --// Calls KYBER_encap_external_entropy| with random bytes from |RAND_bytes| --void KYBER_encap(uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], -- uint8_t *out_shared_secret, size_t out_shared_secret_len, -- const struct KYBER_public_key *public_key) { -- uint8_t entropy[KYBER_ENCAP_ENTROPY]; -- RAND_bytes(entropy, KYBER_ENCAP_ENTROPY); -- KYBER_encap_external_entropy(out_ciphertext, out_shared_secret, -- out_shared_secret_len, public_key, entropy); --} -- --// Algorithm 8 of the Kyber spec, safe for line 2 of the spec. The spec there --// hashes the output of the system's random number generator, since the FO --// transform will reveal it to the decrypting party. There is no reason to do --// this when a secure random number generator is used. When an insecure random --// number generator is used, the caller should switch to a secure one before --// calling this method. --void KYBER_encap_external_entropy( -- uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], uint8_t *out_shared_secret, -- size_t out_shared_secret_len, const struct KYBER_public_key *public_key, -- const uint8_t entropy[KYBER_ENCAP_ENTROPY]) { -- const struct public_key *pub = public_key_from_external(public_key); -- uint8_t input[64]; -- OPENSSL_memcpy(input, entropy, KYBER_ENCAP_ENTROPY); -- OPENSSL_memcpy(input + KYBER_ENCAP_ENTROPY, pub->public_key_hash, -- sizeof(input) - KYBER_ENCAP_ENTROPY); -- uint8_t prekey_and_randomness[64]; -- BORINGSSL_keccak(prekey_and_randomness, sizeof(prekey_and_randomness), input, -- sizeof(input), boringssl_sha3_512); -- encrypt_cpa(out_ciphertext, pub, entropy, prekey_and_randomness + 32); -- BORINGSSL_keccak(prekey_and_randomness + 32, 32, out_ciphertext, -- KYBER_CIPHERTEXT_BYTES, boringssl_sha3_256); -- BORINGSSL_keccak(out_shared_secret, out_shared_secret_len, -- prekey_and_randomness, sizeof(prekey_and_randomness), -- boringssl_shake256); --} -- --// Algorithm 6 of the Kyber spec. --static void decrypt_cpa(uint8_t out[32], const struct private_key *priv, -- const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES]) { -- vector u; -- vector_decode(&u, ciphertext, kDU); -- vector_decompress(&u, kDU); -- vector_ntt(&u); -- scalar v; -- scalar_decode(&v, ciphertext + kCompressedVectorSize, kDV); -- scalar_decompress(&v, kDV); -- scalar mask; -- scalar_inner_product(&mask, &priv->s, &u); -- scalar_inverse_ntt(&mask); -- scalar_sub(&v, &mask); -- scalar_compress(&v, 1); -- scalar_encode_1(out, &v); --} -- --// Algorithm 9 of the Kyber spec, performing the FO transform by running --// encrypt_cpa on the decrypted message. The spec does not allow the decryption --// failure to be passed on to the caller, and instead returns a result that is --// deterministic but unpredictable to anyone without knowledge of the private --// key. --void KYBER_decap(uint8_t *out_shared_secret, size_t out_shared_secret_len, -- const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES], -- const struct KYBER_private_key *private_key) { -- const struct private_key *priv = private_key_from_external(private_key); -- uint8_t decrypted[64]; -- decrypt_cpa(decrypted, priv, ciphertext); -- OPENSSL_memcpy(decrypted + 32, priv->pub.public_key_hash, -- sizeof(decrypted) - 32); -- uint8_t prekey_and_randomness[64]; -- BORINGSSL_keccak(prekey_and_randomness, sizeof(prekey_and_randomness), -- decrypted, sizeof(decrypted), boringssl_sha3_512); -- uint8_t expected_ciphertext[KYBER_CIPHERTEXT_BYTES]; -- encrypt_cpa(expected_ciphertext, &priv->pub, decrypted, -- prekey_and_randomness + 32); -- uint8_t mask = -- constant_time_eq_int_8(CRYPTO_memcmp(ciphertext, expected_ciphertext, -- sizeof(expected_ciphertext)), -- 0); -- uint8_t input[64]; -- for (int i = 0; i < 32; i++) { -- input[i] = constant_time_select_8(mask, prekey_and_randomness[i], -- priv->fo_failure_secret[i]); -+ for(i=0;is, SHAKE128_RATE, in, inlen, 0x1F); -+ state->pos = SHAKE128_RATE; - } - --// kyber_parse_public_key_no_hash parses |in| into |pub| but doesn't calculate --// the value of |pub->public_key_hash|. --static int kyber_parse_public_key_no_hash(struct public_key *pub, CBS *in) { -- CBS t_bytes; -- if (!CBS_get_bytes(in, &t_bytes, kEncodedVectorSize) || -- !vector_decode(&pub->t, CBS_data(&t_bytes), kLog2Prime) || -- !CBS_copy_bytes(in, pub->rho, sizeof(pub->rho))) { -- return 0; -- } -- matrix_expand(&pub->m, pub->rho); -- return 1; -+/************************************************* -+* Name: shake128_squeezeblocks -+* -+* Description: Squeeze step of SHAKE128 XOF. Squeezes full blocks of -+* SHAKE128_RATE bytes each. Can be called multiple times -+* to keep squeezing. Assumes new block has not yet been -+* started (state->pos = SHAKE128_RATE). -+* -+* Arguments: - uint8_t *out: pointer to output blocks -+* - size_t nblocks: number of blocks to be squeezed (written to output) -+* - keccak_state *s: pointer to input/output Keccak state -+**************************************************/ -+static void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) -+{ -+ keccak_squeezeblocks(out, nblocks, state->s, SHAKE128_RATE); -+} -+ -+/************************************************* -+* Name: shake256_squeeze -+* -+* Description: Squeeze step of SHAKE256 XOF. Squeezes arbitraily many -+* bytes. Can be called multiple times to keep squeezing. -+* -+* Arguments: - uint8_t *out: pointer to output blocks -+* - size_t outlen : number of bytes to be squeezed (written to output) -+* - keccak_state *s: pointer to input/output Keccak state -+**************************************************/ -+static void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state) -+{ -+ state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE256_RATE); -+} -+ -+/************************************************* -+* Name: shake256_absorb_once -+* -+* Description: Initialize, absorb into and finalize SHAKE256 XOF; non-incremental. -+* -+* Arguments: - keccak_state *state: pointer to (uninitialized) output Keccak state -+* - const uint8_t *in: pointer to input to be absorbed into s -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void shake256_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen) -+{ -+ keccak_absorb_once(state->s, SHAKE256_RATE, in, inlen, 0x1F); -+ state->pos = SHAKE256_RATE; -+} -+ -+/************************************************* -+* Name: shake256_squeezeblocks -+* -+* Description: Squeeze step of SHAKE256 XOF. Squeezes full blocks of -+* SHAKE256_RATE bytes each. Can be called multiple times -+* to keep squeezing. Assumes next block has not yet been -+* started (state->pos = SHAKE256_RATE). -+* -+* Arguments: - uint8_t *out: pointer to output blocks -+* - size_t nblocks: number of blocks to be squeezed (written to output) -+* - keccak_state *s: pointer to input/output Keccak state -+**************************************************/ -+static void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) -+{ -+ keccak_squeezeblocks(out, nblocks, state->s, SHAKE256_RATE); -+} -+ -+/************************************************* -+* Name: shake256_absorb -+* -+* Description: Absorb step of the SHAKE256 XOF; incremental. -+* -+* Arguments: - keccak_state *state: pointer to (initialized) output Keccak state -+* - const uint8_t *in: pointer to input to be absorbed into s -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen) -+{ -+ state->pos = keccak_absorb(state->s, state->pos, SHAKE256_RATE, in, inlen); -+} -+ -+/************************************************* -+* Name: shake256_finalize -+* -+* Description: Finalize absorb step of the SHAKE256 XOF. -+* -+* Arguments: - keccak_state *state: pointer to Keccak state -+**************************************************/ -+static void shake256_finalize(keccak_state *state) -+{ -+ keccak_finalize(state->s, state->pos, SHAKE256_RATE, 0x1F); -+ state->pos = SHAKE256_RATE; -+} -+ -+/************************************************* -+* Name: keccak_init -+* -+* Description: Initializes the Keccak state. -+* -+* Arguments: - uint64_t *s: pointer to Keccak state -+**************************************************/ -+static void keccak_init(uint64_t s[25]) -+{ -+ unsigned int i; -+ for(i=0;i<25;i++) -+ s[i] = 0; -+} -+ -+/************************************************* -+* Name: shake256_init -+* -+* Description: Initilizes Keccak state for use as SHAKE256 XOF -+* -+* Arguments: - keccak_state *state: pointer to (uninitialized) Keccak state -+**************************************************/ -+static void shake256_init(keccak_state *state) -+{ -+ keccak_init(state->s); -+ state->pos = 0; -+} -+ -+ -+/************************************************* -+* Name: shake256 -+* -+* Description: SHAKE256 XOF with non-incremental API -+* -+* Arguments: - uint8_t *out: pointer to output -+* - size_t outlen: requested output length in bytes -+* - const uint8_t *in: pointer to input -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen) -+{ -+ size_t nblocks; -+ keccak_state state; -+ -+ shake256_absorb_once(&state, in, inlen); -+ nblocks = outlen/SHAKE256_RATE; -+ shake256_squeezeblocks(out, nblocks, &state); -+ outlen -= nblocks*SHAKE256_RATE; -+ out += nblocks*SHAKE256_RATE; -+ shake256_squeeze(out, outlen, &state); -+} -+ -+/************************************************* -+* Name: sha3_256 -+* -+* Description: SHA3-256 with non-incremental API -+* -+* Arguments: - uint8_t *h: pointer to output (32 bytes) -+* - const uint8_t *in: pointer to input -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void sha3_256(uint8_t h[32], const uint8_t *in, size_t inlen) -+{ -+ unsigned int i; -+ uint64_t s[25]; -+ -+ keccak_absorb_once(s, SHA3_256_RATE, in, inlen, 0x06); -+ KeccakF1600_StatePermute(s); -+ for(i=0;i<4;i++) -+ store64(h+8*i,s[i]); -+} -+ -+/************************************************* -+* Name: sha3_512 -+* -+* Description: SHA3-512 with non-incremental API -+* -+* Arguments: - uint8_t *h: pointer to output (64 bytes) -+* - const uint8_t *in: pointer to input -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void sha3_512(uint8_t h[64], const uint8_t *in, size_t inlen) -+{ -+ unsigned int i; -+ uint64_t s[25]; -+ -+ keccak_absorb_once(s, SHA3_512_RATE, in, inlen, 0x06); -+ KeccakF1600_StatePermute(s); -+ for(i=0;i<8;i++) -+ store64(h+8*i,s[i]); -+} -+ -+// -+// symmetric-shake.c -+// -+ -+/************************************************* -+* Name: kyber_shake128_absorb -+* -+* Description: Absorb step of the SHAKE128 specialized for the Kyber context. -+* -+* Arguments: - keccak_state *state: pointer to (uninitialized) output Keccak state -+* - const uint8_t *seed: pointer to KYBER_SYMBYTES input to be absorbed into state -+* - uint8_t i: additional byte of input -+* - uint8_t j: additional byte of input -+**************************************************/ -+static void kyber_shake128_absorb(keccak_state *state, -+ const uint8_t seed[KYBER_SYMBYTES], -+ uint8_t x, -+ uint8_t y) -+{ -+ uint8_t extseed[KYBER_SYMBYTES+2]; -+ -+ memcpy(extseed, seed, KYBER_SYMBYTES); -+ extseed[KYBER_SYMBYTES+0] = x; -+ extseed[KYBER_SYMBYTES+1] = y; -+ -+ shake128_absorb_once(state, extseed, sizeof(extseed)); - } - --int KYBER_parse_public_key(struct KYBER_public_key *public_key, CBS *in) { -- struct public_key *pub = public_key_from_external(public_key); -- CBS orig_in = *in; -- if (!kyber_parse_public_key_no_hash(pub, in) || // -- CBS_len(in) != 0) { -+/************************************************* -+* Name: kyber_shake256_prf -+* -+* Description: Usage of SHAKE256 as a PRF, concatenates secret and public input -+* and then generates outlen bytes of SHAKE256 output -+* -+* Arguments: - uint8_t *out: pointer to output -+* - size_t outlen: number of requested output bytes -+* - const uint8_t *key: pointer to the key (of length KYBER_SYMBYTES) -+* - uint8_t nonce: single-byte nonce (public PRF input) -+**************************************************/ -+static void kyber_shake256_prf(uint8_t *out, size_t outlen, const uint8_t key[KYBER_SYMBYTES], uint8_t nonce) -+{ -+ uint8_t extkey[KYBER_SYMBYTES+1]; -+ -+ memcpy(extkey, key, KYBER_SYMBYTES); -+ extkey[KYBER_SYMBYTES] = nonce; -+ -+ shake256(out, outlen, extkey, sizeof(extkey)); -+} -+ -+// -+// kem.c -+// -+ -+// Modified crypto_kem_keypair to BoringSSL style API -+void generate_key(struct public_key *out_pub, struct private_key *out_priv, -+ const uint8_t seed[KYBER_GENERATE_KEY_BYTES]) -+{ -+ size_t i; -+ uint8_t* pk = &out_pub->opaque[0]; -+ uint8_t* sk = &out_priv->opaque[0]; -+ -+ indcpa_keypair(pk, sk, seed); -+ for(i=0;iopaque[0]; -+ uint8_t *ct = out_ciphertext; -+ -+ uint8_t buf[2*KYBER_SYMBYTES]; -+ /* Will contain key, coins */ -+ uint8_t kr[2*KYBER_SYMBYTES]; -+ -+ memcpy(buf, seed, KYBER_SYMBYTES); -+ -+ /* Don't release system RNG output */ -+ hash_h(buf, buf, KYBER_SYMBYTES); -+ -+ /* Multitarget countermeasure for coins + contributory KEM */ -+ hash_h(buf+KYBER_SYMBYTES, pk, KYBER_PUBLICKEYBYTES); -+ hash_g(kr, buf, 2*KYBER_SYMBYTES); -+ -+ /* coins are in kr+KYBER_SYMBYTES */ -+ if(!indcpa_enc(ct, buf, pk, kr+KYBER_SYMBYTES)) - return 0; -+ -+ if (mlkem == 1) { -+ memcpy(ss, kr, KYBER_SYMBYTES); -+ } else { -+ /* overwrite coins in kr with H(c) */ -+ hash_h(kr+KYBER_SYMBYTES, ct, KYBER_CIPHERTEXTBYTES); -+ /* hash concatenation of pre-k and H(c) to k */ -+ kdf(ss, kr, 2*KYBER_SYMBYTES); - } -- BORINGSSL_keccak(pub->public_key_hash, sizeof(pub->public_key_hash), -- CBS_data(&orig_in), CBS_len(&orig_in), boringssl_sha3_256); - return 1; - } - --int KYBER_marshal_private_key(CBB *out, -- const struct KYBER_private_key *private_key) { -- const struct private_key *const priv = private_key_from_external(private_key); -- uint8_t *s_output; -- if (!CBB_add_space(out, &s_output, kEncodedVectorSize)) { -- return 0; -+// Modified crypto_kem_decap to BoringSSL style API -+void decap(uint8_t out_shared_key[KYBER_SSBYTES], -+ const struct private_key *in_priv, -+ const uint8_t *ct, size_t ciphertext_len, int mlkem) -+{ -+ uint8_t *ss = out_shared_key; -+ const uint8_t *sk = &in_priv->opaque[0]; -+ -+ size_t i; -+ int fail = 1; -+ uint8_t buf[2*KYBER_SYMBYTES]; -+ /* Will contain key, coins */ -+ uint8_t kr[2*KYBER_SYMBYTES]; -+ uint8_t cmp[KYBER_CIPHERTEXTBYTES]; -+ const uint8_t *pk = sk+KYBER_INDCPA_SECRETKEYBYTES; -+ -+ if (ciphertext_len == KYBER_CIPHERTEXTBYTES) { -+ indcpa_dec(buf, ct, sk); -+ -+ /* Multitarget countermeasure for coins + contributory KEM */ -+ for(i=0;is, kLog2Prime); -- if (!kyber_marshal_public_key(out, &priv->pub) || -- !CBB_add_bytes(out, priv->pub.public_key_hash, -- sizeof(priv->pub.public_key_hash)) || -- !CBB_add_bytes(out, priv->fo_failure_secret, -- sizeof(priv->fo_failure_secret))) { -- return 0; -+ -+ if (mlkem == 1) { -+ /* Compute shared secret in case of rejection: ss2 = PRF(z || c). */ -+ uint8_t ss2[KYBER_SYMBYTES]; -+ keccak_state ks; -+ shake256_init(&ks); -+ shake256_absorb( -+ &ks, -+ sk + KYBER_SECRETKEYBYTES - KYBER_SYMBYTES, -+ KYBER_SYMBYTES -+ ); -+ shake256_absorb(&ks, ct, ciphertext_len); -+ shake256_finalize(&ks); -+ shake256_squeeze(ss2, KYBER_SYMBYTES, &ks); -+ -+ /* Set ss2 to the real shared secret if c = c' */ -+ cmov(ss2, kr, KYBER_SYMBYTES, 1-fail); -+ memcpy(ss, ss2, KYBER_SYMBYTES); -+ } else { -+ /* overwrite coins in kr with H(c) */ -+ hash_h(kr+KYBER_SYMBYTES, ct, ciphertext_len); -+ -+ /* Overwrite pre-k with z on re-encryption failure */ -+ cmov(kr, sk+KYBER_SECRETKEYBYTES-KYBER_SYMBYTES, KYBER_SYMBYTES, fail); -+ -+ /* hash concatenation of pre-k and H(c) to k */ -+ kdf(ss, kr, 2*KYBER_SYMBYTES); - } -- return 1; - } - --int KYBER_parse_private_key(struct KYBER_private_key *out_private_key, -- CBS *in) { -- struct private_key *const priv = private_key_from_external(out_private_key); -+void marshal_public_key(uint8_t out[KYBER_PUBLICKEYBYTES], -+ const struct public_key *in_pub) { -+ memcpy(out, &in_pub->opaque, KYBER_PUBLICKEYBYTES); -+} - -- CBS s_bytes; -- if (!CBS_get_bytes(in, &s_bytes, kEncodedVectorSize) || -- !vector_decode(&priv->s, CBS_data(&s_bytes), kLog2Prime) || -- !kyber_parse_public_key_no_hash(&priv->pub, in) || -- !CBS_copy_bytes(in, priv->pub.public_key_hash, -- sizeof(priv->pub.public_key_hash)) || -- !CBS_copy_bytes(in, priv->fo_failure_secret, -- sizeof(priv->fo_failure_secret)) || -- CBS_len(in) != 0) { -- return 0; -- } -- return 1; -+void parse_public_key(struct public_key *out, -+ const uint8_t in[KYBER_PUBLICKEYBYTES]) { -+ memcpy(&out->opaque, in, KYBER_PUBLICKEYBYTES); - } -diff --git a/crypto/kyber/kyber512.c b/crypto/kyber/kyber512.c -new file mode 100644 -index 000000000..21eed11a2 ---- /dev/null -+++ b/crypto/kyber/kyber512.c -@@ -0,0 +1,5 @@ -+#define KYBER_K 2 -+ -+#include "kyber.c" -+ -+ -diff --git a/crypto/kyber/kyber768.c b/crypto/kyber/kyber768.c -new file mode 100644 -index 000000000..3e572b72e ---- /dev/null -+++ b/crypto/kyber/kyber768.c -@@ -0,0 +1,4 @@ -+#define KYBER_K 3 -+ -+#include "kyber.c" -+ -diff --git a/crypto/kyber/kyber_test.cc b/crypto/kyber/kyber_test.cc -deleted file mode 100644 -index b9daa87d3..000000000 ---- a/crypto/kyber/kyber_test.cc -+++ /dev/null -@@ -1,184 +0,0 @@ --/* Copyright (c) 2023, Google Inc. -- * -- * Permission to use, copy, modify, and/or distribute this software for any -- * purpose with or without fee is hereby granted, provided that the above -- * copyright notice and this permission notice appear in all copies. -- * -- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -- --#include -- --#include -- --#include -- --#include --#include --#include -- --#include "../test/file_test.h" --#include "../test/test_util.h" --#include "../keccak/internal.h" --#include "./internal.h" -- -- --template --static std::vector Marshal(int (*marshal_func)(CBB *, const T *), -- const T *t) { -- bssl::ScopedCBB cbb; -- uint8_t *encoded; -- size_t encoded_len; -- if (!CBB_init(cbb.get(), 1) || // -- !marshal_func(cbb.get(), t) || // -- !CBB_finish(cbb.get(), &encoded, &encoded_len)) { -- abort(); -- } -- -- std::vector ret(encoded, encoded + encoded_len); -- OPENSSL_free(encoded); -- return ret; --} -- --TEST(KyberTest, Basic) { -- uint8_t encoded_public_key[KYBER_PUBLIC_KEY_BYTES]; -- KYBER_private_key priv; -- KYBER_generate_key(encoded_public_key, &priv); -- -- uint8_t first_two_bytes[2]; -- OPENSSL_memcpy(first_two_bytes, encoded_public_key, sizeof(first_two_bytes)); -- OPENSSL_memset(encoded_public_key, 0xff, sizeof(first_two_bytes)); -- CBS encoded_public_key_cbs; -- CBS_init(&encoded_public_key_cbs, encoded_public_key, -- sizeof(encoded_public_key)); -- KYBER_public_key pub; -- // Parsing should fail because the first coefficient is >= kPrime; -- ASSERT_FALSE(KYBER_parse_public_key(&pub, &encoded_public_key_cbs)); -- -- OPENSSL_memcpy(encoded_public_key, first_two_bytes, sizeof(first_two_bytes)); -- CBS_init(&encoded_public_key_cbs, encoded_public_key, -- sizeof(encoded_public_key)); -- ASSERT_TRUE(KYBER_parse_public_key(&pub, &encoded_public_key_cbs)); -- EXPECT_EQ(CBS_len(&encoded_public_key_cbs), 0u); -- -- EXPECT_EQ(Bytes(encoded_public_key), -- Bytes(Marshal(KYBER_marshal_public_key, &pub))); -- -- KYBER_public_key pub2; -- KYBER_public_from_private(&pub2, &priv); -- EXPECT_EQ(Bytes(encoded_public_key), -- Bytes(Marshal(KYBER_marshal_public_key, &pub2))); -- -- std::vector encoded_private_key( -- Marshal(KYBER_marshal_private_key, &priv)); -- EXPECT_EQ(encoded_private_key.size(), size_t{KYBER_PRIVATE_KEY_BYTES}); -- -- OPENSSL_memcpy(first_two_bytes, encoded_private_key.data(), -- sizeof(first_two_bytes)); -- OPENSSL_memset(encoded_private_key.data(), 0xff, sizeof(first_two_bytes)); -- CBS cbs; -- CBS_init(&cbs, encoded_private_key.data(), encoded_private_key.size()); -- KYBER_private_key priv2; -- // Parsing should fail because the first coefficient is >= kPrime. -- ASSERT_FALSE(KYBER_parse_private_key(&priv2, &cbs)); -- -- OPENSSL_memcpy(encoded_private_key.data(), first_two_bytes, -- sizeof(first_two_bytes)); -- CBS_init(&cbs, encoded_private_key.data(), encoded_private_key.size()); -- ASSERT_TRUE(KYBER_parse_private_key(&priv2, &cbs)); -- EXPECT_EQ(Bytes(encoded_private_key), -- Bytes(Marshal(KYBER_marshal_private_key, &priv2))); -- -- uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES]; -- uint8_t shared_secret1[64]; -- uint8_t shared_secret2[sizeof(shared_secret1)]; -- KYBER_encap(ciphertext, shared_secret1, sizeof(shared_secret1), &pub); -- KYBER_decap(shared_secret2, sizeof(shared_secret2), ciphertext, &priv); -- EXPECT_EQ(Bytes(shared_secret1), Bytes(shared_secret2)); -- KYBER_decap(shared_secret2, sizeof(shared_secret2), ciphertext, &priv2); -- EXPECT_EQ(Bytes(shared_secret1), Bytes(shared_secret2)); --} -- --static void KyberFileTest(FileTest *t) { -- std::vector seed, public_key_expected, private_key_expected, -- ciphertext_expected, shared_secret_expected, given_generate_entropy, -- given_encap_entropy_pre_hash; -- t->IgnoreAttribute("count"); -- ASSERT_TRUE(t->GetBytes(&seed, "seed")); -- ASSERT_TRUE(t->GetBytes(&public_key_expected, "pk")); -- ASSERT_TRUE(t->GetBytes(&private_key_expected, "sk")); -- ASSERT_TRUE(t->GetBytes(&ciphertext_expected, "ct")); -- ASSERT_TRUE(t->GetBytes(&shared_secret_expected, "ss")); -- ASSERT_TRUE(t->GetBytes(&given_generate_entropy, "generateEntropy")); -- ASSERT_TRUE( -- t->GetBytes(&given_encap_entropy_pre_hash, "encapEntropyPreHash")); -- -- KYBER_private_key priv; -- uint8_t encoded_private_key[KYBER_PRIVATE_KEY_BYTES]; -- KYBER_public_key pub; -- uint8_t encoded_public_key[KYBER_PUBLIC_KEY_BYTES]; -- uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES]; -- uint8_t gen_key_entropy[KYBER_GENERATE_KEY_ENTROPY]; -- uint8_t encap_entropy[KYBER_ENCAP_ENTROPY]; -- uint8_t encapsulated_key[32]; -- uint8_t decapsulated_key[32]; -- // The test vectors provide a CTR-DRBG seed which is used to generate the -- // input entropy. -- ASSERT_EQ(seed.size(), size_t{CTR_DRBG_ENTROPY_LEN}); -- { -- bssl::UniquePtr state( -- CTR_DRBG_new(seed.data(), nullptr, 0)); -- ASSERT_TRUE(state); -- ASSERT_TRUE( -- CTR_DRBG_generate(state.get(), gen_key_entropy, 32, nullptr, 0)); -- ASSERT_TRUE( -- CTR_DRBG_generate(state.get(), gen_key_entropy + 32, 32, nullptr, 0)); -- ASSERT_TRUE(CTR_DRBG_generate(state.get(), encap_entropy, -- KYBER_ENCAP_ENTROPY, nullptr, 0)); -- } -- -- EXPECT_EQ(Bytes(gen_key_entropy), Bytes(given_generate_entropy)); -- EXPECT_EQ(Bytes(encap_entropy), Bytes(given_encap_entropy_pre_hash)); -- -- BORINGSSL_keccak(encap_entropy, sizeof(encap_entropy), encap_entropy, -- sizeof(encap_entropy), boringssl_sha3_256); -- -- KYBER_generate_key_external_entropy(encoded_public_key, &priv, -- gen_key_entropy); -- CBB cbb; -- CBB_init_fixed(&cbb, encoded_private_key, sizeof(encoded_private_key)); -- ASSERT_TRUE(KYBER_marshal_private_key(&cbb, &priv)); -- CBS encoded_public_key_cbs; -- CBS_init(&encoded_public_key_cbs, encoded_public_key, -- sizeof(encoded_public_key)); -- ASSERT_TRUE(KYBER_parse_public_key(&pub, &encoded_public_key_cbs)); -- KYBER_encap_external_entropy(ciphertext, encapsulated_key, -- sizeof(encapsulated_key), &pub, encap_entropy); -- KYBER_decap(decapsulated_key, sizeof(decapsulated_key), ciphertext, &priv); -- -- EXPECT_EQ(Bytes(encapsulated_key), Bytes(decapsulated_key)); -- EXPECT_EQ(Bytes(private_key_expected), Bytes(encoded_private_key)); -- EXPECT_EQ(Bytes(public_key_expected), Bytes(encoded_public_key)); -- EXPECT_EQ(Bytes(ciphertext_expected), Bytes(ciphertext)); -- EXPECT_EQ(Bytes(shared_secret_expected), Bytes(encapsulated_key)); -- -- uint8_t corrupted_ciphertext[KYBER_CIPHERTEXT_BYTES]; -- OPENSSL_memcpy(corrupted_ciphertext, ciphertext, KYBER_CIPHERTEXT_BYTES); -- corrupted_ciphertext[3] ^= 0x40; -- uint8_t corrupted_decapsulated_key[32]; -- KYBER_decap(corrupted_decapsulated_key, sizeof(corrupted_decapsulated_key), -- corrupted_ciphertext, &priv); -- // It would be nice to have actual test vectors for the failure case, but the -- // NIST submission currently does not include those, so we are just testing -- // for inequality. -- EXPECT_NE(Bytes(encapsulated_key), Bytes(corrupted_decapsulated_key)); --} -- --TEST(KyberTest, TestVectors) { -- FileTestGTest("crypto/kyber/kyber_tests.txt", KyberFileTest); --} -diff --git a/crypto/obj/obj_dat.h b/crypto/obj/obj_dat.h -index 71ef2d2bd..74b99b098 100644 ---- a/crypto/obj/obj_dat.h -+++ b/crypto/obj/obj_dat.h -@@ -57,7 +57,7 @@ - /* This file is generated by crypto/obj/objects.go. */ - - --#define NUM_NID 965 -+#define NUM_NID 969 - - static const uint8_t kObjectData[] = { - /* NID_rsadsi */ -@@ -8783,6 +8783,13 @@ static const ASN1_OBJECT kObjects[NUM_NID] = { - {"HKDF", "hkdf", NID_hkdf, 0, NULL, 0}, - {"X25519Kyber768Draft00", "X25519Kyber768Draft00", - NID_X25519Kyber768Draft00, 0, NULL, 0}, -+ {"X25519Kyber512Draft00", "X25519Kyber512Draft00", -+ NID_X25519Kyber512Draft00, 0, NULL, 0}, -+ {"P256Kyber768Draft00", "P256Kyber768Draft00", NID_P256Kyber768Draft00, 0, -+ NULL, 0}, -+ {"X25519Kyber768Draft00Old", "X25519Kyber768Draft00Old", -+ NID_X25519Kyber768Draft00Old, 0, NULL, 0}, -+ {"X25519MLKEM768", "X25519MLKEM768", NID_X25519MLKEM768, 0, NULL, 0}, - }; - - static const uint16_t kNIDsInShortNameOrder[] = { -@@ -8915,6 +8922,7 @@ static const uint16_t kNIDsInShortNameOrder[] = { - 18 /* OU */, - 749 /* Oakley-EC2N-3 */, - 750 /* Oakley-EC2N-4 */, -+ 966 /* P256Kyber768Draft00 */, - 9 /* PBE-MD2-DES */, - 168 /* PBE-MD2-RC2-64 */, - 10 /* PBE-MD5-DES */, -@@ -8980,7 +8988,10 @@ static const uint16_t kNIDsInShortNameOrder[] = { - 143 /* SXNetID */, - 458 /* UID */, - 948 /* X25519 */, -+ 965 /* X25519Kyber512Draft00 */, - 964 /* X25519Kyber768Draft00 */, -+ 967 /* X25519Kyber768Draft00Old */, -+ 968 /* X25519MLKEM768 */, - 961 /* X448 */, - 11 /* X500 */, - 378 /* X500algorithms */, -@@ -9827,6 +9838,7 @@ static const uint16_t kNIDsInLongNameOrder[] = { - 366 /* OCSP Nonce */, - 371 /* OCSP Service Locator */, - 180 /* OCSP Signing */, -+ 966 /* P256Kyber768Draft00 */, - 161 /* PBES2 */, - 69 /* PBKDF2 */, - 162 /* PBMAC1 */, -@@ -9851,7 +9863,10 @@ static const uint16_t kNIDsInLongNameOrder[] = { - 133 /* Time Stamping */, - 375 /* Trust Root */, - 948 /* X25519 */, -+ 965 /* X25519Kyber512Draft00 */, - 964 /* X25519Kyber768Draft00 */, -+ 967 /* X25519Kyber768Draft00Old */, -+ 968 /* X25519MLKEM768 */, - 961 /* X448 */, - 12 /* X509 */, - 402 /* X509v3 AC Targeting */, -diff --git a/crypto/obj/obj_mac.num b/crypto/obj/obj_mac.num -index a0519acee..2a46adfe8 100644 ---- a/crypto/obj/obj_mac.num -+++ b/crypto/obj/obj_mac.num -@@ -952,3 +952,7 @@ X448 961 - sha512_256 962 - hkdf 963 - X25519Kyber768Draft00 964 -+X25519Kyber512Draft00 965 -+P256Kyber768Draft00 966 -+X25519Kyber768Draft00Old 967 -+X25519MLKEM768 968 -diff --git a/crypto/obj/objects.txt b/crypto/obj/objects.txt -index 3ad32ea3d..347fc556a 100644 ---- a/crypto/obj/objects.txt -+++ b/crypto/obj/objects.txt -@@ -1332,8 +1332,12 @@ secg-scheme 14 3 : dhSinglePass-cofactorDH-sha512kdf-scheme - : dh-std-kdf - : dh-cofactor-kdf - --# NIDs for post quantum hybrid KEMs in TLS (no corresponding OIDs). -+# NID for Kyber hybrids (no corresponding OID). -+ : X25519Kyber512Draft00 - : X25519Kyber768Draft00 -+ : P256Kyber768Draft00 -+ : X25519Kyber768Draft00Old -+ : X25519MLKEM768 - - # See RFC 8410. - 1 3 101 110 : X25519 -diff --git a/include/openssl/kyber.h b/include/openssl/kyber.h -index cafae9d17..a05eb8957 100644 ---- a/include/openssl/kyber.h -+++ b/include/openssl/kyber.h -@@ -1,17 +1,3 @@ --/* Copyright (c) 2023, Google Inc. -- * -- * Permission to use, copy, modify, and/or distribute this software for any -- * purpose with or without fee is hereby granted, provided that the above -- * copyright notice and this permission notice appear in all copies. -- * -- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -- - #ifndef OPENSSL_HEADER_KYBER_H - #define OPENSSL_HEADER_KYBER_H - -@@ -21,105 +7,104 @@ - extern "C" { - #endif - -+#define KYBER512_PUBLIC_KEY_BYTES 800 -+#define KYBER512_CIPHERTEXT_BYTES 768 -+#define KYBER512_PRIVATE_KEY_BYTES 1632 -+#define KYBER768_PUBLIC_KEY_BYTES 1184 -+#define KYBER768_CIPHERTEXT_BYTES 1088 -+#define KYBER768_PRIVATE_KEY_BYTES 2400 - --// Kyber768. -- -- --// KYBER_public_key contains a Kyber768 public key. The contents of this --// object should never leave the address space since the format is unstable. --struct KYBER_public_key { -- union { -- uint8_t bytes[512 * (3 + 9) + 32 + 32]; -- uint16_t alignment; -- } opaque; -+struct KYBER512_private_key { -+ uint8_t opaque[KYBER512_PRIVATE_KEY_BYTES]; - }; -- --// KYBER_private_key contains a Kyber768 private key. The contents of this --// object should never leave the address space since the format is unstable. --struct KYBER_private_key { -- union { -- uint8_t bytes[512 * (3 + 3 + 9) + 32 + 32 + 32]; -- uint16_t alignment; -- } opaque; -+struct KYBER768_private_key { -+ uint8_t opaque[KYBER768_PRIVATE_KEY_BYTES]; -+}; -+struct KYBER512_public_key { -+ uint8_t opaque[KYBER512_PUBLIC_KEY_BYTES]; -+}; -+struct KYBER768_public_key { -+ uint8_t opaque[KYBER768_PUBLIC_KEY_BYTES]; - }; - --// KYBER_PUBLIC_KEY_BYTES is the number of bytes in an encoded Kyber768 public --// key. --#define KYBER_PUBLIC_KEY_BYTES 1184 -- --// KYBER_generate_key generates a random public/private key pair, writes the --// encoded public key to |out_encoded_public_key| and sets |out_private_key| to --// the private key. --OPENSSL_EXPORT void KYBER_generate_key( -- uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], -- struct KYBER_private_key *out_private_key); -- --// KYBER_public_from_private sets |*out_public_key| to the public key that --// corresponds to |private_key|. (This is faster than parsing the output of --// |KYBER_generate_key| if, for some reason, you need to encapsulate to a key --// that was just generated.) --OPENSSL_EXPORT void KYBER_public_from_private( -- struct KYBER_public_key *out_public_key, -- const struct KYBER_private_key *private_key); -- --// KYBER_CIPHERTEXT_BYTES is number of bytes in the Kyber768 ciphertext. --#define KYBER_CIPHERTEXT_BYTES 1088 -- --// KYBER_encap encrypts a random secret key of length |out_shared_secret_len| to --// |public_key|, writes the ciphertext to |ciphertext|, and writes the random --// key to |out_shared_secret|. The party calling |KYBER_decap| must already know --// the correct value of |out_shared_secret_len|. --OPENSSL_EXPORT void KYBER_encap(uint8_t out_ciphertext[KYBER_CIPHERTEXT_BYTES], -- uint8_t *out_shared_secret, -- size_t out_shared_secret_len, -- const struct KYBER_public_key *public_key); -- --// KYBER_decap decrypts a key of length |out_shared_secret_len| from --// |ciphertext| using |private_key| and writes it to |out_shared_secret|. If --// |ciphertext| is invalid, |out_shared_secret| is filled with a key that --// will always be the same for the same |ciphertext| and |private_key|, but --// which appears to be random unless one has access to |private_key|. These --// alternatives occur in constant time. Any subsequent symmetric encryption --// using |out_shared_secret| must use an authenticated encryption scheme in --// order to discover the decapsulation failure. --OPENSSL_EXPORT void KYBER_decap( -- uint8_t *out_shared_secret, size_t out_shared_secret_len, -- const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES], -- const struct KYBER_private_key *private_key); -- -- --// Serialisation of keys. -- --// KYBER_marshal_public_key serializes |public_key| to |out| in the standard --// format for Kyber public keys. It returns one on success or zero on allocation --// error. --OPENSSL_EXPORT int KYBER_marshal_public_key( -- CBB *out, const struct KYBER_public_key *public_key); -- --// KYBER_parse_public_key parses a public key, in the format generated by --// |KYBER_marshal_public_key|, from |in| and writes the result to --// |out_public_key|. It returns one on success or zero on parse error or if --// there are trailing bytes in |in|. --OPENSSL_EXPORT int KYBER_parse_public_key( -- struct KYBER_public_key *out_public_key, CBS *in); -- --// KYBER_marshal_private_key serializes |private_key| to |out| in the standard --// format for Kyber private keys. It returns one on success or zero on --// allocation error. --OPENSSL_EXPORT int KYBER_marshal_private_key( -- CBB *out, const struct KYBER_private_key *private_key); -- --// KYBER_PRIVATE_KEY_BYTES is the length of the data produced by --// |KYBER_marshal_private_key|. --#define KYBER_PRIVATE_KEY_BYTES 2400 -- --// KYBER_parse_private_key parses a private key, in the format generated by --// |KYBER_marshal_private_key|, from |in| and writes the result to --// |out_private_key|. It returns one on success or zero on parse error or if --// there are trailing bytes in |in|. --OPENSSL_EXPORT int KYBER_parse_private_key( -- struct KYBER_private_key *out_private_key, CBS *in); -- -+// KYBER_GENERATE_KEY_BYTES is the number of bytes of entropy needed to -+// generate a keypair. -+#define KYBER_GENERATE_KEY_BYTES 64 -+ -+// KYBER_ENCAP_BYTES is the number of bytes of entropy needed to encapsulate a -+// session key. -+#define KYBER_ENCAP_BYTES 32 -+ -+// KYBER_KEY_BYTES is the number of bytes in a shared key. -+#define KYBER_KEY_BYTES 32 -+ -+// KYBER512_generate_key is a deterministic function that outputs a public and -+// private key based on the given entropy. -+OPENSSL_EXPORT void KYBER512_generate_key( -+ struct KYBER512_public_key *out_pub, struct KYBER512_private_key *out_priv, -+ const uint8_t input[KYBER_GENERATE_KEY_BYTES]); -+ -+// KYBER768_generate_key is a deterministic function that outputs a public and -+// private key based on the given entropy. -+OPENSSL_EXPORT void KYBER768_generate_key( -+ struct KYBER768_public_key *out_pub, struct KYBER768_private_key *out_priv, -+ const uint8_t input[KYBER_GENERATE_KEY_BYTES]); -+ -+// KYBER512_encap is a deterministic function the generates and encrypts a random -+// session key from the given entropy, writing those values to |out_shared_key| -+// and |out_ciphertext|, respectively. If |mlkem| is 1, will use ML-KEM-512. -+OPENSSL_EXPORT int KYBER512_encap(uint8_t out_ciphertext[KYBER512_CIPHERTEXT_BYTES], -+ uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER512_public_key *in_pub, -+ const uint8_t in[KYBER_ENCAP_BYTES], -+ int mlkem); -+ -+// KYBER768_encap is a deterministic function the generates and encrypts a random -+// session key from the given entropy, writing those values to |out_shared_key| -+// and |out_ciphertext|, respectively. If |mlkem| is 1, will use ML-KEM-768. -+OPENSSL_EXPORT int KYBER768_encap(uint8_t out_ciphertext[KYBER768_CIPHERTEXT_BYTES], -+ uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER768_public_key *in_pub, -+ const uint8_t in[KYBER_ENCAP_BYTES], -+ int mlkem); -+ -+// KYBER_decap decrypts a session key from |ciphertext_len| bytes of -+// |ciphertext|. If the ciphertext is valid, the decrypted key is written to -+// |out_shared_key|. Otherwise a key dervied from |ciphertext| and a secret key (kept -+// in |in_priv|) is written. If the ciphertext is the wrong length then it will -+// leak which was done via side-channels. Otherwise it should perform either -+// action in constant-time. If |mlkem| is 1, will use ML-KEM-512. -+OPENSSL_EXPORT void KYBER512_decap(uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER512_private_key *in_priv, -+ const uint8_t *ciphertext, size_t ciphertext_len, -+ int mlkem); -+ -+// KYBER_decap decrypts a session key from |ciphertext_len| bytes of -+// |ciphertext|. If the ciphertext is valid, the decrypted key is written to -+// |out_shared_key|. Otherwise a key dervied from |ciphertext| and a secret key (kept -+// in |in_priv|) is written. If the ciphertext is the wrong length then it will -+// leak which was done via side-channels. Otherwise it should perform either -+// action in constant-time. If |mlkem| is 1, will use ML-KEM-768. -+OPENSSL_EXPORT void KYBER768_decap(uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER768_private_key *in_priv, -+ const uint8_t *ciphertext, size_t ciphertext_len, -+ int mlkem); -+ -+// KYBER512_marshal_public_key serialises |in_pub| to |out|. -+OPENSSL_EXPORT void KYBER512_marshal_public_key( -+ uint8_t out[KYBER512_PUBLIC_KEY_BYTES], const struct KYBER512_public_key *in_pub); -+ -+// KYBER768_marshal_public_key serialises |in_pub| to |out|. -+OPENSSL_EXPORT void KYBER768_marshal_public_key( -+ uint8_t out[KYBER768_PUBLIC_KEY_BYTES], const struct KYBER768_public_key *in_pub); -+ -+// KYBER512_parse_public_key sets |*out| to the public-key encoded in |in|. -+OPENSSL_EXPORT void KYBER512_parse_public_key( -+ struct KYBER512_public_key *out, const uint8_t in[KYBER512_PUBLIC_KEY_BYTES]); -+ -+// KYBER768_parse_public_key sets |*out| to the public-key encoded in |in|. -+OPENSSL_EXPORT void KYBER768_parse_public_key( -+ struct KYBER768_public_key *out, const uint8_t in[KYBER768_PUBLIC_KEY_BYTES]); - - #if defined(__cplusplus) - } // extern C -diff --git a/include/openssl/nid.h b/include/openssl/nid.h -index 4dd8841b1..5b102c610 100644 ---- a/include/openssl/nid.h -+++ b/include/openssl/nid.h -@@ -4255,6 +4255,18 @@ extern "C" { - #define SN_X25519Kyber768Draft00 "X25519Kyber768Draft00" - #define NID_X25519Kyber768Draft00 964 - -+#define SN_X25519Kyber512Draft00 "X25519Kyber512Draft00" -+#define NID_X25519Kyber512Draft00 965 -+ -+#define SN_P256Kyber768Draft00 "P256Kyber768Draft00" -+#define NID_P256Kyber768Draft00 966 -+ -+#define SN_X25519Kyber768Draft00Old "X25519Kyber768Draft00Old" -+#define NID_X25519Kyber768Draft00Old 967 -+ -+#define SN_X25519MLKEM768 "X25519MLKEM768" -+#define NID_X25519MLKEM768 968 -+ - - #if defined(__cplusplus) - } /* extern C */ -diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h -index 003e0a5f7..884685ba9 100644 ---- a/include/openssl/ssl.h -+++ b/include/openssl/ssl.h -@@ -2363,6 +2363,10 @@ OPENSSL_EXPORT size_t SSL_CTX_get_num_tickets(const SSL_CTX *ctx); - #define SSL_GROUP_SECP521R1 25 - #define SSL_GROUP_X25519 29 - #define SSL_GROUP_X25519_KYBER768_DRAFT00 0x6399 -+#define SSL_GROUP_X25519_KYBER512_DRAFT00 0xfe30 -+#define SSL_GROUP_X25519_KYBER768_DRAFT00_OLD 0xfe31 -+#define SSL_GROUP_P256_KYBER768_DRAFT00 0xfe32 -+#define SSL_GROUP_X25519_MLKEM768 0x11ec - - // SSL_CTX_set1_group_ids sets the preferred groups for |ctx| to |group_ids|. - // Each element of |group_ids| should be one of the |SSL_GROUP_*| constants. It -diff --git a/sources.cmake b/sources.cmake -index ba2f5bc9e..d7ef5153a 100644 ---- a/sources.cmake -+++ b/sources.cmake -@@ -52,7 +52,6 @@ set( - crypto/hrss/hrss_test.cc - crypto/impl_dispatch_test.cc - crypto/keccak/keccak_test.cc -- crypto/kyber/kyber_test.cc - crypto/lhash/lhash_test.cc - crypto/obj/obj_test.cc - crypto/pem/pem_test.cc -@@ -145,7 +144,6 @@ set( - crypto/hmac_extra/hmac_tests.txt - crypto/hpke/hpke_test_vectors.txt - crypto/keccak/keccak_tests.txt -- crypto/kyber/kyber_tests.txt - crypto/pkcs8/test/empty_password.p12 - crypto/pkcs8/test/no_encryption.p12 - crypto/pkcs8/test/nss.p12 -diff --git a/ssl/extensions.cc b/ssl/extensions.cc -index b13400097..44a2d0f5c 100644 ---- a/ssl/extensions.cc -+++ b/ssl/extensions.cc -@@ -207,6 +207,10 @@ static bool tls1_check_duplicate_extensions(const CBS *cbs) { - static bool is_post_quantum_group(uint16_t id) { - switch (id) { - case SSL_GROUP_X25519_KYBER768_DRAFT00: -+ case SSL_GROUP_X25519_KYBER768_DRAFT00_OLD: -+ case SSL_GROUP_X25519_KYBER512_DRAFT00: -+ case SSL_GROUP_P256_KYBER768_DRAFT00: -+ case SSL_GROUP_X25519_MLKEM768: - return true; - default: - return false; -@@ -307,6 +311,8 @@ bool ssl_client_hello_get_extension(const SSL_CLIENT_HELLO *client_hello, - } - - static const uint16_t kDefaultGroups[] = { -+ SSL_GROUP_X25519_MLKEM768, -+ SSL_GROUP_P256_KYBER768_DRAFT00, - SSL_GROUP_X25519, - SSL_GROUP_SECP256R1, - SSL_GROUP_SECP384R1, -diff --git a/ssl/ssl_key_share.cc b/ssl/ssl_key_share.cc -index 694bec11d..3e4d2e7c4 100644 ---- a/ssl/ssl_key_share.cc -+++ b/ssl/ssl_key_share.cc -@@ -26,6 +26,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -191,63 +192,292 @@ class X25519KeyShare : public SSLKeyShare { - uint8_t private_key_[32]; - }; - --class X25519Kyber768KeyShare : public SSLKeyShare { -+class P256Kyber768Draft00KeyShare : public SSLKeyShare { - public: -- X25519Kyber768KeyShare() {} -+ P256Kyber768Draft00KeyShare() {} -+ -+ uint16_t GroupID() const override { return SSL_GROUP_P256_KYBER768_DRAFT00; } -+ -+ bool Generate(CBB *out) override { -+ assert(!p256_private_key_); -+ -+ // Set up a shared |BN_CTX| for P-256 operations. -+ UniquePtr bn_ctx(BN_CTX_new()); -+ if (!bn_ctx) { -+ return false; -+ } -+ -+ BN_CTXScope scope(bn_ctx.get()); -+ -+ // Generate a P-256 private key. -+ UniquePtr group; -+ group.reset(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); -+ p256_private_key_.reset(BN_new()); -+ if (!group || !p256_private_key_ || -+ !BN_rand_range_ex(p256_private_key_.get(), 1, -+ EC_GROUP_get0_order(group.get()))) { -+ return false; -+ } -+ -+ // Compute the corresponding P-256 public key and serialize it. -+ UniquePtr p256_public_key(EC_POINT_new(group.get())); -+ if (!p256_public_key || -+ !EC_POINT_mul(group.get(), p256_public_key.get(), p256_private_key_.get(), -+ NULL, NULL, bn_ctx.get()) || -+ !EC_POINT_point2cbb(out, group.get(), p256_public_key.get(), -+ POINT_CONVERSION_UNCOMPRESSED, bn_ctx.get())) { -+ return false; -+ } -+ -+ -+ // Kyber -+ uint8_t kyber_entropy[KYBER_GENERATE_KEY_BYTES]; -+ KYBER768_public_key kyber_public_key; -+ RAND_bytes(kyber_entropy, sizeof(kyber_entropy)); -+ KYBER768_generate_key(&kyber_public_key, &kyber_private_key_, kyber_entropy); -+ -+ uint8_t kyber_public_key_bytes[KYBER768_PUBLIC_KEY_BYTES]; -+ KYBER768_marshal_public_key(kyber_public_key_bytes, &kyber_public_key); - -- uint16_t GroupID() const override { -- return SSL_GROUP_X25519_KYBER768_DRAFT00; -+ if (!CBB_add_bytes(out, kyber_public_key_bytes, -+ sizeof(kyber_public_key_bytes))) { -+ return false; -+ } -+ -+ return true; - } - -+ bool Encap(CBB *out_public_key, Array *out_secret, -+ uint8_t *out_alert, Span peer_key) override { -+ assert(!p256_private_key_); -+ -+ if (peer_key.size() != 65 + KYBER768_PUBLIC_KEY_BYTES) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } -+ -+ // Set up a shared |BN_CTX| for P-256 operations. -+ UniquePtr bn_ctx(BN_CTX_new()); -+ if (!bn_ctx) { -+ return false; -+ } -+ -+ BN_CTXScope scope(bn_ctx.get()); -+ -+ UniquePtr group; -+ group.reset(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); -+ if (!group) { -+ return false; -+ } -+ -+ // Parse peer point -+ UniquePtr peer_point(EC_POINT_new(group.get())); -+ UniquePtr result(EC_POINT_new(group.get())); -+ BIGNUM *x = BN_CTX_get(bn_ctx.get()); -+ if (!peer_point || !result || !x) { -+ return false; -+ } -+ -+ if (peer_key.empty() || peer_key[0] != POINT_CONVERSION_UNCOMPRESSED || -+ !EC_POINT_oct2point(group.get(), peer_point.get(), peer_key.data(), -+ 65, bn_ctx.get())) { -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ *out_alert = SSL_AD_DECODE_ERROR; -+ return false; -+ } -+ -+ p256_private_key_.reset(BN_new()); -+ if (!p256_private_key_ || !BN_rand_range_ex(p256_private_key_.get(), 1, -+ EC_GROUP_get0_order(group.get()))) { -+ return false; -+ } -+ -+ // Compute the corresponding P-256 public key and serialize it. -+ UniquePtr p256_public_key(EC_POINT_new(group.get())); -+ if (!p256_public_key || -+ !EC_POINT_mul(group.get(), p256_public_key.get(), p256_private_key_.get(), -+ NULL, NULL, bn_ctx.get()) || -+ !EC_POINT_point2cbb(out_public_key, group.get(), p256_public_key.get(), -+ POINT_CONVERSION_UNCOMPRESSED, bn_ctx.get())) { -+ return false; -+ } -+ -+ // Compute the x-coordinate of |peer_key| * |p256_private_key_|. -+ if (!EC_POINT_mul(group.get(), result.get(), NULL, peer_point.get(), -+ p256_private_key_.get(), bn_ctx.get()) || -+ !EC_POINT_get_affine_coordinates_GFp(group.get(), result.get(), x, NULL, -+ bn_ctx.get())) { -+ return false; -+ } -+ -+ // Encode the x-coordinate left-padded with zeros. -+ Array secret; -+ if (!secret.Init(32 + KYBER_KEY_BYTES) || -+ !BN_bn2bin_padded(secret.data(), 32, x)) { -+ return false; -+ } -+ -+ -+ KYBER768_public_key peer_public_key; -+ KYBER768_parse_public_key(&peer_public_key, peer_key.data() + 65); -+ -+ uint8_t ciphertext[KYBER768_CIPHERTEXT_BYTES]; -+ uint8_t entropy[KYBER_ENCAP_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); -+ -+ if(!KYBER768_encap(ciphertext, secret.data() + 32, &peer_public_key, entropy, 0)) { -+ *out_alert = SSL_AD_ILLEGAL_PARAMETER; -+ return false; -+ } -+ if(!CBB_add_bytes(out_public_key, ciphertext, sizeof(ciphertext))) { -+ return false; -+ } -+ -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ bool Decap(Array *out_secret, uint8_t *out_alert, -+ Span peer_key) override { -+ assert(p256_private_key_); -+ *out_alert = SSL_AD_INTERNAL_ERROR; -+ -+ Array secret; -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } -+ -+ if (peer_key.size() != 65 + KYBER768_CIPHERTEXT_BYTES) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } -+ -+ // Set up a shared |BN_CTX| for P-256 operations. -+ UniquePtr bn_ctx(BN_CTX_new()); -+ if (!bn_ctx) { -+ return false; -+ } -+ -+ BN_CTXScope scope(bn_ctx.get()); -+ -+ UniquePtr group; -+ group.reset(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); -+ if (!group) { -+ return false; -+ } -+ -+ // Parse peer point -+ UniquePtr peer_point(EC_POINT_new(group.get())); -+ UniquePtr result(EC_POINT_new(group.get())); -+ BIGNUM *x = BN_CTX_get(bn_ctx.get()); -+ if (!peer_point || !result || !x) { -+ return false; -+ } -+ -+ if (peer_key.empty() || peer_key[0] != POINT_CONVERSION_UNCOMPRESSED || -+ !EC_POINT_oct2point(group.get(), peer_point.get(), peer_key.data(), -+ 65, bn_ctx.get())) { -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ *out_alert = SSL_AD_DECODE_ERROR; -+ return false; -+ } -+ -+ // Compute the x-coordinate of |peer_key| * |p256_private_key_|. -+ if (!EC_POINT_mul(group.get(), result.get(), NULL, peer_point.get(), -+ p256_private_key_.get(), bn_ctx.get()) || -+ !EC_POINT_get_affine_coordinates_GFp(group.get(), result.get(), x, NULL, -+ bn_ctx.get())) { -+ return false; -+ } -+ -+ // Encode the x-coordinate left-padded with zeros. -+ if (!secret.Init(32 + KYBER_KEY_BYTES) || -+ !BN_bn2bin_padded(secret.data(), 32, x)) { -+ return false; -+ } -+ -+ KYBER768_decap(secret.data() + 32, &kyber_private_key_, -+ peer_key.data() + 65, peer_key.size() - 65, 0); -+ -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ private: -+ UniquePtr p256_private_key_; -+ KYBER768_private_key kyber_private_key_; -+}; -+ -+class X25519Kyber768Draft00KeyShare : public SSLKeyShare { -+ public: -+ X25519Kyber768Draft00KeyShare(uint16_t group_id) : group_id_(group_id) { -+ assert(group_id == SSL_GROUP_X25519_KYBER768_DRAFT00 -+ || group_id == SSL_GROUP_X25519_KYBER768_DRAFT00_OLD); -+ } -+ -+ uint16_t GroupID() const override { return group_id_; } -+ - bool Generate(CBB *out) override { - uint8_t x25519_public_key[32]; - X25519_keypair(x25519_public_key, x25519_private_key_); +This patch adds: + +1. Enable X25519MLKEM768 by default. + +2. Supports for P256Kyber768Draft00 under 0xfe32, which we temporarily + need for compliance reasons. (Note that this is not the codepoint + allocated for that exchange in the IANA table.) + Enables by default and in FIPS mode. + +3. Add SSL(_CTX)_use_second_keyshare. By default BoringSSL will send a + non post-quantum and a post-quantum keyshare if available. These + functions allow one to change the behaviour to only send a single + keyshare. +--- + crypto/obj/obj_dat.h | 6 +- + crypto/obj/obj_mac.num | 1 + + crypto/obj/objects.txt | 1 + + include/openssl/nid.h | 3 + + include/openssl/ssl.h | 15 ++++ + ssl/extensions.cc | 26 ++++--- + ssl/internal.h | 12 ++- + ssl/ssl_key_share.cc | 111 +++++++++++++++++++++++++++- + ssl/ssl_lib.cc | 16 +++- + ssl/ssl_test.cc | 19 ++++- + ssl/test/runner/basic_tests.go | 2 + + ssl/test/runner/cbc_tests.go | 3 + + ssl/test/runner/common.go | 2 +- + ssl/test/runner/curve_tests.go | 28 +++---- + ssl/test/runner/ech_tests.go | 24 +++++- + ssl/test/runner/extension_tests.go | 3 +- + ssl/test/runner/key_update_tests.go | 6 +- + tool/client.cc | 9 +++ + 18 files changed, 245 insertions(+), 42 deletions(-) + +diff --git a/crypto/obj/obj_dat.h b/crypto/obj/obj_dat.h +index d8b86dcd2..6dd49ec36 100644 +--- a/crypto/obj/obj_dat.h ++++ b/crypto/obj/obj_dat.h +@@ -15,7 +15,7 @@ + // This file is generated by crypto/obj/objects.go. -- uint8_t kyber_public_key[KYBER_PUBLIC_KEY_BYTES]; -- KYBER_generate_key(kyber_public_key, &kyber_private_key_); -+ uint8_t kyber_entropy[KYBER_GENERATE_KEY_BYTES]; -+ KYBER768_public_key kyber_public_key; -+ RAND_bytes(kyber_entropy, sizeof(kyber_entropy)); -+ KYBER768_generate_key(&kyber_public_key, &kyber_private_key_, kyber_entropy); -+ -+ uint8_t kyber_public_key_bytes[KYBER768_PUBLIC_KEY_BYTES]; -+ KYBER768_marshal_public_key(kyber_public_key_bytes, &kyber_public_key); - if (!CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key)) || -- !CBB_add_bytes(out, kyber_public_key, sizeof(kyber_public_key))) { -+ !CBB_add_bytes(out, kyber_public_key_bytes, -+ sizeof(kyber_public_key_bytes))) { - return false; - } +-#define NUM_NID 971 ++#define NUM_NID 972 - return true; - } + static const uint8_t kObjectData[] = { + /* NID_rsadsi */ +@@ -8799,6 +8799,8 @@ static const ASN1_OBJECT kObjects[NUM_NID] = { + {"id-ml-dsa-87", "ML-DSA-87", NID_ML_DSA_87, 9, &kObjectData[6223], 0}, + {"id-alg-ml-kem-768", "ML-KEM-768", NID_ML_KEM_768, 9, &kObjectData[6232], + 0}, ++ {"P256Kyber768Draft00", "P256Kyber768Draft00", NID_P256Kyber768Draft00, 0, ++ NULL, 0}, + }; -- bool Encap(CBB *out_ciphertext, Array *out_secret, -- uint8_t *out_alert, Span peer_key) override { -+ bool Encap(CBB *out_public_key, Array *out_secret, -+ uint8_t *out_alert, Span peer_key) override { - Array secret; -- if (!secret.Init(32 + 32)) { -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); - return false; - } + static const uint16_t kNIDsInShortNameOrder[] = { +@@ -8931,6 +8933,7 @@ static const uint16_t kNIDsInShortNameOrder[] = { + 18 /* OU */, + 749 /* Oakley-EC2N-3 */, + 750 /* Oakley-EC2N-4 */, ++ 971 /* P256Kyber768Draft00 */, + 9 /* PBE-MD2-DES */, + 168 /* PBE-MD2-RC2-64 */, + 10 /* PBE-MD5-DES */, +@@ -9854,6 +9857,7 @@ static const uint16_t kNIDsInLongNameOrder[] = { + 366 /* OCSP Nonce */, + 371 /* OCSP Service Locator */, + 180 /* OCSP Signing */, ++ 971 /* P256Kyber768Draft00 */, + 161 /* PBES2 */, + 69 /* PBKDF2 */, + 162 /* PBMAC1 */, +diff --git a/crypto/obj/obj_mac.num b/crypto/obj/obj_mac.num +index ae863e29d..7231b9a58 100644 +--- a/crypto/obj/obj_mac.num ++++ b/crypto/obj/obj_mac.num +@@ -958,3 +958,4 @@ ML_DSA_44 967 + ML_DSA_65 968 + ML_DSA_87 969 + ML_KEM_768 970 ++P256Kyber768Draft00 971 +diff --git a/crypto/obj/objects.txt b/crypto/obj/objects.txt +index 1e0cb76db..e8b249dfd 100644 +--- a/crypto/obj/objects.txt ++++ b/crypto/obj/objects.txt +@@ -1340,6 +1340,7 @@ secg-scheme 14 3 : dhSinglePass-cofactorDH-sha512kdf-scheme + + # NIDs for post quantum hybrid KEMs in TLS (no corresponding OIDs). + : X25519Kyber768Draft00 ++ : P256Kyber768Draft00 + : X25519MLKEM768 + + # See RFC 8410. +diff --git a/include/openssl/nid.h b/include/openssl/nid.h +index 83a1cf592..7265f15f6 100644 +--- a/include/openssl/nid.h ++++ b/include/openssl/nid.h +@@ -5508,6 +5508,9 @@ extern "C" { + #define OBJ_ML_KEM_768 2L, 16L, 840L, 1L, 101L, 3L, 4L, 4L, 2L + #define OBJ_ENC_ML_KEM_768 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x02 - uint8_t x25519_public_key[32]; - X25519_keypair(x25519_public_key, x25519_private_key_); -- KYBER_public_key peer_kyber_pub; -- CBS peer_key_cbs; -- CBS peer_x25519_cbs; -- CBS peer_kyber_cbs; -- CBS_init(&peer_key_cbs, peer_key.data(), peer_key.size()); -- if (!CBS_get_bytes(&peer_key_cbs, &peer_x25519_cbs, 32) || -- !CBS_get_bytes(&peer_key_cbs, &peer_kyber_cbs, -- KYBER_PUBLIC_KEY_BYTES) || -- CBS_len(&peer_key_cbs) != 0 || -- !X25519(secret.data(), x25519_private_key_, -- CBS_data(&peer_x25519_cbs)) || -- !KYBER_parse_public_key(&peer_kyber_pub, &peer_kyber_cbs)) { ++#define SN_P256Kyber768Draft00 "P256Kyber768Draft00" ++#define NID_P256Kyber768Draft00 971 + -+ KYBER768_public_key peer_public_key; -+ if (peer_key.size() != 32 + KYBER768_PUBLIC_KEY_BYTES) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } + + #if defined(__cplusplus) + } /* extern C */ +diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h +index ff68ba69e..0730e769a 100644 +--- a/include/openssl/ssl.h ++++ b/include/openssl/ssl.h +@@ -2550,6 +2550,7 @@ OPENSSL_EXPORT size_t SSL_CTX_get_num_tickets(const SSL_CTX *ctx); + #define SSL_GROUP_X25519_MLKEM768 0x11ec + #define SSL_GROUP_X25519_KYBER768_DRAFT00 0x6399 + #define SSL_GROUP_MLKEM1024 0x0202 ++#define SSL_GROUP_P256_KYBER768_DRAFT00 0xfe32 + + // SSL_CTX_set1_group_ids sets the preferred groups for |ctx| to |group_ids|. + // Each element of |group_ids| should be a unique one of the |SSL_GROUP_*| +@@ -5964,6 +5965,20 @@ OPENSSL_EXPORT int SSL_CTX_set1_curves_list(SSL_CTX *ctx, const char *curves); + // SSL_set1_curves_list calls |SSL_set1_groups_list|. + OPENSSL_EXPORT int SSL_set1_curves_list(SSL *ssl, const char *curves); + ++// By default, a client will send both a non post-quantum and a post-quantum ++// keyshare if available. ++// ++// SSL_use_second_keyshare controls this behaviour. If |enabled| is 0, then ++// a client using |ssl| will only send one keyshare. ++OPENSSL_EXPORT void SSL_use_second_keyshare(SSL *ssl, int enabled); + -+ KYBER768_parse_public_key(&peer_public_key, peer_key.data() + 32); ++// By default, a client will send both a non post-quantum and a post-quantum ++// keyshare if available. ++// ++// SSL_CTX_use_second_keyshare controls this behaviour. If |enabled| is 0, then ++// a client using |ctx| will only send one keyshare. ++OPENSSL_EXPORT void SSL_CTX_use_second_keyshare(SSL_CTX *ctx, int enabled); + -+ if (!X25519(secret.data(), x25519_private_key_, peer_key.data())) { - *out_alert = SSL_AD_DECODE_ERROR; - OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + // TLSEXT_nid_unknown is a constant used in OpenSSL for + // |SSL_get_negotiated_group| to return an unrecognized group. BoringSSL never + // returns this value, but we define this constant for compatibility. +diff --git a/ssl/extensions.cc b/ssl/extensions.cc +index c5f90688c..e0514fed3 100644 +--- a/ssl/extensions.cc ++++ b/ssl/extensions.cc +@@ -101,6 +101,7 @@ static bool tls1_check_duplicate_extensions(const CBS *cbs) { + static bool is_post_quantum_group(uint16_t id) { + switch (id) { + case SSL_GROUP_X25519_KYBER768_DRAFT00: ++ case SSL_GROUP_P256_KYBER768_DRAFT00: + case SSL_GROUP_X25519_MLKEM768: + case SSL_GROUP_MLKEM1024: + return true; +@@ -2241,18 +2242,21 @@ bool ssl_setup_key_shares(SSL_HANDSHAKE *hs, uint16_t override_group_id) { + if (!default_key_shares.TryPushBack(supported_group_list[0])) { return false; } - -- uint8_t kyber_ciphertext[KYBER_CIPHERTEXT_BYTES]; -- KYBER_encap(kyber_ciphertext, secret.data() + 32, secret.size() - 32, -- &peer_kyber_pub); -+ uint8_t ciphertext[KYBER768_CIPHERTEXT_BYTES]; -+ uint8_t entropy[KYBER_ENCAP_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); - -- if (!CBB_add_bytes(out_ciphertext, x25519_public_key, -+ if(!KYBER768_encap(ciphertext, secret.data() + 32, &peer_public_key, entropy, 0)) { -+ *out_alert = SSL_AD_ILLEGAL_PARAMETER; -+ return false; -+ } -+ if(!CBB_add_bytes(out_public_key, x25519_public_key, - sizeof(x25519_public_key)) || -- !CBB_add_bytes(out_ciphertext, kyber_ciphertext, -- sizeof(kyber_ciphertext))) { -+ !CBB_add_bytes(out_public_key, ciphertext, sizeof(ciphertext))) { - return false; +- // We'll try to include one post-quantum and one classical initial key +- // share. +- for (size_t i = 1; i < supported_group_list.size(); i++) { +- if (is_post_quantum_group(default_key_shares[0]) == +- is_post_quantum_group(supported_group_list[i])) { +- continue; +- } +- if (!default_key_shares.TryPushBack(supported_group_list[i])) { +- return false; ++ ++ if (!ssl->config->disable_second_keyshare) { ++ // We'll try to include one post-quantum and one classical initial key ++ // share. ++ for (size_t i = 1; i < supported_group_list.size(); i++) { ++ if (is_post_quantum_group(default_key_shares[0]) == ++ is_post_quantum_group(supported_group_list[i])) { ++ continue; ++ } ++ if (!default_key_shares.TryPushBack(supported_group_list[i])) { ++ return false; ++ } ++ assert(default_key_shares[1] != default_key_shares[0]); ++ break; + } +- assert(default_key_shares[1] != default_key_shares[0]); +- break; } - -@@ -256,30 +486,233 @@ class X25519Kyber768KeyShare : public SSLKeyShare { + selected_key_shares.emplace(default_key_shares); } +diff --git a/ssl/internal.h b/ssl/internal.h +index a69505b47..1f5ce51e6 100644 +--- a/ssl/internal.h ++++ b/ssl/internal.h +@@ -955,7 +955,7 @@ struct NamedGroup { + Span NamedGroups(); + + // kNumNamedGroups is the number of supported groups. +-constexpr size_t kNumNamedGroups = 7u; ++constexpr size_t kNumNamedGroups = 8u; + + // DefaultSupportedGroupIds returns the list of IDs for the default groups that + // are supported when the caller hasn't explicitly configured supported groups. +@@ -3388,6 +3388,11 @@ struct SSL_CONFIG { + // permute_extensions is whether to permute extensions when sending messages. + bool permute_extensions : 1; + ++ // As a client by default we will send a non post-quantum share and ++ // a post-quantum share if available. If disable_second_keyshare is set, ++ // we will only send the most preferred keyshare. ++ bool disable_second_keyshare : 1; ++ + // aes_hw_override if set indicates we should override checking for aes + // hardware support, and use the value in aes_hw_override_value instead. + bool aes_hw_override : 1; +@@ -4015,6 +4020,11 @@ struct ssl_ctx_st : public bssl::RefCounted { + // permute_extensions is whether to permute extensions when sending messages. + bool permute_extensions : 1; + ++ // As a client by default we will send a non post-quantum share and ++ // a post-quantum share if available. If disable_second_keyshare is set, ++ // we will only send the most preferred keyshare. ++ bool disable_second_keyshare : 1; ++ + // allow_unknown_alpn_protos is whether the client allows unsolicited ALPN + // protocols from the peer. + bool allow_unknown_alpn_protos : 1; +diff --git a/ssl/ssl_key_share.cc b/ssl/ssl_key_share.cc +index d155b5527..4fb08906b 100644 +--- a/ssl/ssl_key_share.cc ++++ b/ssl/ssl_key_share.cc +@@ -193,6 +193,109 @@ class X25519KeyShare : public SSLKeyShare { + uint8_t private_key_[32]; + }; - bool Decap(Array *out_secret, uint8_t *out_alert, -- Span ciphertext) override { -+ Span peer_key) override { -+ *out_alert = SSL_AD_INTERNAL_ERROR; -+ -+ Array secret; -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } -+ -+ if (peer_key.size() != 32 + KYBER768_CIPHERTEXT_BYTES || -+ !X25519(secret.data(), x25519_private_key_, peer_key.data())) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } -+ -+ KYBER768_decap(secret.data() + 32, &kyber_private_key_, -+ peer_key.data() + 32, peer_key.size() - 32, 0); -+ -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ private: -+ uint8_t x25519_private_key_[32]; -+ KYBER768_private_key kyber_private_key_; -+ uint16_t group_id_; -+}; -+ -+class X25519MLKEM768KeyShare : public SSLKeyShare { ++class P256Kyber768Draft00KeyShare : public SSLKeyShare { + public: -+ X25519MLKEM768KeyShare() {} ++ P256Kyber768Draft00KeyShare() ++ : ecks_(EC_group_p256(), SSL_GROUP_SECP256R1) {} + -+ uint16_t GroupID() const override { return SSL_GROUP_X25519_MLKEM768; } ++ uint16_t GroupID() const override { ++ return SSL_GROUP_P256_KYBER768_DRAFT00; ++ } + + bool Generate(CBB *out) override { -+ uint8_t x25519_public_key[32]; -+ X25519_keypair(x25519_public_key, x25519_private_key_); -+ -+ uint8_t kyber_entropy[KYBER_GENERATE_KEY_BYTES]; -+ KYBER768_public_key kyber_public_key; -+ RAND_bytes(kyber_entropy, sizeof(kyber_entropy)); -+ KYBER768_generate_key(&kyber_public_key, &kyber_private_key_, kyber_entropy); ++ uint8_t kyber_public_key[KYBER_PUBLIC_KEY_BYTES]; ++ KYBER_generate_key(kyber_public_key, &kyber_private_key_); + -+ uint8_t kyber_public_key_bytes[KYBER768_PUBLIC_KEY_BYTES]; -+ KYBER768_marshal_public_key(kyber_public_key_bytes, &kyber_public_key); -+ -+ if (!CBB_add_bytes(out, kyber_public_key_bytes, sizeof(kyber_public_key_bytes)) || -+ !CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key))) { ++ if(!ecks_.Generate(out) || ++ !CBB_add_bytes(out, kyber_public_key, sizeof(kyber_public_key))) { + return false; + } + + return true; + } + -+ bool Encap(CBB *out_public_key, Array *out_secret, -+ uint8_t *out_alert, Span peer_key) override { -+ Array secret; -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } ++ bool Encap(CBB *out_ciphertext, Array *out_secret, ++ uint8_t *out_alert, Span peer_key) override { ++ Array ec_secret; + -+ uint8_t x25519_public_key[32]; -+ X25519_keypair(x25519_public_key, x25519_private_key_); ++ *out_alert = SSL_AD_INTERNAL_ERROR; + -+ KYBER768_public_key peer_public_key; -+ if (peer_key.size() != KYBER768_PUBLIC_KEY_BYTES + 32) { -+ *out_alert = SSL_AD_DECODE_ERROR; ++ if(peer_key.size() != p256_share_size + KYBER_PUBLIC_KEY_BYTES) { ++ *out_alert = SSL_AD_ILLEGAL_PARAMETER; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + -+ KYBER768_parse_public_key(&peer_public_key, peer_key.data()); -+ -+ if (!X25519(secret.data() + 32, x25519_private_key_, -+ peer_key.data() + KYBER768_PUBLIC_KEY_BYTES)) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); ++ if (!ecks_.Encap(out_ciphertext, &ec_secret, out_alert, ++ peer_key.subspan(0, p256_share_size))) { + return false; + } + -+ uint8_t ciphertext[KYBER768_CIPHERTEXT_BYTES]; -+ uint8_t entropy[KYBER_ENCAP_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); ++ KYBER_public_key peer_kyber_pub; ++ CBS peer_kyber_cbs; ++ CBS_init(&peer_kyber_cbs, peer_key.data() + p256_share_size, ++ KYBER_PUBLIC_KEY_BYTES); + -+ if(!KYBER768_encap(ciphertext, secret.data(), &peer_public_key, entropy, 1)) { ++ if (!KYBER_parse_public_key(&peer_kyber_pub, &peer_kyber_cbs)) { + *out_alert = SSL_AD_ILLEGAL_PARAMETER; ++ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } -+ if(!CBB_add_bytes(out_public_key, ciphertext, sizeof(ciphertext)) || -+ !CBB_add_bytes(out_public_key, x25519_public_key, sizeof(x25519_public_key))) { ++ ++ uint8_t kyber_ciphertext[KYBER_CIPHERTEXT_BYTES]; ++ Array secret; ++ if (!secret.InitForOverwrite(p256_secret_size + KYBER_SHARED_SECRET_BYTES)) { + return false; + } ++ OPENSSL_memcpy(secret.data(), ec_secret.data(), ec_secret.size()); ++ KYBER_encap(kyber_ciphertext, secret.data() + p256_secret_size, ++ &peer_kyber_pub); + -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ bool Decap(Array *out_secret, uint8_t *out_alert, -+ Span peer_key) override { - *out_alert = SSL_AD_INTERNAL_ERROR; - - Array secret; -- if (!secret.Init(32 + 32)) { -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); - return false; - } - -- if (ciphertext.size() != 32 + KYBER_CIPHERTEXT_BYTES || -- !X25519(secret.data(), x25519_private_key_, ciphertext.data())) { -+ if (peer_key.size() != KYBER768_CIPHERTEXT_BYTES + 32 || -+ !X25519(secret.data() + 32, x25519_private_key_, -+ peer_key.data() + KYBER768_CIPHERTEXT_BYTES )) { - *out_alert = SSL_AD_DECODE_ERROR; - OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); - return false; - } - -- KYBER_decap(secret.data() + 32, secret.size() - 32, ciphertext.data() + 32, -- &kyber_private_key_); -+ KYBER768_decap(secret.data(), &kyber_private_key_, -+ peer_key.data(), peer_key.size() - 32, 1); -+ -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ private: -+ uint8_t x25519_private_key_[32]; -+ KYBER768_private_key kyber_private_key_; -+}; -+ -+class X25519Kyber512Draft00KeyShare : public SSLKeyShare { -+ public: -+ X25519Kyber512Draft00KeyShare() {} -+ -+ uint16_t GroupID() const override { return SSL_GROUP_X25519_KYBER512_DRAFT00; } -+ -+ bool Generate(CBB *out) override { -+ uint8_t x25519_public_key[32]; -+ X25519_keypair(x25519_public_key, x25519_private_key_); -+ -+ uint8_t kyber_entropy[KYBER_GENERATE_KEY_BYTES]; -+ KYBER512_public_key kyber_public_key; -+ RAND_bytes(kyber_entropy, sizeof(kyber_entropy)); -+ KYBER512_generate_key(&kyber_public_key, &kyber_private_key_, kyber_entropy); -+ -+ uint8_t kyber_public_key_bytes[KYBER512_PUBLIC_KEY_BYTES]; -+ KYBER512_marshal_public_key(kyber_public_key_bytes, &kyber_public_key); -+ -+ if (!CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key)) || -+ !CBB_add_bytes(out, kyber_public_key_bytes, -+ sizeof(kyber_public_key_bytes))) { ++ if(!CBB_add_bytes(out_ciphertext, kyber_ciphertext, ++ sizeof(kyber_ciphertext))) { + return false; + } + ++ *out_secret = std::move(secret); + return true; + } + -+ bool Encap(CBB *out_public_key, Array *out_secret, -+ uint8_t *out_alert, Span peer_key) override { -+ Array secret; -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } ++ bool Decap(Array *out_secret, uint8_t *out_alert, ++ Span ciphertext) override { ++ *out_alert = SSL_AD_INTERNAL_ERROR; + -+ uint8_t x25519_public_key[32]; -+ X25519_keypair(x25519_public_key, x25519_private_key_); ++ Array ec_secret; + -+ KYBER512_public_key peer_public_key; -+ if (peer_key.size() != 32 + KYBER512_PUBLIC_KEY_BYTES) { -+ *out_alert = SSL_AD_DECODE_ERROR; ++ if (ciphertext.size() != p256_share_size + KYBER_CIPHERTEXT_BYTES) { ++ *out_alert = SSL_AD_ILLEGAL_PARAMETER; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + -+ KYBER512_parse_public_key(&peer_public_key, peer_key.data() + 32); -+ -+ if (!X25519(secret.data(), x25519_private_key_, peer_key.data())) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); ++ if (!ecks_.Decap(&ec_secret, out_alert, ++ ciphertext.subspan(0, p256_share_size))) { + return false; + } + -+ uint8_t ciphertext[KYBER512_CIPHERTEXT_BYTES]; -+ uint8_t entropy[KYBER_ENCAP_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); -+ -+ if(!KYBER512_encap(ciphertext, secret.data() + 32, &peer_public_key, entropy, 0)) { -+ *out_alert = SSL_AD_ILLEGAL_PARAMETER; -+ return false; -+ } -+ if(!CBB_add_bytes(out_public_key, x25519_public_key, -+ sizeof(x25519_public_key)) || -+ !CBB_add_bytes(out_public_key, ciphertext, sizeof(ciphertext))) { ++ Array secret; ++ if (!secret.InitForOverwrite(p256_secret_size + KYBER_SHARED_SECRET_BYTES)) { + return false; + } -+ ++ OPENSSL_memcpy(secret.data(), ec_secret.data(), ec_secret.size()); ++ KYBER_decap(secret.data() + p256_secret_size, ++ ciphertext.data() + p256_share_size, &kyber_private_key_); + *out_secret = std::move(secret); + return true; + } + -+ bool Decap(Array *out_secret, uint8_t *out_alert, -+ Span peer_key) override { -+ *out_alert = SSL_AD_INTERNAL_ERROR; -+ -+ Array secret; -+ if (!secret.Init(32 + KYBER_KEY_BYTES)) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } -+ -+ if (peer_key.size() != 32 + KYBER512_CIPHERTEXT_BYTES || -+ !X25519(secret.data(), x25519_private_key_, peer_key.data())) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } ++ private: ++ ECKeyShare ecks_; ++ KYBER_private_key kyber_private_key_; + -+ KYBER512_decap(secret.data() + 32, &kyber_private_key_, -+ peer_key.data() + 32, peer_key.size() - 32, 0); ++ static constexpr size_t p256_share_size = 65; ++ static constexpr size_t p256_secret_size = 32; ++}; + - *out_secret = std::move(secret); - return true; - } - - private: - uint8_t x25519_private_key_[32]; -- KYBER_private_key kyber_private_key_; -+ KYBER512_private_key kyber_private_key_; - }; - - constexpr NamedGroup kNamedGroups[] = { -@@ -288,8 +721,16 @@ constexpr NamedGroup kNamedGroups[] = { - {NID_secp384r1, SSL_GROUP_SECP384R1, "P-384", "secp384r1"}, + // draft-tls-westerbaan-xyber768d00-03 + class X25519Kyber768KeyShare : public SSLKeyShare { + public: +@@ -441,9 +544,11 @@ constexpr NamedGroup kNamedGroups[] = { {NID_secp521r1, SSL_GROUP_SECP521R1, "P-521", "secp521r1"}, {NID_X25519, SSL_GROUP_X25519, "X25519", "x25519"}, -+ {NID_X25519Kyber512Draft00, SSL_GROUP_X25519_KYBER512_DRAFT00, -+ "X25519Kyber512Draft00", "Xyber512D00"}, {NID_X25519Kyber768Draft00, SSL_GROUP_X25519_KYBER768_DRAFT00, - "X25519Kyber768Draft00", ""}, -+ "X25519Kyber768Draft00", "Xyber768D00"}, -+ {NID_X25519Kyber768Draft00Old, SSL_GROUP_X25519_KYBER768_DRAFT00_OLD, -+ "X25519Kyber768Draft00Old", "Xyber768D00Old"}, ++ "X25519Kyber768Draft00", "Xyber768D00"}, + {NID_X25519MLKEM768, SSL_GROUP_X25519_MLKEM768, "X25519MLKEM768", ""}, + {NID_ML_KEM_1024, SSL_GROUP_MLKEM1024, "MLKEM1024", ""}, + {NID_P256Kyber768Draft00, SSL_GROUP_P256_KYBER768_DRAFT00, -+ "P256Kyber768Draft00", "P256Kyber768D00"}, -+ {NID_X25519MLKEM768, SSL_GROUP_X25519_MLKEM768, -+ "X25519MLKEM768", "X25519MLKEM768"} ++ "P256Kyber768Draft00", "P256Kyber768D00"}, }; - } // namespace -@@ -310,8 +751,18 @@ UniquePtr SSLKeyShare::Create(uint16_t group_id) { - return MakeUnique(EC_group_p521(), SSL_GROUP_SECP521R1); - case SSL_GROUP_X25519: - return MakeUnique(); -+ case SSL_GROUP_X25519_KYBER512_DRAFT00: -+ return UniquePtr(New()); - case SSL_GROUP_X25519_KYBER768_DRAFT00: -- return MakeUnique(); -+ return UniquePtr(New( -+ group_id)); -+ case SSL_GROUP_X25519_KYBER768_DRAFT00_OLD: -+ return UniquePtr(New( -+ group_id)); + static_assert(std::size(kNamedGroups) == kNumNamedGroups, +@@ -455,6 +560,8 @@ Span NamedGroups() { return kNamedGroups; } + + Span DefaultSupportedGroupIds() { + static const uint16_t kDefaultSupportedGroupIds[] = { ++ SSL_GROUP_X25519_MLKEM768, ++ SSL_GROUP_P256_KYBER768_DRAFT00, + SSL_GROUP_X25519, + SSL_GROUP_SECP256R1, + SSL_GROUP_SECP384R1, +@@ -478,6 +585,8 @@ UniquePtr SSLKeyShare::Create(uint16_t group_id) { + return MakeUnique(); + case SSL_GROUP_MLKEM1024: + return MakeUnique(); + case SSL_GROUP_P256_KYBER768_DRAFT00: -+ return UniquePtr(New()); -+ case SSL_GROUP_X25519_MLKEM768: -+ return UniquePtr(New()); ++ return MakeUnique(); default: return nullptr; } diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc -index 58b68e675..38c8e906c 100644 +index f64b103fb..fe5bb9bc7 100644 --- a/ssl/ssl_lib.cc +++ b/ssl/ssl_lib.cc -@@ -3260,7 +3260,7 @@ namespace fips202205 { +@@ -397,6 +397,7 @@ ssl_ctx_st::ssl_ctx_st(const SSL_METHOD *ssl_method) + channel_id_enabled(false), + grease_enabled(false), + permute_extensions(false), ++ disable_second_keyshare(false), + allow_unknown_alpn_protos(false), + false_start_allowed_without_alpn(false), + handoff(false), +@@ -527,6 +528,7 @@ SSL *SSL_new(SSL_CTX *ctx) { + ssl->config->retain_only_sha256_of_client_certs = + ctx->retain_only_sha256_of_client_certs; + ssl->config->permute_extensions = ctx->permute_extensions; ++ ssl->config->disable_second_keyshare = ctx->disable_second_keyshare; + ssl->config->aes_hw_override = ctx->aes_hw_override; + ssl->config->aes_hw_override_value = ctx->aes_hw_override_value; + ssl->config->compliance_policy = ctx->compliance_policy; +@@ -586,6 +588,7 @@ SSL_CONFIG::SSL_CONFIG(SSL *ssl_arg) + jdk11_workaround(false), + quic_use_legacy_codepoint(false), + permute_extensions(false), ++ disable_second_keyshare(false), + alps_use_new_codepoint(true) { + assert(ssl); + } +@@ -3331,6 +3334,15 @@ int SSL_set1_curves_list(SSL *ssl, const char *curves) { + return SSL_set1_groups_list(ssl, curves); + } + ++void SSL_use_second_keyshare(SSL *ssl, int enabled) { ++ ssl->config->disable_second_keyshare = !enabled; ++} ++ ++void SSL_CTX_use_second_keyshare(SSL_CTX *ctx, int enabled) { ++ ctx->disable_second_keyshare = !enabled; ++} ++ ++ + namespace fips202205 { + + // (References are to SP 800-52r2): +@@ -3342,7 +3354,9 @@ namespace fips202205 { // Section 3.3.1 // "The server shall be configured to only use cipher suites that are // composed entirely of NIST approved algorithms" -static const uint16_t kGroups[] = {SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1}; -+static const uint16_t kGroups[] = {SSL_GROUP_P256_KYBER768_DRAFT00, SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1}; ++static const uint16_t kGroups[] = { ++ SSL_GROUP_P256_KYBER768_DRAFT00, ++ SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1}; static const uint16_t kSigAlgs[] = { SSL_SIGN_RSA_PKCS1_SHA256, diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc -index a8f4f215b..e0ebb505e 100644 +index 779a2c37a..36a0cab3b 100644 --- a/ssl/ssl_test.cc +++ b/ssl/ssl_test.cc -@@ -484,7 +484,34 @@ static const CurveTest kCurveTests[] = { - "P-256:X25519Kyber768Draft00", - { SSL_GROUP_SECP256R1, SSL_GROUP_X25519_KYBER768_DRAFT00 }, - }, -- -+ { -+ "Xyber512D00", -+ { SSL_GROUP_X25519_KYBER512_DRAFT00 }, -+ }, -+ { -+ "Xyber768D00", -+ { SSL_GROUP_X25519_KYBER768_DRAFT00 }, -+ }, -+ { -+ "Xyber768D00:Xyber768D00Old", -+ { SSL_GROUP_X25519_KYBER768_DRAFT00, SSL_GROUP_X25519_KYBER768_DRAFT00_OLD }, -+ }, -+ { -+ "P-256:Xyber512D00", -+ { SSL_GROUP_SECP256R1, SSL_GROUP_X25519_KYBER512_DRAFT00 }, -+ }, -+ { -+ "P256Kyber768D00", -+ { SSL_GROUP_P256_KYBER768_DRAFT00 }, -+ }, -+ { -+ "X25519MLKEM768", -+ { SSL_GROUP_X25519_MLKEM768 }, -+ }, -+ { -+ "P-256:P256Kyber768D00", -+ { SSL_GROUP_SECP256R1, SSL_GROUP_P256_KYBER768_DRAFT00 }, -+ }, - { - "P-256:P-384:P-521:X25519", +@@ -506,6 +506,14 @@ static const CurveTest kCurveTests[] = { + "MLKEM1024:X25519MLKEM768", + {SSL_GROUP_MLKEM1024, SSL_GROUP_X25519_MLKEM768}, + }, ++ { ++ "P256Kyber768Draft00", ++ {SSL_GROUP_P256_KYBER768_DRAFT00}, ++ }, ++ { ++ "P-256:P256Kyber768Draft00", ++ {SSL_GROUP_SECP256R1, SSL_GROUP_P256_KYBER768_DRAFT00}, ++ }, + { -diff --git a/tool/speed.cc b/tool/speed.cc -index 942dcade1..f31e9e244 100644 ---- a/tool/speed.cc -+++ b/tool/speed.cc -@@ -1018,6 +1018,116 @@ static bool SpeedScrypt(const std::string &selected) { - return true; - } + "P-256:P-384:P-521:X25519", +@@ -668,7 +676,9 @@ TEST(SSLTest, CurveRules) { + } + + TEST(SSLTest, DefaultCurves) { +- const uint16_t kDefaults[] = {SSL_GROUP_X25519, SSL_GROUP_SECP256R1, ++ const uint16_t kDefaults[] = {SSL_GROUP_X25519_MLKEM768, ++ SSL_GROUP_P256_KYBER768_DRAFT00, ++ SSL_GROUP_X25519, SSL_GROUP_SECP256R1, + SSL_GROUP_SECP384R1}; + + // Test the group ID APIs. +@@ -1522,6 +1532,9 @@ static bool GetClientHello(SSL *ssl, std::vector *out) { + static size_t GetClientHelloLen(uint16_t max_version, uint16_t session_version, + size_t ticket_len) { + bssl::UniquePtr ctx(SSL_CTX_new(TLS_method())); ++ // RTG-3417 bas: we need to disable PQ here so that the small ClientHello ++ // padding tests properly tests things. ++ SSL_CTX_set1_curves_list(ctx.get(), "X25519"); + bssl::UniquePtr session = + CreateSessionWithTicket(session_version, ticket_len); + if (!ctx || !session) { +@@ -6815,7 +6828,9 @@ TEST(SSLTest, ApplyHandoffRemovesUnsupportedCurves) { + + // The default list of groups is used before applying the handoff. + EXPECT_THAT(server->config->supported_group_list, +- ElementsAreArray({SSL_GROUP_X25519, SSL_GROUP_SECP256R1, ++ ElementsAreArray({SSL_GROUP_X25519_MLKEM768, ++ SSL_GROUP_P256_KYBER768_DRAFT00, ++ SSL_GROUP_X25519, SSL_GROUP_SECP256R1, + SSL_GROUP_SECP384R1})); + ASSERT_TRUE(SSL_apply_handoff(server.get(), handoff)); + EXPECT_EQ(1u, server->config->supported_group_list.size()); +diff --git a/ssl/test/runner/basic_tests.go b/ssl/test/runner/basic_tests.go +index 08de8fa5f..dd945fa49 100644 +--- a/ssl/test/runner/basic_tests.go ++++ b/ssl/test/runner/basic_tests.go +@@ -129,6 +129,7 @@ read alert 1 0 + `write hs 1 + read hs 3 + write hs 1 ++write hs 1 + read hs 2 + read hs 11 + read hs 12 +@@ -1956,6 +1957,7 @@ read alert 1 0 + write hs 2 + write hs 8 + write hs 11 ++write hs 11 + write hs 15 + write hs 20 + read hs 20 +diff --git a/ssl/test/runner/cbc_tests.go b/ssl/test/runner/cbc_tests.go +index 6f49d12af..5e970b2b5 100644 +--- a/ssl/test/runner/cbc_tests.go ++++ b/ssl/test/runner/cbc_tests.go +@@ -14,6 +14,8 @@ + + package runner + ++import "strconv" ++ + func addCBCPaddingTests() { + testCases = append(testCases, testCase{ + name: "MaxCBCPadding", +@@ -104,6 +106,7 @@ func addCBCSplittingTests() { + "-partial-write", + // BoringSSL disables 3DES by default. + "-cipher", "ALL:3DES", ++ "-curves", strconv.Itoa(int(CurveX25519)), + }, + }) + } +diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go +index 7dbde72c9..9d18d9d45 100644 +--- a/ssl/test/runner/common.go ++++ b/ssl/test/runner/common.go +@@ -2095,7 +2095,7 @@ type ProtocolBugs struct { + FailIfHelloRetryRequested bool + + // FailIfPostQuantumOffered will cause a server to reject a ClientHello if +- // post-quantum curves are supported. ++ // post-quantum curves are not supported. + FailIfPostQuantumOffered bool + + // ExpectKeyShares, if not nil, lists (in order) the curves that a ClientHello +diff --git a/ssl/test/runner/curve_tests.go b/ssl/test/runner/curve_tests.go +index 8e7b0a45b..556bf314d 100644 +--- a/ssl/test/runner/curve_tests.go ++++ b/ssl/test/runner/curve_tests.go +@@ -579,17 +579,6 @@ func addCurveTests() { + }) + } + +- // ML-KEM and Kyber should not be offered by default as a client. +- testCases = append(testCases, testCase{ +- name: "PostQuantumNotEnabledByDefaultInClients", +- config: Config{ +- MinVersion: VersionTLS13, +- Bugs: ProtocolBugs{ +- FailIfPostQuantumOffered: true, +- }, +- }, +- }) +- + for _, curve := range testCurves { + if !isMLKEMGroup(curve.id) { + continue +@@ -679,18 +668,19 @@ func addCurveTests() { + }) + } + +- // As a server, ML-KEMs and Kyber are not yet supported by default. ++ // If ML-KEM is offered, both X25519 and ML-KEM should have a key-share. + testCases = append(testCases, testCase{ +- testType: serverTest, +- name: "PostQuantumNotEnabledByDefaultForAServer", ++ name: "NotJustMLKEMKeyShare", + config: Config{ +- MinVersion: VersionTLS13, +- CurvePreferences: []CurveID{CurveX25519MLKEM768, CurveMLKEM1024, CurveX25519Kyber768, CurveX25519}, +- DefaultCurves: []CurveID{CurveX25519MLKEM768, CurveMLKEM1024, CurveX25519Kyber768}, ++ MinVersion: VersionTLS13, ++ Bugs: ProtocolBugs{ ++ ExpectedKeyShares: []CurveID{CurveX25519MLKEM768, CurveX25519}, ++ }, + }, + flags: []string{ +- "-server-preference", +- "-expect-curve-id", strconv.Itoa(int(CurveX25519)), ++ "-curves", strconv.Itoa(int(CurveX25519MLKEM768)), ++ "-curves", strconv.Itoa(int(CurveX25519)), ++ "-expect-curve-id", strconv.Itoa(int(CurveX25519MLKEM768)), + }, + }) + +diff --git a/ssl/test/runner/ech_tests.go b/ssl/test/runner/ech_tests.go +index 2cd3c10d3..f19d8d20a 100644 +--- a/ssl/test/runner/ech_tests.go ++++ b/ssl/test/runner/ech_tests.go +@@ -451,7 +451,8 @@ func addEncryptedClientHelloTests() { + expectMsgCallback += clientAndServerHello + } + // EncryptedExtensions onwards. +- expectMsgCallback += `write hs 8 ++ if protocol != dtls { ++ expectMsgCallback += `write hs 8 + write hs 11 + write hs 15 + write hs 20 +@@ -462,6 +463,20 @@ write hs 4 + read ack + read ack + ` ++ } else { ++ expectMsgCallback += `write hs 8 ++write hs 11 ++write hs 11 ++write hs 15 ++write hs 20 ++read hs 20 ++write ack ++write hs 4 ++write hs 4 ++read ack ++read ack ++` ++ } + if protocol != dtls { + expectMsgCallback = strings.ReplaceAll(expectMsgCallback, "write ack\n", "") + expectMsgCallback = strings.ReplaceAll(expectMsgCallback, "read ack\n", "") +@@ -2349,8 +2364,11 @@ read ack + + // Test the message callback is correctly reported, with and without + // HelloRetryRequest. +- clientAndServerHello := "write clienthelloinner\nwrite hs 1\nread hs 2\n" +- clientAndServerHelloInitial := clientAndServerHello ++ clientAndServerHelloInitial := "write clienthelloinner\nwrite hs 1\nwrite hs 1\nread hs 2\n" ++ clientAndServerHello := "write clienthelloinner\nwrite hs 1\nread hs 2\n" ++ if protocol != dtls { ++ clientAndServerHelloInitial = clientAndServerHello ++ } + if protocol == tls { + clientAndServerHelloInitial += "write ccs\n" + } +diff --git a/ssl/test/runner/extension_tests.go b/ssl/test/runner/extension_tests.go +index d6adb7759..4eb80aa8e 100644 +--- a/ssl/test/runner/extension_tests.go ++++ b/ssl/test/runner/extension_tests.go +@@ -16,6 +16,7 @@ package runner + + import ( + "fmt" ++ "strconv" + ) + + func addExtensionTests() { +@@ -1967,7 +1968,7 @@ func addExtensionTests() { + // This hostname just needs to be long enough to push the + // ClientHello into F5's danger zone between 256 and 511 bytes + // long. +- flags: []string{"-host-name", "01234567890123456789012345678901234567890123456789012345678901234567890123456789.com"}, ++ flags: []string{"-host-name", "01234567890123456789012345678901234567890123456789012345678901234567890123456789.com", "-curves", strconv.Itoa(int(CurveX25519))}, + }) + + // Test that illegal extensions in TLS 1.3 are rejected by the client if +diff --git a/ssl/test/runner/key_update_tests.go b/ssl/test/runner/key_update_tests.go +index 0a9053038..5ce709589 100644 +--- a/ssl/test/runner/key_update_tests.go ++++ b/ssl/test/runner/key_update_tests.go +@@ -14,7 +14,10 @@ + + package runner + +-import "slices" ++import ( ++ "slices" ++ "strconv" ++) + + func addKeyUpdateTests() { + // TLS tests. +@@ -295,6 +298,7 @@ func addKeyUpdateTests() { + }, + }, + shimSendsKeyUpdateBeforeRead: true, ++ flags: []string{"-curves", strconv.Itoa(int(CurveX25519))}, + }) + + // Test that shim responds to KeyUpdate requests. +diff --git a/tool/client.cc b/tool/client.cc +index 0839d4880..be9b79259 100644 +--- a/tool/client.cc ++++ b/tool/client.cc +@@ -125,6 +125,11 @@ static const struct argument kArguments[] = { + kBooleanArgument, + "Permute extensions in handshake messages", + }, ++ { ++ "-disable-second-keyshare", ++ kBooleanArgument, ++ "Do not send a second keyshare", ++ }, + { + "-test-resumption", kBooleanArgument, + "Connect to the server twice. The first connection is closed once a " +@@ -538,6 +543,10 @@ bool Client(const std::vector &args) { + SSL_CTX_set_permute_extensions(ctx.get(), 1); + } -+static bool SpeedKyber768(const std::string &selected) { -+ if (!selected.empty() && selected != "Kyber768") { -+ return true; -+ } -+ -+ TimeResults results; -+ -+ if (!TimeFunction(&results, []() -> bool { -+ struct KYBER768_public_key pub; -+ struct KYBER768_private_key priv; -+ uint8_t entropy[KYBER_GENERATE_KEY_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); -+ KYBER768_generate_key(&pub, &priv, entropy); -+ return true; -+ })) { -+ fprintf(stderr, "Failed to time KYBER768_generate_key.\n"); -+ return false; -+ } -+ -+ results.Print("Kyber768 generate"); -+ -+ struct KYBER768_public_key pub; -+ struct KYBER768_private_key priv; -+ uint8_t key_entropy[KYBER_GENERATE_KEY_BYTES]; -+ RAND_bytes(key_entropy, sizeof(key_entropy)); -+ KYBER768_generate_key(&pub, &priv, key_entropy); -+ -+ uint8_t ciphertext[KYBER768_CIPHERTEXT_BYTES]; -+ if (!TimeFunction(&results, [&pub, &ciphertext]() -> bool { -+ uint8_t entropy[KYBER_ENCAP_BYTES]; -+ uint8_t shared_key[KYBER_KEY_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); -+ KYBER768_encap(ciphertext, shared_key, &pub, entropy, 0); -+ return true; -+ })) { -+ fprintf(stderr, "Failed to time KYBER768_encap.\n"); -+ return false; -+ } -+ -+ results.Print("Kyber768 encap"); -+ -+ if (!TimeFunction(&results, [&priv, &ciphertext]() -> bool { -+ uint8_t shared_key[KYBER_KEY_BYTES]; -+ KYBER768_decap(shared_key, &priv, ciphertext, sizeof(ciphertext), 0); -+ return true; -+ })) { -+ fprintf(stderr, "Failed to time KYBER768_decap.\n"); -+ return false; -+ } -+ -+ results.Print("Kyber768 decap"); -+ -+ return true; -+} -+ -+static bool SpeedKyber512(const std::string &selected) { -+ if (!selected.empty() && selected != "Kyber512") { -+ return true; -+ } -+ -+ TimeResults results; -+ -+ if (!TimeFunction(&results, []() -> bool { -+ struct KYBER512_public_key pub; -+ struct KYBER512_private_key priv; -+ uint8_t entropy[KYBER_GENERATE_KEY_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); -+ KYBER512_generate_key(&pub, &priv, entropy); -+ return true; -+ })) { -+ fprintf(stderr, "Failed to time KYBER512_generate_key.\n"); -+ return false; -+ } -+ -+ results.Print("Kyber512 generate"); -+ -+ struct KYBER512_public_key pub; -+ struct KYBER512_private_key priv; -+ uint8_t key_entropy[KYBER_GENERATE_KEY_BYTES]; -+ RAND_bytes(key_entropy, sizeof(key_entropy)); -+ KYBER512_generate_key(&pub, &priv, key_entropy); -+ -+ uint8_t ciphertext[KYBER512_CIPHERTEXT_BYTES]; -+ if (!TimeFunction(&results, [&pub, &ciphertext]() -> bool { -+ uint8_t entropy[KYBER_ENCAP_BYTES]; -+ uint8_t shared_key[KYBER_KEY_BYTES]; -+ RAND_bytes(entropy, sizeof(entropy)); -+ KYBER512_encap(ciphertext, shared_key, &pub, entropy, 0); -+ return true; -+ })) { -+ fprintf(stderr, "Failed to time KYBER512_encap.\n"); -+ return false; -+ } -+ -+ results.Print("Kyber512 encap"); -+ -+ if (!TimeFunction(&results, [&priv, &ciphertext]() -> bool { -+ uint8_t shared_key[KYBER_KEY_BYTES]; -+ KYBER512_decap(shared_key, &priv, ciphertext, sizeof(ciphertext), 0); -+ return true; -+ })) { -+ fprintf(stderr, "Failed to time KYBER512_decap.\n"); -+ return false; ++ if (args_map.count("-disable-second-keyshare") != 0) { ++ SSL_CTX_use_second_keyshare(ctx.get(), 0); + } + -+ results.Print("Kyber512 decap"); -+ -+ return true; -+} -+ - static bool SpeedHRSS(const std::string &selected) { - if (!selected.empty() && selected != "HRSS") { - return true; -@@ -1079,55 +1189,6 @@ static bool SpeedHRSS(const std::string &selected) { - return true; - } - --static bool SpeedKyber(const std::string &selected) { -- if (!selected.empty() && selected != "Kyber") { -- return true; -- } -- -- TimeResults results; -- -- uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES]; -- // This ciphertext is nonsense, but Kyber decap is constant-time so, for the -- // purposes of timing, it's fine. -- memset(ciphertext, 42, sizeof(ciphertext)); -- if (!TimeFunctionParallel(&results, [&]() -> bool { -- KYBER_private_key priv; -- uint8_t encoded_public_key[KYBER_PUBLIC_KEY_BYTES]; -- KYBER_generate_key(encoded_public_key, &priv); -- uint8_t shared_secret[32]; -- KYBER_decap(shared_secret, sizeof(shared_secret), ciphertext, &priv); -- return true; -- })) { -- fprintf(stderr, "Failed to time KYBER_generate_key + KYBER_decap.\n"); -- return false; -- } -- -- results.Print("Kyber generate + decap"); -- -- KYBER_private_key priv; -- uint8_t encoded_public_key[KYBER_PUBLIC_KEY_BYTES]; -- KYBER_generate_key(encoded_public_key, &priv); -- KYBER_public_key pub; -- if (!TimeFunctionParallel(&results, [&]() -> bool { -- CBS encoded_public_key_cbs; -- CBS_init(&encoded_public_key_cbs, encoded_public_key, -- sizeof(encoded_public_key)); -- if (!KYBER_parse_public_key(&pub, &encoded_public_key_cbs)) { -- return false; -- } -- uint8_t shared_secret[32]; -- KYBER_encap(ciphertext, shared_secret, sizeof(shared_secret), &pub); -- return true; -- })) { -- fprintf(stderr, "Failed to time KYBER_encap.\n"); -- return false; -- } -- -- results.Print("Kyber parse + encap"); -- -- return true; --} -- - static bool SpeedSpx(const std::string &selected) { - if (!selected.empty() && selected.find("spx") == std::string::npos) { - return true; -@@ -1661,7 +1722,8 @@ bool Speed(const std::vector &args) { - !SpeedScrypt(selected) || // - !SpeedRSAKeyGen(selected) || // - !SpeedHRSS(selected) || // -- !SpeedKyber(selected) || // -+ !SpeedKyber512(selected) || -+ !SpeedKyber768(selected) || - !SpeedSpx(selected) || // - !SpeedHashToCurve(selected) || // - !SpeedTrustToken("TrustToken-Exp1-Batch1", TRUST_TOKEN_experiment_v1(), 1, + if (args_map.count("-root-certs") != 0) { + if (!SSL_CTX_load_verify_locations( + ctx.get(), args_map["-root-certs"].c_str(), nullptr)) { -- -2.50.1 (Apple Git-155) +2.40.0 diff --git a/boring-sys/patches/rpk.patch b/boring-sys/patches/rpk.patch index edf977088..512566e94 100644 --- a/boring-sys/patches/rpk.patch +++ b/boring-sys/patches/rpk.patch @@ -1,120 +1,146 @@ +From 9725dabfc86f57607e60e48e09e10615a05bb053 Mon Sep 17 00:00:00 2001 +From: Anthony Ramine +Date: Wed, 17 Dec 2025 13:27:50 +0100 +Subject: [PATCH] Implement support for raw public keys as server certificates + (RFC 7250) + +--- + crypto/err/ssl.errordata | 1 + + include/openssl/ssl.h | 65 +++++++++++++ + include/openssl/tls1.h | 3 + + ssl/extensions.cc | 122 +++++++++++++++++++++++ + ssl/internal.h | 18 ++++ + ssl/ssl_cert.cc | 8 ++ + ssl/ssl_credential.cc | 48 +++++++++ + ssl/ssl_lib.cc | 52 +++++++++- + ssl/ssl_test.cc | 67 +++++++++++++ + ssl/test/bssl_shim.cc | 3 +- + ssl/test/runner/certificate_tests.go | 134 +++++++++++++++++++++++++- + ssl/test/runner/common.go | 19 ++++ + ssl/test/runner/handshake_client.go | 71 +++++++++++++- + ssl/test/runner/handshake_messages.go | 33 ++++++- + ssl/test/runner/handshake_server.go | 53 +++++++--- + ssl/test/runner/runner.go | 3 +- + ssl/test/test_config.cc | 92 +++++++++++++++--- + ssl/test/test_config.h | 2 + + ssl/tls13_both.cc | 98 +++++++++++++------ + ssl/tls13_server.cc | 34 ++++++- + 20 files changed, 861 insertions(+), 65 deletions(-) + +diff --git a/crypto/err/ssl.errordata b/crypto/err/ssl.errordata +index 01c4ca616..c9f4994d9 100644 +--- a/crypto/err/ssl.errordata ++++ b/crypto/err/ssl.errordata +@@ -95,6 +95,7 @@ SSL,159,INVALID_MESSAGE + SSL,320,INVALID_OUTER_EXTENSION + SSL,251,INVALID_OUTER_RECORD_TYPE + SSL,269,INVALID_SCT_LIST ++SSL,331,INVALID_SERVER_CERTIFICATE_TYPE_LIST + SSL,295,INVALID_SIGNATURE_ALGORITHM + SSL,324,INVALID_SPAKE2PLUSV1_VALUE + SSL,160,INVALID_SSL_SESSION diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h -index 003e0a5f7..b8f8d49c8 100644 +index ff68ba69e..5a1cf42ca 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h -@@ -138,6 +138,25 @@ - * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR - * OTHERWISE. - */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #ifndef OPENSSL_HEADER_SSL_H - #define OPENSSL_HEADER_SSL_H -@@ -1138,6 +1157,16 @@ OPENSSL_EXPORT int SSL_CTX_set_chain_and_key( - SSL_CTX *ctx, CRYPTO_BUFFER *const *certs, size_t num_certs, - EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method); - -+// SSL_CTX_set_nullchain_and_key sets the private key for a -+// TLS client or server. Reference to the given |EVP_PKEY| -+// object is added as needed. Exactly one of |privkey| or |privkey_method| -+// may be non-NULL. Returns one on success and zero on error. -+// Note the lack of a corresponding public-key certificate. -+// See SSL_CTX_set_server_raw_public_key_certificate. -+OPENSSL_EXPORT int SSL_CTX_set_nullchain_and_key( -+ SSL_CTX *ctx, -+ EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method); -+ - // SSL_set_chain_and_key sets the certificate chain and private key for a TLS - // client or server. References to the given |CRYPTO_BUFFER| and |EVP_PKEY| - // objects are added as needed. Exactly one of |privkey| or |privkey_method| -@@ -1146,6 +1175,16 @@ OPENSSL_EXPORT int SSL_set_chain_and_key( - SSL *ssl, CRYPTO_BUFFER *const *certs, size_t num_certs, EVP_PKEY *privkey, - const SSL_PRIVATE_KEY_METHOD *privkey_method); - -+// SSL_set_nullchain_and_key sets the private key for a TLS -+// client or server. Reference to the given |EVP_PKEY| -+// object is added as needed. Exactly one of |privkey| or |privkey_method| -+// may be non-NULL. Returns one on success and zero on error. -+// Note the lack of a corresponding public-key certificate. -+// See SSL_set_server_raw_public_key_certificate. -+OPENSSL_EXPORT int SSL_set_nullchain_and_key( -+ SSL *ssl, EVP_PKEY *privkey, -+ const SSL_PRIVATE_KEY_METHOD *privkey_method); -+ - // SSL_CTX_get0_chain returns the list of |CRYPTO_BUFFER|s that were set by - // |SSL_CTX_set_chain_and_key|. Reference counts are not incremented by this - // call. The return value may be |NULL| if no chain has been set. -@@ -3041,6 +3080,21 @@ OPENSSL_EXPORT int SSL_has_application_settings(const SSL *ssl); - OPENSSL_EXPORT void SSL_set_alps_use_new_codepoint(SSL *ssl, int use_new); +@@ -1781,6 +1781,10 @@ OPENSSL_EXPORT STACK_OF(X509) *SSL_get_peer_full_cert_chain(const SSL *ssl); + OPENSSL_EXPORT const STACK_OF(CRYPTO_BUFFER) *SSL_get0_peer_certificates( + const SSL *ssl); ++// SSL_get0_peer_pubkey returns the peer's public key during a handshake, or ++// NULL if unavailable. The caller does not take ownership of the result. ++OPENSSL_EXPORT const EVP_PKEY *SSL_get0_peer_pubkey(const SSL *ssl); ++ + // SSL_get0_signed_cert_timestamp_list sets |*out| and |*out_len| to point to + // |*out_len| bytes of SCT information from the server. This is only valid if + // |ssl| is a client. The SCT information is a SignedCertificateTimestampList +@@ -3406,6 +3410,49 @@ OPENSSL_EXPORT int SSL_has_application_settings(const SSL *ssl); + // codepoint. By default, the old codepoint is used. + OPENSSL_EXPORT void SSL_set_alps_use_new_codepoint(SSL *ssl, int use_new); -+// Server Certificate Type. ++// Server Certificate Type (RFC 7250). ++// ++// The Server Certificate Type extension (RFC 7301) allows negotiating ++// different server certificate types. This is used, for example, to receive ++// a raw public key instead of a full-fedged X.509 certificate from a server. + -+#define TLSEXT_CERTIFICATETYPE_X509 0 -+#define TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY 2 ++// SSL_CTX_set_server_certificate_types sets the server certificate type list ++// on |ctx| to |types|. This is the list of certificate types that the client ++// is willing to receive from the server. |types| must be an array ++// of |TLS_CERTIFICATE_TYPE_*| values. Configuring a non-empty array enables ++// the server_certificate_type extension on a client. ++OPENSSL_EXPORT int SSL_CTX_set_server_certificate_types(SSL_CTX *ctx, ++ const uint8_t *types, ++ size_t types_len); + -+OPENSSL_EXPORT int SSL_CTX_set_server_raw_public_key_certificate( -+ SSL_CTX *ctx, const uint8_t *raw_public_key, unsigned raw_public_key_len); ++// SSL_CTX_get0_server_certificate_types returns the server certificate type ++// list configured on |ctx|. ++OPENSSL_EXPORT void SSL_CTX_get0_server_certificate_types(const SSL_CTX *ctx, ++ const uint8_t **types, ++ size_t *types_len); + -+OPENSSL_EXPORT int SSL_CTX_has_server_raw_public_key_certificate(SSL_CTX *ctx); ++// SSL_set_server_certificate_types sets the server certificate type list ++// on |ssl| to |types|. This is the list of certificate types that the client ++// is willing to receive from the server. |types| must be an array ++// of |TLS_CERTIFICATE_TYPE_*| values. Configuring a non-empty array enables ++// the server_certificate_type extension on a client. ++OPENSSL_EXPORT int SSL_set_server_certificate_types(SSL *ssl, ++ const uint8_t *types, ++ size_t types_len); + -+OPENSSL_EXPORT int SSL_set_server_raw_public_key_certificate( -+ SSL *ssl, const uint8_t *raw_public_key, unsigned raw_public_key_len); ++// SSL_get0_server_certificate_types returns the server certificate type list ++// configured on |ssl|. ++OPENSSL_EXPORT void SSL_get0_server_certificate_types(const SSL *ssl, ++ const uint8_t **types, ++ size_t *types_len); + -+OPENSSL_EXPORT int SSL_has_server_raw_public_key_certificate(SSL *ssl); ++// SSL_get_server_certificate_type_selected gets the selected server ++// certificate type from |ssl|. ++OPENSSL_EXPORT uint8_t SSL_get_server_certificate_type_selected(const SSL *ssl); + ++#define TLS_CERTIFICATE_TYPE_X509 0 ++#define TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY 2 ++ + // Certificate compression. // - // Certificates in TLS 1.3 can be compressed (RFC 8879). BoringSSL supports this +@@ -3759,6 +3806,23 @@ OPENSSL_EXPORT SSL_CREDENTIAL *SSL_CREDENTIAL_new_delegated(void); + OPENSSL_EXPORT int SSL_CREDENTIAL_set1_delegated_credential( + SSL_CREDENTIAL *cred, CRYPTO_BUFFER *dc); + ++// Raw Public Key Credentials ++ ++// SSL_CREDENTIAL_new_raw_public_key returns a new, empty Raw Public Key ++// credential, or NULL on error. Callers should release the result with ++// |SSL_CREDENTIAL_free| when done. ++// ++// Callers should configure a raw public key and a private key on the ++// credential, then add it with |SSL_CTX_add1_credential|. ++OPENSSL_EXPORT SSL_CREDENTIAL *SSL_CREDENTIAL_new_raw_public_key(void); ++ ++// SSL_CREDENTIAL_set1_spki |cred|'s raw public key from |spki|. ++// If |spki| is NULL, the public key is extracted from |cred|'s private key. ++// It returns one on success and zero on error, including if |spki| is ++// malformed or if it is NULL and |cred| has no private key. |spki| should ++// be a SubjectPublicKeyInfo structure, as described in RFC 5280. ++int SSL_CREDENTIAL_set1_spki(SSL_CREDENTIAL *cred, ++ CRYPTO_BUFFER *spki); + + // Password Authenticated Key Exchange (PAKE). + // +@@ -6569,6 +6633,7 @@ BSSL_NAMESPACE_END + #define SSL_R_INVALID_TRUST_ANCHOR_LIST 328 + #define SSL_R_INVALID_CERTIFICATE_PROPERTY_LIST 329 + #define SSL_R_DUPLICATE_GROUP 330 ++#define SSL_R_INVALID_SERVER_CERTIFICATE_TYPE_LIST 331 + #define SSL_R_SSLV3_ALERT_CLOSE_NOTIFY 1000 + #define SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE 1010 + #define SSL_R_SSLV3_ALERT_BAD_RECORD_MAC 1020 diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h -index c1207a3b7..ac6ed222a 100644 +index ea55e2a07..f55c0d441 100644 --- a/include/openssl/tls1.h +++ b/include/openssl/tls1.h -@@ -146,6 +146,25 @@ - * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR - * OTHERWISE. - */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #ifndef OPENSSL_HEADER_TLS1_H - #define OPENSSL_HEADER_TLS1_H -@@ -197,6 +216,9 @@ extern "C" { +@@ -64,6 +64,9 @@ extern "C" { // ExtensionType value from RFC 7301 #define TLSEXT_TYPE_application_layer_protocol_negotiation 16 @@ -125,668 +151,1317 @@ index c1207a3b7..ac6ed222a 100644 #define TLSEXT_TYPE_padding 21 diff --git a/ssl/extensions.cc b/ssl/extensions.cc -index b13400097..8694712fd 100644 +index c5f90688c..eb5ef58e8 100644 --- a/ssl/extensions.cc +++ b/ssl/extensions.cc -@@ -105,6 +105,25 @@ - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #include - -@@ -3108,6 +3127,146 @@ bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert, +@@ -3523,6 +3523,121 @@ bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert, return true; } +// Server Certificate Type ++// ++// https://datatracker.ietf.org/doc/html/rfc7250#section-3 + -+static bool ext_server_certificate_type_add_clienthello(const SSL_HANDSHAKE *hs, -+ CBB *out, -+ CBB *out_compressible, -+ ssl_client_hello_type_t type) { -+ -+ if (hs->max_version <= TLS1_2_VERSION) { -+ return true; -+ } -+ -+ if (hs->config->server_certificate_type_list.empty()) { -+ return true; -+ } -+ -+ CBB contents, server_certificate_types; -+ if (!CBB_add_u16(out, TLSEXT_TYPE_server_certificate_type) || -+ !CBB_add_u16_length_prefixed(out, &contents) || -+ !CBB_add_u8_length_prefixed(&contents, &server_certificate_types) || -+ !CBB_add_bytes(&server_certificate_types, -+ hs->config->server_certificate_type_list.data(), -+ hs->config->server_certificate_type_list.size()) || -+ !CBB_flush(out)) { ++bool ssl_is_valid_certificate_type_list(Span in) { ++ CBS type_list = in; ++ if (CBS_len(&type_list) == 0) { + return false; + } -+ ++ uint8_t type; ++ while (CBS_get_u8(&type_list, &type)) { ++ switch (type) { ++ case TLS_CERTIFICATE_TYPE_X509: ++ case TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY: ++ break; ++ default: ++ return false; ++ } ++ } + return true; +} + -+static bool ssl_is_certificate_type_allowed(CBS *certificate_type_list, -+ uint8_t certificate_type) -+{ -+ uint8_t supported_certificate_type; -+ while (CBS_len(certificate_type_list) > 0) { -+ if (!CBS_get_u8(certificate_type_list, -+ &supported_certificate_type)) { -+ break; -+ } -+ -+ if (supported_certificate_type != certificate_type) { -+ continue; -+ } -+ ++static bool ext_server_certificate_type_add_clienthello( ++ const SSL_HANDSHAKE *hs, CBB *out, CBB *out_compressible, ++ ssl_client_hello_type_t type) { ++ if (hs->max_version < TLS1_3_VERSION || ++ hs->config->server_certificate_type_list.empty()) { + return true; + } + -+ return false; ++ CBB contents, type_list; ++ return CBB_add_u16(out, TLSEXT_TYPE_server_certificate_type) && ++ CBB_add_u16_length_prefixed(out, &contents) && ++ CBB_add_u8_length_prefixed(&contents, &type_list) && ++ CBB_add_bytes(&type_list, ++ hs->config->server_certificate_type_list.data(), ++ hs->config->server_certificate_type_list.size()) && ++ CBB_flush(out); +} + +static bool ext_server_certificate_type_parse_serverhello(SSL_HANDSHAKE *hs, + uint8_t *out_alert, -+ CBS *content) -+{ -+ if (hs->max_version <= TLS1_2_VERSION || -+ hs->config->server_certificate_type_list.empty()) { ++ CBS *contents) { ++ if (hs->ssl->s3->session_reused) { + return true; + } + -+ // Strict -+ if (!content) { -+ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); -+ *out_alert = SSL_AD_ILLEGAL_PARAMETER; -+ return false; ++ uint8_t cert_type = TLS_CERTIFICATE_TYPE_X509; ++ if (contents != nullptr) { ++ assert(!hs->config->server_certificate_type_list.empty()); ++ if (!CBS_get_u8(contents, &cert_type) || CBS_len(contents) != 0) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); ++ goto err; ++ } + } + -+ CBS certificate_type_list = -+ MakeConstSpan(hs->config->server_certificate_type_list); -+ -+ uint8_t certificate_type; -+ if (CBS_get_u8(content, &certificate_type) && -+ ssl_is_certificate_type_allowed(&certificate_type_list, -+ certificate_type)) { -+ hs->server_certificate_type = certificate_type; -+ hs->server_certificate_type_negotiated = 1; -+ return true; ++ if (!hs->config->server_certificate_type_list.empty() && ++ std::none_of( ++ hs->config->server_certificate_type_list.begin(), ++ hs->config->server_certificate_type_list.end(), ++ [cert_type](const auto &type) { return type == cert_type; })) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); ++ goto err; + } + -+ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); ++ hs->server_certificate_type = cert_type; ++ return true; ++ ++err: + *out_alert = SSL_AD_ILLEGAL_PARAMETER; + return false; +} + +static bool ext_server_certificate_type_parse_clienthello(SSL_HANDSHAKE *hs, + uint8_t *out_alert, -+ CBS *content) -+{ -+ if (!content) { ++ CBS *contents) { ++ if (contents == nullptr || ssl_protocol_version(hs->ssl) < TLS1_3_VERSION) { + return true; + } + -+ if (hs->max_version <= TLS1_2_VERSION || -+ hs->config->server_certificate_type_list.empty()) { -+ return true; -+ } -+ -+ CBS certificate_type_list = -+ MakeConstSpan(hs->config->server_certificate_type_list); -+ + CBS type_list; -+ if (!CBS_get_u8_length_prefixed(content, &type_list)) { -+ type_list.len = 0; ++ if (!CBS_get_u8_length_prefixed(contents, &type_list) || ++ CBS_len(contents) != 0 || CBS_len(&type_list) == 0) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); ++ *out_alert = SSL_AD_ILLEGAL_PARAMETER; ++ return false; + } + -+ uint8_t type; -+ while(CBS_len(&type_list) > 0) { -+ if (!CBS_get_u8(&type_list, &type)) { -+ break; -+ } -+ -+ if (!ssl_is_certificate_type_allowed(&certificate_type_list, type)) { -+ continue; -+ } -+ -+ hs->server_certificate_type = type; -+ hs->server_certificate_type_negotiated = 1; -+ return true; ++ if (!hs->server_certificate_type_list.CopyFrom(type_list)) { ++ *out_alert = SSL_AD_INTERNAL_ERROR; ++ return false; + } + -+ *out_alert = SSL_AD_ILLEGAL_PARAMETER; -+ return false; ++ return true; +} + +static bool ext_server_certificate_type_add_serverhello(SSL_HANDSHAKE *hs, -+ CBB *out) -+{ -+ if (!hs->server_certificate_type_negotiated) { ++ CBB *out) { ++ if (hs->ssl->s3->session_reused || hs->credential == nullptr || ++ hs->credential->type == SSLCredentialType::kX509) { + return true; + } + -+ CBB contents; -+ if (!CBB_add_u16(out, TLSEXT_TYPE_server_certificate_type) || -+ !CBB_add_u16_length_prefixed(out, &contents) || -+ !CBB_add_u8(&contents, hs->server_certificate_type) || -+ !CBB_flush(out)) { -+ return false; ++ if (hs->credential->type != SSLCredentialType::kRawPublicKey) { ++ OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); ++ return false; + } + -+ return true; ++ CBB cert_types; ++ return CBB_add_u16(out, TLSEXT_TYPE_server_certificate_type) && ++ CBB_add_u16_length_prefixed(out, &cert_types) && ++ CBB_add_u8(&cert_types, TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY) && ++ CBB_flush(out); +} + // kExtensions contains all the supported extensions. static const struct tls_extension kExtensions[] = { - { -@@ -3289,6 +3448,13 @@ static const struct tls_extension kExtensions[] = { - ignore_parse_clienthello, - ext_alps_add_serverhello_old, - }, -+ { -+ TLSEXT_TYPE_server_certificate_type, -+ ext_server_certificate_type_add_clienthello, -+ ext_server_certificate_type_parse_serverhello, -+ ext_server_certificate_type_parse_clienthello, -+ ext_server_certificate_type_add_serverhello, -+ }, + { +@@ -3727,6 +3842,13 @@ static const struct tls_extension kExtensions[] = { + ext_trust_anchors_parse_clienthello, + ext_trust_anchors_add_serverhello, + }, ++ { ++ TLSEXT_TYPE_server_certificate_type, ++ ext_server_certificate_type_add_clienthello, ++ ext_server_certificate_type_parse_serverhello, ++ ext_server_certificate_type_parse_clienthello, ++ ext_server_certificate_type_add_serverhello, ++ }, }; #define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension)) -diff --git a/ssl/handshake.cc b/ssl/handshake.cc -index 8d5a23872..c8ca629e8 100644 ---- a/ssl/handshake.cc -+++ b/ssl/handshake.cc -@@ -109,6 +109,25 @@ - * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. - * ECC cipher suite support in OpenSSL originally developed by - * SUN MICROSYSTEMS, INC., and contributed to the OpenSSL project. */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #include - -@@ -148,6 +167,7 @@ SSL_HANDSHAKE::SSL_HANDSHAKE(SSL *ssl_arg) - handback(false), - hints_requested(false), - cert_compression_negotiated(false), -+ server_certificate_type_negotiated(false), - apply_jdk11_workaround(false), - can_release_private_key(false), - channel_id_negotiated(false) { -@@ -365,7 +385,21 @@ enum ssl_verify_result_t ssl_verify_peer_cert(SSL_HANDSHAKE *hs) { - - uint8_t alert = SSL_AD_CERTIFICATE_UNKNOWN; - enum ssl_verify_result_t ret; -- if (hs->config->custom_verify_callback != nullptr) { -+ if (hs->server_certificate_type_negotiated && -+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY) { -+ ret = ssl_verify_invalid; -+ EVP_PKEY *peer_pubkey = hs->peer_pubkey.get(); -+ CBS spki = MakeConstSpan(ssl->config->server_raw_public_key_certificate); -+ EVP_PKEY *pubkey = EVP_parse_public_key(&spki); -+ if (!pubkey) { -+ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); -+ alert = SSL_AD_INTERNAL_ERROR; -+ } else if (EVP_PKEY_cmp(peer_pubkey, pubkey) == 1 /* Equal */) { -+ ret = ssl_verify_ok; -+ } else { -+ alert = SSL_AD_BAD_CERTIFICATE; -+ } -+ } else if (hs->config->custom_verify_callback != nullptr) { - ret = hs->config->custom_verify_callback(ssl, &alert); - switch (ret) { - case ssl_verify_ok: diff --git a/ssl/internal.h b/ssl/internal.h -index c9facb699..d7363e729 100644 +index a69505b47..867c62bd6 100644 --- a/ssl/internal.h +++ b/ssl/internal.h -@@ -138,6 +138,25 @@ - * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR - * OTHERWISE. - */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #ifndef OPENSSL_HEADER_SSL_INTERNAL_H - #define OPENSSL_HEADER_SSL_INTERNAL_H -@@ -1311,6 +1330,8 @@ int ssl_write_buffer_flush(SSL *ssl); - // configured. - bool ssl_has_certificate(const SSL_HANDSHAKE *hs); - -+bool ssl_has_raw_public_key_certificate(const SSL_HANDSHAKE *hs); -+ - // ssl_parse_cert_chain parses a certificate list from |cbs| in the format used - // by a TLS Certificate message. On success, it advances |cbs| and returns - // true. Otherwise, it returns false and sets |*out_alert| to an alert to send -@@ -1912,6 +1933,8 @@ struct SSL_HANDSHAKE { - // |cert_compression_negotiated| is true. - uint16_t cert_compression_alg_id; - -+ uint8_t server_certificate_type; -+ - // ech_hpke_ctx is the HPKE context used in ECH. On the server, it is - // initialized if |ech_status| is |ssl_ech_accepted|. On the client, it is - // initialized if |selected_ech_config| is not nullptr. -@@ -2062,6 +2085,8 @@ struct SSL_HANDSHAKE { - // cert_compression_negotiated is true iff |cert_compression_alg_id| is valid. - bool cert_compression_negotiated : 1; - -+ bool server_certificate_type_negotiated : 1; -+ - // apply_jdk11_workaround is true if the peer is probably a JDK 11 client - // which implemented TLS 1.3 incorrectly. - bool apply_jdk11_workaround : 1; -@@ -3074,6 +3099,9 @@ struct SSL_CONFIG { - // along with their corresponding ALPS values. - GrowableArray alps_configs; +@@ -1408,6 +1408,7 @@ enum class SSLCredentialType { + kDelegated, + kSPAKE2PlusV1Client, + kSPAKE2PlusV1Server, ++ kRawPublicKey, + }; + + BSSL_NAMESPACE_END +@@ -2062,6 +2063,14 @@ struct SSL_HANDSHAKE { + // pake_verifier is the PAKE context for a server. + UniquePtr pake_verifier; ++ ++ // server_certificate_type_list indicates the types of certificates ++ // the client is able to process. + Array server_certificate_type_list; -+ Array server_raw_public_key_certificate; + - // Contains the QUIC transport params that this endpoint will send. - Array quic_transport_params; ++ // server_certificate_type indicates the type of certificates the server ++ // selected to send as the certificate payload. ++ uint8_t server_certificate_type = TLS_CERTIFICATE_TYPE_X509; + }; -@@ -3666,6 +3694,9 @@ struct ssl_ctx_st { - // format. - bssl::Array alpn_client_proto_list; + // kMaxTickets is the maximum number of tickets to send immediately after the +@@ -2256,6 +2265,10 @@ bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert, + // identifiers list. + bool ssl_is_valid_trust_anchor_list(Span in); -+ bssl::Array server_certificate_type_list; -+ bssl::Array server_raw_public_key_certificate; ++// ssl_is_valid_certificate_type_list returns whether |in| is a valid ++// certificate type list. ++bool ssl_is_valid_certificate_type_list(Span in); ++ + struct SSLExtension { + SSLExtension(uint16_t type_arg, bool allowed_arg = true) + : type(type_arg), allowed(allowed_arg), present(false) { +@@ -3339,6 +3352,8 @@ struct SSL_CONFIG { + // negotiating a TLS 1.3 connection. + enum ssl_compliance_policy_t compliance_policy = ssl_compliance_policy_none; + ++ Array server_certificate_type_list; + - // SRTP profiles we are willing to do from RFC 5764 - bssl::UniquePtr srtp_profiles; + // verify_mode is a bitmask of |SSL_VERIFY_*| values. + uint8_t verify_mode = SSL_VERIFY_NONE; +@@ -3988,6 +4003,9 @@ struct ssl_ctx_st : public bssl::RefCounted { + // accepted from the peer in decreasing order of preference. + bssl::Array verify_sigalgs; + ++ // For a client, this contains the list of supported server certificate types. ++ bssl::Array server_certificate_type_list; ++ + // retain_only_sha256_of_client_certs is true if we should compute the SHA256 + // hash of the peer's certificate and then discard it to save memory and + // session space. Only effective on the server side. diff --git a/ssl/ssl_cert.cc b/ssl/ssl_cert.cc -index aa46a8bb6..d90840fce 100644 +index 72218aeea..a1a8f328b 100644 --- a/ssl/ssl_cert.cc +++ b/ssl/ssl_cert.cc -@@ -111,6 +111,25 @@ - * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. - * ECC cipher suite support in OpenSSL originally developed by - * SUN MICROSYSTEMS, INC., and contributed to the OpenSSL project. */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ +@@ -587,6 +587,14 @@ const STACK_OF(CRYPTO_BUFFER) *SSL_get0_peer_certificates(const SSL *ssl) { + return session->certs.get(); + } - #include ++const EVP_PKEY *SSL_get0_peer_pubkey(const SSL *ssl) { ++ if (ssl->s3->hs == nullptr) { ++ return nullptr; ++ } ++ ++ return ssl->s3->hs->peer_pubkey.get(); ++} ++ + const STACK_OF(CRYPTO_BUFFER) *SSL_get0_server_requested_CAs(const SSL *ssl) { + if (ssl->s3->hs == nullptr) { + return nullptr; +diff --git a/ssl/ssl_credential.cc b/ssl/ssl_credential.cc +index bbbd76701..16a069c28 100644 +--- a/ssl/ssl_credential.cc ++++ b/ssl/ssl_credential.cc +@@ -164,6 +164,7 @@ bool ssl_credential_st::UsesX509() const { + return true; + case SSLCredentialType::kSPAKE2PlusV1Client: + case SSLCredentialType::kSPAKE2PlusV1Server: ++ case SSLCredentialType::kRawPublicKey: + return false; + } + abort(); +@@ -173,6 +174,7 @@ bool ssl_credential_st::UsesPrivateKey() const { + switch (type) { + case SSLCredentialType::kX509: + case SSLCredentialType::kDelegated: ++ case SSLCredentialType::kRawPublicKey: + return true; + case SSLCredentialType::kSPAKE2PlusV1Client: + case SSLCredentialType::kSPAKE2PlusV1Server: +@@ -335,6 +337,10 @@ SSL_CREDENTIAL *SSL_CREDENTIAL_new_delegated(void) { + return New(SSLCredentialType::kDelegated); + } -@@ -302,6 +321,25 @@ static int cert_set_chain_and_key( ++SSL_CREDENTIAL *SSL_CREDENTIAL_new_raw_public_key(void) { ++ return New(SSLCredentialType::kRawPublicKey); ++} ++ + void SSL_CREDENTIAL_up_ref(SSL_CREDENTIAL *cred) { cred->UpRefInternal(); } + + void SSL_CREDENTIAL_free(SSL_CREDENTIAL *cred) { +@@ -448,6 +454,44 @@ int SSL_CREDENTIAL_set1_delegated_credential(SSL_CREDENTIAL *cred, return 1; } -+static int cert_set_key( -+ CERT *cert, -+ EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method) { -+ if (privkey == NULL && privkey_method == NULL) { -+ OPENSSL_PUT_ERROR(SSL, ERR_R_PASSED_NULL_PARAMETER); ++int SSL_CREDENTIAL_set1_spki(SSL_CREDENTIAL *cred, CRYPTO_BUFFER *spki) { ++ if (cred->type != SSLCredentialType::kRawPublicKey) { ++ OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); + return 0; + } + -+ if (privkey != NULL && privkey_method != NULL) { -+ OPENSSL_PUT_ERROR(SSL, SSL_R_CANNOT_HAVE_BOTH_PRIVKEY_AND_METHOD); ++ ScopedCBB cbb; ++ CBS cbs; ++ if (spki == nullptr) { ++ if (cred->privkey == nullptr) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_NO_PRIVATE_KEY_ASSIGNED); ++ return 0; ++ } ++ ++ if (!CBB_init(cbb.get(), /*initial_capacity=*/512) || ++ !EVP_marshal_public_key(cbb.get(), cred->privkey.get())) { ++ return 0; ++ } ++ CBS_init(&cbs, CBB_data(cbb.get()), CBB_len(cbb.get())); ++ } else { ++ CRYPTO_BUFFER_init_CBS(spki, &cbs); ++ } ++ ++ bssl::UniquePtr pubkey = ++ ssl_parse_peer_subject_public_key_info(cbs); ++ if (pubkey == nullptr) { + return 0; + } + -+ cert->privatekey = UpRef(privkey); -+ cert->key_method = privkey_method; ++ if (cred->privkey != nullptr && ++ !ssl_compare_public_and_private_key(pubkey.get(), cred->privkey.get())) { ++ return 0; ++ } + ++ cred->pubkey = std::move(pubkey); + return 1; +} + - bool ssl_set_cert(CERT *cert, UniquePtr buffer) { - switch (check_leaf_cert_and_privkey(buffer.get(), cert->privatekey.get())) { - case leaf_cert_and_privkey_error: -@@ -343,6 +381,12 @@ bool ssl_has_certificate(const SSL_HANDSHAKE *hs) { - ssl_has_private_key(hs); + int SSL_CREDENTIAL_set1_ocsp_response(SSL_CREDENTIAL *cred, + CRYPTO_BUFFER *ocsp) { + if (!cred->UsesX509()) { +@@ -611,6 +655,10 @@ void *SSL_CREDENTIAL_get_ex_data(const SSL_CREDENTIAL *cred, int idx) { } -+bool ssl_has_raw_public_key_certificate(const SSL_HANDSHAKE *hs) { -+ return hs->server_certificate_type_negotiated && -+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY && -+ ssl_has_private_key(hs); -+} + void SSL_CREDENTIAL_set_must_match_issuer(SSL_CREDENTIAL *cred, int match) { ++ if (cred->type == SSLCredentialType::kRawPublicKey) { ++ return; ++ } + - bool ssl_parse_cert_chain(uint8_t *out_alert, - UniquePtr *out_chain, - UniquePtr *out_pubkey, -@@ -721,11 +765,20 @@ bool ssl_check_leaf_certificate(SSL_HANDSHAKE *hs, EVP_PKEY *pkey, - - bool ssl_on_certificate_selected(SSL_HANDSHAKE *hs) { - SSL *const ssl = hs->ssl; -- if (!ssl_has_certificate(hs)) { -+ if (!ssl_has_certificate(hs) && -+ !ssl_has_raw_public_key_certificate(hs)) { - // Nothing to do. - return true; + cred->must_match_issuer = !!match; + } + +diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc +index f64b103fb..d87c3f3c4 100644 +--- a/ssl/ssl_lib.cc ++++ b/ssl/ssl_lib.cc +@@ -534,7 +534,9 @@ SSL *SSL_new(SSL_CTX *ctx) { + if (!ssl->config->supported_group_list.CopyFrom(ctx->supported_group_list) || + !ssl->config->alpn_client_proto_list.CopyFrom( + ctx->alpn_client_proto_list) || +- !ssl->config->verify_sigalgs.CopyFrom(ctx->verify_sigalgs)) { ++ !ssl->config->verify_sigalgs.CopyFrom(ctx->verify_sigalgs) || ++ !ssl->config->server_certificate_type_list.CopyFrom( ++ ctx->server_certificate_type_list)) { + return nullptr; } -+ if (ssl_has_raw_public_key_certificate(hs)) { -+ CBS spki = MakeConstSpan( -+ ssl->config->server_raw_public_key_certificate.data(), -+ ssl->config->server_raw_public_key_certificate.size()); -+ hs->local_pubkey = UniquePtr(EVP_parse_public_key(&spki)); -+ return hs->local_pubkey != NULL; +@@ -3305,6 +3307,54 @@ int SSL_CTX_set_tlsext_status_arg(SSL_CTX *ctx, void *arg) { + return 1; + } + ++int SSL_CTX_set_server_certificate_types(SSL_CTX *ctx, const uint8_t *types, ++ size_t types_len) { ++ auto span = Span(types, types_len); ++ if (!span.empty() && !ssl_is_valid_certificate_type_list(span)) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SERVER_CERTIFICATE_TYPE_LIST); ++ return 0; + } ++ return ctx->server_certificate_type_list.CopyFrom(span); ++} + - if (!ssl->ctx->x509_method->ssl_auto_chain_if_needed(hs)) { - return false; - } -@@ -880,6 +933,15 @@ int SSL_set_chain_and_key(SSL *ssl, CRYPTO_BUFFER *const *certs, - privkey, privkey_method); ++void SSL_CTX_get0_server_certificate_types(const SSL_CTX *ctx, ++ const uint8_t **types, ++ size_t *types_len) { ++ *types = ctx->server_certificate_type_list.data(); ++ *types_len = ctx->server_certificate_type_list.size(); ++} ++ ++int SSL_set_server_certificate_types(SSL *ssl, const uint8_t *types, ++ size_t types_len) { ++ if (ssl->server || ssl->config == nullptr) { ++ return 0; ++ } ++ auto span = Span(types, types_len); ++ if (!span.empty() && !ssl_is_valid_certificate_type_list(span)) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SERVER_CERTIFICATE_TYPE_LIST); ++ return 0; ++ } ++ return ssl->config->server_certificate_type_list.CopyFrom(span); ++} ++ ++void SSL_get0_server_certificate_types(const SSL *ssl, const uint8_t **types, ++ size_t *types_len) { ++ if (ssl->server || ssl->config == nullptr) { ++ *types = nullptr; ++ *types_len = 0; ++ return; ++ } ++ *types = ssl->config->server_certificate_type_list.data(); ++ *types_len = ssl->config->server_certificate_type_list.size(); ++} ++ ++uint8_t SSL_get_server_certificate_type_selected(const SSL *ssl) { ++ if (ssl->s3->hs == nullptr) { ++ return TLS_CERTIFICATE_TYPE_X509; ++ } ++ return ssl->s3->hs->server_certificate_type; ++} ++ + uint16_t SSL_get_curve_id(const SSL *ssl) { return SSL_get_group_id(ssl); } + + const char *SSL_get_curve_name(uint16_t curve_id) { +diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc +index 779a2c37a..85aeb817f 100644 +--- a/ssl/ssl_test.cc ++++ b/ssl/ssl_test.cc +@@ -4398,6 +4398,73 @@ TEST_P(SSLVersionTest, DefaultTicketKeyRotation) { + new_session.get(), true /* reused */)); } -+int SSL_set_nullchain_and_key(SSL *ssl, -+ EVP_PKEY *privkey, -+ const SSL_PRIVATE_KEY_METHOD *privkey_method) { -+ if (!ssl->config) { -+ return 0; ++TEST_P(SSLVersionTest, RawPublicKeyCertificate) { ++ static const uint8_t kCertificateTypes[] = { ++ TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY, 18}; ++ ASSERT_TRUE(SSL_CTX_set_server_certificate_types(client_ctx_.get(), ++ kCertificateTypes, 0)); ++ ASSERT_FALSE(SSL_CTX_set_server_certificate_types(client_ctx_.get(), ++ kCertificateTypes, 2)); ++ ASSERT_TRUE(SSL_CTX_set_server_certificate_types(client_ctx_.get(), ++ kCertificateTypes, 1)); ++ ++ SSL_CTX_set_custom_verify( ++ client_ctx_.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, ++ [](SSL *ssl, uint8_t *out_alert) -> ssl_verify_result_t { ++ EXPECT_EQ(SSL_get_server_certificate_type_selected(ssl), ++ TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY); ++ ++ const EVP_PKEY *peer_pubkey = SSL_get0_peer_pubkey(ssl); ++ EXPECT_TRUE(peer_pubkey); ++ ++ if (!peer_pubkey) { ++ *out_alert = SSL_AD_CERTIFICATE_UNKNOWN; ++ return ssl_verify_invalid; ++ } ++ ++ SSL_CTX *ctx = SSL_get_SSL_CTX(ssl); ++ if (EVP_PKEY_cmp(reinterpret_cast SSL_CTX_get_app_data(ctx), ++ peer_pubkey) == 1) { ++ return ssl_verify_ok; ++ } ++ ++ *out_alert = SSL_AD_BAD_CERTIFICATE; ++ return ssl_verify_invalid; ++ }); ++ SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_CLIENT); ++ ++ // Server is not configured for raw public keys. ++ ASSERT_FALSE(Connect()); ++ ++ bssl::UniquePtr cred(SSL_CREDENTIAL_new_raw_public_key()); ++ bssl::UniquePtr key = GetECDSATestKey(); ++ ASSERT_TRUE(SSL_CREDENTIAL_set1_private_key(cred.get(), key.get())); ++ ASSERT_FALSE(SSL_CTX_add1_credential(server_ctx_.get(), cred.get())); ++ ASSERT_TRUE(SSL_CREDENTIAL_set1_spki(cred.get(), nullptr)); ++ ASSERT_TRUE(SSL_CTX_add1_credential(server_ctx_.get(), cred.get())); ++ ++ // Client is expecting |wrong_key|. ++ bssl::UniquePtr wrong_key = GetTestKey(); ++ ASSERT_TRUE(wrong_key); ++ SSL_CTX_set_app_data(client_ctx_.get(), wrong_key.get()); ++ ASSERT_FALSE(Connect()); ++ ++ if (!is_tls13()) { ++ return; + } -+ return cert_set_key(ssl->config->cert.get(), privkey, privkey_method); ++ ++ SSL_CTX_set_app_data(client_ctx_.get(), key.get()); ++ ASSERT_TRUE(Connect()); ++ ++ bssl::UniquePtr session = ++ CreateClientSession(client_ctx_.get(), server_ctx_.get()); ++ ASSERT_TRUE(session); ++ ++ TRACED_CALL(ExpectSessionReused(client_ctx_.get(), server_ctx_.get(), ++ session.get(), ++ true /* expect session reused */)); +} + - int SSL_CTX_set_chain_and_key(SSL_CTX *ctx, CRYPTO_BUFFER *const *certs, - size_t num_certs, EVP_PKEY *privkey, - const SSL_PRIVATE_KEY_METHOD *privkey_method) { -@@ -887,6 +949,12 @@ int SSL_CTX_set_chain_and_key(SSL_CTX *ctx, CRYPTO_BUFFER *const *certs, - privkey_method); + static int SwitchContext(SSL *ssl, int *out_alert, void *arg) { + SSL_CTX *ctx = reinterpret_cast(arg); + SSL_set_SSL_CTX(ssl, ctx); +diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc +index 0eba60d22..9e2fe8da9 100644 +--- a/ssl/test/bssl_shim.cc ++++ b/ssl/test/bssl_shim.cc +@@ -672,7 +672,8 @@ static bool CheckHandshakeProperties(SSL *ssl, bool is_resume, + return false; + } + } else if (!config->is_server || config->require_any_client_certificate) { +- if (SSL_get_peer_cert_chain(ssl) == nullptr) { ++ if (!config->raw_public_key_mode && ++ SSL_get_peer_cert_chain(ssl) == nullptr) { + fprintf(stderr, "Received no peer certificate but expected one.\n"); + return false; + } +diff --git a/ssl/test/runner/certificate_tests.go b/ssl/test/runner/certificate_tests.go +index 7f6c4b82d..0007f9048 100644 +--- a/ssl/test/runner/certificate_tests.go ++++ b/ssl/test/runner/certificate_tests.go +@@ -14,7 +14,11 @@ + + package runner + +-import "crypto/x509" ++import ( ++ "crypto/x509" ++ "encoding/base64" ++ "strconv" ++) + + func makeCertPoolFromRoots(creds ...*Credential) *x509.CertPool { + certPool := x509.NewCertPool() +@@ -327,6 +331,134 @@ func addCertificateTests() { + } } -+int SSL_CTX_set_nullchain_and_key(SSL_CTX *ctx, -+ EVP_PKEY *privkey, -+ const SSL_PRIVATE_KEY_METHOD *privkey_method) { -+ return cert_set_key(ctx->cert.get(), privkey, privkey_method); ++func addRawPublicKeyCertificateTests() { ++ const decodeError = ":DECODE_ERROR:" ++ const unknownCertType = ":UNKNOWN_CERTIFICATE_TYPE:" ++ var extValueTests = []struct { ++ serverCertificateTypes []uint8 ++ expectedError string ++ }{ ++ // Explicitly requesting X.509 should be fine. ++ {[]uint8{certificateTypeX509}, ""}, ++ // ... even when mixed with unknown types. ++ {[]uint8{80, certificateTypeX509, 81, 82}, ""}, ++ // ... even when mixed with a request for raw public keys. ++ {[]uint8{certificateTypeRawPublicKey, certificateTypeX509, 81, 82}, ""}, ++ {[]uint8{certificateTypeX509, certificateTypeRawPublicKey, 81, 82}, ""}, ++ // Requesting only unknown certificate types should cause an error. ++ {[]uint8{80, 81, 82}, unknownCertType}, ++ // ... as should requesting a raw public key when the server is configured ++ // for X.509. ++ {[]uint8{certificateTypeRawPublicKey}, unknownCertType}, ++ // Listing no types is an error. ++ {[]uint8{}, decodeError}, ++ } ++ ++ for i, test := range extValueTests { ++ testCases = append(testCases, testCase{ ++ testType: serverTest, ++ name: "RawPublicKey-Server-ExtValue-" + strconv.Itoa(i), ++ config: Config{ ++ MinVersion: VersionTLS13, ++ MaxVersion: VersionTLS13, ++ Bugs: ProtocolBugs{ ++ ServerCertificateTypes: test.serverCertificateTypes, ++ }, ++ }, ++ shouldFail: len(test.expectedError) != 0, ++ expectedError: test.expectedError, ++ }) ++ } ++ ++ // An X.509 client should be rejected by a raw-public-key server. ++ testCases = append(testCases, testCase{ ++ testType: serverTest, ++ name: "RawPublicKey-Server-TLS13X509Client", ++ config: Config{ ++ MinVersion: VersionTLS13, ++ MaxVersion: VersionTLS13, ++ }, ++ flags: []string{ ++ "-raw-public-key-mode", ++ }, ++ shouldFail: true, ++ expectedError: unknownCertType, ++ }) ++ ++ testCases = append(testCases, testCase{ ++ testType: serverTest, ++ name: "RawPublicKey-Server", ++ config: Config{ ++ MinVersion: VersionTLS13, ++ MaxVersion: VersionTLS13, ++ Credential: ecdsaP384Certificate.WithSignatureAlgorithms(signatureECDSAWithP384AndSHA384), ++ useServerRawPublicKeyCertificate: true, ++ }, ++ flags: []string{ ++ "-raw-public-key-mode", ++ }, ++ shimCertificate: &ecdsaP384Certificate, ++ }) ++ ++ leaf, _ := x509.ParseCertificate(ecdsaP384Certificate.Certificate[0]) ++ base64SPKI := base64.StdEncoding.EncodeToString(leaf.RawSubjectPublicKeyInfo) ++ wrongLeaf, _ := x509.ParseCertificate(ecdsaP256Certificate.Certificate[0]) ++ wrongBase64SPKI := base64.StdEncoding.EncodeToString(wrongLeaf.RawSubjectPublicKeyInfo) ++ ++ for _, ok := range []bool{false, true} { ++ expectedSPKI, suffix, expectedError := base64SPKI, "", "" ++ if !ok { ++ expectedSPKI = wrongBase64SPKI ++ suffix = "-Mismatch" ++ expectedError = ":CERTIFICATE_VERIFY_FAILED:" ++ } ++ ++ testCases = append(testCases, testCase{ ++ testType: clientTest, ++ name: "RawPublicKey-Client" + suffix, ++ config: Config{ ++ MinVersion: VersionTLS13, ++ MaxVersion: VersionTLS13, ++ Credential: ecdsaP384Certificate.WithSignatureAlgorithms(signatureECDSAWithP384AndSHA384), ++ useServerRawPublicKeyCertificate: true, ++ }, ++ flags: []string{ ++ "-raw-public-key-mode", ++ "-verify-peer", ++ "-use-custom-verify-callback", ++ "-expect-spki", expectedSPKI, ++ }, ++ shouldFail: !ok, ++ expectedError: expectedError, ++ }) ++ } ++ ++ // Read the server's raw public key in a CompressedCertificate message. ++ testCases = append(testCases, testCase{ ++ testType: clientTest, ++ name: "RawPublicKey-Client-With-Compression", ++ config: Config{ ++ MinVersion: VersionTLS13, ++ MaxVersion: VersionTLS13, ++ Credential: ecdsaP384Certificate.WithSignatureAlgorithms(signatureECDSAWithP384AndSHA384), ++ useServerRawPublicKeyCertificate: true, ++ CertCompressionAlgs: map[uint16]CertCompressionAlg{ ++ expandingCompressionAlgID: expandingCompression, ++ }, ++ Bugs: ProtocolBugs{ ++ ExpectedCompressedCert: expandingCompressionAlgID, ++ }, ++ }, ++ flags: []string{ ++ "-raw-public-key-mode", ++ "-verify-peer", ++ "-use-custom-verify-callback", ++ "-expect-spki", base64SPKI, ++ "-install-cert-compression-algs", ++ }, ++ }) +} + - const STACK_OF(CRYPTO_BUFFER)* SSL_CTX_get0_chain(const SSL_CTX *ctx) { - return ctx->cert->chain.get(); + func addRetainOnlySHA256ClientCertTests() { + for _, ver := range tlsVersions { + // Test that enabling +diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go +index 7dbde72c9..f1fb623ea 100644 +--- a/ssl/test/runner/common.go ++++ b/ssl/test/runner/common.go +@@ -140,6 +140,12 @@ func messageTypeToString(typ uint8) string { + return fmt.Sprintf("unknown(%d)", typ) } -diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc -index 58b68e675..384debbd3 100644 ---- a/ssl/ssl_lib.cc -+++ b/ssl/ssl_lib.cc -@@ -137,6 +137,25 @@ - * SPECIFICALLY DISCLAIMS ANY LIABILITY FOR CLAIMS BROUGHT BY YOU OR ANY - * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR - * OTHERWISE. */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - #include ++// TLS certificate type extension values. ++const ( ++ certificateTypeX509 uint8 = 0 ++ certificateTypeRawPublicKey uint8 = 2 ++) ++ + // TLS compression types. + const ( + compressionNone uint8 = 0 +@@ -155,6 +161,7 @@ const ( + extensionUseSRTP uint16 = 14 + extensionALPN uint16 = 16 + extensionSignedCertificateTimestamp uint16 = 18 ++ extensionServerCertificateType uint16 = 20 // RFC7250 + extensionPadding uint16 = 21 + extensionExtendedMasterSecret uint16 = 23 + extensionCompressedCertAlgs uint16 = 27 +@@ -505,6 +512,14 @@ type Config struct { + // If Time is nil, TLS uses time.Now. + Time func() time.Time -@@ -687,6 +706,11 @@ SSL *SSL_new(SSL_CTX *ctx) { - ssl->config->handoff = ctx->handoff; - ssl->quic_method = ctx->quic_method; ++ // useServerRawPublicKeyCertificate indicates, for TLS 1.3 only, that raw ++ // public keys should be used. For servers, the DER-encoded X.509 ++ // SubjectPublicKeyInfo field of Certificates[0].Certificate[0] will be the ++ // CertificateEntry of Certificate messages, not including any ++ // CertificateEntry extensions. For clients, the field should be used to ++ // verify the server's Certificate message. ++ useServerRawPublicKeyCertificate bool ++ + // Credential contains the credential to present to the other side of + // the connection. Server configurations must include this field. + Credential *Credential +@@ -2172,6 +2187,10 @@ type ProtocolBugs struct { + // NewSessionTicket messages to have or not have the resumption_across_names + // flag set. + ExpectResumptionAcrossNames *bool ++ ++ // ServerCertificateTypes, if not nil, contains the contents of the server ++ // certificate types extension sent by a client, or echoed by a server. ++ ServerCertificateTypes []uint8 + } + + func (c *Config) serverInit() { +diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go +index 1c4610815..cce76f47b 100644 +--- a/ssl/test/runner/handshake_client.go ++++ b/ssl/test/runner/handshake_client.go +@@ -557,6 +557,14 @@ func (hs *clientHandshakeState) createClientHello(innerHello *clientHelloMsg, ec + hello.vers = mapClientHelloVersion(maxVersion, c.isDTLS) + } -+ ssl->config->server_certificate_type_list.CopyFrom( -+ ctx->server_certificate_type_list); -+ ssl->config->server_raw_public_key_certificate.CopyFrom( -+ ctx->server_raw_public_key_certificate); ++ if maxVersion >= VersionTLS13 && c.config.useServerRawPublicKeyCertificate { ++ hello.serverCertificateTypes = []uint8{certificateTypeRawPublicKey} ++ } + - if (!ssl->method->ssl_new(ssl.get()) || - !ssl->ctx->x509_method->ssl_new(ssl->s3->hs.get())) { - return nullptr; -@@ -3249,6 +3273,53 @@ int SSL_set1_curves_list(SSL *ssl, const char *curves) { - return SSL_set1_groups_list(ssl, curves); ++ if c.config.Bugs.ServerCertificateTypes != nil { ++ hello.serverCertificateTypes = c.config.Bugs.ServerCertificateTypes ++ } ++ + if c.config.Bugs.SendClientVersion != 0 { + hello.vers = c.config.Bugs.SendClientVersion + } +@@ -1345,11 +1353,22 @@ func (hs *clientHandshakeState) doTLS13Handshake(msg any) error { + return errors.New("tls: server certificate unexpectedly did not match trust anchor") + } + +- if err := hs.verifyCertificates(certMsg); err != nil { +- return err ++ ex := encryptedExtensions.extensions ++ if c.config.useServerRawPublicKeyCertificate { ++ if !ex.hasServerCertificateType || ex.serverCertificateType != certificateTypeRawPublicKey { ++ c.sendAlert(alertUnsupportedCertificate) ++ return errors.New("tls: server did not support raw public keys") ++ } ++ if err := hs.verifyRawPublicKeyCertificates(certMsg); err != nil { ++ return err ++ } ++ } else { ++ if err := hs.verifyCertificates(certMsg); err != nil { ++ return err ++ } ++ c.ocspResponse = certMsg.certificates[0].ocspResponse ++ c.sctList = certMsg.certificates[0].sctList + } +- c.ocspResponse = certMsg.certificates[0].ocspResponse +- c.sctList = certMsg.certificates[0].sctList + + certVerifyMsg, err := readHandshakeType[certificateVerifyMsg](c) + if err != nil { +@@ -1854,6 +1873,50 @@ func delegatedCredentialSignedMessage(credBytes []byte, algorithm signatureAlgor + return ret } -+int SSL_CTX_set_server_raw_public_key_certificate(SSL_CTX *ctx, -+ const uint8_t *raw_public_key, unsigned raw_public_key_len) { -+ if (!ctx->server_raw_public_key_certificate.CopyFrom( -+ MakeConstSpan(raw_public_key, raw_public_key_len))) { -+ return 0; /* Failure */ -+ } ++func (hs *clientHandshakeState) verifyRawPublicKeyCertificates(certMsg *certificateMsg) error { ++ c := hs.c + -+ if (!ctx->server_certificate_type_list.Init(1)) { -+ return 0; -+ } -+ ctx->server_certificate_type_list[0] = TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY; ++ if len(certMsg.certificates) != 1 { ++ c.sendAlert(alertIllegalParameter) ++ return errors.New("tls: incorrect number of certificates") ++ } + -+ return 1; /* Success */ -+} ++ leafSPKI := certMsg.certificates[0].data + -+int SSL_CTX_has_server_raw_public_key_certificate(SSL_CTX *ctx) { -+ return !ctx->server_raw_public_key_certificate.empty(); -+} ++ if !c.config.InsecureSkipVerify { ++ expectedCert, err := x509.ParseCertificate(c.config.Credential.Certificate[0]) ++ if err != nil { ++ c.sendAlert(alertInternalError) ++ return errors.New("tls: failed to parse configured certificate: " + err.Error()) ++ } ++ expectedSPKI := expectedCert.RawSubjectPublicKeyInfo + -+int SSL_set_server_raw_public_key_certificate(SSL *ssl, -+ const uint8_t *raw_public_key, unsigned raw_public_key_len) { -+ if (!ssl->config) { -+ return 0; /* Failure */ -+ } ++ if !bytes.Equal(expectedSPKI, leafSPKI) { ++ c.sendAlert(alertBadCertificate) ++ return errors.New("tls: raw public key verification failed") ++ } ++ } + -+ if (!ssl->config->server_raw_public_key_certificate.CopyFrom( -+ MakeConstSpan(raw_public_key, raw_public_key_len))) { -+ return 0; -+ } ++ leafPublicKey, err := x509.ParsePKIXPublicKey(leafSPKI) ++ if err != nil { ++ c.sendAlert(alertBadCertificate) ++ return errors.New("tls: failed to parse raw public key certificate from server: " + err.Error()) ++ } + -+ if (!ssl->config->server_certificate_type_list.Init(1)) { -+ return 0; -+ } -+ ssl->config->server_certificate_type_list[0] = -+ TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY; ++ switch leafPublicKey.(type) { ++ case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey: ++ break ++ default: ++ c.sendAlert(alertUnsupportedCertificate) ++ return fmt.Errorf("tls: server's certificate contains an unsupported type of public key: %T", leafPublicKey) ++ } ++ ++ c.peerCertificates = nil ++ hs.peerPublicKey = leafPublicKey + -+ return 1; /* Success */ ++ return nil +} + -+int SSL_has_server_raw_public_key_certificate(SSL *ssl) { -+ if (!ssl->config) { -+ return 0; /* Failure */ + func (hs *clientHandshakeState) verifyCertificates(certMsg *certificateMsg) error { + c := hs.c + +diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go +index d5097f37c..d570a76ab 100644 +--- a/ssl/test/runner/handshake_messages.go ++++ b/ssl/test/runner/handshake_messages.go +@@ -259,9 +259,10 @@ type clientHelloMsg struct { + prefixExtensions []uint16 + // The following fields are only filled in by |unmarshal| and ignored when + // marshaling a new ClientHello. +- echPayloadStart int +- echPayloadEnd int +- rawExtensions []byte ++ echPayloadStart int ++ echPayloadEnd int ++ rawExtensions []byte ++ serverCertificateTypes []uint8 + } + + func (m *clientHelloMsg) marshalKeyShares(bb *cryptobyte.Builder) { +@@ -634,6 +635,14 @@ func (m *clientHelloMsg) marshalBody(hello *cryptobyte.Builder, typ clientHelloT + body: body.BytesOrPanic(), + }) + } ++ if m.serverCertificateTypes != nil { ++ body := cryptobyte.NewBuilder(nil) ++ addUint8LengthPrefixedBytes(body, m.serverCertificateTypes) ++ extensions = append(extensions, extension{ ++ id: extensionServerCertificateType, ++ body: body.BytesOrPanic(), ++ }) ++ } + // The PSK extension must be last. See https://tools.ietf.org/html/rfc8446#section-4.2.11 + if len(m.pskIdentities) > 0 { + pskExtension := cryptobyte.NewBuilder(nil) +@@ -1144,6 +1153,10 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { + } + m.alpsProtocols = append(m.alpsProtocols, string(protocol)) + } ++ case extensionServerCertificateType: ++ if !readUint8LengthPrefixedBytes(&body, &m.serverCertificateTypes) || len(body) != 0 { ++ return false ++ } + case extensionApplicationSettingsOld: + var protocols cryptobyte.String + if !body.ReadUint16LengthPrefixed(&protocols) || len(body) != 0 { +@@ -1597,6 +1610,8 @@ type serverExtensions struct { + hasApplicationSettingsOld bool + echRetryConfigs []byte + trustAnchors [][]byte ++ hasServerCertificateType bool ++ serverCertificateType uint8 + } + + func (m *serverExtensions) marshal(extensions *cryptobyte.Builder) { +@@ -1731,6 +1746,10 @@ func (m *serverExtensions) marshal(extensions *cryptobyte.Builder) { + extensions.AddUint16(extensionEncryptedClientHello) + addUint16LengthPrefixedBytes(extensions, m.echRetryConfigs) + } ++ if m.hasServerCertificateType { ++ extensions.AddUint16(extensionServerCertificateType) ++ addUint16LengthPrefixedBytes(extensions, []byte{m.serverCertificateType}) ++ } + if len(m.trustAnchors) > 0 { + extensions.AddUint16(extensionTrustAnchors) + extensions.AddUint16LengthPrefixed(func(extension *cryptobyte.Builder) { +@@ -1797,6 +1816,14 @@ func (m *serverExtensions) unmarshal(data cryptobyte.String, version uint16) boo + return false + } + m.channelIDRequested = true ++ case extensionServerCertificateType: ++ if version < VersionTLS13 { ++ return false ++ } ++ if !body.ReadUint8(&m.serverCertificateType) || len(body) != 0 { ++ return false ++ } ++ m.hasServerCertificateType = true + case extensionExtendedMasterSecret: + if len(body) != 0 { + return false +diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go +index 4e6ae98e4..14353b5e9 100644 +--- a/ssl/test/runner/handshake_server.go ++++ b/ssl/test/runner/handshake_server.go +@@ -28,20 +28,22 @@ import ( + // serverHandshakeState contains details of a server handshake in progress. + // It's discarded once the handshake has completed. + type serverHandshakeState struct { +- c *Conn +- clientHello *clientHelloMsg +- hello *serverHelloMsg +- suite *cipherSuite +- ellipticOk bool +- ecdsaOk bool +- sessionState *sessionState +- finishedHash finishedHash +- masterSecret []byte +- certsFromClient [][]byte +- cert *Credential +- finishedBytes []byte +- echHPKEContext *hpke.Context +- echConfigID uint8 ++ c *Conn ++ clientHello *clientHelloMsg ++ hello *serverHelloMsg ++ suite *cipherSuite ++ ellipticOk bool ++ ecdsaOk bool ++ sessionState *sessionState ++ finishedHash finishedHash ++ masterSecret []byte ++ certsFromClient [][]byte ++ cert *Credential ++ finishedBytes []byte ++ echHPKEContext *hpke.Context ++ echConfigID uint8 ++ hasServerCertificateType bool ++ serverCertificateType uint8 + } + + // serverHandshake performs a TLS handshake as a server. +@@ -983,6 +985,18 @@ func (hs *serverHandshakeState) doTLS13Handshake() error { + encryptedExtensions.extensions.hasEarlyData = true + } + ++ if c.vers >= VersionTLS13 && config.useServerRawPublicKeyCertificate { ++ for _, t := range hs.clientHello.serverCertificateTypes { ++ if t != certificateTypeRawPublicKey { ++ continue ++ } ++ hs.hasServerCertificateType = true ++ hs.serverCertificateType = certificateTypeRawPublicKey ++ encryptedExtensions.extensions.hasServerCertificateType = true ++ encryptedExtensions.extensions.serverCertificateType = certificateTypeRawPublicKey ++ } ++ } ++ + // Resolve ECDHE and compute the handshake secret. + if hs.hello.hasKeyShare { + // Once a curve has been selected and a key share identified, +@@ -1184,6 +1198,17 @@ func (hs *serverHandshakeState) doTLS13Handshake() error { + } + if !config.Bugs.EmptyCertificateList { + for i, certData := range useCert.Certificate { ++ if hs.hasServerCertificateType && ++ hs.serverCertificateType == certificateTypeRawPublicKey { ++ cert, err := x509.ParseCertificate(certData) ++ if err != nil { ++ return fmt.Errorf("tls: failed to parse configured certificate: %s", err.Error()) ++ } ++ certMsg.certificates = append( ++ certMsg.certificates, ++ certificateEntry{data: cert.RawSubjectPublicKeyInfo}) ++ break ++ } + cert := certificateEntry{ + data: certData, + } +diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go +index 57f9cc410..091e44f48 100644 +--- a/ssl/test/runner/runner.go ++++ b/ssl/test/runner/runner.go +@@ -789,7 +789,7 @@ func doExchange(test *testCase, config *Config, conn net.Conn, isResume bool, tr + tlsConn = Server(conn, config) + } + } else { +- config.InsecureSkipVerify = true ++ config.InsecureSkipVerify = !config.useServerRawPublicKeyCertificate + if test.protocol == dtls { + tlsConn = DTLSClient(conn, config) + } else { +@@ -2244,6 +2244,7 @@ func main() { + addKeyUpdateTests() + addPAKETests() + addTrustAnchorTests() ++ addRawPublicKeyCertificateTests() + + toAppend, err := convertToSplitHandshakeTests(testCases) + if err != nil { +diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc +index 9a7ee6a68..5b8cd5fa6 100644 +--- a/ssl/test/test_config.cc ++++ b/ssl/test/test_config.cc +@@ -37,6 +37,8 @@ + #include + + #include "../../crypto/internal.h" ++#include "../../crypto/mem_internal.h" ++#include "../../ssl/internal.h" + #include "handshake_util.h" + #include "mock_quic_transport.h" + #include "test_state.h" +@@ -639,6 +641,8 @@ const Flag *FindFlag(const char *name) { + OptionalBoolFalseFlag("-expect-not-resumable-across-names", + &TestConfig::expect_resumable_across_names), + BoolFlag("-no-server-name-ack", &TestConfig::no_server_name_ack), ++ BoolFlag("-raw-public-key-mode", &TestConfig::raw_public_key_mode), ++ Base64Flag("-expect-spki", &TestConfig::expect_spki), + }; + std::sort(ret.begin(), ret.end(), FlagNameComparator{}); + return ret; +@@ -1145,6 +1149,17 @@ static bool CheckVerifyCallback(SSL *ssl) { + fprintf(stderr, "ECH name did not match expected value.\n"); + return false; + } ++ if (!config->expect_spki.empty()) { ++ const EVP_PKEY *pkey = SSL_get0_peer_pubkey(ssl); ++ bssl::ScopedCBB cbb; ++ if (pkey == nullptr || !CBB_init(cbb.get(), /*initial_capacity=*/512) || ++ !EVP_marshal_public_key(cbb.get(), pkey) || ++ OPENSSL_memcmp(config->expect_spki.data(), CBB_data(cbb.get()), ++ CBB_len(cbb.get())) != 0) { ++ fprintf(stderr, "Incorrect SPKI observed\n"); ++ return false; ++ } + } + + if (config->expect_peer_match_trust_anchor.has_value() && + !!SSL_peer_matched_trust_anchor(ssl) != +@@ -1852,17 +1867,64 @@ static bool InstallCertificate(SSL *ssl) { + return false; + } + ++ const TestConfig *config = GetTestConfig(ssl); + -+ return !ssl->config->server_raw_public_key_certificate.empty(); -+} + if (pkey) { + TestState *test_state = GetTestState(ssl); +- const TestConfig *config = GetTestConfig(ssl); +- if (config->async || config->handshake_hints) { ++ // Install a custom private key if testing asynchronous callbacks, or if ++ // testing handshake hints. In the handshake hints case, we wish to check ++ // that hints only mismatch when allowed. ++ const bool use_private_key_method = ++ config->async || config->handshake_hints; ++ if (use_private_key_method) { + // Install a custom private key if testing asynchronous callbacks, or if + // testing handshake hints. In the handshake hints case, we wish to check + // that hints only mismatch when allowed. + test_state->private_key = std::move(pkey); +- SSL_set_private_key_method(ssl, &g_async_private_key_method); +- } else if (!SSL_use_PrivateKey(ssl, pkey.get())) { +- return false; ++ } ++ ++ if (config->raw_public_key_mode) { ++ bssl::UniquePtr cred(SSL_CREDENTIAL_new_raw_public_key()); ++ if (cred == nullptr) { ++ return false; ++ } ++ ++ if (use_private_key_method) { ++ SSL_CREDENTIAL_set_private_key_method(cred.get(), ++ &g_async_private_key_method); + - namespace fips202205 { ++ bssl::ScopedCBB cbb; ++ if (!CBB_init(cbb.get(), /*initial_capacity=*/512) || ++ !EVP_marshal_public_key(cbb.get(), test_state->private_key.get())) { ++ return false; ++ } ++ bssl::UniquePtr spki(CRYPTO_BUFFER_new( ++ CBB_data(cbb.get()), CBB_len(cbb.get()), /*pool=*/nullptr)); ++ if (spki == nullptr) { ++ return false; ++ } ++ if (!SSL_CREDENTIAL_set1_spki(cred.get(), spki.get())) { ++ return false; ++ } ++ } else { ++ if (!SSL_CREDENTIAL_set1_private_key(cred.get(), pkey.get())) { ++ return false; ++ } ++ if (!SSL_CREDENTIAL_set1_spki(cred.get(), nullptr)) { ++ return false; ++ } ++ } ++ ++ if (!SSL_add1_credential(ssl, cred.get())) { ++ return false; ++ } ++ return true; ++ } else { ++ if (use_private_key_method) { ++ SSL_set_private_key_method(ssl, &g_async_private_key_method); ++ } else if (!SSL_use_PrivateKey(ssl, pkey.get())) { ++ return false; ++ } + } + } + +@@ -2015,8 +2077,8 @@ bssl::UniquePtr TestConfig::SetupCtx(SSL_CTX *old_ctx) const { + } + + if (async && is_server) { +- // Disable the internal session cache. To test asynchronous session lookup, +- // we use an external session cache. ++ // Disable the internal session cache. To test asynchronous session ++ // lookup, we use an external session cache. + SSL_CTX_set_session_cache_mode( + ssl_ctx.get(), SSL_SESS_CACHE_BOTH | SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_get_cb(ssl_ctx.get(), GetSessionCallback); +@@ -2338,6 +2400,12 @@ bssl::UniquePtr TestConfig::NewSSL( + if (verify_peer) { + mode = SSL_VERIFY_PEER; + } ++ static const uint8_t kCertificateTypes[] = { ++ TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY}; ++ if (!is_server && raw_public_key_mode && ++ !SSL_set_server_certificate_types(ssl.get(), kCertificateTypes, 1)) { ++ return nullptr; ++ } + if (use_custom_verify_callback) { + SSL_set_custom_verify(ssl.get(), mode, CustomVerifyCallback); + } else if (mode != SSL_VERIFY_NONE) { +@@ -2484,8 +2552,8 @@ bssl::UniquePtr TestConfig::NewSSL( + if (enable_signed_cert_timestamps) { + SSL_enable_signed_cert_timestamps(ssl.get()); + } +- // (D)TLS 1.0 and 1.1 are disabled by default, but the runner expects them to +- // be enabled. ++ // (D)TLS 1.0 and 1.1 are disabled by default, but the runner expects them ++ // to be enabled. + // TODO(davidben): Update the tests to explicitly enable the versions they + // need. + if (!SSL_set_min_proto_version( +@@ -2495,7 +2563,8 @@ bssl::UniquePtr TestConfig::NewSSL( + if (min_version != 0 && !SSL_set_min_proto_version(ssl.get(), min_version)) { + return nullptr; + } +- // TODO(crbug.com/42290594): Remove this once DTLS 1.3 is enabled by default. ++ // TODO(crbug.com/42290594): Remove this once DTLS 1.3 is enabled by ++ // default. + if (is_dtls && max_version == 0 && + !SSL_set_max_proto_version(ssl.get(), DTLS1_3_VERSION)) { + return nullptr; +@@ -2515,7 +2584,8 @@ bssl::UniquePtr TestConfig::NewSSL( + SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_once); + } + if (renegotiate_freely || forbid_renegotiation_after_handshake) { +- // |forbid_renegotiation_after_handshake| will disable renegotiation later. ++ // |forbid_renegotiation_after_handshake| will disable renegotiation ++ // later. + SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_freely); + } + if (renegotiate_ignore) { +diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h +index 9745de035..dc32f8136 100644 +--- a/ssl/test/test_config.h ++++ b/ssl/test/test_config.h +@@ -243,6 +243,8 @@ struct TestConfig { + bool resumption_across_names_enabled = false; + std::optional expect_resumable_across_names; + bool no_server_name_ack = false; ++ bool raw_public_key_mode = false; ++ std::vector expect_spki; + + std::vector handshaker_args; - // (References are to SP 800-52r2): diff --git a/ssl/tls13_both.cc b/ssl/tls13_both.cc -index 5ab5a1c93..79135613e 100644 +index 257e4c997..951ab7d52 100644 --- a/ssl/tls13_both.cc +++ b/ssl/tls13_both.cc -@@ -11,6 +11,25 @@ - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION - * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #include - -@@ -197,7 +216,16 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg, - return false; +@@ -198,6 +198,45 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg, } -- if (sk_CRYPTO_BUFFER_num(certs.get()) == 0) { -+ if (hs->server_certificate_type_negotiated && -+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY) { + const bool is_leaf = sk_CRYPTO_BUFFER_num(certs.get()) == 0; ++ ++ // Parse out the extensions. ++ SSLExtension status_request( ++ TLSEXT_TYPE_status_request, ++ !ssl->server && hs->config->ocsp_stapling_enabled); ++ SSLExtension sct( ++ TLSEXT_TYPE_certificate_timestamp, ++ !ssl->server && hs->config->signed_cert_timestamps_enabled); ++ SSLExtension trust_anchors( ++ TLSEXT_TYPE_trust_anchors, ++ !ssl->server && is_leaf && ++ hs->config->requested_trust_anchors.has_value()); ++ uint8_t alert = SSL_AD_DECODE_ERROR; ++ if (!ssl_parse_extensions(&extensions, &alert, ++ {&status_request, &sct, &trust_anchors}, ++ /*ignore_unknown=*/false)) { ++ ssl_send_alert(ssl, SSL3_AL_FATAL, alert); ++ return false; ++ } ++ ++ if (!ssl->server && ++ hs->server_certificate_type == TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY) { ++ if (pkey) { ++ // Only a single "certificate" is allowed if using raw public keys. ++ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); ++ ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER); ++ return false; ++ } ++ + pkey = UniquePtr(EVP_parse_public_key(&certificate)); -+ if (!pkey) { ++ if (!pkey || CBS_len(&certificate) != 0) { + ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR); + OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); + return false; + } ++ ++ continue; + } -+ else if (sk_CRYPTO_BUFFER_num(certs.get()) == 0) { ++ + if (is_leaf) { pkey = ssl_cert_parse_pubkey(&certificate); if (!pkey) { - ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR); -@@ -299,7 +327,10 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg, +@@ -228,25 +267,6 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg, + return false; + } + +- // Parse out the extensions. +- SSLExtension status_request( +- TLSEXT_TYPE_status_request, +- !ssl->server && hs->config->ocsp_stapling_enabled); +- SSLExtension sct( +- TLSEXT_TYPE_certificate_timestamp, +- !ssl->server && hs->config->signed_cert_timestamps_enabled); +- SSLExtension trust_anchors( +- TLSEXT_TYPE_trust_anchors, +- !ssl->server && is_leaf && +- hs->config->requested_trust_anchors.has_value()); +- uint8_t alert = SSL_AD_DECODE_ERROR; +- if (!ssl_parse_extensions(&extensions, &alert, +- {&status_request, &sct, &trust_anchors}, +- /*ignore_unknown=*/false)) { +- ssl_send_alert(ssl, SSL3_AL_FATAL, alert); +- return false; +- } +- + // All Certificate extensions are parsed, but only the leaf extensions are + // stored. + if (status_request.present) { +@@ -313,7 +333,16 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg, + return false; } - if (sk_CRYPTO_BUFFER_num(hs->new_session->certs.get()) == 0) { -- if (!allow_anonymous) { -+ if (!allow_anonymous && -+ !(hs->server_certificate_type_negotiated && -+ hs->server_certificate_type == -+ TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY)) { +- if (sk_CRYPTO_BUFFER_num(hs->new_session->certs.get()) == 0) { ++ if (!ssl->server && ++ hs->server_certificate_type == TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY) { ++ if (!hs->peer_pubkey) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE); ++ ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_CERTIFICATE_REQUIRED); ++ return false; ++ } ++ ++ return true; ++ } else if (sk_CRYPTO_BUFFER_num(hs->new_session->certs.get()) == 0) { + if (!allow_anonymous) { OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE); ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_CERTIFICATE_REQUIRED); - return false; -@@ -416,6 +447,20 @@ bool tls13_add_certificate(SSL_HANDSHAKE *hs) { - return false; +@@ -436,13 +465,28 @@ bool tls13_add_certificate(SSL_HANDSHAKE *hs) { + return ssl_add_message_cbb(ssl, cbb.get()); } -+ if (hs->server_certificate_type_negotiated && -+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY) { -+ CBB leaf, extensions; -+ if (!CBB_add_u24_length_prefixed(&certificate_list, &leaf) || -+ !CBB_add_bytes(&leaf, -+ ssl->config->server_raw_public_key_certificate.data(), -+ ssl->config->server_raw_public_key_certificate.size()) || -+ !CBB_add_u16_length_prefixed(&certificate_list, &extensions)) { +- assert(hs->credential->UsesX509()); +- CRYPTO_BUFFER *leaf_buf = sk_CRYPTO_BUFFER_value(cred->chain.get(), 0); +- CBB leaf, extensions; +- if (!CBB_add_u24_length_prefixed(&certificate_list, &leaf) || +- !CBB_add_bytes(&leaf, CRYPTO_BUFFER_data(leaf_buf), +- CRYPTO_BUFFER_len(leaf_buf)) || +- !CBB_add_u16_length_prefixed(&certificate_list, &extensions)) { ++ CBB leaf; ++ if (!CBB_add_u24_length_prefixed(&certificate_list, &leaf)) { ++ OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); ++ return false; ++ } ++ ++ if (hs->credential->type == SSLCredentialType::kRawPublicKey) { ++ if (!EVP_marshal_public_key(&leaf, cred->pubkey.get())) { ++ OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); ++ return false; ++ } ++ } else { ++ CRYPTO_BUFFER *leaf_buf = sk_CRYPTO_BUFFER_value(cred->chain.get(), 0); ++ if (!CBB_add_bytes(&leaf, CRYPTO_BUFFER_data(leaf_buf), ++ CRYPTO_BUFFER_len(leaf_buf))) { + OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); + return false; + } -+ return ssl_add_message_cbb(ssl, cbb.get()); + } + - if (!ssl_has_certificate(hs)) { - return ssl_add_message_cbb(ssl, cbb.get()); ++ CBB extensions; ++ if (!CBB_add_u16_length_prefixed(&certificate_list, &extensions)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); + return false; } diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc -index 707cf846b..6916606c2 100644 +index eade4bd66..9de0bea28 100644 --- a/ssl/tls13_server.cc +++ b/ssl/tls13_server.cc -@@ -11,6 +11,25 @@ - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION - * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -+/* ==================================================================== -+ * Copyright 2020 Apple Inc. -+ * -+ * Permission is hereby granted, free of charge, to any person obtaining a -+ * copy of this software and associated documentation files (the “Software”), -+ * to deal in the Software without restriction, including without limitation -+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ * and/or sell copies of the Software, and to permit persons to whom -+ * the Software is furnished to do so, subject to the following conditions: -+ * The above copyright notice and this permission notice shall be included in -+ * all copies or substantial portions of the Software. -+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS -+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -+ * IN THE SOFTWARE. -+ */ - - #include +@@ -261,6 +261,7 @@ bool ssl_check_tls13_credential_ignoring_issuer(SSL_HANDSHAKE *hs, + uint16_t *out_sigalg) { + switch (cred->type) { + case SSLCredentialType::kX509: ++ case SSLCredentialType::kRawPublicKey: + break; + case SSLCredentialType::kDelegated: + // Check that the peer supports the signature over the delegated +@@ -284,7 +285,34 @@ bool ssl_check_tls13_credential_ignoring_issuer(SSL_HANDSHAKE *hs, -@@ -860,7 +879,8 @@ static enum ssl_hs_wait_t do_send_server_hello(SSL_HANDSHAKE *hs) { - - // Send the server Certificate message, if necessary. - if (!ssl->s3->session_reused) { -- if (!ssl_has_certificate(hs)) { -+ if (!ssl_has_certificate(hs) && -+ !ssl_has_raw_public_key_certificate(hs)) { - OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CERTIFICATE_SET); - return ssl_hs_error; + static bool check_signature_credential(SSL_HANDSHAKE *hs, + const SSL_CREDENTIAL *cred, +- uint16_t *out_sigalg) { ++ uint16_t *out_sigalg, ++ uint8_t *cert_type) { ++ switch (cred->type) { ++ case SSLCredentialType::kDelegated: ++ case SSLCredentialType::kX509: ++ *cert_type = TLS_CERTIFICATE_TYPE_X509; ++ break; ++ case SSLCredentialType::kRawPublicKey: ++ if (hs->server_certificate_type_list.empty()) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); ++ return false; ++ } ++ *cert_type = TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY; ++ break; ++ default: ++ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); ++ return false; ++ } ++ ++ if (!hs->server_certificate_type_list.empty() && ++ std::none_of( ++ hs->server_certificate_type_list.begin(), ++ hs->server_certificate_type_list.end(), ++ [cert_type](const auto &type) { return *cert_type == type; })) { ++ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE); ++ return false; ++ } ++ + return ssl_check_tls13_credential_ignoring_issuer(hs, cred, out_sigalg) && + // Use this credential if it either matches a requested issuer, + // or does not require issuer matching. +@@ -359,9 +387,11 @@ static enum ssl_hs_wait_t do_select_parameters(SSL_HANDSHAKE *hs) { + } + } else { + uint16_t sigalg; +- if (check_signature_credential(hs, cred, &sigalg)) { ++ uint8_t cert_type; ++ if (check_signature_credential(hs, cred, &sigalg, &cert_type)) { + hs->credential = UpRef(cred); + hs->signature_algorithm = sigalg; ++ hs->server_certificate_type = cert_type; + break; + } } +-- +2.40.0 + diff --git a/boring-sys/patches/underscore-wildcards.patch b/boring-sys/patches/underscore-wildcards.patch index 38e406a22..67272a733 100644 --- a/boring-sys/patches/underscore-wildcards.patch +++ b/boring-sys/patches/underscore-wildcards.patch @@ -1,10 +1,34 @@ -https://github.com/google/boringssl/compare/master...cloudflare:boringssl:underscore-wildcards +From 2128aa4382ba668e2c4f77bf18da719b2ad0087e Mon Sep 17 00:00:00 2001 +From: Anthony Ramine +Date: Fri, 5 Dec 2025 08:19:56 +0100 +Subject: [PATCH] Introduce X509_CHECK_FLAG_UNDERSCORE_WILDCARDS +--- + crypto/x509/v3_utl.cc | 4 +++- + crypto/x509/x509_test.cc | 25 +++++++++++++++++++++++++ + include/openssl/x509.h | 3 +++ + 3 files changed, 31 insertions(+), 1 deletion(-) + +diff --git a/crypto/x509/v3_utl.cc b/crypto/x509/v3_utl.cc +index 015bbcad2..2b9b63430 100644 +--- a/crypto/x509/v3_utl.cc ++++ b/crypto/x509/v3_utl.cc +@@ -740,7 +740,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len, + // Check that the part matched by the wildcard contains only + // permitted characters and only matches a single label. + for (p = wildcard_start; p != wildcard_end; ++p) { +- if (!OPENSSL_isalnum(*p) && *p != '-') { ++ if (!OPENSSL_isalnum(*p) && *p != '-' && ++ !(*p == '_' && ++ (flags & X509_CHECK_FLAG_UNDERSCORE_WILDCARDS))) { + return 0; + } + } diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc -index 9699b5a75..b0e9b34a6 100644 +index c6ce62dd1..f284f421f 100644 --- a/crypto/x509/x509_test.cc +++ b/crypto/x509/x509_test.cc -@@ -4420,6 +4420,31 @@ TEST(X509Test, Names) { +@@ -5209,6 +5209,31 @@ TEST(X509Test, Names) { /*invalid_emails=*/{}, /*flags=*/0, }, @@ -36,31 +60,20 @@ index 9699b5a75..b0e9b34a6 100644 }; size_t i = 0; -diff --git a/crypto/x509v3/v3_utl.c b/crypto/x509v3/v3_utl.c -index bbc82e283..e61e1901d 100644 ---- a/crypto/x509v3/v3_utl.c -+++ b/crypto/x509v3/v3_utl.c -@@ -790,7 +790,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len, - // Check that the part matched by the wildcard contains only - // permitted characters and only matches a single label. - for (p = wildcard_start; p != wildcard_end; ++p) { -- if (!OPENSSL_isalnum(*p) && *p != '-') { -+ if (!OPENSSL_isalnum(*p) && *p != '-' && -+ !(*p == '_' && -+ (flags & X509_CHECK_FLAG_UNDERSCORE_WILDCARDS))) { - return 0; - } - } -diff --git a/include/openssl/x509v3.h b/include/openssl/x509v3.h -index 2a2e02c2e..24e0604b0 100644 ---- a/include/openssl/x509v3.h -+++ b/include/openssl/x509v3.h -@@ -939,6 +939,8 @@ OPENSSL_EXPORT STACK_OF(OPENSSL_STRING) *X509_get1_ocsp(X509 *x); - #define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS 0 - // Skip the subject common name fallback if subjectAltNames is missing. +diff --git a/include/openssl/x509.h b/include/openssl/x509.h +index 926f365f4..cc538cceb 100644 +--- a/include/openssl/x509.h ++++ b/include/openssl/x509.h +@@ -3359,6 +3359,9 @@ OPENSSL_EXPORT int X509_VERIFY_PARAM_add1_host(X509_VERIFY_PARAM *param, + // enabled when subjectAltNames is missing. #define X509_CHECK_FLAG_NEVER_CHECK_SUBJECT 0x20 -+// Allow underscores in DNS wildcard matches. -+#define X509_CHECK_FLAG_UNDERSCORE_WILDCARDS 0x40 - OPENSSL_EXPORT int X509_check_host(X509 *x, const char *chk, size_t chklen, - unsigned int flags, char **peername); ++// X509_CHECK_FLAG_UNDERSCORE_WILDCARDS allows underscores in DNS wildcard matches. ++#define X509_CHECK_FLAG_UNDERSCORE_WILDCARDS 0x40 ++ + // X509_VERIFY_PARAM_set_hostflags sets the name-checking flags on |param| to + // |flags|. |flags| should be a combination of |X509_CHECK_FLAG_*| constants. + OPENSSL_EXPORT void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *param, +-- +2.40.0 + diff --git a/boring/src/ssl/async_callbacks.rs b/boring/src/ssl/async_callbacks.rs index 4ab18d11a..ad9683a08 100644 --- a/boring/src/ssl/async_callbacks.rs +++ b/boring/src/ssl/async_callbacks.rs @@ -4,7 +4,9 @@ use super::{ Ssl, SslAlert, SslContextBuilder, SslRef, SslSession, SslSignatureAlgorithm, SslVerifyError, SslVerifyMode, }; +use crate::error::ErrorStack; use crate::ex_data::Index; +use crate::ssl::SslCredentialBuilder; use std::convert::identity; use std::future::Future; use std::pin::Pin; @@ -171,6 +173,21 @@ impl SslContextBuilder { } } +impl SslCredentialBuilder { + /// Configures a custom private key method on the context. + /// + /// A task waker must be set on `Ssl` values associated with the resulting + /// `SslContext` with [`SslRef::set_task_waker`]. + /// + /// See [`AsyncPrivateKeyMethod`] for more details. + pub fn set_async_private_key_method( + &mut self, + method: impl AsyncPrivateKeyMethod, + ) -> Result<(), ErrorStack> { + self.set_private_key_method(AsyncPrivateKeyMethodBridge(Box::new(method))) + } +} + impl SslRef { pub fn set_async_custom_verify_callback(&mut self, mode: SslVerifyMode, callback: F) where diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 1509d724a..dd3df81a1 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -83,6 +83,8 @@ use crate::error::ErrorStack; use crate::ex_data::Index; use crate::hmac::HmacCtxRef; use crate::nid::Nid; +#[cfg(feature = "rpk")] +use crate::pkey::Public; use crate::pkey::{HasPrivate, PKeyRef, Params, Private}; use crate::srtp::{SrtpProtectionProfile, SrtpProtectionProfileRef}; use crate::ssl::bio::BioMethod; @@ -447,6 +449,8 @@ static SSL_INDEXES: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); static SESSION_CTX_INDEX: LazyLock> = LazyLock::new(|| Ssl::new_ex_index().unwrap()); +static SSL_CREDENTIAL_INDEXES: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); #[cfg(feature = "rpk")] static RPK_FLAG_INDEX: LazyLock> = LazyLock::new(|| SslContext::new_ex_index().unwrap()); @@ -914,33 +918,6 @@ impl SslContextBuilder { Ok(builder) } } - - /// Sets raw public key certificate in DER format. - pub fn set_rpk_certificate(&mut self, cert: &[u8]) -> Result<(), ErrorStack> { - unsafe { - cvt(ffi::SSL_CTX_set_server_raw_public_key_certificate( - self.as_ptr(), - cert.as_ptr(), - cert.len() as u32, - )) - .map(|_| ()) - } - } - - /// Sets RPK null chain private key. - pub fn set_null_chain_private_key(&mut self, key: &PKeyRef) -> Result<(), ErrorStack> - where - T: HasPrivate, - { - unsafe { - cvt(ffi::SSL_CTX_set_nullchain_and_key( - self.as_ptr(), - key.as_ptr(), - ptr::null_mut(), - )) - .map(|_| ()) - } - } } impl SslContextBuilder { @@ -1081,8 +1058,6 @@ impl SslContextBuilder { where F: Fn(&mut SslRef) -> Result<(), SslVerifyError> + 'static + Sync + Send, { - self.ctx.check_x509(); - unsafe { self.replace_ex_data(SslContext::cached_ex_index::(), callback); ffi::SSL_CTX_set_custom_verify( @@ -1940,7 +1915,7 @@ impl SslContextBuilder { /// Sets or overwrites the extra data at the specified index. /// /// This can be used to provide data to callbacks registered with the context. Use the - /// `Ssl::new_ex_index` method to create an `Index`. + /// `SslContext::new_ex_index` method to create an `Index`. /// /// Any previous value will be returned and replaced by the new one. #[corresponds(SSL_CTX_set_ex_data)] @@ -2049,6 +2024,36 @@ impl SslContextBuilder { unsafe { cvt(ffi::SSL_CTX_set1_ech_keys(self.as_ptr(), keys.as_ptr())).map(|_| ()) } } + /// Adds a credential. + #[corresponds(SSL_CTX_add1_credential)] + pub fn add_credential(&mut self, credential: &SslCredentialRef) -> Result<(), ErrorStack> { + unsafe { + cvt_0i(ffi::SSL_CTX_add1_credential( + self.as_ptr(), + credential.as_ptr(), + )) + .map(|_| ()) + } + } + + /// Sets the list of server certificate types that clients attached to this context + /// can process. + #[corresponds(SSL_CTX_set_server_certificate_types)] + #[cfg(feature = "rpk")] + pub fn set_server_certificate_types( + &mut self, + types: &[CertificateType], + ) -> Result<(), ErrorStack> { + unsafe { + cvt_0i(ffi::SSL_CTX_set_server_certificate_types( + self.as_ptr(), + types.as_ptr() as *const u8, + types.len(), + )) + .map(|_| ()) + } + } + /// Consumes the builder, returning a new `SslContext`. #[must_use] pub fn build(self) -> SslContext { @@ -2322,6 +2327,26 @@ impl SslContextRef { pub fn set_ech_keys(&self, keys: &SslEchKeys) -> Result<(), ErrorStack> { unsafe { cvt(ffi::SSL_CTX_set1_ech_keys(self.as_ptr(), keys.as_ptr())).map(|_| ()) } } + + /// Returns the list of server certificate types. + #[corresponds(SSL_CTX_get0_server_certificate_types)] + #[cfg(feature = "rpk")] + pub fn server_certificate_types(&self) -> Option<&[CertificateType]> { + let mut types = ptr::null(); + let mut types_len = 0; + unsafe { + ffi::SSL_CTX_get0_server_certificate_types(self.as_ptr(), &mut types, &mut types_len); + + if types_len == 0 { + return None; + } + + Some(slice::from_raw_parts( + types as *const CertificateType, + types_len, + )) + } + } } /// Error returned by the callback to get a session when operation @@ -3809,6 +3834,75 @@ impl SslRef { pub fn set_compliance_policy(&mut self, policy: CompliancePolicy) -> Result<(), ErrorStack> { unsafe { cvt_0i(ffi::SSL_set_compliance_policy(self.as_ptr(), policy.0)).map(|_| ()) } } + + /// Adds a credential. + #[corresponds(SSL_add1_credential)] + pub fn add_credential(&mut self, credential: &SslCredentialRef) -> Result<(), ErrorStack> { + unsafe { cvt_0i(ffi::SSL_add1_credential(self.as_ptr(), credential.as_ptr())).map(|_| ()) } + } + + /// Returns the public key sent by the other peer, `None` if there is no ongoing handshake. + #[corresponds(SSL_get0_peer_pubkey)] + #[cfg(feature = "rpk")] + pub fn peer_pubkey(&self) -> Option<&PKeyRef> { + unsafe { + let pubkey = ffi::SSL_get0_peer_pubkey(self.as_ptr()); + + if pubkey.is_null() { + return None; + } + + Some(PKeyRef::from_ptr(pubkey as *mut _)) + } + } + + /// Sets the list of server certificate types that clients attached to this `Ssl` + /// can process. + #[corresponds(SSL_set_server_certificate_types)] + #[cfg(feature = "rpk")] + pub fn set_server_certificate_types( + &mut self, + types: &[CertificateType], + ) -> Result<(), ErrorStack> { + unsafe { + cvt_0i(ffi::SSL_set_server_certificate_types( + self.as_ptr(), + types.as_ptr() as *const u8, + types.len(), + )) + .map(|_| ()) + } + } + + /// Returns the list of server certificate types. + #[corresponds(SSL_get0_server_certificate_types)] + #[must_use] + #[cfg(feature = "rpk")] + pub fn server_certificate_types(&self) -> Option<&[CertificateType]> { + let mut types = ptr::null(); + let mut types_len = 0; + unsafe { + ffi::SSL_get0_server_certificate_types(self.as_ptr(), &mut types, &mut types_len); + + if types_len == 0 { + return None; + } + + Some(slice::from_raw_parts( + types as *const CertificateType, + types_len, + )) + } + } + + /// Returns the server certificate type selected by the server, or `CertificateType::X509` + /// if there is no handshake. + #[corresponds(SSL_get_server_certificate_type_selected)] + #[must_use] + #[cfg(feature = "rpk")] + pub fn selected_server_certificate_type(&self) -> CertificateType { + unsafe { CertificateType(ffi::SSL_get_server_certificate_type_selected(self.as_ptr())) } + } } /// An SSL stream midway through the handshake process. @@ -4351,6 +4445,264 @@ impl SslStreamBuilder { } } +/// A credential. +pub struct SslCredential(NonNull); + +unsafe impl ForeignType for SslCredential { + type CType = ffi::SSL_CREDENTIAL; + type Ref = SslCredentialRef; + + #[inline] + unsafe fn from_ptr(ptr: *mut ffi::SSL_CREDENTIAL) -> Self { + Self(NonNull::new_unchecked(ptr)) + } + + #[inline] + fn as_ptr(&self) -> *mut ffi::SSL_CREDENTIAL { + self.0.as_ptr() + } +} + +impl Drop for SslCredential { + fn drop(&mut self) { + unsafe { ffi::SSL_CREDENTIAL_free(self.as_ptr()) } + } +} + +impl Deref for SslCredential { + type Target = SslCredentialRef; + + fn deref(&self) -> &SslCredentialRef { + unsafe { SslCredentialRef::from_ptr(self.as_ptr()) } + } +} + +impl SslCredential { + /// Create a credential suitable for a handshake using a raw public key. + #[corresponds(SSL_CREDENTIAL_new_raw_public_key)] + #[cfg(feature = "rpk")] + pub fn new_raw_public_key() -> Result { + unsafe { + Ok(SslCredentialBuilder(Self::from_ptr(cvt_p( + ffi::SSL_CREDENTIAL_new_raw_public_key(), + )?))) + } + } + + /// Returns a new extra data index. + /// + /// Each invocation of this function is guaranteed to return a distinct index. These can be used + /// to store data in the context that can be retrieved later by callbacks, for example. + #[corresponds(SSL_C_get_ex_new_index)] + pub fn new_ex_index() -> Result, ErrorStack> + where + T: 'static + Sync + Send, + { + unsafe { + ffi::init(); + let idx = cvt_n(get_new_ssl_credential_idx(Some(free_data_box::)))?; + Ok(Index::from_raw(idx)) + } + } + + // FIXME should return a result? + fn cached_ex_index() -> Index + where + T: 'static + Sync + Send, + { + unsafe { + let idx = *SSL_CREDENTIAL_INDEXES + .lock() + .unwrap_or_else(|e| e.into_inner()) + .entry(TypeId::of::()) + .or_insert_with(|| Self::new_ex_index::().unwrap().as_raw()); + Index::from_raw(idx) + } + } +} + +/// Reference to an [`SslCredential`]. +/// +/// [`SslCredential`]: struct.SslCredential.html +pub struct SslCredentialRef(Opaque); + +impl SslCredentialRef { + /// Returns a reference to the extra data at the specified index. + #[corresponds(SSL_CREDENTIAL_get_ex_data)] + #[must_use] + pub fn ex_data(&self, index: Index) -> Option<&T> { + unsafe { + let data = ffi::SSL_CREDENTIAL_get_ex_data(self.as_ptr(), index.as_raw()); + if data.is_null() { + None + } else { + Some(&*(data as *const T)) + } + } + } + + // Unsafe because SSL contexts are not guaranteed to be unique, we call + // this only from SslCredentialBuilder. + #[corresponds(SSL_CREDENTIAL_get_ex_data)] + unsafe fn ex_data_mut(&mut self, index: Index) -> Option<&mut T> { + let data = ffi::SSL_CREDENTIAL_get_ex_data(self.as_ptr(), index.as_raw()); + if data.is_null() { + None + } else { + Some(&mut *(data as *mut T)) + } + } + + // Unsafe because SSL contexts are not guaranteed to be unique, we call + // this only from SslCredentialBuilder. + #[corresponds(SSL_CREDENTIAL_set_ex_data)] + unsafe fn set_ex_data(&mut self, index: Index, data: T) { + unsafe { + let data = Box::into_raw(Box::new(data)) as *mut c_void; + ffi::SSL_CREDENTIAL_set_ex_data(self.as_ptr(), index.as_raw(), data); + } + } + + // Unsafe because SSL contexts are not guaranteed to be unique, we call + // this only from SslCredentialBuilder. + #[corresponds(SSL_CREDENTIAL_set_ex_data)] + unsafe fn replace_ex_data(&mut self, index: Index, data: T) -> Option { + if let Some(old) = self.ex_data_mut(index) { + return Some(mem::replace(old, data)); + } + + self.set_ex_data(index, data); + + None + } +} + +unsafe impl Send for SslCredentialRef {} +unsafe impl Sync for SslCredentialRef {} + +unsafe impl ForeignTypeRef for SslCredentialRef { + type CType = ffi::SSL_CREDENTIAL; +} + +pub struct SslCredentialBuilder(SslCredential); + +impl SslCredentialBuilder { + /// Sets the extra data at the specified index. + /// + /// This can be used to provide data to callbacks registered with the context. Use the + /// `SslCredential::new_ex_index` method to create an `Index`. + /// + /// Note that if this method is called multiple times with the same index, any previous + /// value stored in the `SslCredentialBuilder` will be leaked. + #[corresponds(SSL_CREDENTIAL_set_ex_data)] + pub fn set_ex_data(&mut self, index: Index, data: T) { + unsafe { + self.as_mut().set_ex_data(index, data); + } + } + + /// Sets or overwrites the extra data at the specified index. + /// + /// This can be used to provide data to callbacks registered with the context. Use the + /// `SslCredential::new_ex_index` method to create an `Index`. + /// + /// Any previous value will be returned and replaced by the new one. + #[corresponds(SSL_CREDENTIAL_set_ex_data)] + pub fn replace_ex_data(&mut self, index: Index, data: T) -> Option { + unsafe { self.as_mut().replace_ex_data(index, data) } + } + + // Sets the private key of the credential. + #[corresponds(SSL_CREDENTIAL_set1_private_key)] + pub fn set_private_key(&mut self, private_key: &PKeyRef) -> Result<(), ErrorStack> { + unsafe { + cvt_0i(ffi::SSL_CREDENTIAL_set1_private_key( + self.0.as_ptr(), + private_key.as_ptr(), + )) + .map(|_| ()) + } + } + + /// Configures a custom private key method on the credential. + /// + /// See [`PrivateKeyMethod`] for more details. + #[corresponds(SSL_CREDENTIAL_set_private_key_method)] + pub fn set_private_key_method(&mut self, method: M) -> Result<(), ErrorStack> + where + M: PrivateKeyMethod, + { + unsafe { + let this = self.as_mut(); + + this.replace_ex_data(SslCredential::cached_ex_index::(), method); + + cvt_0i(ffi::SSL_CREDENTIAL_set_private_key_method( + this.as_ptr(), + &ffi::SSL_PRIVATE_KEY_METHOD { + sign: Some(callbacks::raw_sign::), + decrypt: Some(callbacks::raw_decrypt::), + complete: Some(callbacks::raw_complete::), + }, + )) + .map(|_| ()) + } + } + + // Sets the SPKI of the raw public key credential. + // + // If `spki` is `None`, the SPKI is extracted from the credential's private key. + #[corresponds(SSL_CREDENTIAL_set1_spki)] + #[cfg(feature = "rpk")] + pub fn set_spki_bytes(&mut self, spki: Option<&[u8]>) -> Result<(), ErrorStack> { + unsafe { + let spki = spki + .map(|spki| { + cvt_p(ffi::CRYPTO_BUFFER_new( + spki.as_ptr(), + spki.len(), + ptr::null_mut(), + )) + }) + .transpose()? + .unwrap_or(ptr::null_mut()); + + let ret = cvt_0i(ffi::SSL_CREDENTIAL_set1_spki(self.0.as_ptr(), spki)); + + if spki.is_null() { + ffi::CRYPTO_BUFFER_free(spki); + } + + ret?; + + Ok(()) + } + } + + unsafe fn as_mut(&mut self) -> &mut SslCredentialRef { + SslCredentialRef::from_ptr_mut(self.0.as_ptr()) + } + + pub fn build(self) -> SslCredential { + self.0 + } +} + +/// A certificate type. +#[cfg(feature = "rpk")] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(transparent)] +pub struct CertificateType(u8); + +#[cfg(feature = "rpk")] +impl CertificateType { + /// A X.509 certificate. + pub const X509: Self = Self(ffi::TLS_CERTIFICATE_TYPE_X509 as u8); + + /// A raw public key. + pub const RAW_PUBLIC_KEY: Self = Self(ffi::TLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY as u8); +} + /// The result of a shutdown request. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ShutdownResult { @@ -4498,3 +4850,13 @@ unsafe fn get_new_ssl_idx(f: ffi::CRYPTO_EX_free) -> c_int { ffi::SSL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) } + +unsafe fn get_new_ssl_credential_idx(f: ffi::CRYPTO_EX_free) -> c_int { + // hack around https://rt.openssl.org/Ticket/Display.html?id=3710&user=guest&pass=guest + static ONCE: Once = Once::new(); + ONCE.call_once(|| { + ffi::SSL_CREDENTIAL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, None); + }); + + ffi::SSL_CREDENTIAL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) +} diff --git a/tokio-boring/tests/rpk.rs b/tokio-boring/tests/rpk.rs index 5492767ab..4b0cbe944 100644 --- a/tokio-boring/tests/rpk.rs +++ b/tokio-boring/tests/rpk.rs @@ -1,109 +1,151 @@ -#[cfg(feature = "rpk")] -mod test_rpk { - use boring::pkey::PKey; - use boring::ssl::{SslAcceptor, SslConnector}; - use futures::future; - use std::future::Future; - use std::net::SocketAddr; - use std::pin::Pin; - use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}; - use tokio::net::{TcpListener, TcpStream}; - use tokio_boring::{HandshakeError, SslStream}; - - fn create_server() -> ( - impl Future, HandshakeError>>, - SocketAddr, - ) { - let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); - - listener.set_nonblocking(true).unwrap(); - - let listener = TcpListener::from_std(listener).unwrap(); - let addr = listener.local_addr().unwrap(); +#![cfg(feature = "rpk")] + +use boring::pkey::PKey; +use boring::ssl::{ + CertificateType, SslAcceptor, SslAlert, SslConnector, SslCredential, SslVerifyError, + SslVerifyMode, +}; +use futures::future; +use std::future::Future; +use std::net::SocketAddr; +use std::pin::Pin; +use std::sync::Arc; +use std::sync::OnceLock; +use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}; +use tokio::net::{TcpListener, TcpStream}; +use tokio_boring::{HandshakeError, SslStream}; + +fn create_server() -> ( + impl Future, HandshakeError>>, + SocketAddr, +) { + let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); + + listener.set_nonblocking(true).unwrap(); + + let listener = TcpListener::from_std(listener).unwrap(); + let addr = listener.local_addr().unwrap(); + + let server = async move { + let mut acceptor = SslAcceptor::rpk().unwrap(); + let private_key = + PKey::private_key_from_pem(&std::fs::read("tests/key.pem").unwrap()).unwrap(); + let spki = std::fs::read("tests/pubkey.der").unwrap(); + + acceptor + .add_credential({ + let mut cred = SslCredential::new_raw_public_key().unwrap(); + + cred.set_private_key(&private_key).unwrap(); + cred.set_spki_bytes(Some(&spki)).unwrap(); + + &cred.build() + }) + .unwrap(); + + let acceptor = acceptor.build(); + + let stream = listener.accept().await.unwrap().0; + + tokio_boring::accept(&acceptor, stream).await + }; + + (server, addr) +} - let server = async move { - let mut acceptor = SslAcceptor::rpk().unwrap(); - let pkey = std::fs::read("tests/key.pem").unwrap(); - let pkey = PKey::private_key_from_pem(&pkey).unwrap(); - let cert = std::fs::read("tests/pubkey.der").unwrap(); +async fn connect( + addr: SocketAddr, + spki_path: &str, + is_ok_cell: &Arc>, +) -> Result, HandshakeError> { + let mut connector = SslConnector::rpk_builder().unwrap(); + let spki = PKey::public_key_from_der(&std::fs::read(spki_path).unwrap()).unwrap(); + let is_ok_cell = Arc::clone(is_ok_cell); - acceptor.set_rpk_certificate(&cert).unwrap(); - acceptor.set_null_chain_private_key(&pkey).unwrap(); + connector + .set_server_certificate_types(&[CertificateType::RAW_PUBLIC_KEY]) + .unwrap(); - let acceptor = acceptor.build(); + connector.set_custom_verify_callback(SslVerifyMode::PEER, move |ssl| { + let public_key = ssl + .peer_pubkey() + .ok_or(SslVerifyError::Invalid(SslAlert::CERTIFICATE_UNKNOWN))?; - let stream = listener.accept().await.unwrap().0; + let is_ok = public_key.public_eq(&spki); - tokio_boring::accept(&acceptor, stream).await - }; + is_ok_cell.set(is_ok).unwrap(); - (server, addr) - } + if !is_ok { + return Err(SslVerifyError::Invalid(SslAlert::BAD_CERTIFICATE)); + } - #[tokio::test] - async fn server_rpk() { - let (stream, addr) = create_server(); + Ok(()) + }); - let server = async { - let mut stream = stream.await.unwrap(); - let mut buf = [0; 4]; - stream.read_exact(&mut buf).await.unwrap(); - assert_eq!(&buf, b"asdf"); + let config = connector.build().configure().unwrap(); - stream.write_all(b"jkl;").await.unwrap(); + tokio_boring::connect( + config, + "localhost", + TcpStream::connect(&addr).await.unwrap(), + ) + .await +} - future::poll_fn(|ctx| Pin::new(&mut stream).poll_shutdown(ctx)) - .await - .unwrap(); - }; +#[tokio::test] +async fn server_rpk() { + let (stream, addr) = create_server(); - let client = async { - let mut connector = SslConnector::rpk_builder().unwrap(); - let cert = std::fs::read("tests/pubkey.der").unwrap(); + let server = async { + let mut stream = stream.await.unwrap(); + let mut buf = [0; 4]; + stream.read_exact(&mut buf).await.unwrap(); + assert_eq!(&buf, b"asdf"); - connector.set_rpk_certificate(&cert).unwrap(); - let config = connector.build().configure().unwrap(); + stream.write_all(b"jkl;").await.unwrap(); - let stream = TcpStream::connect(&addr).await.unwrap(); - let mut stream = tokio_boring::connect(config, "localhost", stream) - .await - .unwrap(); + future::poll_fn(|ctx| Pin::new(&mut stream).poll_shutdown(ctx)) + .await + .unwrap(); + }; - stream.write_all(b"asdf").await.unwrap(); + let client = async { + let is_ok_cell = Arc::new(OnceLock::new()); + let mut stream = connect(addr, "tests/pubkey.der", &is_ok_cell) + .await + .unwrap(); - let mut buf = vec![]; - stream.read_to_end(&mut buf).await.unwrap(); - assert_eq!(buf, b"jkl;"); - }; + assert!(is_ok_cell.get().unwrap()); - future::join(server, client).await; - } + stream.write_all(b"asdf").await.unwrap(); - #[tokio::test] - async fn client_rpk_unknown_cert() { - let (stream, addr) = create_server(); + let mut buf = vec![]; + stream.read_to_end(&mut buf).await.unwrap(); + assert_eq!(buf, b"jkl;"); + }; - let server = async { - assert!(stream.await.is_err()); - }; + future::join(server, client).await; +} - let client = async { - let mut connector = SslConnector::rpk_builder().unwrap(); - let cert = std::fs::read("tests/pubkey2.der").unwrap(); +#[tokio::test] +async fn client_rpk_unknown_cert() { + let (stream, addr) = create_server(); - connector.set_rpk_certificate(&cert).unwrap(); - let config = connector.build().configure().unwrap(); + let server = async { + assert!(stream.await.is_err()); + }; - let stream = TcpStream::connect(&addr).await.unwrap(); + let client = async { + let is_ok_cell = Arc::new(OnceLock::new()); + let err = connect(addr, "tests/pubkey2.der", &is_ok_cell) + .await + .unwrap_err(); - let err = tokio_boring::connect(config, "localhost", stream) - .await - .unwrap_err(); + assert!(!is_ok_cell.get().unwrap()); - // NOTE: smoke test for https://github.com/cloudflare/boring/issues/140 - let _ = err.to_string(); - }; + // NOTE: smoke test for https://github.com/cloudflare/boring/issues/140 + let _ = err.to_string(); + }; - future::join(server, client).await; - } + future::join(server, client).await; } From c2f063cf4711f15b8b417b6926496fbf1c2a03ac Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Sat, 20 Dec 2025 12:27:10 +0100 Subject: [PATCH 062/111] Rework SslMethod Instead of keeping around a flag on contexts to know whether we are configured for RPK, we start from SslMethod with a flag to know whether it is configured for X.509 certificates. We then propagate this flag to context builders and contexts, defaulting to false, and introduce `assume_x509` methods to inform the crate of X.509 support for contexts created with other means than our own functions. This improves the safety of the crate as any `SslContextBuilder` configured with `SslMethod::tls_with_buffer` would crash if used with functions involving X.509 certificates. This `SslMethod` is made unsafe because we can't guarantee that we check for X.509 support from all FFI bindingsi (for example, BoringSSL crashes if there is a mismatch in X.509 support in `SSL_set_SSL_CTX`). Note that there is no point anyway in forbidding X.509 functions on a context that supports RPK, as current BoringSSL is able to negociate both raw public keys and X.509 certificates on the same context. Finally, I removed `SslMethod::tls_client` and other peer-specific methods as they are just the same as there non-peer-specific equivalent methods. --- boring/src/ssl/connector.rs | 56 +------ boring/src/ssl/error.rs | 1 - boring/src/ssl/mod.rs | 201 ++++++++++++++------------ tokio-boring/examples/simple-async.rs | 2 +- tokio-boring/tests/rpk.rs | 6 +- 5 files changed, 123 insertions(+), 143 deletions(-) diff --git a/boring/src/ssl/connector.rs b/boring/src/ssl/connector.rs index dc9c35e6b..49f146aac 100644 --- a/boring/src/ssl/connector.rs +++ b/boring/src/ssl/connector.rs @@ -23,19 +23,9 @@ ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== -----END DH PARAMETERS----- "; -enum ContextType { - WithMethod(SslMethod), - #[cfg(feature = "rpk")] - Rpk, -} - #[allow(clippy::inconsistent_digit_grouping)] -fn ctx(ty: ContextType) -> Result { - let mut ctx = match ty { - ContextType::WithMethod(method) => SslContextBuilder::new(method), - #[cfg(feature = "rpk")] - ContextType::Rpk => SslContextBuilder::new_rpk(), - }?; +fn ctx(method: SslMethod) -> Result { + let mut ctx = SslContextBuilder::new(method)?; let mut opts = SslOptions::ALL | SslOptions::NO_COMPRESSION @@ -77,7 +67,7 @@ impl SslConnector { /// /// The default configuration is subject to change, and is currently derived from Python. pub fn builder(method: SslMethod) -> Result { - let mut ctx = ctx(ContextType::WithMethod(method))?; + let mut ctx = ctx(method)?; ctx.set_default_verify_paths()?; ctx.set_cipher_list( "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK", @@ -87,17 +77,6 @@ impl SslConnector { Ok(SslConnectorBuilder(ctx)) } - /// Creates a new builder for TLS connections with raw public key. - #[cfg(feature = "rpk")] - pub fn rpk_builder() -> Result { - let mut ctx = ctx(ContextType::Rpk)?; - ctx.set_cipher_list( - "DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK", - )?; - - Ok(SslConnectorBuilder(ctx)) - } - /// Initiates a client-side TLS session on a stream. /// /// The domain is used for SNI and hostname verification. @@ -224,13 +203,7 @@ impl ConnectConfiguration { self.ssl.set_hostname(domain)?; } - #[cfg(feature = "rpk")] - let verify_hostname = self.ssl.ssl_context().has_x509_support() && self.verify_hostname; - - #[cfg(not(feature = "rpk"))] - let verify_hostname = self.verify_hostname; - - if verify_hostname { + if self.verify_hostname { setup_verify_hostname(&mut self.ssl, domain)?; } @@ -292,21 +265,6 @@ impl DerefMut for ConnectConfiguration { pub struct SslAcceptor(SslContext); impl SslAcceptor { - /// Creates a new builder configured to connect to clients that support Raw Public Keys. - #[cfg(feature = "rpk")] - pub fn rpk() -> Result { - let mut ctx = ctx(ContextType::Rpk)?; - ctx.set_options(SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1); - let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?; - ctx.set_tmp_dh(&dh)?; - ctx.set_cipher_list( - "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:\ - ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\ - DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384" - )?; - Ok(SslAcceptorBuilder(ctx)) - } - /// Creates a new builder configured to connect to non-legacy clients. This should generally be /// considered a reasonable default choice. /// @@ -315,7 +273,7 @@ impl SslAcceptor { /// /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS pub fn mozilla_intermediate_v5(method: SslMethod) -> Result { - let mut ctx = ctx(ContextType::WithMethod(method))?; + let mut ctx = ctx(method)?; ctx.set_options(SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1); let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?; ctx.set_tmp_dh(&dh)?; @@ -336,7 +294,7 @@ impl SslAcceptor { /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS // FIXME remove in next major version pub fn mozilla_intermediate(method: SslMethod) -> Result { - let mut ctx = ctx(ContextType::WithMethod(method))?; + let mut ctx = ctx(method)?; ctx.set_options(SslOptions::CIPHER_SERVER_PREFERENCE); ctx.set_options(SslOptions::NO_TLSV1_3); let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?; @@ -362,7 +320,7 @@ impl SslAcceptor { /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS // FIXME remove in next major version pub fn mozilla_modern(method: SslMethod) -> Result { - let mut ctx = ctx(ContextType::WithMethod(method))?; + let mut ctx = ctx(method)?; ctx.set_options( SslOptions::CIPHER_SERVER_PREFERENCE | SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1, ); diff --git a/boring/src/ssl/error.rs b/boring/src/ssl/error.rs index 1289c7484..2209c503a 100644 --- a/boring/src/ssl/error.rs +++ b/boring/src/ssl/error.rs @@ -249,7 +249,6 @@ fn fmt_mid_handshake_error( f: &mut fmt::Formatter, prefix: &str, ) -> fmt::Result { - #[cfg(feature = "rpk")] if !s.ssl().ssl_context().has_x509_support() { write!(f, "{}", prefix)?; return write!(f, " {}", s.error()); diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index dd3df81a1..283594149 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -245,59 +245,87 @@ bitflags! { /// A type specifying the kind of protocol an `SslContext` will speak. #[derive(Copy, Clone)] -pub struct SslMethod(*const ffi::SSL_METHOD); +pub struct SslMethod { + ptr: *const ffi::SSL_METHOD, + is_x509_method: bool, +} impl SslMethod { /// Support all versions of the TLS protocol. #[corresponds(TLS_method)] #[must_use] - pub fn tls() -> SslMethod { - unsafe { SslMethod(TLS_method()) } + pub fn tls() -> Self { + unsafe { + Self { + ptr: ffi::TLS_method(), + is_x509_method: true, + } + } } - /// Same as `tls`, but doesn't create X509 for certificates. - #[cfg(feature = "rpk")] - pub fn tls_with_buffer() -> SslMethod { - unsafe { SslMethod(ffi::TLS_with_buffers_method()) } + /// Same as `tls`, but doesn't create X.509 for certificates. + /// + /// # Safety + /// + /// BoringSSL will crash if the user calls a function that involves + /// X.509 certificates with an object configured with this method. + /// You most probably don't need it. + #[must_use] + pub unsafe fn tls_with_buffer() -> Self { + unsafe { + Self { + ptr: ffi::TLS_with_buffers_method(), + is_x509_method: false, + } + } } /// Support all versions of the DTLS protocol. #[corresponds(DTLS_method)] #[must_use] - pub fn dtls() -> SslMethod { - unsafe { SslMethod(DTLS_method()) } - } - - /// Support all versions of the TLS protocol, explicitly as a client. - #[corresponds(TLS_client_method)] - #[must_use] - pub fn tls_client() -> SslMethod { - unsafe { SslMethod(TLS_client_method()) } - } - - /// Support all versions of the TLS protocol, explicitly as a server. - #[corresponds(TLS_server_method)] - #[must_use] - pub fn tls_server() -> SslMethod { - unsafe { SslMethod(TLS_server_method()) } + pub fn dtls() -> Self { + unsafe { + Self { + ptr: ffi::DTLS_method(), + is_x509_method: true, + } + } } /// Constructs an `SslMethod` from a pointer to the underlying OpenSSL value. /// + /// This method assumes that the `SslMethod` is not configured for X.509 + /// certificates. The user can call `SslMethod::assume_x509_method` + /// to change that. + /// /// # Safety /// /// The caller must ensure the pointer is valid. #[corresponds(TLS_server_method)] #[must_use] pub unsafe fn from_ptr(ptr: *const ffi::SSL_METHOD) -> SslMethod { - SslMethod(ptr) + SslMethod { + ptr, + is_x509_method: false, + } + } + + /// Assumes that this `SslMethod` is configured for X.509 certificates. + /// + /// # Safety + /// + /// BoringSSL will crash if the user calls a function that involves + /// X.509 certificates with an object configured with this method. + /// You most probably don't need it. + pub unsafe fn assume_x509(&mut self) { + self.is_x509_method = true; } /// Returns a pointer to the underlying OpenSSL value. #[allow(clippy::trivially_copy_pass_by_ref)] #[must_use] pub fn as_ptr(&self) -> *const ffi::SSL_METHOD { - self.0 + self.ptr } } @@ -451,8 +479,7 @@ static SESSION_CTX_INDEX: LazyLock> = LazyLock::new(|| Ssl::new_ex_index().unwrap()); static SSL_CREDENTIAL_INDEXES: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); -#[cfg(feature = "rpk")] -static RPK_FLAG_INDEX: LazyLock> = +static X509_FLAG_INDEX: LazyLock> = LazyLock::new(|| SslContext::new_ex_index().unwrap()); /// An error returned from the SNI callback. @@ -884,40 +911,11 @@ impl Ssl3AlertLevel { pub const FATAL: Ssl3AlertLevel = Self(ffi::SSL3_AL_FATAL); } -#[cfg(feature = "rpk")] -extern "C" fn rpk_verify_failure_callback( - _ssl: *mut ffi::SSL, - _out_alert: *mut u8, -) -> ffi::ssl_verify_result_t { - // Always verify the peer. - ffi::ssl_verify_result_t::ssl_verify_invalid -} - /// A builder for `SslContext`s. pub struct SslContextBuilder { ctx: SslContext, /// If it's not shared, it can be exposed as mutable has_shared_cert_store: bool, - #[cfg(feature = "rpk")] - is_rpk: bool, -} - -#[cfg(feature = "rpk")] -impl SslContextBuilder { - /// Creates a new `SslContextBuilder` to be used with Raw Public Key. - #[corresponds(SSL_CTX_new)] - pub fn new_rpk() -> Result { - unsafe { - init(); - let ctx = cvt_p(ffi::SSL_CTX_new(SslMethod::tls_with_buffer().as_ptr()))?; - - let mut builder = SslContextBuilder::from_ptr(ctx); - builder.is_rpk = true; - builder.set_ex_data(*RPK_FLAG_INDEX, true); - - Ok(builder) - } - } } impl SslContextBuilder { @@ -927,31 +925,45 @@ impl SslContextBuilder { unsafe { init(); let ctx = cvt_p(ffi::SSL_CTX_new(method.as_ptr()))?; - Ok(SslContextBuilder::from_ptr(ctx)) + let mut builder = SslContextBuilder::from_ptr(ctx); + + if method.is_x509_method { + builder.ctx.assume_x509(); + } + + Ok(builder) } } /// Creates an `SslContextBuilder` from a pointer to a raw OpenSSL value. /// - #[cfg_attr( - feature = "rpk", - doc = "Keeps previous RPK state. Use `new_rpk()` to enable RPK." - )] + /// This method can find out whether `ctx` is configured for X.509 certificates + /// if `ctx` was itself a context created by this crate. If it was created by + /// other means and it supports X.509 certificates, the use can call + /// `SslContextBuilder::assume_x509`. /// /// # Safety /// /// The caller must ensure that the pointer is valid and uniquely owned by the builder. /// The context must own its cert store exclusively. - pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX) -> SslContextBuilder { - let ctx = SslContext::from_ptr(ctx); - SslContextBuilder { - #[cfg(feature = "rpk")] - is_rpk: !ctx.has_x509_support(), + pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX) -> Self { + Self { + ctx: SslContext::from_ptr(ctx), has_shared_cert_store: false, - ctx, } } + /// Assumes that this `SslContextBuilder` is configured for X.509 certificates. + /// + /// # Safety + /// + /// BoringSSL will crash if the user calls a function that involves + /// X.509 certificates with an object configured with this method. + /// You most probably don't need it. + pub unsafe fn assume_x509(&mut self) { + self.ctx.assume_x509(); + } + /// Returns a pointer to the raw OpenSSL value. #[must_use] pub fn as_ptr(&self) -> *mut ffi::SSL_CTX { @@ -2303,12 +2315,20 @@ impl SslContextRef { SslVerifyMode::from_bits(mode).expect("SSL_CTX_get_verify_mode returned invalid mode") } - /// Returns `true` if context was NOT created for Raw Public Key verification + /// Assumes that this `SslContext` is configured for X.509 certificates. + /// + /// # Safety + /// + /// BoringSSL will crash if the user calls a function that involves + /// X.509 certificates with an object configured with this method. + /// You most probably don't need it. + pub unsafe fn assume_x509(&mut self) { + self.replace_ex_data(*X509_FLAG_INDEX, true); + } + + /// Returns `true` if context is configured for X.509 certificates. pub fn has_x509_support(&self) -> bool { - #[cfg(feature = "rpk")] - return !self.ex_data(*RPK_FLAG_INDEX).copied().unwrap_or_default(); - #[cfg(not(feature = "rpk"))] - return true; + self.ex_data(*X509_FLAG_INDEX).copied().unwrap_or_default() } #[track_caller] @@ -2810,20 +2830,20 @@ impl Ssl { where S: Read + Write, { - #[cfg(feature = "rpk")] - { - let ctx = self.ssl_context(); - - if !ctx.has_x509_support() { - unsafe { - ffi::SSL_CTX_set_custom_verify( - ctx.as_ptr(), - SslVerifyMode::PEER.bits(), - Some(rpk_verify_failure_callback), - ); - } - } - } + // #[cfg(feature = "rpk")] + // { + // let ctx = self.ssl_context(); + + // if !ctx.has_x509_support() { + // unsafe { + // ffi::SSL_CTX_set_custom_verify( + // ctx.as_ptr(), + // SslVerifyMode::PEER.bits(), + // Some(rpk_verify_failure_callback), + // ); + // } + // } + // } SslStreamBuilder::new(self, stream).setup_accept() } @@ -2853,7 +2873,6 @@ impl fmt::Debug for SslRef { builder.field("state", &self.state_string_long()); - #[cfg(feature = "rpk")] if self.ssl_context().has_x509_support() { builder.field("verify_result", &self.verify_result()); } @@ -3427,6 +3446,12 @@ impl SslRef { /// It is most commonly used in the Server Name Indication (SNI) callback. #[corresponds(SSL_set_SSL_CTX)] pub fn set_ssl_context(&mut self, ctx: &SslContextRef) -> Result<(), ErrorStack> { + assert_eq!( + self.ssl_context().has_x509_support(), + ctx.has_x509_support(), + "X.509 certificate support in old and new contexts doesn't match", + ); + unsafe { cvt_p(ffi::SSL_set_SSL_CTX(self.as_ptr(), ctx.as_ptr())).map(|_| ()) } } @@ -4827,8 +4852,6 @@ pub trait CertificateCompressor: Send + Sync + 'static { use crate::ffi::{SSL_CTX_up_ref, SSL_SESSION_get_master_key, SSL_SESSION_up_ref, SSL_is_server}; -use crate::ffi::{DTLS_method, TLS_client_method, TLS_method, TLS_server_method}; - use std::sync::Once; unsafe fn get_new_idx(f: ffi::CRYPTO_EX_free) -> c_int { diff --git a/tokio-boring/examples/simple-async.rs b/tokio-boring/examples/simple-async.rs index f4a69a1c9..c51368bd1 100644 --- a/tokio-boring/examples/simple-async.rs +++ b/tokio-boring/examples/simple-async.rs @@ -6,7 +6,7 @@ async fn main() -> anyhow::Result<()> { let listener = TcpListener::bind("127.0.0.1:8080").await?; let (tcp_stream, _addr) = listener.accept().await?; - let server = ssl::SslMethod::tls_server(); + let server = ssl::SslMethod::tls(); let mut ssl_builder = boring::ssl::SslAcceptor::mozilla_modern(server)?; ssl_builder.set_default_verify_paths()?; ssl_builder.set_verify(ssl::SslVerifyMode::PEER); diff --git a/tokio-boring/tests/rpk.rs b/tokio-boring/tests/rpk.rs index 4b0cbe944..b39f9d53c 100644 --- a/tokio-boring/tests/rpk.rs +++ b/tokio-boring/tests/rpk.rs @@ -2,7 +2,7 @@ use boring::pkey::PKey; use boring::ssl::{ - CertificateType, SslAcceptor, SslAlert, SslConnector, SslCredential, SslVerifyError, + CertificateType, SslAcceptor, SslAlert, SslConnector, SslCredential, SslMethod, SslVerifyError, SslVerifyMode, }; use futures::future; @@ -27,7 +27,7 @@ fn create_server() -> ( let addr = listener.local_addr().unwrap(); let server = async move { - let mut acceptor = SslAcceptor::rpk().unwrap(); + let mut acceptor = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).unwrap(); let private_key = PKey::private_key_from_pem(&std::fs::read("tests/key.pem").unwrap()).unwrap(); let spki = std::fs::read("tests/pubkey.der").unwrap(); @@ -58,7 +58,7 @@ async fn connect( spki_path: &str, is_ok_cell: &Arc>, ) -> Result, HandshakeError> { - let mut connector = SslConnector::rpk_builder().unwrap(); + let mut connector = SslConnector::builder(SslMethod::tls()).unwrap(); let spki = PKey::public_key_from_der(&std::fs::read(spki_path).unwrap()).unwrap(); let is_ok_cell = Arc::clone(is_ok_cell); From a2c57ecc0fc2ff7e0b1c2ee634058e8f20508161 Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 20 Jan 2026 01:40:35 +0000 Subject: [PATCH 063/111] Use existing macro for ForeignType --- boring/src/ssl/mod.rs | 45 +++++-------------------------------------- 1 file changed, 5 insertions(+), 40 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 283594149..57390b463 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -4470,36 +4470,12 @@ impl SslStreamBuilder { } } -/// A credential. -pub struct SslCredential(NonNull); - -unsafe impl ForeignType for SslCredential { +foreign_type_and_impl_send_sync! { type CType = ffi::SSL_CREDENTIAL; - type Ref = SslCredentialRef; - - #[inline] - unsafe fn from_ptr(ptr: *mut ffi::SSL_CREDENTIAL) -> Self { - Self(NonNull::new_unchecked(ptr)) - } - - #[inline] - fn as_ptr(&self) -> *mut ffi::SSL_CREDENTIAL { - self.0.as_ptr() - } -} - -impl Drop for SslCredential { - fn drop(&mut self) { - unsafe { ffi::SSL_CREDENTIAL_free(self.as_ptr()) } - } -} + fn drop = ffi::SSL_CREDENTIAL_free; -impl Deref for SslCredential { - type Target = SslCredentialRef; - - fn deref(&self) -> &SslCredentialRef { - unsafe { SslCredentialRef::from_ptr(self.as_ptr()) } - } + /// A credential. + pub struct SslCredential; } impl SslCredential { @@ -4546,11 +4522,6 @@ impl SslCredential { } } -/// Reference to an [`SslCredential`]. -/// -/// [`SslCredential`]: struct.SslCredential.html -pub struct SslCredentialRef(Opaque); - impl SslCredentialRef { /// Returns a reference to the extra data at the specified index. #[corresponds(SSL_CREDENTIAL_get_ex_data)] @@ -4602,13 +4573,7 @@ impl SslCredentialRef { } } -unsafe impl Send for SslCredentialRef {} -unsafe impl Sync for SslCredentialRef {} - -unsafe impl ForeignTypeRef for SslCredentialRef { - type CType = ffi::SSL_CREDENTIAL; -} - +/// A builder for [`SslCredential`] pub struct SslCredentialBuilder(SslCredential); impl SslCredentialBuilder { From b32059ee88292f1f0233d7a7db48537b27a1f464 Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 20 Jan 2026 01:43:15 +0000 Subject: [PATCH 064/111] Fix spki leak --- boring/src/ssl/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 57390b463..1ec57dde6 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -4659,7 +4659,7 @@ impl SslCredentialBuilder { let ret = cvt_0i(ffi::SSL_CREDENTIAL_set1_spki(self.0.as_ptr(), spki)); - if spki.is_null() { + if !spki.is_null() { ffi::CRYPTO_BUFFER_free(spki); } From 968999cf460578317a73b82db9084052da8b7fb6 Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 20 Jan 2026 01:47:20 +0000 Subject: [PATCH 065/111] Don't readd leaky set_ex_data --- boring/src/ssl/mod.rs | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 1ec57dde6..80c7f71ac 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -4549,16 +4549,6 @@ impl SslCredentialRef { } } - // Unsafe because SSL contexts are not guaranteed to be unique, we call - // this only from SslCredentialBuilder. - #[corresponds(SSL_CREDENTIAL_set_ex_data)] - unsafe fn set_ex_data(&mut self, index: Index, data: T) { - unsafe { - let data = Box::into_raw(Box::new(data)) as *mut c_void; - ffi::SSL_CREDENTIAL_set_ex_data(self.as_ptr(), index.as_raw(), data); - } - } - // Unsafe because SSL contexts are not guaranteed to be unique, we call // this only from SslCredentialBuilder. #[corresponds(SSL_CREDENTIAL_set_ex_data)] @@ -4567,7 +4557,10 @@ impl SslCredentialRef { return Some(mem::replace(old, data)); } - self.set_ex_data(index, data); + unsafe { + let data = Box::into_raw(Box::new(data)) as *mut c_void; + ffi::SSL_CREDENTIAL_set_ex_data(self.as_ptr(), index.as_raw(), data); + } None } @@ -4577,20 +4570,6 @@ impl SslCredentialRef { pub struct SslCredentialBuilder(SslCredential); impl SslCredentialBuilder { - /// Sets the extra data at the specified index. - /// - /// This can be used to provide data to callbacks registered with the context. Use the - /// `SslCredential::new_ex_index` method to create an `Index`. - /// - /// Note that if this method is called multiple times with the same index, any previous - /// value stored in the `SslCredentialBuilder` will be leaked. - #[corresponds(SSL_CREDENTIAL_set_ex_data)] - pub fn set_ex_data(&mut self, index: Index, data: T) { - unsafe { - self.as_mut().set_ex_data(index, data); - } - } - /// Sets or overwrites the extra data at the specified index. /// /// This can be used to provide data to callbacks registered with the context. Use the From 1ba39985e1855c0a752430b19f3b73f7f3870704 Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 20 Jan 2026 01:48:02 +0000 Subject: [PATCH 066/111] Remove unnecessary as_mut --- boring/src/ssl/mod.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 80c7f71ac..99a26fb1c 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -4578,7 +4578,7 @@ impl SslCredentialBuilder { /// Any previous value will be returned and replaced by the new one. #[corresponds(SSL_CREDENTIAL_set_ex_data)] pub fn replace_ex_data(&mut self, index: Index, data: T) -> Option { - unsafe { self.as_mut().replace_ex_data(index, data) } + unsafe { self.0.replace_ex_data(index, data) } } // Sets the private key of the credential. @@ -4602,12 +4602,10 @@ impl SslCredentialBuilder { M: PrivateKeyMethod, { unsafe { - let this = self.as_mut(); - - this.replace_ex_data(SslCredential::cached_ex_index::(), method); + self.replace_ex_data(SslCredential::cached_ex_index::(), method); cvt_0i(ffi::SSL_CREDENTIAL_set_private_key_method( - this.as_ptr(), + self.0.as_ptr(), &ffi::SSL_PRIVATE_KEY_METHOD { sign: Some(callbacks::raw_sign::), decrypt: Some(callbacks::raw_decrypt::), @@ -4636,22 +4634,16 @@ impl SslCredentialBuilder { .transpose()? .unwrap_or(ptr::null_mut()); - let ret = cvt_0i(ffi::SSL_CREDENTIAL_set1_spki(self.0.as_ptr(), spki)); + let ret = cvt_0i(ffi::SSL_CREDENTIAL_set1_spki(self.0.as_ptr(), spki)).map(|_| ()); if !spki.is_null() { ffi::CRYPTO_BUFFER_free(spki); } - ret?; - - Ok(()) + ret } } - unsafe fn as_mut(&mut self) -> &mut SslCredentialRef { - SslCredentialRef::from_ptr_mut(self.0.as_ptr()) - } - pub fn build(self) -> SslCredential { self.0 } From 4deec237226ffe82dd5701894f0bb3b649d1f856 Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 5 Jan 2026 17:44:57 +0000 Subject: [PATCH 067/111] Remove obsolete hack for OpenSSL 1.0.2 It already had a fix in 2019 rust-openssl#1133 --- boring/src/ssl/mod.rs | 20 -------------------- boring/src/x509/mod.rs | 9 +-------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 99a26fb1c..939725e70 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -4788,34 +4788,14 @@ pub trait CertificateCompressor: Send + Sync + 'static { use crate::ffi::{SSL_CTX_up_ref, SSL_SESSION_get_master_key, SSL_SESSION_up_ref, SSL_is_server}; -use std::sync::Once; - unsafe fn get_new_idx(f: ffi::CRYPTO_EX_free) -> c_int { - // hack around https://rt.openssl.org/Ticket/Display.html?id=3710&user=guest&pass=guest - static ONCE: Once = Once::new(); - ONCE.call_once(|| { - ffi::SSL_CTX_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, None); - }); - ffi::SSL_CTX_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) } unsafe fn get_new_ssl_idx(f: ffi::CRYPTO_EX_free) -> c_int { - // hack around https://rt.openssl.org/Ticket/Display.html?id=3710&user=guest&pass=guest - static ONCE: Once = Once::new(); - ONCE.call_once(|| { - ffi::SSL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, None); - }); - ffi::SSL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) } unsafe fn get_new_ssl_credential_idx(f: ffi::CRYPTO_EX_free) -> c_int { - // hack around https://rt.openssl.org/Ticket/Display.html?id=3710&user=guest&pass=guest - static ONCE: Once = Once::new(); - ONCE.call_once(|| { - ffi::SSL_CREDENTIAL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, None); - }); - ffi::SSL_CREDENTIAL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) } diff --git a/boring/src/x509/mod.rs b/boring/src/x509/mod.rs index cd5d428e8..e2a234375 100644 --- a/boring/src/x509/mod.rs +++ b/boring/src/x509/mod.rs @@ -20,7 +20,7 @@ use std::net::IpAddr; use std::path::Path; use std::ptr; use std::str; -use std::sync::{LazyLock, Once}; +use std::sync::LazyLock; use crate::asn1::{ Asn1BitStringRef, Asn1IntegerRef, Asn1Object, Asn1ObjectRef, Asn1StringRef, Asn1TimeRef, @@ -1813,12 +1813,5 @@ unsafe fn X509_OBJECT_free(x: *mut ffi::X509_OBJECT) { } unsafe fn get_new_x509_store_ctx_idx(f: ffi::CRYPTO_EX_free) -> c_int { - // hack around https://rt.openssl.org/Ticket/Display.html?id=3710&user=guest&pass=guest - static ONCE: Once = Once::new(); - - ONCE.call_once(|| { - ffi::X509_STORE_CTX_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, None); - }); - ffi::X509_STORE_CTX_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) } From 59483981d2f37052298f72d40b56a57dc5437168 Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 13 Jan 2026 19:31:57 +0000 Subject: [PATCH 068/111] Test MinGW32 --- .github/workflows/ci.yml | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d2ffaee2..685a86a26 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,6 +69,7 @@ jobs: matrix: thing: - stable + - i686-mingw - arm-android - arm64-android - i686-android @@ -184,6 +185,16 @@ jobs: LIBRARY_PATH: "C:\\msys64\\usr\\lib" # CI's Windows doesn't have required root certs extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring + - thing: i686-mingw + target: i686-pc-windows-gnu + rust: stable + os: windows-latest + check_only: true + custom_env: + CMAKE_GENERATOR: "MinGW Makefiles" + COLLECT_GCC: null + # CI's Windows doesn't have required root certs + extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring - thing: i686-msvc target: i686-pc-windows-msvc rust: stable-x86_64-msvc @@ -232,6 +243,32 @@ jobs: if: startsWith(matrix.os, 'windows') run: choco install nasm shell: cmd + - name: Setup 32-bit MSYS2 + if: matrix.thing == 'i686-mingw' + uses: msys2/setup-msys2@v2 + id: msys2 + with: + msystem: MINGW32 + path-type: inherit + install: >- + mingw-w64-i686-gcc + mingw-w64-i686-cmake + - name: Setup 32-bit MSYS2 Env vars + if: matrix.thing == 'i686-mingw' + shell: bash + run: | + MSYS_ROOT='${{ steps.msys2.outputs.msys2-location }}' + test -d "$MSYS_ROOT\\mingw32\\bin" + echo >> $GITHUB_PATH "$MSYS_ROOT\\mingw32\\bin" + echo >> $GITHUB_PATH "$MSYS_ROOT\\usr\\bin" + echo >> $GITHUB_ENV CC="$MSYS_ROOT\\mingw32\\bin\\gcc" + echo >> $GITHUB_ENV CXX="$MSYS_ROOT\\mingw32\\bin\\g++" + echo >> $GITHUB_ENV AR="$MSYS_ROOT\\mingw32\\bin\\ar" + echo >> $GITHUB_ENV CFLAGS="-mlong-double-64 -I$MSYS_ROOT\\mingw32\\include" + echo >> $GITHUB_ENV CXXFLAGS="-mlong-double-64 -I$MSYS_ROOT\\mingw32\\include" + echo >> $GITHUB_ENV BINDGEN_EXTRA_CLANG_ARGS="-mlong-double-64 -I$MSYS_ROOT\\mingw32\\include" + echo >> $GITHUB_ENV LIBRARY_PATH="$MSYS_ROOT\\mingw32\\lib" + echo >> $GITHUB_ENV LDFLAGS="-L$MSYS_ROOT\\mingw32\\lib" - name: Install LLVM and Clang if: startsWith(matrix.os, 'windows') uses: KyleMayes/install-llvm-action@v1 @@ -246,7 +283,7 @@ jobs: run: echo "CARGO_TARGET_$(echo ${{ matrix.target }} | tr \\-a-z _A-Z)_LINKER=$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/$(echo ${{ matrix.target }} | sed s/armv7/armv7a/)21-clang++" >> "$GITHUB_ENV" - name: Build tests # We `build` because we want the linker to verify we are cross-compiling correctly for check-only targets. - run: cargo build --target ${{ matrix.target }} --tests ${{ matrix.extra_test_args }} + run: cargo build -v --target ${{ matrix.target }} --tests ${{ matrix.extra_test_args }} shell: bash env: ${{ matrix.custom_env }} - name: Run tests (skip=${{ matrix.check_only }}) From 13ef735d35bb59de5217a3e3f389d4d084623ebb Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 6 Jan 2026 13:31:24 +0000 Subject: [PATCH 069/111] Cleaner include path check --- boring-sys/build/main.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 0f7053cb8..35d5bd249 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -658,15 +658,15 @@ fn generate_bindings(config: &Config) { .clang_arg("--sysroot") .clang_arg(sysroot.display().to_string()); - let c_target = format!( - "{}-{}-{}", - &config.target_arch, &config.target_os, &config.target_env - ); - // we need to add special platform header file with env for support cross building - let header = format!("{}/usr/include/{}", sysroot.display(), c_target); - if PathBuf::from(&header).is_dir() { - builder = builder.clang_arg("-I").clang_arg(&header); + let target_include_dir = sysroot.join(format!( + "usr/include/{}-{}-{}", + config.target_arch, config.target_os, config.target_env + )); + if target_include_dir.is_dir() { + builder = builder + .clang_arg("-I") + .clang_arg(target_include_dir.display().to_string()); } } From 96b7d5ff21f2acc927fbc9cee0e92d69578e57c8 Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 29 Dec 2025 18:23:24 +0000 Subject: [PATCH 070/111] Avoid unicode chars --- hyper-boring/src/v1.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/hyper-boring/src/v1.rs b/hyper-boring/src/v1.rs index f4cb0168d..392344ea4 100644 --- a/hyper-boring/src/v1.rs +++ b/hyper-boring/src/v1.rs @@ -253,15 +253,12 @@ where // If `host` is an IPv6 address, we must strip away the square brackets that surround // it (otherwise, boring will fail to parse the host as an IP address, eventually // causing the handshake to fail due a hostname verification error). - if !host.is_empty() { - let last = host.len() - 1; - let mut chars = host.chars(); - - if let (Some('['), Some(']')) = (chars.next(), chars.last()) { - if host[1..last].parse::().is_ok() { - host = &host[1..last]; - } - } + if let Some(ipv6) = host + .strip_prefix('[') + .and_then(|h| h.strip_suffix(']')) + .filter(|h| h.parse::().is_ok()) + { + host = ipv6; } let ssl = inner.setup_ssl(&uri, host)?; From d322d3a3809d1a6f6bac77e45cdac4b4069c3a6f Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 6 Jan 2026 12:59:18 +0000 Subject: [PATCH 071/111] Clippy --- boring-sys/build/main.rs | 2 +- boring/src/dsa.rs | 2 +- boring/src/ssl/error.rs | 1 + boring/src/ssl/mod.rs | 7 +++++-- boring/src/ssl/test/ech.rs | 6 +++--- boring/src/ssl/test/mod.rs | 8 ++++---- boring/src/ssl/test/session.rs | 4 ++-- boring/src/ssl/test/session_resumption.rs | 4 ++-- boring/src/x509/store.rs | 1 + boring/src/x509/tests/mod.rs | 2 +- boring/src/x509/tests/trusted_first.rs | 2 +- hyper-boring/src/cache.rs | 5 ++--- hyper-boring/src/v1.rs | 7 +++---- hyper-boring/tests/v1.rs | 6 +++--- tokio-boring/tests/async_get_session.rs | 4 ++-- 15 files changed, 32 insertions(+), 29 deletions(-) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 35d5bd249..5e744cd6a 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -722,7 +722,7 @@ fn ensure_err_lib_enum_is_named(source_code: &mut Vec) { let src = String::from_utf8_lossy(source_code); let enum_type = src .split_once("ERR_LIB_SSL:") - .and_then(|(_, def)| Some(def.split_once("=")?.0)) + .and_then(|(_, def)| Some(def.split_once('=')?.0)) .unwrap_or("_bindgen_ty_1"); source_code.extend_from_slice( diff --git a/boring/src/dsa.rs b/boring/src/dsa.rs index be13da9fa..ca7efaf32 100644 --- a/boring/src/dsa.rs +++ b/boring/src/dsa.rs @@ -300,7 +300,7 @@ mod test { let mut ctx = BigNumContext::new().unwrap(); let mut calc = BigNum::new().unwrap(); calc.mod_exp(g, priv_key, p, &mut ctx).unwrap(); - assert_eq!(&calc, pub_key) + assert_eq!(&calc, pub_key); } #[test] diff --git a/boring/src/ssl/error.rs b/boring/src/ssl/error.rs index 2209c503a..1766724cb 100644 --- a/boring/src/ssl/error.rs +++ b/boring/src/ssl/error.rs @@ -79,6 +79,7 @@ impl ErrorCode { } #[corresponds(SSL_error_description)] + #[must_use] pub fn description(self) -> Option<&'static str> { unsafe { let msg = ffi::SSL_error_description(self.0); diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 939725e70..41c3295e4 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -1658,7 +1658,7 @@ impl SslContextBuilder { C: CertificateCompressor, { const { - assert!(C::CAN_COMPRESS || C::CAN_DECOMPRESS, "Either compression or decompression must be supported for algorithm to be registered") + assert!(C::CAN_COMPRESS || C::CAN_DECOMPRESS, "Either compression or decompression must be supported for algorithm to be registered"); }; let success = unsafe { ffi::SSL_CTX_add_cert_compression_alg( @@ -1705,7 +1705,7 @@ impl SslContextBuilder { decrypt: Some(callbacks::raw_decrypt::), complete: Some(callbacks::raw_complete::), }, - ) + ); } } @@ -2327,6 +2327,7 @@ impl SslContextRef { } /// Returns `true` if context is configured for X.509 certificates. + #[must_use] pub fn has_x509_support(&self) -> bool { self.ex_data(*X509_FLAG_INDEX).copied().unwrap_or_default() } @@ -2351,6 +2352,7 @@ impl SslContextRef { /// Returns the list of server certificate types. #[corresponds(SSL_CTX_get0_server_certificate_types)] #[cfg(feature = "rpk")] + #[must_use] pub fn server_certificate_types(&self) -> Option<&[CertificateType]> { let mut types = ptr::null(); let mut types_len = 0; @@ -4644,6 +4646,7 @@ impl SslCredentialBuilder { } } + #[must_use] pub fn build(self) -> SslCredential { self.0 } diff --git a/boring/src/ssl/test/ech.rs b/boring/src/ssl/test/ech.rs index d2797d427..db4d43149 100644 --- a/boring/src/ssl/test/ech.rs +++ b/boring/src/ssl/test/ech.rs @@ -40,7 +40,7 @@ fn ech() { let (_server, client) = bootstrap_ech(ECH_CONFIG, ECH_KEY, ECH_CONFIG_LIST); let ssl_stream = client.connect(); - assert!(ssl_stream.ssl().ech_accepted()) + assert!(ssl_stream.ssl().ech_accepted()); } #[test] @@ -57,7 +57,7 @@ fn ech_rejection() { Some(b"ech.com".to_vec().as_ref()) ); assert!(failed_ssl_stream.ssl().get_ech_retry_configs().is_some()); - assert!(!failed_ssl_stream.ssl().ech_accepted()) + assert!(!failed_ssl_stream.ssl().ech_accepted()); } #[test] @@ -69,5 +69,5 @@ fn ech_grease() { client.ssl().set_enable_ech_grease(true); let ssl_stream = client.connect(); - assert!(!ssl_stream.ssl().ech_accepted()) + assert!(!ssl_stream.ssl().ech_accepted()); } diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index e6c61cf85..324163897 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -1010,7 +1010,7 @@ fn test_set_compliance() { assert_eq!(ciphers.len(), FIPS_CIPHERS.len()); for cipher in ciphers.into_iter().zip(FIPS_CIPHERS) { - assert_eq!(cipher.0.name(), cipher.1) + assert_eq!(cipher.0.name(), cipher.1); } let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); @@ -1029,7 +1029,7 @@ fn test_set_compliance() { assert_eq!(ciphers.len(), WPA3_192_CIPHERS.len()); for cipher in ciphers.into_iter().zip(WPA3_192_CIPHERS) { - assert_eq!(cipher.0.name(), cipher.1) + assert_eq!(cipher.0.name(), cipher.1); } ctx.set_compliance_policy(CompliancePolicy::NONE) @@ -1092,7 +1092,7 @@ fn test_ssl_set_compliance() { assert_eq!(ciphers.len(), FIPS_CIPHERS.len()); for cipher in ciphers.into_iter().zip(FIPS_CIPHERS) { - assert_eq!(cipher.0.name(), cipher.1) + assert_eq!(cipher.0.name(), cipher.1); } let ctx = SslContext::builder(SslMethod::tls()).unwrap().build(); @@ -1112,7 +1112,7 @@ fn test_ssl_set_compliance() { assert_eq!(ciphers.len(), WPA3_192_CIPHERS.len()); for cipher in ciphers.into_iter().zip(WPA3_192_CIPHERS) { - assert_eq!(cipher.0.name(), cipher.1) + assert_eq!(cipher.0.name(), cipher.1); } ssl.set_compliance_policy(CompliancePolicy::NONE) diff --git a/boring/src/ssl/test/session.rs b/boring/src/ssl/test/session.rs index 23c0f4d5d..97ed8c1c3 100644 --- a/boring/src/ssl/test/session.rs +++ b/boring/src/ssl/test/session.rs @@ -49,7 +49,7 @@ fn new_get_session_callback() { .ctx() .set_session_cache_mode(SslSessionCacheMode::SERVER | SslSessionCacheMode::NO_INTERNAL); server.ctx().set_new_session_callback(|_, session| { - SERVER_SESSION_DER.set(session.to_der().unwrap()).unwrap() + SERVER_SESSION_DER.set(session.to_der().unwrap()).unwrap(); }); unsafe { server.ctx().set_get_session_callback(|_, id| { @@ -76,7 +76,7 @@ fn new_get_session_callback() { .ctx() .set_session_cache_mode(SslSessionCacheMode::CLIENT); client.ctx().set_new_session_callback(|_, session| { - CLIENT_SESSION_DER.set(session.to_der().unwrap()).unwrap() + CLIENT_SESSION_DER.set(session.to_der().unwrap()).unwrap(); }); let client = client.build(); diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs index 808abe304..5c65c53ce 100644 --- a/boring/src/ssl/test/session_resumption.rs +++ b/boring/src/ssl/test/session_resumption.rs @@ -61,7 +61,7 @@ fn custom_callback_success() { unsafe { server .ctx() - .set_ticket_key_callback(test_success_tickey_key_callback) + .set_ticket_key_callback(test_success_tickey_key_callback); }; let server = server.build(); @@ -106,7 +106,7 @@ fn custom_callback_unrecognized_decryption_ticket() { unsafe { server .ctx() - .set_ticket_key_callback(test_noop_tickey_key_callback) + .set_ticket_key_callback(test_noop_tickey_key_callback); }; let server = server.build(); diff --git a/boring/src/x509/store.rs b/boring/src/x509/store.rs index e7621e739..1c2fd0ff1 100644 --- a/boring/src/x509/store.rs +++ b/boring/src/x509/store.rs @@ -177,6 +177,7 @@ impl X509StoreRef { } #[test] +#[allow(clippy::redundant_clone)] #[should_panic = "Shared X509Store can't be mutated"] fn set_cert_store_pevents_mutability() { use crate::ssl::*; diff --git a/boring/src/x509/tests/mod.rs b/boring/src/x509/tests/mod.rs index 371cd9b63..2a1b3fd56 100644 --- a/boring/src/x509/tests/mod.rs +++ b/boring/src/x509/tests/mod.rs @@ -73,7 +73,7 @@ fn test_subject_read_cn() { let cert = X509::from_pem(cert).unwrap(); let subject = cert.subject_name(); let cn = subject.entries_by_nid(Nid::COMMONNAME).next().unwrap(); - assert_eq!(cn.data().as_slice(), b"foobar.com") + assert_eq!(cn.data().as_slice(), b"foobar.com"); } #[test] diff --git a/boring/src/x509/tests/trusted_first.rs b/boring/src/x509/tests/trusted_first.rs index 187a49b0b..ad660a3b1 100644 --- a/boring/src/x509/tests/trusted_first.rs +++ b/boring/src/x509/tests/trusted_first.rs @@ -60,7 +60,7 @@ fn test_verify_cert() { assert_eq!( Ok(()), verify(&leaf, &[&root1], &[&intermediate, &root1_cross], |param| { - param.clear_flags(X509VerifyFlags::TRUSTED_FIRST) + param.clear_flags(X509VerifyFlags::TRUSTED_FIRST); }) ); } diff --git a/hyper-boring/src/cache.rs b/hyper-boring/src/cache.rs index 185c3dc41..ad0bddbb5 100644 --- a/hyper-boring/src/cache.rs +++ b/hyper-boring/src/cache.rs @@ -86,9 +86,8 @@ impl SessionCache { } pub fn remove(&mut self, session: &SslSessionRef) { - let key = match self.reverse.remove(session.id()) { - Some(key) => key, - None => return, + let Some(key) = self.reverse.remove(session.id()) else { + return; }; if let Entry::Occupied(mut sessions) = self.sessions.entry(key) { diff --git a/hyper-boring/src/v1.rs b/hyper-boring/src/v1.rs index 392344ea4..38b454229 100644 --- a/hyper-boring/src/v1.rs +++ b/hyper-boring/src/v1.rs @@ -113,7 +113,7 @@ impl HttpsLayer { /// /// The session cache configuration of `ssl` will be overwritten. pub fn with_connector(ssl: SslConnectorBuilder) -> Result { - Self::with_connector_and_settings(ssl, Default::default()) + Self::with_connector_and_settings(ssl, HttpsLayerSettings::default()) } /// Creates a new `HttpsLayer` with settings @@ -243,9 +243,8 @@ where let f = async { let conn = connect.await.map_err(Into::into)?.into_inner(); - let (inner, uri) = match tls_setup { - Some((inner, uri)) => (inner, uri), - None => return Ok(MaybeHttpsStream::Http(conn)), + let Some((inner, uri)) = tls_setup else { + return Ok(MaybeHttpsStream::Http(conn)); }; let mut host = uri.host().ok_or("URI missing host")?; diff --git a/hyper-boring/tests/v1.rs b/hyper-boring/tests/v1.rs index 4082d2cef..1965959f3 100644 --- a/hyper-boring/tests/v1.rs +++ b/hyper-boring/tests/v1.rs @@ -78,7 +78,7 @@ async fn localhost() { let file = File::create("../target/keyfile.log").unwrap(); ssl.set_keylog_callback(move |_, line| { - let _ = writeln!(&file, "{}", line); + let _ = writeln!(&file, "{line}"); }); let ssl = HttpsConnector::with_connector(connector, ssl).unwrap(); @@ -86,7 +86,7 @@ async fn localhost() { for _ in 0..3 { let resp = client - .get(format!("https://foobar.com:{}", port).parse().unwrap()) + .get(format!("https://foobar.com:{port}").parse().unwrap()) .await .unwrap(); assert!(resp.status().is_success(), "{}", resp.status()); @@ -149,7 +149,7 @@ async fn alpn_h2() { let client = Client::builder(TokioExecutor::new()).build::<_, Empty>(ssl); let resp = client - .get(format!("https://foobar.com:{}", port).parse().unwrap()) + .get(format!("https://foobar.com:{port}").parse().unwrap()) .await .unwrap(); assert!(resp.status().is_success(), "{}", resp.status()); diff --git a/tokio-boring/tests/async_get_session.rs b/tokio-boring/tests/async_get_session.rs index 0ab9b396e..177d84940 100644 --- a/tokio-boring/tests/async_get_session.rs +++ b/tokio-boring/tests/async_get_session.rs @@ -26,7 +26,7 @@ async fn test() { builder .set_session_cache_mode(SslSessionCacheMode::SERVER | SslSessionCacheMode::NO_INTERNAL); builder.set_new_session_callback(|_, session| { - SERVER_SESSION_DER.set(session.to_der().unwrap()).unwrap() + SERVER_SESSION_DER.set(session.to_der().unwrap()).unwrap(); }); unsafe { @@ -49,7 +49,7 @@ async fn test() { let connector = create_connector(|builder| { builder.set_session_cache_mode(SslSessionCacheMode::CLIENT); builder.set_new_session_callback(|_, session| { - CLIENT_SESSION_DER.set(session.to_der().unwrap()).unwrap() + CLIENT_SESSION_DER.set(session.to_der().unwrap()).unwrap(); }); builder.set_ca_file("tests/cert.pem") From 4ca7589c2f53540c9d330b519c41f086e83cf364 Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 19 Jan 2026 23:22:07 +0000 Subject: [PATCH 072/111] MaybeUninit is stable now --- boring/src/sha.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/boring/src/sha.rs b/boring/src/sha.rs index f0f9b15c1..4bea478b9 100644 --- a/boring/src/sha.rs +++ b/boring/src/sha.rs @@ -54,7 +54,6 @@ use std::mem::MaybeUninit; /// SHA1 is known to be insecure - it should not be used unless required for /// compatibility with existing systems. #[inline] -#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn sha1(data: &[u8]) -> [u8; 20] { unsafe { @@ -66,7 +65,6 @@ pub fn sha1(data: &[u8]) -> [u8; 20] { /// Computes the SHA224 hash of some data. #[inline] -#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn sha224(data: &[u8]) -> [u8; 28] { unsafe { @@ -78,7 +76,6 @@ pub fn sha224(data: &[u8]) -> [u8; 28] { /// Computes the SHA256 hash of some data. #[inline] -#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn sha256(data: &[u8]) -> [u8; 32] { unsafe { @@ -90,7 +87,6 @@ pub fn sha256(data: &[u8]) -> [u8; 32] { /// Computes the SHA384 hash of some data. #[inline] -#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn sha384(data: &[u8]) -> [u8; 48] { unsafe { @@ -102,7 +98,6 @@ pub fn sha384(data: &[u8]) -> [u8; 48] { /// Computes the SHA512 hash of some data. #[inline] -#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn sha512(data: &[u8]) -> [u8; 64] { unsafe { @@ -114,7 +109,6 @@ pub fn sha512(data: &[u8]) -> [u8; 64] { /// Computes the SHA512-256 hash of some data. #[inline] -#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn sha512_256(data: &[u8]) -> [u8; 32] { unsafe { @@ -143,7 +137,6 @@ impl Default for Sha1 { impl Sha1 { /// Creates a new hasher. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn new() -> Sha1 { unsafe { @@ -165,7 +158,6 @@ impl Sha1 { /// Returns the hash of the data. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn finish(mut self) -> [u8; 20] { unsafe { @@ -190,7 +182,6 @@ impl Default for Sha224 { impl Sha224 { /// Creates a new hasher. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn new() -> Sha224 { unsafe { @@ -212,7 +203,6 @@ impl Sha224 { /// Returns the hash of the data. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn finish(mut self) -> [u8; 28] { unsafe { @@ -237,7 +227,6 @@ impl Default for Sha256 { impl Sha256 { /// Creates a new hasher. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn new() -> Sha256 { unsafe { @@ -259,7 +248,6 @@ impl Sha256 { /// Returns the hash of the data. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn finish(mut self) -> [u8; 32] { unsafe { @@ -284,7 +272,6 @@ impl Default for Sha384 { impl Sha384 { /// Creates a new hasher. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn new() -> Sha384 { unsafe { @@ -306,7 +293,6 @@ impl Sha384 { /// Returns the hash of the data. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn finish(mut self) -> [u8; 48] { unsafe { @@ -331,7 +317,6 @@ impl Default for Sha512 { impl Sha512 { /// Creates a new hasher. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn new() -> Sha512 { unsafe { @@ -353,7 +338,6 @@ impl Sha512 { /// Returns the hash of the data. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn finish(mut self) -> [u8; 64] { unsafe { @@ -378,7 +362,6 @@ impl Default for Sha512_256 { impl Sha512_256 { /// Creates a new hasher. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn new() -> Sha512_256 { unsafe { @@ -400,7 +383,6 @@ impl Sha512_256 { /// Returns the hash of the data. #[inline] - #[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566 #[must_use] pub fn finish(mut self) -> [u8; 32] { unsafe { From b65a064e769ccbefc7edacfad2ba1c39b9952df3 Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 14 Nov 2025 14:28:33 +0000 Subject: [PATCH 073/111] Remove blanket Eq from FFI types --- boring-sys/build/main.rs | 3 ++- boring-sys/src/lib.rs | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 5e744cd6a..2aade03df 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -637,7 +637,8 @@ fn generate_bindings(config: &Config) { .derive_copy(true) .derive_debug(true) .derive_default(true) - .derive_eq(true) + .derive_eq(false) + .derive_partialeq(false) .default_enum_style(bindgen::EnumVariation::NewType { is_bitfield: false, is_global: false, diff --git a/boring-sys/src/lib.rs b/boring-sys/src/lib.rs index 6f027919c..8463fe1f2 100644 --- a/boring-sys/src/lib.rs +++ b/boring-sys/src/lib.rs @@ -20,7 +20,6 @@ use std::os::raw::{c_char, c_int, c_uint, c_ulong}; clippy::useless_transmute, clippy::derive_partial_eq_without_eq, clippy::ptr_offset_with_cast, - unpredictable_function_pointer_comparisons, // TODO: remove Eq/PartialEq in v5 dead_code )] mod generated { From ed768854a495fb919478f8d90480d3354db7c774 Mon Sep 17 00:00:00 2001 From: ihciah Date: Thu, 11 May 2023 09:48:42 +0000 Subject: [PATCH 074/111] fix: BIO_set_retry_write when BIO_CTRL_FLUSH to allow writer returns WouldBlock on flush --- boring/src/ssl/bio.rs | 4 +++ boring/src/ssl/test/mod.rs | 62 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/boring/src/ssl/bio.rs b/boring/src/ssl/bio.rs index e700dbe3d..4b3492faf 100644 --- a/boring/src/ssl/bio.rs +++ b/boring/src/ssl/bio.rs @@ -163,9 +163,13 @@ unsafe extern "C" fn ctrl( let state = state::(bio); if cmd == BIO_CTRL_FLUSH { + BIO_clear_retry_flags(bio); match catch_unwind(AssertUnwindSafe(|| state.stream.flush())) { Ok(Ok(())) => 1, Ok(Err(err)) => { + if retriable_error(&err) { + BIO_set_retry_write(bio); + } state.error = Some(err); 0 } diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index 324163897..782fc12ae 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -417,6 +417,68 @@ fn test_select_cert_alpn_extension() { ); } +#[test] +fn test_io_retry() { + #[derive(Debug)] + struct RetryStream { + inner: TcpStream, + first_read: bool, + first_write: bool, + first_flush: bool, + } + + impl Read for RetryStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + if mem::replace(&mut self.first_read, false) { + Err(io::Error::new(io::ErrorKind::WouldBlock, "first read")) + } else { + self.inner.read(buf) + } + } + } + + impl Write for RetryStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + if mem::replace(&mut self.first_write, false) { + Err(io::Error::new(io::ErrorKind::WouldBlock, "first write")) + } else { + self.inner.write(buf) + } + } + + fn flush(&mut self) -> io::Result<()> { + if mem::replace(&mut self.first_flush, false) { + Err(io::Error::new(io::ErrorKind::WouldBlock, "first flush")) + } else { + self.inner.flush() + } + } + } + + let server = Server::builder().build(); + + let stream = RetryStream { + inner: server.connect_tcp(), + first_read: true, + first_write: true, + first_flush: true, + }; + + let ctx = SslContext::builder(SslMethod::tls()).unwrap(); + let mut s = match Ssl::new(&ctx.build()).unwrap().connect(stream) { + Ok(mut s) => return s.read_exact(&mut [0]).unwrap(), + Err(HandshakeError::WouldBlock(s)) => s, + Err(_) => panic!("should not fail on setup"), + }; + loop { + match s.handshake() { + Ok(mut s) => return s.read_exact(&mut [0]).unwrap(), + Err(HandshakeError::WouldBlock(mid_s)) => s = mid_s, + Err(_) => panic!("should not fail on handshake"), + } + } +} + #[test] #[should_panic(expected = "blammo")] fn write_panic() { From 077f134c75a55fbef8762d303066e1149dec584d Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 7 Jan 2026 19:47:24 +0000 Subject: [PATCH 075/111] Upgrading to v5 --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 37ebcc633..42611afdc 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,18 @@ and [hyper](https://github.com/hyperium/hyper) built on top of it. - hyper HTTPS connector: - FFI bindings: +# Upgrading from `boring` v4 + + * First update to boring 4.20 and ensure it builds without any deprecation warnings. + * `pq-experimental` Cargo feature is no longer needed. Post-quantum crypto is enabled by default. + * `fips-precompiled` Cargo feature has been merged into `fips`. Set `BORING_BSSL_FIPS_PATH` env var to use a precompiled library. + * `fips-compat` Cargo feature has been renamed to `legacy-compat-deprecated` (4cb7e260a85b7) + * `SslCurve` and `SslCurveNid` have been removed. Use `set_curves_list()`. + * `Ssl::new_from_ref` -> `Ssl::new()`. + * `X509Builder::append_extension2` -> `X509Builder::append_extension`. + * `X509Store` is now cheaply cloneable, but immutable. `SslContextBuilder.cert_store_mut()` can't be used after `.set_cert_store()`. Use `.set_cert_store_builder()` if you need `.cert_store_mut()`. + * `hyper` 0.x support has been removed. Use `hyper` 1.x. + ## Contribution Unless you explicitly state otherwise, any contribution intentionally From 99dbbb3437a497631111f986fed555e9800d1a10 Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 20 Jan 2026 21:50:52 +0000 Subject: [PATCH 076/111] Fix missing import in tests --- boring/src/ssl/test/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index 782fc12ae..e66d0cc85 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -12,11 +12,11 @@ use crate::hash::MessageDigest; use crate::pkey::PKey; use crate::srtp::SrtpProfileId; use crate::ssl::test::server::Server; -use crate::ssl::SslVersion; use crate::ssl::{ self, ExtensionType, ShutdownResult, ShutdownState, Ssl, SslAcceptor, SslAcceptorBuilder, SslConnector, SslContext, SslFiletype, SslMethod, SslOptions, SslStream, SslVerifyMode, }; +use crate::ssl::{HandshakeError, SslVersion}; use crate::x509::store::X509StoreBuilder; use crate::x509::verify::X509CheckFlags; use crate::x509::{X509Name, X509}; From 3751abeecaade4656f1fdce36633daa1278268d9 Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 16 Jan 2026 16:42:11 +0000 Subject: [PATCH 077/111] Ensure dependency requirements are bumped #436 --- .github/workflows/ci.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 685a86a26..ee86cced0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - master + - v4.x push: branches: - master @@ -62,6 +63,15 @@ jobs: run: cargo doc --no-deps -p boring -p boring-sys --features rpk,underscore-wildcards env: DOCS_RS: 1 + - name: Cargo.toml boring versions consistency + shell: bash + run: | + WORKSPACE_VERSION=$(grep -F '[workspace.package]' -A1 Cargo.toml | grep -F version | grep -Eo '".*"') + if [[ -z "$WORKSPACE_VERSION" ]]; then echo 2>&1 "error: can't find boring version"; exit 1; fi + if grep -E 'boring.* =' Cargo.toml | grep -vF "$WORKSPACE_VERSION"; then + echo 2>&1 "error: boring dependencies must match workspace version $WORKSPACE_VERSION" + exit 1 + fi test: name: Test runs-on: ${{ matrix.os }} From f4a7f8d34535c57e32150df6fb4b2083f02adb97 Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 14 Nov 2025 16:14:38 +0000 Subject: [PATCH 078/111] Release 5.0.0-alpha.1 --- Cargo.toml | 8 ++++---- RELEASE_NOTES | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8fd3835ea..8c5ab6901 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ resolver = "2" [workspace.package] -version = "4.19.0" +version = "5.0.0-alpha.1" repository = "https://github.com/cloudflare/boring" edition = "2021" @@ -19,9 +19,9 @@ tag-prefix = "" publish = false [workspace.dependencies] -boring-sys = { version = "4.19.0", path = "./boring-sys" } -boring = { version = "4.19.0", path = "./boring" } -tokio-boring = { version = "4.19.0", path = "./tokio-boring" } +boring-sys = { version = "5.0.0-alpha.1", path = "./boring-sys" } +boring = { version = "5.0.0-alpha.1", path = "./boring" } +tokio-boring = { version = "5.0.0-alpha.1", path = "./tokio-boring" } bindgen = { version = "0.72.0", default-features = false, features = ["runtime"] } bitflags = "2.9" diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 352793768..df122b521 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -1,3 +1,45 @@ +5.0.0 +- 2025-12-19 Update vendored boring to a newer version (2023.11 to 2025.11) +- 2025-12-20 Rework RPK/SslMethod (c2f063cf4711f15b8b417b6926496fbf1c2a03ac) +- 2025-09-29 Remove `SslCurve` API +- 2025-09-30 Remove the "kx-*" features +- 2025-09-25 Remove legacy FIPS options (they're controlled via `BORING_BSSL_` env vars instead) +- 2026-01-05 Remove deprecated X509CheckFlags flag +- 2025-09-30 Remove "pq-experimental" Cargo feature, apply PQ patch by default + P256Kyber768Draft00 +- 2026-01-05 Safe clone for X509Store +- 2025-03-08 Add set_ticket_key_callback (SSL_CTX_set_tlsext_ticket_key_cb) +- 2025-09-30 Add SslRef::curve_name() +- 2025-09-30 Expose a safe Rust interface for the session resumption callback +- 2026-01-05 Fix leaky set_ex_data() API +- 2025-12-12 Add boring specific api set_strict_cipher_list to SslContextBuilder +- 2025-11-20 Introduce SslCipherRef::protocol_id +- 2023-05-11 fix: BIO_set_retry_write when BIO_CTRL_FLUSH to allow writer returns WouldBlock on flush +- 2025-11-14 Remove blanket Eq from FFI types +- 2025-12-20 Never use the debug CRT on Windows +- 2025-02-19 X509Builder::append_extension2 -> X509Builder::append_extension +- 2025-02-19 `Ssl::new_from_ref` -> `Ssl::new()` +- 2025-02-19 Align SslStream APIs with upstream +- 2025-09-26 Remove support for Hyper v0 + +4.21.0 +- 2026-01-05 Warn about set_curves() removal +- 2026-01-05 Deprecate set_ex_data() +- 2026-01-05 Fix build with --no-default-features +- 2026-01-05 Make set_curves_list always available +- 2026-01-19 Use fips-build-compatible ERR_add_error_data + +4.20.0 +- 2025-08-26 Support TARGET_CC and CC_{target} +- 2025-08-26 Fix swapped host/target args +- 2025-06-13 CStr UTF-8 improvements +- 2025-09-26 Skip Rust version detection for bindgen +- 2025-09-26 Upgrade deps +- 2025-06-13 Ensure that ERR_LIB type can be named +- 2025-06-13 Add more reliable library_reason() +- 2025-09-30 pq: fix MSVC C4146 warning +- 2025-10-14 Freebsd build +- 2025-10-01 Fix string data conversion in ErrorStack::put() + 4.19.0 - 2025-09-03 Add binding for X509_check_ip_asc - 2025-06-13 Use ERR_clear_error From f10b98672a91b60b5f95f4f8fa60789942846e6f Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 21 Jan 2026 18:04:02 +0000 Subject: [PATCH 079/111] Fix docs.rs build --- .github/workflows/ci.yml | 4 +++- boring-sys/src/lib.rs | 1 - boring/src/lib.rs | 2 -- hyper-boring/src/lib.rs | 1 - tokio-boring/src/lib.rs | 1 - 5 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee86cced0..f5b45b3ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,8 +60,10 @@ jobs: - name: Run clippy run: cargo clippy --all --all-targets - name: Check docs - run: cargo doc --no-deps -p boring -p boring-sys --features rpk,underscore-wildcards + run: cargo doc --no-deps -p boring -p boring-sys -p hyper-boring -p tokio-boring --features rpk,underscore-wildcards env: + CARGO_BUILD_RUSTDOCFLAGS: "--cfg=docsrs" + RUST_BOOTSTRAP: 1 DOCS_RS: 1 - name: Cargo.toml boring versions consistency shell: bash diff --git a/boring-sys/src/lib.rs b/boring-sys/src/lib.rs index 8463fe1f2..650b3842b 100644 --- a/boring-sys/src/lib.rs +++ b/boring-sys/src/lib.rs @@ -10,7 +10,6 @@ non_upper_case_globals, unused_imports )] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] use std::convert::TryInto; use std::ffi::c_void; diff --git a/boring/src/lib.rs b/boring/src/lib.rs index 932bdd354..9de4ba258 100644 --- a/boring/src/lib.rs +++ b/boring/src/lib.rs @@ -96,8 +96,6 @@ //! Presently all these key agreements are deployed by Cloudflare, but we do not guarantee continued //! support for them. -#![cfg_attr(docsrs, feature(doc_auto_cfg))] - #[macro_use] extern crate bitflags; #[macro_use] diff --git a/hyper-boring/src/lib.rs b/hyper-boring/src/lib.rs index 0e1f2b171..c43d48456 100644 --- a/hyper-boring/src/lib.rs +++ b/hyper-boring/src/lib.rs @@ -1,6 +1,5 @@ //! Hyper SSL support via BoringSSL. #![warn(missing_docs)] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] use crate::cache::SessionKey; use boring::error::ErrorStack; diff --git a/tokio-boring/src/lib.rs b/tokio-boring/src/lib.rs index 374a0bde0..433e2f398 100644 --- a/tokio-boring/src/lib.rs +++ b/tokio-boring/src/lib.rs @@ -11,7 +11,6 @@ //! [`boring`] crate, on which this crate is built. Configuration of TLS parameters is still //! primarily done through the [`boring`] crate. #![warn(missing_docs)] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] use boring::ssl::{ self, ConnectConfiguration, ErrorCode, MidHandshakeSslStream, ShutdownResult, SslAcceptor, From c13c69be3db96d688c2893c49285d7a8226acf63 Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 21 Jan 2026 18:04:37 +0000 Subject: [PATCH 080/111] Fix docs warnings --- Cargo.toml | 8 ++++---- boring/src/hmac.rs | 3 ++- boring/src/ssl/mod.rs | 10 +++++----- boring/src/symm.rs | 4 ++-- tokio-boring/src/async_callbacks.rs | 2 +- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8c5ab6901..230a2ccb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ resolver = "2" [workspace.package] -version = "5.0.0-alpha.1" +version = "5.0.0-alpha.2" repository = "https://github.com/cloudflare/boring" edition = "2021" @@ -19,9 +19,9 @@ tag-prefix = "" publish = false [workspace.dependencies] -boring-sys = { version = "5.0.0-alpha.1", path = "./boring-sys" } -boring = { version = "5.0.0-alpha.1", path = "./boring" } -tokio-boring = { version = "5.0.0-alpha.1", path = "./tokio-boring" } +boring-sys = { version = "5.0.0-alpha.2", path = "./boring-sys" } +boring = { version = "5.0.0-alpha.2", path = "./boring" } +tokio-boring = { version = "5.0.0-alpha.2", path = "./tokio-boring" } bindgen = { version = "0.72.0", default-features = false, features = ["runtime"] } bitflags = "2.9" diff --git a/boring/src/hmac.rs b/boring/src/hmac.rs index 7e50e6137..3011e102a 100644 --- a/boring/src/hmac.rs +++ b/boring/src/hmac.rs @@ -2,6 +2,7 @@ use crate::cvt; use crate::error::ErrorStack; use crate::foreign_types::ForeignTypeRef; use crate::hash::MessageDigest; +use openssl_macros::corresponds; foreign_type_and_impl_send_sync! { type CType = ffi::HMAC_CTX; @@ -13,7 +14,7 @@ foreign_type_and_impl_send_sync! { impl HmacCtxRef { /// Configures HmacCtx to use `md` as the hash function and `key` as the key. /// - /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/hmac.h.html#HMAC_Init_ex + #[corresponds(HMAC_Init_ex)] pub fn init(&mut self, key: &[u8], md: &MessageDigest) -> Result<(), ErrorStack> { ffi::init(); diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 41c3295e4..dd17a8ac8 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -980,11 +980,11 @@ impl SslContextBuilder { /// whether the chain is accepted or not. /// /// *Warning*: Providing a complete verification procedure is a complex task. See - /// https://docs.openssl.org/master/man3/SSL_CTX_set_cert_verify_callback/#notes for more - /// information. + /// [`SSL_CTX_set_cert_verify_callback`](https://docs.openssl.org/master/man3/SSL_CTX_set_cert_verify_callback/#notes) + /// for more information. /// - /// TODO: Add the ability to unset the callback by either adding a new function or wrapping the - /// callback in an `Option`. + // TODO: Add the ability to unset the callback by either adding a new function or wrapping the + // callback in an `Option`. /// /// # Panics /// @@ -1426,7 +1426,7 @@ impl SslContextBuilder { /// /// The `set_ciphersuites` method controls the cipher suites for TLSv1.3 in OpenSSL. /// BoringSSL doesn't implement `set_ciphersuites`. - /// See https://github.com/google/boringssl/blob/master/include/openssl/ssl.h#L1542-L1544 + /// See [ssl.h](https://github.com/google/boringssl/blob/master/include/openssl/ssl.h#L1542-L1544). /// /// See [`ciphers`] for details on the format. /// diff --git a/boring/src/symm.rs b/boring/src/symm.rs index a1346e6e5..9e9810f03 100644 --- a/boring/src/symm.rs +++ b/boring/src/symm.rs @@ -79,7 +79,7 @@ foreign_type_and_impl_send_sync! { impl CipherCtxRef { /// Configures CipherCtx for a fresh encryption operation using `cipher`. /// - /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/cipher.h.html#EVP_EncryptInit_ex + #[corresponds(EVP_EncryptInit_ex)] pub fn init_encrypt( &mut self, cipher: &Cipher, @@ -107,7 +107,7 @@ impl CipherCtxRef { /// Configures CipherCtx for a fresh decryption operation using `cipher`. /// - /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/cipher.h.html#EVP_DecryptInit_ex + #[corresponds(EVP_DecryptInit_ex)] pub fn init_decrypt( &mut self, cipher: &Cipher, diff --git a/tokio-boring/src/async_callbacks.rs b/tokio-boring/src/async_callbacks.rs index 7d3888beb..734606888 100644 --- a/tokio-boring/src/async_callbacks.rs +++ b/tokio-boring/src/async_callbacks.rs @@ -41,7 +41,7 @@ pub trait SslContextBuilderExt: private::Sealed { /// /// # Safety /// - /// The returned [`SslSession`] must not be associated with a different [`SslContext`]. + /// The returned [`boring::ssl::SslSession`] must not be associated with a different [`boring::ssl::SslContext`]. unsafe fn set_async_get_session_callback(&mut self, callback: F) where F: Fn(&mut SslRef, &[u8]) -> Option + Send + Sync + 'static; From 3c33edfd432a3c066a70860d05a825f56490ea75 Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 21 Jan 2026 18:24:38 +0000 Subject: [PATCH 081/111] Cache test deps on Windows --- .github/workflows/ci.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5b45b3ae..7a2978fe1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -223,6 +223,7 @@ jobs: extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring env: CARGO_HOME: ${{ github.workspace }}/.cache/cargo + CARGO_BUILD_BUILD_DIR: ${{ github.workspace }}/.cache/build-dir steps: - uses: actions/checkout@v4 with: @@ -293,6 +294,17 @@ jobs: - name: Set Android Linker path if: endsWith(matrix.thing, '-android') run: echo "CARGO_TARGET_$(echo ${{ matrix.target }} | tr \\-a-z _A-Z)_LINKER=$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/$(echo ${{ matrix.target }} | sed s/armv7/armv7a/)21-clang++" >> "$GITHUB_ENV" + - name: Fetch deps + run: cargo fetch --target ${{ matrix.target }} + shell: bash + env: ${{ matrix.custom_env }} + # Windows builds are the slowest + - name: Cache deps in Windows tests + if: startsWith(matrix.os, 'windows') + uses: actions/cache@v4 + with: + path: .cache/build-dir # CARGO_BUILD_BUILD_DIR + key: test-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }} - name: Build tests # We `build` because we want the linker to verify we are cross-compiling correctly for check-only targets. run: cargo build -v --target ${{ matrix.target }} --tests ${{ matrix.extra_test_args }} From 6413deb356a7a458a5515fc4659fc9b0a54ebe79 Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 29 Dec 2025 20:00:35 +0000 Subject: [PATCH 082/111] Avoid cvt.map(drop) --- boring/src/asn1.rs | 1 - boring/src/bn.rs | 45 +++++++++++--------------------- boring/src/derive.rs | 2 +- boring/src/ec.rs | 11 +------- boring/src/hmac.rs | 1 - boring/src/lib.rs | 20 ++++++++++----- boring/src/macros.rs | 4 +-- boring/src/pkcs5.rs | 8 +++--- boring/src/rand.rs | 2 +- boring/src/sign.rs | 8 ------ boring/src/ssl/mod.rs | 54 ++++++++++++--------------------------- boring/src/symm.rs | 7 ----- boring/src/x509/mod.rs | 24 +++++------------ boring/src/x509/store.rs | 6 ++--- boring/src/x509/verify.rs | 5 +--- 15 files changed, 64 insertions(+), 134 deletions(-) diff --git a/boring/src/asn1.rs b/boring/src/asn1.rs index 21fbc48c4..6099217dd 100644 --- a/boring/src/asn1.rs +++ b/boring/src/asn1.rs @@ -508,7 +508,6 @@ impl Asn1IntegerRef { self.as_ptr(), c_long::from(value), )) - .map(|_| ()) } } } diff --git a/boring/src/bn.rs b/boring/src/bn.rs index bf4ca1c77..b8464e1d2 100644 --- a/boring/src/bn.rs +++ b/boring/src/bn.rs @@ -121,19 +121,19 @@ impl BigNumRef { /// Adds a `u32` to `self`. #[corresponds(BN_add_word)] pub fn add_word(&mut self, w: u32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_add_word(self.as_ptr(), ffi::BN_ULONG::from(w))).map(|_| ()) } + unsafe { cvt(ffi::BN_add_word(self.as_ptr(), ffi::BN_ULONG::from(w))) } } /// Subtracts a `u32` from `self`. #[corresponds(BN_sub_word)] pub fn sub_word(&mut self, w: u32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_sub_word(self.as_ptr(), ffi::BN_ULONG::from(w))).map(|_| ()) } + unsafe { cvt(ffi::BN_sub_word(self.as_ptr(), ffi::BN_ULONG::from(w))) } } /// Multiplies a `u32` by `self`. #[corresponds(BN_mul_word)] pub fn mul_word(&mut self, w: u32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_mul_word(self.as_ptr(), ffi::BN_ULONG::from(w))).map(|_| ()) } + unsafe { cvt(ffi::BN_mul_word(self.as_ptr(), ffi::BN_ULONG::from(w))) } } /// Divides `self` by a `u32`, returning the remainder. @@ -168,13 +168,13 @@ impl BigNumRef { /// number less than `self` in `rnd`. #[corresponds(BN_rand_range)] pub fn rand_range(&self, rnd: &mut BigNumRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_rand_range(rnd.as_ptr(), self.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::BN_rand_range(rnd.as_ptr(), self.as_ptr())) } } /// The cryptographically weak counterpart to `rand_in_range`. #[corresponds(BN_pseudo_rand_range)] pub fn pseudo_rand_range(&self, rnd: &mut BigNumRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_pseudo_rand_range(rnd.as_ptr(), self.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::BN_pseudo_rand_range(rnd.as_ptr(), self.as_ptr())) } } /// Sets bit `n`. Equivalent to `self |= (1 << n)`. @@ -183,7 +183,7 @@ impl BigNumRef { #[corresponds(BN_set_bit)] #[allow(clippy::useless_conversion)] pub fn set_bit(&mut self, n: i32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_set_bit(self.as_ptr(), n.into())).map(|_| ()) } + unsafe { cvt(ffi::BN_set_bit(self.as_ptr(), n.into())) } } /// Clears bit `n`, setting it to 0. Equivalent to `self &= ~(1 << n)`. @@ -192,7 +192,7 @@ impl BigNumRef { #[corresponds(BN_clear_bit)] #[allow(clippy::useless_conversion)] pub fn clear_bit(&mut self, n: i32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_clear_bit(self.as_ptr(), n.into())).map(|_| ()) } + unsafe { cvt(ffi::BN_clear_bit(self.as_ptr(), n.into())) } } /// Returns `true` if the `n`th bit of `self` is set to 1, `false` otherwise. @@ -209,19 +209,19 @@ impl BigNumRef { #[corresponds(BN_mask_bits)] #[allow(clippy::useless_conversion)] pub fn mask_bits(&mut self, n: i32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_mask_bits(self.as_ptr(), n.into())).map(|_| ()) } + unsafe { cvt(ffi::BN_mask_bits(self.as_ptr(), n.into())) } } /// Places `a << 1` in `self`. Equivalent to `self * 2`. #[corresponds(BN_lshift1)] pub fn lshift1(&mut self, a: &BigNumRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_lshift1(self.as_ptr(), a.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::BN_lshift1(self.as_ptr(), a.as_ptr())) } } /// Places `a >> 1` in `self`. Equivalent to `self / 2`. #[corresponds(BN_rshift1)] pub fn rshift1(&mut self, a: &BigNumRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_rshift1(self.as_ptr(), a.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::BN_rshift1(self.as_ptr(), a.as_ptr())) } } /// Places `a + b` in `self`. [`core::ops::Add`] is also implemented for `BigNumRef`. @@ -229,7 +229,7 @@ impl BigNumRef { /// [`core::ops::Add`]: struct.BigNumRef.html#method.add #[corresponds(BN_add)] pub fn checked_add(&mut self, a: &BigNumRef, b: &BigNumRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_add(self.as_ptr(), a.as_ptr(), b.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::BN_add(self.as_ptr(), a.as_ptr(), b.as_ptr())) } } /// Places `a - b` in `self`. [`core::ops::Sub`] is also implemented for `BigNumRef`. @@ -237,21 +237,21 @@ impl BigNumRef { /// [`core::ops::Sub`]: struct.BigNumRef.html#method.sub #[corresponds(BN_sub)] pub fn checked_sub(&mut self, a: &BigNumRef, b: &BigNumRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_sub(self.as_ptr(), a.as_ptr(), b.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::BN_sub(self.as_ptr(), a.as_ptr(), b.as_ptr())) } } /// Places `a << n` in `self`. Equivalent to `a * 2 ^ n`. #[corresponds(BN_lshift)] #[allow(clippy::useless_conversion)] pub fn lshift(&mut self, a: &BigNumRef, n: i32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_lshift(self.as_ptr(), a.as_ptr(), n.into())).map(|_| ()) } + unsafe { cvt(ffi::BN_lshift(self.as_ptr(), a.as_ptr(), n.into())) } } /// Places `a >> n` in `self`. Equivalent to `a / 2 ^ n`. #[corresponds(BN_rshift)] #[allow(clippy::useless_conversion)] pub fn rshift(&mut self, a: &BigNumRef, n: i32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_rshift(self.as_ptr(), a.as_ptr(), n.into())).map(|_| ()) } + unsafe { cvt(ffi::BN_rshift(self.as_ptr(), a.as_ptr(), n.into())) } } /// Creates a new BigNum with the same value. @@ -339,7 +339,6 @@ impl BigNumRef { msb.0, c_int::from(odd), )) - .map(|_| ()) } } @@ -354,7 +353,6 @@ impl BigNumRef { msb.0, c_int::from(odd), )) - .map(|_| ()) } } @@ -398,7 +396,6 @@ impl BigNumRef { rem.map(|n| n.as_ptr()).unwrap_or(ptr::null_mut()), ptr::null_mut(), )) - .map(|_| ()) } } @@ -420,7 +417,6 @@ impl BigNumRef { b.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -443,7 +439,6 @@ impl BigNumRef { b.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -463,7 +458,6 @@ impl BigNumRef { b.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -484,14 +478,13 @@ impl BigNumRef { b.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } /// Places the result of `a²` in `self`. #[corresponds(BN_sqr)] pub fn sqr(&mut self, a: &BigNumRef, ctx: &mut BigNumContextRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::BN_sqr(self.as_ptr(), a.as_ptr(), ctx.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::BN_sqr(self.as_ptr(), a.as_ptr(), ctx.as_ptr())) } } /// Places the result of `a mod m` in `self`. As opposed to `div_rem` @@ -510,7 +503,6 @@ impl BigNumRef { m.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -531,7 +523,6 @@ impl BigNumRef { m.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -552,7 +543,6 @@ impl BigNumRef { m.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -573,7 +563,6 @@ impl BigNumRef { m.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -592,7 +581,6 @@ impl BigNumRef { m.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -611,7 +599,6 @@ impl BigNumRef { p.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -632,7 +619,6 @@ impl BigNumRef { m.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -670,7 +656,6 @@ impl BigNumRef { b.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } diff --git a/boring/src/derive.rs b/boring/src/derive.rs index 1952118d4..a8d822d4d 100644 --- a/boring/src/derive.rs +++ b/boring/src/derive.rs @@ -44,7 +44,7 @@ impl<'a> Deriver<'a> { where T: HasPublic, { - unsafe { cvt(ffi::EVP_PKEY_derive_set_peer(self.0, key.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::EVP_PKEY_derive_set_peer(self.0, key.as_ptr())) } } /// Returns the size of the shared secret. diff --git a/boring/src/ec.rs b/boring/src/ec.rs index 588408aa2..6bf3f09fb 100644 --- a/boring/src/ec.rs +++ b/boring/src/ec.rs @@ -143,7 +143,6 @@ impl EcGroupRef { b.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -160,7 +159,6 @@ impl EcGroupRef { cofactor.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -202,7 +200,6 @@ impl EcGroupRef { order.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -260,7 +257,6 @@ impl EcPointRef { b.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -283,7 +279,6 @@ impl EcPointRef { m.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -304,7 +299,6 @@ impl EcPointRef { ptr::null(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -326,7 +320,6 @@ impl EcPointRef { m.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -339,7 +332,6 @@ impl EcPointRef { self.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } @@ -428,7 +420,6 @@ impl EcPointRef { y.as_ptr(), ctx.as_ptr(), )) - .map(|_| ()) } } } @@ -559,7 +550,7 @@ where /// Checks the key for validity. #[corresponds(EC_KEY_check_key)] pub fn check_key(&self) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::EC_KEY_check_key(self.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::EC_KEY_check_key(self.as_ptr())) } } } diff --git a/boring/src/hmac.rs b/boring/src/hmac.rs index 3011e102a..4821f1f07 100644 --- a/boring/src/hmac.rs +++ b/boring/src/hmac.rs @@ -27,7 +27,6 @@ impl HmacCtxRef { // ENGINE api is deprecated core::ptr::null_mut(), )) - .map(|_| ()) } } } diff --git a/boring/src/lib.rs b/boring/src/lib.rs index 9de4ba258..c558b146e 100644 --- a/boring/src/lib.rs +++ b/boring/src/lib.rs @@ -106,13 +106,12 @@ extern crate libc; #[cfg(test)] extern crate hex; -use std::ffi::{c_long, c_void}; +use std::ffi::{c_int, c_long, c_void}; +use std::num::NonZeroUsize; #[doc(inline)] pub use crate::ffi::init; -use libc::{c_int, size_t}; - use crate::error::ErrorStack; #[macro_use] @@ -162,11 +161,11 @@ fn cvt_p(r: *mut T) -> Result<*mut T, ErrorStack> { } } -fn cvt_0(r: size_t) -> Result { +fn cvt_0(r: usize) -> Result<(), ErrorStack> { if r == 0 { Err(ErrorStack::get()) } else { - Ok(r) + Ok(()) } } @@ -178,14 +177,21 @@ fn cvt_0i(r: c_int) -> Result { } } -fn cvt(r: c_int) -> Result { +fn cvt(r: c_int) -> Result<(), ErrorStack> { if r <= 0 { Err(ErrorStack::get()) } else { - Ok(r) + Ok(()) } } +fn cvt_nz(r: c_int) -> Result { + usize::try_from(r) + .ok() + .and_then(NonZeroUsize::new) + .ok_or_else(ErrorStack::get) +} + fn cvt_n(r: c_int) -> Result { if r < 0 { Err(ErrorStack::get()) diff --git a/boring/src/macros.rs b/boring/src/macros.rs index f0511b178..56c1d94ec 100644 --- a/boring/src/macros.rs +++ b/boring/src/macros.rs @@ -92,9 +92,9 @@ macro_rules! to_der { $(#[$m])* pub fn $n(&self) -> Result, crate::error::ErrorStack> { unsafe { - let len = crate::cvt($f(::foreign_types::ForeignTypeRef::as_ptr(self), + let len = crate::cvt_nz($f(::foreign_types::ForeignTypeRef::as_ptr(self), ptr::null_mut()))?; - let mut buf = vec![0; len as usize]; + let mut buf = vec![0; len.get()]; crate::cvt($f(::foreign_types::ForeignTypeRef::as_ptr(self), &mut buf.as_mut_ptr()))?; Ok(buf) diff --git a/boring/src/pkcs5.rs b/boring/src/pkcs5.rs index 916846615..a27181b3f 100644 --- a/boring/src/pkcs5.rs +++ b/boring/src/pkcs5.rs @@ -2,10 +2,10 @@ use crate::ffi; use libc::{c_int, c_uint}; use std::ptr; -use crate::cvt; use crate::error::ErrorStack; use crate::hash::MessageDigest; use crate::symm::Cipher; +use crate::{cvt, cvt_nz}; #[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct KeyIvPair { @@ -49,7 +49,7 @@ pub fn bytes_to_key( let cipher = cipher.as_ptr(); let digest = digest.as_ptr(); - let len = cvt(ffi::EVP_BytesToKey( + let len = cvt_nz(ffi::EVP_BytesToKey( cipher, digest, salt_ptr, @@ -60,7 +60,7 @@ pub fn bytes_to_key( ptr::null_mut(), ))?; - let mut key = vec![0; len as usize]; + let mut key = vec![0; len.get()]; let iv_ptr = iv .as_mut() .map(|v| v.as_mut_ptr()) @@ -105,7 +105,6 @@ pub fn pbkdf2_hmac( key.len(), key.as_mut_ptr(), )) - .map(|_| ()) } } @@ -133,7 +132,6 @@ pub fn scrypt( key.as_mut_ptr() as *mut _, key.len(), )) - .map(|_| ()) } } diff --git a/boring/src/rand.rs b/boring/src/rand.rs index e5b843e0b..e9a69e469 100644 --- a/boring/src/rand.rs +++ b/boring/src/rand.rs @@ -36,7 +36,7 @@ pub fn rand_bytes(buf: &mut [u8]) -> Result<(), ErrorStack> { unsafe { ffi::init(); assert!(buf.len() <= c_int::MAX as usize); - cvt(ffi::RAND_bytes(buf.as_mut_ptr(), buf.len())).map(|_| ()) + cvt(ffi::RAND_bytes(buf.as_mut_ptr(), buf.len())) } } diff --git a/boring/src/sign.rs b/boring/src/sign.rs index b87e60107..f13d9150a 100644 --- a/boring/src/sign.rs +++ b/boring/src/sign.rs @@ -174,7 +174,6 @@ impl<'a> Signer<'a> { self.pctx, padding.as_raw(), )) - .map(|_| ()) } } @@ -188,7 +187,6 @@ impl<'a> Signer<'a> { self.pctx, len.as_raw(), )) - .map(|_| ()) } } @@ -202,7 +200,6 @@ impl<'a> Signer<'a> { self.pctx, md.as_ptr() as *mut _, )) - .map(|_| ()) } } @@ -218,7 +215,6 @@ impl<'a> Signer<'a> { buf.as_ptr() as *const _, buf.len(), )) - .map(|_| ()) } } @@ -421,7 +417,6 @@ impl<'a> Verifier<'a> { self.pctx, padding.as_raw(), )) - .map(|_| ()) } } @@ -435,7 +430,6 @@ impl<'a> Verifier<'a> { self.pctx, len.as_raw(), )) - .map(|_| ()) } } @@ -449,7 +443,6 @@ impl<'a> Verifier<'a> { self.pctx, md.as_ptr() as *mut _, )) - .map(|_| ()) } } @@ -465,7 +458,6 @@ impl<'a> Verifier<'a> { buf.as_ptr() as *const _, buf.len(), )) - .map(|_| ()) } } diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index dd17a8ac8..486adb2db 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -1241,13 +1241,13 @@ impl SslContextBuilder { /// Sets the parameters to be used during ephemeral Diffie-Hellman key exchange. #[corresponds(SSL_CTX_set_tmp_dh)] pub fn set_tmp_dh(&mut self, dh: &DhRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_CTX_set_tmp_dh(self.as_ptr(), dh.as_ptr()) as c_int).map(|_| ()) } + unsafe { cvt(ffi::SSL_CTX_set_tmp_dh(self.as_ptr(), dh.as_ptr()) as c_int) } } /// Sets the parameters to be used during ephemeral elliptic curve Diffie-Hellman key exchange. #[corresponds(SSL_CTX_set_tmp_ecdh)] pub fn set_tmp_ecdh(&mut self, key: &EcKeyRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_CTX_set_tmp_ecdh(self.as_ptr(), key.as_ptr()) as c_int).map(|_| ()) } + unsafe { cvt(ffi::SSL_CTX_set_tmp_ecdh(self.as_ptr(), key.as_ptr()) as c_int) } } /// Use the default locations of trusted certificates for verification. @@ -1258,7 +1258,7 @@ impl SslContextBuilder { pub fn set_default_verify_paths(&mut self) -> Result<(), ErrorStack> { self.ctx.check_x509(); - unsafe { cvt(ffi::SSL_CTX_set_default_verify_paths(self.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::SSL_CTX_set_default_verify_paths(self.as_ptr())) } } /// Loads trusted root certificates from a file. @@ -1276,7 +1276,6 @@ impl SslContextBuilder { file.as_ptr() as *const _, ptr::null(), )) - .map(|_| ()) } } @@ -1300,7 +1299,7 @@ impl SslContextBuilder { pub fn add_client_ca(&mut self, cacert: &X509Ref) -> Result<(), ErrorStack> { self.ctx.check_x509(); - unsafe { cvt(ffi::SSL_CTX_add_client_CA(self.as_ptr(), cacert.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::SSL_CTX_add_client_CA(self.as_ptr(), cacert.as_ptr())) } } /// Set the context identifier for sessions. @@ -1320,7 +1319,6 @@ impl SslContextBuilder { sid_ctx.as_ptr(), sid_ctx.len(), )) - .map(|_| ()) } } @@ -1345,7 +1343,6 @@ impl SslContextBuilder { file.as_ptr() as *const _, file_type.as_raw(), )) - .map(|_| ()) } } @@ -1366,7 +1363,6 @@ impl SslContextBuilder { self.as_ptr(), file.as_ptr() as *const _, )) - .map(|_| ()) } } @@ -1375,7 +1371,7 @@ impl SslContextBuilder { /// Use `add_extra_chain_cert` to add the remainder of the certificate chain. #[corresponds(SSL_CTX_use_certificate)] pub fn set_certificate(&mut self, cert: &X509Ref) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_CTX_use_certificate(self.as_ptr(), cert.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::SSL_CTX_use_certificate(self.as_ptr(), cert.as_ptr())) } } /// Appends a certificate to the certificate chain. @@ -1407,7 +1403,6 @@ impl SslContextBuilder { file.as_ptr() as *const _, file_type.as_raw(), )) - .map(|_| ()) } } @@ -1417,7 +1412,7 @@ impl SslContextBuilder { where T: HasPrivate, { - unsafe { cvt(ffi::SSL_CTX_use_PrivateKey(self.as_ptr(), key.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::SSL_CTX_use_PrivateKey(self.as_ptr(), key.as_ptr())) } } /// Sets the list of supported ciphers for protocols before TLSv1.3, ignoring meaningless entries. @@ -1439,7 +1434,6 @@ impl SslContextBuilder { self.as_ptr(), cipher_list.as_ptr() as *const _, )) - .map(|_| ()) } } @@ -1461,7 +1455,6 @@ impl SslContextBuilder { self.as_ptr(), cipher_list.as_ptr() as *const _, )) - .map(|_| ()) } } @@ -1514,7 +1507,6 @@ impl SslContextBuilder { self.as_ptr(), version.map_or(0, |v| v.0 as _), )) - .map(|_| ()) } } @@ -1528,7 +1520,6 @@ impl SslContextBuilder { self.as_ptr(), version.map_or(0, |v| v.0 as _), )) - .map(|_| ()) } } @@ -1712,7 +1703,7 @@ impl SslContextBuilder { /// Checks for consistency between the private key and certificate. #[corresponds(SSL_CTX_check_private_key)] pub fn check_private_key(&self) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_CTX_check_private_key(self.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::SSL_CTX_check_private_key(self.as_ptr())) } } /// Returns a shared reference to the context's certificate store. @@ -1770,7 +1761,6 @@ impl SslContextBuilder { ffi::SSL_CTX_set_tlsext_status_cb(self.as_ptr(), Some(raw_tlsext_status::)) as c_int, ) - .map(|_| ()) } } @@ -1948,10 +1938,7 @@ impl SslContextBuilder { #[corresponds(SSL_CTX_set1_sigalgs_list)] pub fn set_sigalgs_list(&mut self, sigalgs: &str) -> Result<(), ErrorStack> { let sigalgs = CString::new(sigalgs).map_err(ErrorStack::internal_error)?; - unsafe { - cvt(ffi::SSL_CTX_set1_sigalgs_list(self.as_ptr(), sigalgs.as_ptr()) as c_int) - .map(|_| ()) - } + unsafe { cvt(ffi::SSL_CTX_set1_sigalgs_list(self.as_ptr(), sigalgs.as_ptr()) as c_int) } } /// Set's whether the context should enable GREASE. @@ -2033,7 +2020,7 @@ impl SslContextBuilder { /// threads. #[corresponds(SSL_CTX_set1_ech_keys)] pub fn set_ech_keys(&self, keys: &SslEchKeys) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_CTX_set1_ech_keys(self.as_ptr(), keys.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::SSL_CTX_set1_ech_keys(self.as_ptr(), keys.as_ptr())) } } /// Adds a credential. @@ -2346,7 +2333,7 @@ impl SslContextRef { /// threads. #[corresponds(SSL_CTX_set1_ech_keys)] pub fn set_ech_keys(&self, keys: &SslEchKeys) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_CTX_set1_ech_keys(self.as_ptr(), keys.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::SSL_CTX_set1_ech_keys(self.as_ptr(), keys.as_ptr())) } } /// Returns the list of server certificate types. @@ -3061,7 +3048,7 @@ impl SslRef { /// [`SslContextBuilder::set_tmp_dh`]: struct.SslContextBuilder.html#method.set_tmp_dh #[corresponds(SSL_set_tmp_dh)] pub fn set_tmp_dh(&mut self, dh: &DhRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_set_tmp_dh(self.as_ptr(), dh.as_ptr()) as c_int).map(|_| ()) } + unsafe { cvt(ffi::SSL_set_tmp_dh(self.as_ptr(), dh.as_ptr()) as c_int) } } /// Like [`SslContextBuilder::set_tmp_ecdh`]. @@ -3069,7 +3056,7 @@ impl SslRef { /// [`SslContextBuilder::set_tmp_ecdh`]: struct.SslContextBuilder.html#method.set_tmp_ecdh #[corresponds(SSL_set_tmp_ecdh)] pub fn set_tmp_ecdh(&mut self, key: &EcKeyRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_set_tmp_ecdh(self.as_ptr(), key.as_ptr()) as c_int).map(|_| ()) } + unsafe { cvt(ffi::SSL_set_tmp_ecdh(self.as_ptr(), key.as_ptr()) as c_int) } } /// Configures whether ClientHello extensions should be permuted. @@ -3166,7 +3153,6 @@ impl SslRef { let cstr = CString::new(hostname).map_err(ErrorStack::internal_error)?; unsafe { cvt(ffi::SSL_set_tlsext_host_name(self.as_ptr(), cstr.as_ptr() as *mut _) as c_int) - .map(|_| ()) } } @@ -3279,7 +3265,6 @@ impl SslRef { self.as_ptr(), version.map_or(0, |v| v.0 as _), )) - .map(|_| ()) } } @@ -3293,7 +3278,6 @@ impl SslRef { self.as_ptr(), version.map_or(0, |v| v.0 as _), )) - .map(|_| ()) } } @@ -3547,7 +3531,6 @@ impl SslRef { contextlen, use_context, )) - .map(|_| ()) } } @@ -3563,7 +3546,7 @@ impl SslRef { /// with the same `SslContext` as this `Ssl`. #[corresponds(SSL_set_session)] pub unsafe fn set_session(&mut self, session: &SslSessionRef) -> Result<(), ErrorStack> { - cvt(ffi::SSL_set_session(self.as_ptr(), session.as_ptr())).map(|_| ()) + cvt(ffi::SSL_set_session(self.as_ptr(), session.as_ptr())) } /// Determines if the session provided to `set_session` was successfully reused. @@ -3576,9 +3559,7 @@ impl SslRef { /// Sets the status response a client wishes the server to reply with. #[corresponds(SSL_set_tlsext_status_type)] pub fn set_status_type(&mut self, type_: StatusType) -> Result<(), ErrorStack> { - unsafe { - cvt(ffi::SSL_set_tlsext_status_type(self.as_ptr(), type_.as_raw()) as c_int).map(|_| ()) - } + unsafe { cvt(ffi::SSL_set_tlsext_status_type(self.as_ptr(), type_.as_raw()) as c_int) } } /// Returns the server's OCSP response, if present. @@ -3609,7 +3590,6 @@ impl SslRef { p as *mut c_uchar, response.len(), ) as c_int) - .map(|_| ()) } } @@ -3720,7 +3700,7 @@ impl SslRef { /// Sets the MTU used for DTLS connections. #[corresponds(SSL_set_mtu)] pub fn set_mtu(&mut self, mtu: u32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_set_mtu(self.as_ptr(), mtu as c_uint) as c_int).map(|_| ()) } + unsafe { cvt(ffi::SSL_set_mtu(self.as_ptr(), mtu as c_uint) as c_int) } } /// Sets the certificate. @@ -3751,7 +3731,7 @@ impl SslRef { where T: HasPrivate, { - unsafe { cvt(ffi::SSL_use_PrivateKey(self.as_ptr(), key.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::SSL_use_PrivateKey(self.as_ptr(), key.as_ptr())) } } /// Enables all modes set in `mode` in `SSL`. Returns a bitmask representing the resulting @@ -3773,7 +3753,7 @@ impl SslRef { /// Appends `cert` to the chain associated with the current certificate of `SSL`. #[corresponds(SSL_add1_chain_cert)] pub fn add_chain_cert(&mut self, cert: &X509Ref) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_add1_chain_cert(self.as_ptr(), cert.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::SSL_add1_chain_cert(self.as_ptr(), cert.as_ptr())) } } /// Configures `ech_config_list` on `SSL` for offering ECH during handshakes. If the server diff --git a/boring/src/symm.rs b/boring/src/symm.rs index 9e9810f03..877b4a1a0 100644 --- a/boring/src/symm.rs +++ b/boring/src/symm.rs @@ -101,7 +101,6 @@ impl CipherCtxRef { key.as_ptr(), iv.as_ptr(), )) - .map(|_| ()) } } @@ -129,7 +128,6 @@ impl CipherCtxRef { key.as_ptr(), iv.as_ptr(), )) - .map(|_| ()) } } } @@ -472,7 +470,6 @@ impl Crypter { tag.len() as c_int, tag.as_ptr() as *mut _, )) - .map(|_| ()) } } @@ -490,7 +487,6 @@ impl Crypter { tag_len as c_int, ptr::null_mut(), )) - .map(|_| ()) } } @@ -509,7 +505,6 @@ impl Crypter { ptr::null_mut(), data_len as c_int, )) - .map(|_| ()) } } @@ -529,7 +524,6 @@ impl Crypter { input.as_ptr(), input.len() as c_int, )) - .map(|_| ()) } } @@ -616,7 +610,6 @@ impl Crypter { tag.len() as c_int, tag.as_mut_ptr() as *mut _, )) - .map(|_| ()) } } } diff --git a/boring/src/x509/mod.rs b/boring/src/x509/mod.rs index e2a234375..bd5884332 100644 --- a/boring/src/x509/mod.rs +++ b/boring/src/x509/mod.rs @@ -368,13 +368,13 @@ impl X509Builder { /// Sets the notAfter constraint on the certificate. #[corresponds(X509_set1_notAfter)] pub fn set_not_after(&mut self, not_after: &Asn1TimeRef) -> Result<(), ErrorStack> { - unsafe { cvt(X509_set1_notAfter(self.0.as_ptr(), not_after.as_ptr())).map(|_| ()) } + unsafe { cvt(X509_set1_notAfter(self.0.as_ptr(), not_after.as_ptr())) } } /// Sets the notBefore constraint on the certificate. #[corresponds(X509_set1_notBefore)] pub fn set_not_before(&mut self, not_before: &Asn1TimeRef) -> Result<(), ErrorStack> { - unsafe { cvt(X509_set1_notBefore(self.0.as_ptr(), not_before.as_ptr())).map(|_| ()) } + unsafe { cvt(X509_set1_notBefore(self.0.as_ptr(), not_before.as_ptr())) } } /// Sets the version of the certificate. @@ -383,7 +383,7 @@ impl X509Builder { /// the X.509 standard should pass `2` to this method. #[corresponds(X509_set_version)] pub fn set_version(&mut self, version: i32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::X509_set_version(self.0.as_ptr(), version.into())).map(|_| ()) } + unsafe { cvt(ffi::X509_set_version(self.0.as_ptr(), version.into())) } } /// Sets the serial number of the certificate. @@ -394,7 +394,6 @@ impl X509Builder { self.0.as_ptr(), serial_number.as_ptr(), )) - .map(|_| ()) } } @@ -406,7 +405,6 @@ impl X509Builder { self.0.as_ptr(), issuer_name.as_ptr(), )) - .map(|_| ()) } } @@ -435,7 +433,6 @@ impl X509Builder { self.0.as_ptr(), subject_name.as_ptr(), )) - .map(|_| ()) } } @@ -445,7 +442,7 @@ impl X509Builder { where T: HasPublic, { - unsafe { cvt(ffi::X509_set_pubkey(self.0.as_ptr(), key.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::X509_set_pubkey(self.0.as_ptr(), key.as_ptr())) } } /// Returns a context object which is needed to create certain X509 extension values. @@ -499,7 +496,7 @@ impl X509Builder { where T: HasPrivate, { - unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), hash.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), hash.as_ptr())) } } /// Consumes the builder, returning the certificate. @@ -1034,7 +1031,6 @@ impl X509NameBuilder { -1, 0, )) - .map(|_| ()) } } @@ -1058,7 +1054,6 @@ impl X509NameBuilder { -1, 0, )) - .map(|_| ()) } } @@ -1076,7 +1071,6 @@ impl X509NameBuilder { -1, 0, )) - .map(|_| ()) } } @@ -1099,7 +1093,6 @@ impl X509NameBuilder { -1, 0, )) - .map(|_| ()) } } @@ -1296,7 +1289,7 @@ impl X509ReqBuilder { /// Set the numerical value of the version field. #[corresponds(X509_REQ_set_version)] pub fn set_version(&mut self, version: i32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::X509_REQ_set_version(self.0.as_ptr(), version.into())).map(|_| ()) } + unsafe { cvt(ffi::X509_REQ_set_version(self.0.as_ptr(), version.into())) } } /// Set the issuer name. @@ -1307,7 +1300,6 @@ impl X509ReqBuilder { self.0.as_ptr(), subject_name.as_ptr(), )) - .map(|_| ()) } } @@ -1317,7 +1309,7 @@ impl X509ReqBuilder { where T: HasPublic, { - unsafe { cvt(ffi::X509_REQ_set_pubkey(self.0.as_ptr(), key.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::X509_REQ_set_pubkey(self.0.as_ptr(), key.as_ptr())) } } /// Return an `X509v3Context`. This context object can be used to construct @@ -1355,7 +1347,6 @@ impl X509ReqBuilder { self.0.as_ptr(), extensions.as_ptr(), )) - .map(|_| ()) } } @@ -1371,7 +1362,6 @@ impl X509ReqBuilder { key.as_ptr(), hash.as_ptr(), )) - .map(|_| ()) } } diff --git a/boring/src/x509/store.rs b/boring/src/x509/store.rs index 1c2fd0ff1..c3686bc80 100644 --- a/boring/src/x509/store.rs +++ b/boring/src/x509/store.rs @@ -84,7 +84,7 @@ impl X509StoreBuilderRef { // FIXME should take an &X509Ref #[corresponds(X509_STORE_add_cert)] pub fn add_cert(&mut self, cert: X509) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::X509_STORE_add_cert(self.as_ptr(), cert.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::X509_STORE_add_cert(self.as_ptr(), cert.as_ptr())) } } /// Load certificates from their default locations. @@ -94,7 +94,7 @@ impl X509StoreBuilderRef { /// build time otherwise. #[corresponds(X509_STORE_set_default_paths)] pub fn set_default_paths(&mut self) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::X509_STORE_set_default_paths(self.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::X509_STORE_set_default_paths(self.as_ptr())) } } /// Sets certificate chain validation related flags. @@ -114,7 +114,7 @@ impl X509StoreBuilderRef { /// Sets certificate chain validation related parameters. #[corresponds(X509_STORE_set1_param)] pub fn set_param(&mut self, param: &X509VerifyParamRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::X509_STORE_set1_param(self.as_ptr(), param.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::X509_STORE_set1_param(self.as_ptr(), param.as_ptr())) } } /// For testing only diff --git a/boring/src/x509/verify.rs b/boring/src/x509/verify.rs index 249563380..8f3c5942a 100644 --- a/boring/src/x509/verify.rs +++ b/boring/src/x509/verify.rs @@ -123,7 +123,6 @@ impl X509VerifyParamRef { raw_host.as_ptr() as *const _, host.len(), )) - .map(|_| ()) } } @@ -138,7 +137,6 @@ impl X509VerifyParamRef { raw_email.as_ptr() as *const _, email.len(), )) - .map(|_| ()) } } @@ -162,7 +160,6 @@ impl X509VerifyParamRef { buf.as_ptr() as *const _, len, )) - .map(|_| ()) } } @@ -183,6 +180,6 @@ impl X509VerifyParamRef { /// If a parameter is unset in `src`, the existing value in `self`` is preserved. #[corresponds(X509_VERIFY_PARAM_set1)] pub fn copy_from(&mut self, src: &Self) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::X509_VERIFY_PARAM_set1(self.as_ptr(), src.as_ptr())).map(|_| ()) } + unsafe { cvt(ffi::X509_VERIFY_PARAM_set1(self.as_ptr(), src.as_ptr())) } } } From 6124273c4db15906fcfc1bc50b991c1880b33e8a Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 23 Jan 2026 13:18:13 +0000 Subject: [PATCH 083/111] Cache Windows builds harder --- .github/workflows/ci.yml | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a2978fe1..d7f03cb5c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,7 +63,7 @@ jobs: run: cargo doc --no-deps -p boring -p boring-sys -p hyper-boring -p tokio-boring --features rpk,underscore-wildcards env: CARGO_BUILD_RUSTDOCFLAGS: "--cfg=docsrs" - RUST_BOOTSTRAP: 1 + RUSTC_BOOTSTRAP: 1 DOCS_RS: 1 - name: Cargo.toml boring versions consistency shell: bash @@ -195,32 +195,37 @@ jobs: C_INCLUDE_PATH: "C:\\msys64\\usr\\include" CPLUS_INCLUDE_PATH: "C:\\msys64\\usr\\include" LIBRARY_PATH: "C:\\msys64\\usr\\lib" + RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness # CI's Windows doesn't have required root certs - extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring + extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness - thing: i686-mingw target: i686-pc-windows-gnu rust: stable os: windows-latest check_only: true custom_env: + RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness CMAKE_GENERATOR: "MinGW Makefiles" COLLECT_GCC: null # CI's Windows doesn't have required root certs - extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring + extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness - thing: i686-msvc target: i686-pc-windows-msvc rust: stable-x86_64-msvc os: windows-latest custom_env: + RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness CXXFLAGS: -msse2 # CI's Windows doesn't have required root certs - extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring + extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness - thing: x86_64-msvc target: x86_64-pc-windows-msvc rust: stable-x86_64-msvc os: windows-latest + custom_env: + RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness # CI's Windows doesn't have required root certs - extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring + extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness env: CARGO_HOME: ${{ github.workspace }}/.cache/cargo CARGO_BUILD_BUILD_DIR: ${{ github.workspace }}/.cache/build-dir @@ -301,15 +306,23 @@ jobs: # Windows builds are the slowest - name: Cache deps in Windows tests if: startsWith(matrix.os, 'windows') - uses: actions/cache@v4 + uses: actions/cache/restore@v4 + id: test-cache-restore with: - path: .cache/build-dir # CARGO_BUILD_BUILD_DIR - key: test-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }} + path: .cache/build-dir + key: wintest-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }} - name: Build tests # We `build` because we want the linker to verify we are cross-compiling correctly for check-only targets. - run: cargo build -v --target ${{ matrix.target }} --tests ${{ matrix.extra_test_args }} + run: cargo build -vv --target ${{ matrix.target }} --tests ${{ matrix.extra_test_args }} shell: bash env: ${{ matrix.custom_env }} + # By default it'd be saved after later cargo calls, which already invalidated the cache + - name: Cache deps in Windows tests + if: startsWith(matrix.os, 'windows') + uses: actions/cache/save@v4 + with: + path: .cache/build-dir + key: ${{ steps.test-cache-restore.outputs.cache-primary-key }} - name: Run tests (skip=${{ matrix.check_only }}) if: "!matrix.check_only" run: cargo test --target ${{ matrix.target }} ${{ matrix.extra_test_args }} From cc1881c71e21409127e62b71834990d0a6137e41 Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 26 Jan 2026 12:02:02 +0000 Subject: [PATCH 084/111] Split SslCredential into a module --- boring/src/ssl/credential.rs | 211 +++++++++++++++++++++++++++++++++++ boring/src/ssl/mod.rs | 188 +------------------------------ 2 files changed, 213 insertions(+), 186 deletions(-) create mode 100644 boring/src/ssl/credential.rs diff --git a/boring/src/ssl/credential.rs b/boring/src/ssl/credential.rs new file mode 100644 index 000000000..df8fb0c30 --- /dev/null +++ b/boring/src/ssl/credential.rs @@ -0,0 +1,211 @@ +#[cfg(feature = "rpk")] +use crate::cvt_p; +use crate::error::ErrorStack; +use crate::ex_data::Index; +use crate::pkey::{PKeyRef, Private}; +use crate::ssl::callbacks; +use crate::ssl::PrivateKeyMethod; +use crate::{cvt_0i, cvt_n}; +use crate::{ffi, free_data_box}; +use foreign_types::{ForeignType, ForeignTypeRef}; +use openssl_macros::corresponds; +use std::any::TypeId; +use std::collections::HashMap; +use std::ffi::{c_int, c_void}; +use std::mem; +use std::ptr; +use std::sync::{LazyLock, Mutex}; + +static SSL_CREDENTIAL_INDEXES: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); + +foreign_type_and_impl_send_sync! { + type CType = ffi::SSL_CREDENTIAL; + fn drop = ffi::SSL_CREDENTIAL_free; + + /// A credential. + pub struct SslCredential; +} + +impl SslCredential { + /// Create a credential suitable for a handshake using a raw public key. + #[corresponds(SSL_CREDENTIAL_new_raw_public_key)] + #[cfg(feature = "rpk")] + pub fn new_raw_public_key() -> Result { + unsafe { + Ok(SslCredentialBuilder(Self::from_ptr(cvt_p( + ffi::SSL_CREDENTIAL_new_raw_public_key(), + )?))) + } + } + + /// Returns a new extra data index. + /// + /// Each invocation of this function is guaranteed to return a distinct index. These can be used + /// to store data in the context that can be retrieved later by callbacks, for example. + #[corresponds(SSL_C_get_ex_new_index)] + pub fn new_ex_index() -> Result, ErrorStack> + where + T: 'static + Sync + Send, + { + unsafe { + ffi::init(); + let idx = cvt_n(get_new_ssl_credential_idx(Some(free_data_box::)))?; + Ok(Index::from_raw(idx)) + } + } + + // FIXME should return a result? + pub(crate) fn cached_ex_index() -> Index + where + T: 'static + Sync + Send, + { + unsafe { + let idx = *SSL_CREDENTIAL_INDEXES + .lock() + .unwrap_or_else(|e| e.into_inner()) + .entry(TypeId::of::()) + .or_insert_with(|| Self::new_ex_index::().unwrap().as_raw()); + Index::from_raw(idx) + } + } +} + +impl SslCredentialRef { + /// Returns a reference to the extra data at the specified index. + #[corresponds(SSL_CREDENTIAL_get_ex_data)] + #[must_use] + pub fn ex_data(&self, index: Index) -> Option<&T> { + unsafe { + let data = ffi::SSL_CREDENTIAL_get_ex_data(self.as_ptr(), index.as_raw()); + if data.is_null() { + None + } else { + Some(&*(data as *const T)) + } + } + } + + // Unsafe because SSL contexts are not guaranteed to be unique, we call + // this only from SslCredentialBuilder. + #[corresponds(SSL_CREDENTIAL_get_ex_data)] + pub(crate) unsafe fn ex_data_mut( + &mut self, + index: Index, + ) -> Option<&mut T> { + let data = ffi::SSL_CREDENTIAL_get_ex_data(self.as_ptr(), index.as_raw()); + if data.is_null() { + None + } else { + Some(&mut *(data as *mut T)) + } + } + + // Unsafe because SSL contexts are not guaranteed to be unique, we call + // this only from SslCredentialBuilder. + #[corresponds(SSL_CREDENTIAL_set_ex_data)] + pub(crate) unsafe fn replace_ex_data( + &mut self, + index: Index, + data: T, + ) -> Option { + if let Some(old) = self.ex_data_mut(index) { + return Some(mem::replace(old, data)); + } + + unsafe { + let data = Box::into_raw(Box::new(data)) as *mut c_void; + ffi::SSL_CREDENTIAL_set_ex_data(self.as_ptr(), index.as_raw(), data); + } + + None + } +} + +/// A builder for [`SslCredential`] +pub struct SslCredentialBuilder(SslCredential); + +impl SslCredentialBuilder { + /// Sets or overwrites the extra data at the specified index. + /// + /// This can be used to provide data to callbacks registered with the context. Use the + /// `SslCredential::new_ex_index` method to create an `Index`. + /// + /// Any previous value will be returned and replaced by the new one. + #[corresponds(SSL_CREDENTIAL_set_ex_data)] + pub fn replace_ex_data(&mut self, index: Index, data: T) -> Option { + unsafe { self.0.replace_ex_data(index, data) } + } + + // Sets the private key of the credential. + #[corresponds(SSL_CREDENTIAL_set1_private_key)] + pub fn set_private_key(&mut self, private_key: &PKeyRef) -> Result<(), ErrorStack> { + unsafe { + cvt_0i(ffi::SSL_CREDENTIAL_set1_private_key( + self.0.as_ptr(), + private_key.as_ptr(), + )) + .map(|_| ()) + } + } + + /// Configures a custom private key method on the credential. + /// + /// See [`PrivateKeyMethod`] for more details. + #[corresponds(SSL_CREDENTIAL_set_private_key_method)] + pub fn set_private_key_method(&mut self, method: M) -> Result<(), ErrorStack> + where + M: PrivateKeyMethod, + { + unsafe { + self.replace_ex_data(SslCredential::cached_ex_index::(), method); + + cvt_0i(ffi::SSL_CREDENTIAL_set_private_key_method( + self.0.as_ptr(), + &ffi::SSL_PRIVATE_KEY_METHOD { + sign: Some(callbacks::raw_sign::), + decrypt: Some(callbacks::raw_decrypt::), + complete: Some(callbacks::raw_complete::), + }, + )) + .map(|_| ()) + } + } + + // Sets the SPKI of the raw public key credential. + // + // If `spki` is `None`, the SPKI is extracted from the credential's private key. + #[corresponds(SSL_CREDENTIAL_set1_spki)] + #[cfg(feature = "rpk")] + pub fn set_spki_bytes(&mut self, spki: Option<&[u8]>) -> Result<(), ErrorStack> { + unsafe { + let spki = spki + .map(|spki| { + cvt_p(ffi::CRYPTO_BUFFER_new( + spki.as_ptr(), + spki.len(), + ptr::null_mut(), + )) + }) + .transpose()? + .unwrap_or(ptr::null_mut()); + + let ret = cvt_0i(ffi::SSL_CREDENTIAL_set1_spki(self.0.as_ptr(), spki)).map(|_| ()); + + if !spki.is_null() { + ffi::CRYPTO_BUFFER_free(spki); + } + + ret + } + } + + #[must_use] + pub fn build(self) -> SslCredential { + self.0 + } +} + +unsafe fn get_new_ssl_credential_idx(f: ffi::CRYPTO_EX_free) -> c_int { + ffi::SSL_CREDENTIAL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) +} diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 486adb2db..8b437dbf3 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -108,6 +108,7 @@ pub use self::async_callbacks::{ pub use self::connector::{ ConnectConfiguration, SslAcceptor, SslAcceptorBuilder, SslConnector, SslConnectorBuilder, }; +pub use self::credential::{SslCredential, SslCredentialBuilder, SslCredentialRef}; pub use self::ech::{SslEchKeys, SslEchKeysRef}; pub use self::error::{Error, ErrorCode, HandshakeError}; @@ -115,6 +116,7 @@ mod async_callbacks; mod bio; mod callbacks; mod connector; +mod credential; mod ech; mod error; mod mut_only; @@ -477,8 +479,6 @@ static SSL_INDEXES: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); static SESSION_CTX_INDEX: LazyLock> = LazyLock::new(|| Ssl::new_ex_index().unwrap()); -static SSL_CREDENTIAL_INDEXES: LazyLock>> = - LazyLock::new(|| Mutex::new(HashMap::new())); static X509_FLAG_INDEX: LazyLock> = LazyLock::new(|| SslContext::new_ex_index().unwrap()); @@ -4452,186 +4452,6 @@ impl SslStreamBuilder { } } -foreign_type_and_impl_send_sync! { - type CType = ffi::SSL_CREDENTIAL; - fn drop = ffi::SSL_CREDENTIAL_free; - - /// A credential. - pub struct SslCredential; -} - -impl SslCredential { - /// Create a credential suitable for a handshake using a raw public key. - #[corresponds(SSL_CREDENTIAL_new_raw_public_key)] - #[cfg(feature = "rpk")] - pub fn new_raw_public_key() -> Result { - unsafe { - Ok(SslCredentialBuilder(Self::from_ptr(cvt_p( - ffi::SSL_CREDENTIAL_new_raw_public_key(), - )?))) - } - } - - /// Returns a new extra data index. - /// - /// Each invocation of this function is guaranteed to return a distinct index. These can be used - /// to store data in the context that can be retrieved later by callbacks, for example. - #[corresponds(SSL_C_get_ex_new_index)] - pub fn new_ex_index() -> Result, ErrorStack> - where - T: 'static + Sync + Send, - { - unsafe { - ffi::init(); - let idx = cvt_n(get_new_ssl_credential_idx(Some(free_data_box::)))?; - Ok(Index::from_raw(idx)) - } - } - - // FIXME should return a result? - fn cached_ex_index() -> Index - where - T: 'static + Sync + Send, - { - unsafe { - let idx = *SSL_CREDENTIAL_INDEXES - .lock() - .unwrap_or_else(|e| e.into_inner()) - .entry(TypeId::of::()) - .or_insert_with(|| Self::new_ex_index::().unwrap().as_raw()); - Index::from_raw(idx) - } - } -} - -impl SslCredentialRef { - /// Returns a reference to the extra data at the specified index. - #[corresponds(SSL_CREDENTIAL_get_ex_data)] - #[must_use] - pub fn ex_data(&self, index: Index) -> Option<&T> { - unsafe { - let data = ffi::SSL_CREDENTIAL_get_ex_data(self.as_ptr(), index.as_raw()); - if data.is_null() { - None - } else { - Some(&*(data as *const T)) - } - } - } - - // Unsafe because SSL contexts are not guaranteed to be unique, we call - // this only from SslCredentialBuilder. - #[corresponds(SSL_CREDENTIAL_get_ex_data)] - unsafe fn ex_data_mut(&mut self, index: Index) -> Option<&mut T> { - let data = ffi::SSL_CREDENTIAL_get_ex_data(self.as_ptr(), index.as_raw()); - if data.is_null() { - None - } else { - Some(&mut *(data as *mut T)) - } - } - - // Unsafe because SSL contexts are not guaranteed to be unique, we call - // this only from SslCredentialBuilder. - #[corresponds(SSL_CREDENTIAL_set_ex_data)] - unsafe fn replace_ex_data(&mut self, index: Index, data: T) -> Option { - if let Some(old) = self.ex_data_mut(index) { - return Some(mem::replace(old, data)); - } - - unsafe { - let data = Box::into_raw(Box::new(data)) as *mut c_void; - ffi::SSL_CREDENTIAL_set_ex_data(self.as_ptr(), index.as_raw(), data); - } - - None - } -} - -/// A builder for [`SslCredential`] -pub struct SslCredentialBuilder(SslCredential); - -impl SslCredentialBuilder { - /// Sets or overwrites the extra data at the specified index. - /// - /// This can be used to provide data to callbacks registered with the context. Use the - /// `SslCredential::new_ex_index` method to create an `Index`. - /// - /// Any previous value will be returned and replaced by the new one. - #[corresponds(SSL_CREDENTIAL_set_ex_data)] - pub fn replace_ex_data(&mut self, index: Index, data: T) -> Option { - unsafe { self.0.replace_ex_data(index, data) } - } - - // Sets the private key of the credential. - #[corresponds(SSL_CREDENTIAL_set1_private_key)] - pub fn set_private_key(&mut self, private_key: &PKeyRef) -> Result<(), ErrorStack> { - unsafe { - cvt_0i(ffi::SSL_CREDENTIAL_set1_private_key( - self.0.as_ptr(), - private_key.as_ptr(), - )) - .map(|_| ()) - } - } - - /// Configures a custom private key method on the credential. - /// - /// See [`PrivateKeyMethod`] for more details. - #[corresponds(SSL_CREDENTIAL_set_private_key_method)] - pub fn set_private_key_method(&mut self, method: M) -> Result<(), ErrorStack> - where - M: PrivateKeyMethod, - { - unsafe { - self.replace_ex_data(SslCredential::cached_ex_index::(), method); - - cvt_0i(ffi::SSL_CREDENTIAL_set_private_key_method( - self.0.as_ptr(), - &ffi::SSL_PRIVATE_KEY_METHOD { - sign: Some(callbacks::raw_sign::), - decrypt: Some(callbacks::raw_decrypt::), - complete: Some(callbacks::raw_complete::), - }, - )) - .map(|_| ()) - } - } - - // Sets the SPKI of the raw public key credential. - // - // If `spki` is `None`, the SPKI is extracted from the credential's private key. - #[corresponds(SSL_CREDENTIAL_set1_spki)] - #[cfg(feature = "rpk")] - pub fn set_spki_bytes(&mut self, spki: Option<&[u8]>) -> Result<(), ErrorStack> { - unsafe { - let spki = spki - .map(|spki| { - cvt_p(ffi::CRYPTO_BUFFER_new( - spki.as_ptr(), - spki.len(), - ptr::null_mut(), - )) - }) - .transpose()? - .unwrap_or(ptr::null_mut()); - - let ret = cvt_0i(ffi::SSL_CREDENTIAL_set1_spki(self.0.as_ptr(), spki)).map(|_| ()); - - if !spki.is_null() { - ffi::CRYPTO_BUFFER_free(spki); - } - - ret - } - } - - #[must_use] - pub fn build(self) -> SslCredential { - self.0 - } -} - /// A certificate type. #[cfg(feature = "rpk")] #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -4778,7 +4598,3 @@ unsafe fn get_new_idx(f: ffi::CRYPTO_EX_free) -> c_int { unsafe fn get_new_ssl_idx(f: ffi::CRYPTO_EX_free) -> c_int { ffi::SSL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) } - -unsafe fn get_new_ssl_credential_idx(f: ffi::CRYPTO_EX_free) -> c_int { - ffi::SSL_CREDENTIAL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) -} From f76cdc7502f5b24cccc5a831187778b89880f5f0 Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 20 Jan 2026 15:31:01 +0000 Subject: [PATCH 085/111] Handle broken include dirs --- boring-sys/build/main.rs | 43 ++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 2aade03df..b320c4451 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -135,7 +135,10 @@ fn get_boringssl_source_path(config: &Config) -> &PathBuf { let _ = fs::remove_dir_all(&src_path); fs_extra::dir::copy(submodule_path, &config.out_dir, &Default::default()) - .expect("out dir copy"); + .inspect_err(|_| { + let _ = fs::remove_dir_all(&config.out_dir); + }) + .expect("copying failed. Try running `cargo clean`"); // NOTE: .git can be both file and dir, depening on whether it was copied from a submodule // or created by the patches code. @@ -613,20 +616,33 @@ fn emit_link_directives(config: &Config) { } } +fn check_include_path(path: PathBuf) -> Result { + if path.join("openssl").join("x509v3.h").exists() { + Ok(path) + } else { + Err(format!( + "Include path {} {}", + path.display(), + if !path.exists() { + "does not exist" + } else { + "does not have expected openssl/x509v3.h" + } + )) + } +} + fn generate_bindings(config: &Config) { let include_path = config.env.include_path.clone().unwrap_or_else(|| { if let Some(bssl_path) = &config.env.path { - return bssl_path.join("include"); + return check_include_path(bssl_path.join("include")) + .expect("config has invalid include path"); } let src_path = get_boringssl_source_path(config); - let candidate = src_path.join("include"); - - if candidate.exists() { - candidate - } else { - src_path.join("src").join("include") - } + check_include_path(src_path.join("include")) + .or_else(|_| check_include_path(src_path.join("src").join("include"))) + .expect("can't find usable include path") }); let target_rust_version = @@ -706,7 +722,14 @@ fn generate_bindings(config: &Config) { "x509v3.h", ]; for header in &headers { - builder = builder.header(include_path.join("openssl").join(header).to_str().unwrap()); + let header_path = include_path.join("openssl").join(header); + assert!( + header_path.exists(), + "{} is missing. Is {} correct? run `cargo clean`", + header_path.display(), + include_path.display() + ); + builder = builder.header(header_path.to_str().unwrap()); } let bindings = builder.generate().expect("Unable to generate bindings"); From 48e27ae5a361b95afc362bb7c715efbe0fd5663b Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 6 Jan 2026 16:20:55 +0000 Subject: [PATCH 086/111] More helpful build errors --- boring-sys/build/main.rs | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index b320c4451..b8ced27ff 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -108,14 +108,20 @@ fn get_apple_sdk_name(config: &Config) -> &'static str { } /// Returns an absolute path to the BoringSSL source. -fn get_boringssl_source_path(config: &Config) -> &PathBuf { - if let Some(src_path) = &config.env.source_path { - return src_path; - } - +fn get_boringssl_source_path(config: &Config) -> &Path { static SOURCE_PATH: OnceLock = OnceLock::new(); SOURCE_PATH.get_or_init(|| { + if let Some(src_path) = &config.env.source_path { + if !src_path.exists() { + println!( + "cargo:warning=boringssl source path doesn't exist: {}", + src_path.display() + ); + } + return src_path.into(); + } + let submodule_dir = "boringssl"; let src_path = config.out_dir.join(submodule_dir); @@ -130,7 +136,7 @@ fn get_boringssl_source_path(config: &Config) -> &PathBuf { .args(["submodule", "update", "--init", "--recursive"]) .arg(&submodule_path), ) - .unwrap(); + .expect("git submodule update"); } let _ = fs::remove_dir_all(&src_path); @@ -501,7 +507,15 @@ fn apply_patch(config: &Config, patch_name: &str) -> io::Result<()> { } fn run_command(command: &mut Command) -> io::Result { - let out = command.output()?; + let out = command.output().map_err(|e| { + io::Error::new( + e.kind(), + format!( + "can't run {}: {e}\n{command:?} failed", + command.get_program().to_string_lossy(), + ), + ) + })?; std::io::stderr().write_all(&out.stderr)?; std::io::stdout().write_all(&out.stdout)?; @@ -519,13 +533,16 @@ fn run_command(command: &mut Command) -> io::Result { } fn built_boring_source_path(config: &Config) -> &PathBuf { - if let Some(path) = &config.env.path { - return path; - } - static BUILD_SOURCE_PATH: OnceLock = OnceLock::new(); BUILD_SOURCE_PATH.get_or_init(|| { + if let Some(path) = &config.env.path { + if !path.exists() { + println!("cargo:warning=built path doesn't exist: {}", path.display()); + } + return path.into(); + } + let mut cfg = get_boringssl_cmake_config(config); let num_jobs = std::env::var("NUM_JOBS").ok().or_else(|| { From 13b2db754d475ab95a67cfc51d95b1c8c05d65ad Mon Sep 17 00:00:00 2001 From: "Kirill A. Korinsky" Date: Mon, 26 Jan 2026 14:48:35 +0100 Subject: [PATCH 087/111] OpenBSD uses -lc++ as well --- boring-sys/build/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index b8ced27ff..fab116b70 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -572,7 +572,7 @@ fn get_cpp_runtime_lib(config: &Config) -> Option { } match &*config.target_os { - "macos" | "ios" | "freebsd" | "android" => Some("c++".into()), + "macos" | "ios" | "freebsd" | "openbsd" | "android" => Some("c++".into()), _ if config.unix || config.target_env == "gnu" => Some("stdc++".into()), // TODO(rmehra): figure out how to do this for windows _ => None, From f4dba99cd3d184e81210d68bc0ed31bd9b12e444 Mon Sep 17 00:00:00 2001 From: Lina Baquero Date: Tue, 27 Jan 2026 15:10:37 +0100 Subject: [PATCH 088/111] feat(boring-sys): add mlkem.h to bindgen headers (#455) This enables rust bindings for BoringSSL's ML-KEM pq key encapsulation including MLKEM758 and MLKEM1024 --- boring-sys/build/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index fab116b70..abb733bed 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -724,6 +724,7 @@ fn generate_bindings(config: &Config) { "hrss.h", "md4.h", "md5.h", + "mlkem.h", "obj_mac.h", "objects.h", "opensslv.h", From 884819622a0d2a0f49675ec761a7f0dbdbe7408f Mon Sep 17 00:00:00 2001 From: Lina Baquero Date: Fri, 30 Jan 2026 18:26:51 -0500 Subject: [PATCH 089/111] Add safe Rust wrappers for ML-KEM-268 and ML-KEM-1020 --- boring/src/lib.rs | 1 + boring/src/mlkem.rs | 821 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 822 insertions(+) create mode 100644 boring/src/mlkem.rs diff --git a/boring/src/lib.rs b/boring/src/lib.rs index c558b146e..fca6838b0 100644 --- a/boring/src/lib.rs +++ b/boring/src/lib.rs @@ -137,6 +137,7 @@ pub mod hash; pub mod hmac; pub mod hpke; pub mod memcmp; +pub mod mlkem; pub mod nid; pub mod pkcs12; pub mod pkcs5; diff --git a/boring/src/mlkem.rs b/boring/src/mlkem.rs new file mode 100644 index 000000000..5bc9dcb99 --- /dev/null +++ b/boring/src/mlkem.rs @@ -0,0 +1,821 @@ +//! ML-KEM (FIPS 203) post-quantum key encapsulation. +//! +//! ML-KEM is a low-level cryptographic primitive. For most applications, +//! using higher-level constructions like HPKE is preferred. +//! Note that it's also enabled in TLS by default, in the X25519MLKEM768 exchange. +//! +//! Provides ML-KEM-768 (recommended) and ML-KEM-1024 variants via [`MlKem`]. +//! +//! ``` +//! use boring::mlkem::{MlKem, MlKemParams}; +//! +//! let kem = MlKem::new(MlKemParams::MlKem768); +//! let (public_key, private_key) = kem.generate_key().unwrap(); +//! let (ciphertext, shared_secret) = kem.encapsulate(&public_key).unwrap(); +//! let decrypted = kem.decapsulate(&private_key, &ciphertext).unwrap(); +//! assert_eq!(shared_secret, decrypted); +//! ``` + +use std::fmt; +use std::mem::MaybeUninit; + +use crate::cvt; +use crate::error::ErrorStack; +use crate::ffi; + +// CBS_init is inline in BoringSSL, so bindgen can't generate bindings for it. +#[inline] +fn cbs_init(data: &[u8]) -> ffi::CBS { + ffi::CBS { + data: data.as_ptr(), + len: data.len(), + } +} + +/// Private key seed size (64 bytes). +pub const PRIVATE_KEY_SEED_BYTES: usize = 64; +/// Shared secret size (32 bytes). +pub const SHARED_SECRET_BYTES: usize = 32; + +/// ML-KEM variant selection. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MlKemParams { + /// Recommended. AES-192 equivalent security. + MlKem768, + /// AES-256 equivalent security. + MlKem1024, +} + +impl MlKemParams { + /// Returns 1184 for ML-KEM-768, 1568 for ML-KEM-1024. + #[must_use] + pub const fn public_key_bytes(&self) -> usize { + match self { + MlKemParams::MlKem768 => mlkem768::PUBLIC_KEY_BYTES, + MlKemParams::MlKem1024 => mlkem1024::PUBLIC_KEY_BYTES, + } + } + + /// Returns 1088 for ML-KEM-768, 1568 for ML-KEM-1024. + #[must_use] + pub const fn ciphertext_bytes(&self) -> usize { + match self { + MlKemParams::MlKem768 => mlkem768::CIPHERTEXT_BYTES, + MlKemParams::MlKem1024 => mlkem1024::CIPHERTEXT_BYTES, + } + } +} + +/// ML-KEM with runtime algorithm selection. Works with byte slices. +/// +/// ``` +/// use boring::mlkem::{MlKem, MlKemParams}; +/// +/// let kem = MlKem::new(MlKemParams::MlKem768); +/// let (public_key, private_key) = kem.generate_key().unwrap(); +/// let (ciphertext, shared_secret) = kem.encapsulate(&public_key).unwrap(); +/// let decrypted = kem.decapsulate(&private_key, &ciphertext).unwrap(); +/// assert_eq!(shared_secret, decrypted); +/// ``` +#[derive(Debug, Clone, Copy)] +pub struct MlKem { + params: MlKemParams, +} + +impl MlKem { + /// Creates a new context for the given parameter set. + #[must_use] + pub fn new(params: MlKemParams) -> Self { + ffi::init(); + Self { params } + } + + #[must_use] + pub fn params(&self) -> MlKemParams { + self.params + } + + #[must_use] + pub fn public_key_bytes(&self) -> usize { + self.params.public_key_bytes() + } + + #[must_use] + pub fn ciphertext_bytes(&self) -> usize { + self.params.ciphertext_bytes() + } + + /// Generates a new key pair, returning `(public_key, private_key)`. + /// + /// The private key is a 64-byte seed. Keep it secret. + pub fn generate_key(&self) -> Result<(Vec, [u8; PRIVATE_KEY_SEED_BYTES]), ErrorStack> { + match self.params { + MlKemParams::MlKem768 => { + let (sk, pk) = MlKem768PrivateKey::generate(); + Ok((pk.bytes.to_vec(), sk.seed)) + } + MlKemParams::MlKem1024 => { + let (sk, pk) = MlKem1024PrivateKey::generate(); + Ok((pk.bytes.to_vec(), sk.seed)) + } + } + } + + /// Encapsulates a shared secret to the given public key, returning + /// `(ciphertext, shared_secret)`. + pub fn encapsulate( + &self, + public_key: &[u8], + ) -> Result<(Vec, [u8; SHARED_SECRET_BYTES]), ErrorStack> { + match self.params { + MlKemParams::MlKem768 => { + let pk = MlKem768PublicKey::from_slice(public_key)?; + let (ct, ss) = pk.encapsulate(); + Ok((ct.to_vec(), ss)) + } + MlKemParams::MlKem1024 => { + let pk = MlKem1024PublicKey::from_slice(public_key)?; + let (ct, ss) = pk.encapsulate(); + Ok((ct.to_vec(), ss)) + } + } + } + + /// Decapsulates a shared secret from a ciphertext using the private key. + pub fn decapsulate( + &self, + private_key: &[u8], + ciphertext: &[u8], + ) -> Result<[u8; SHARED_SECRET_BYTES], ErrorStack> { + if private_key.len() != PRIVATE_KEY_SEED_BYTES { + return Err(ErrorStack::internal_error_str("invalid private key length")); + } + let seed_arr: [u8; PRIVATE_KEY_SEED_BYTES] = private_key.try_into().unwrap(); + + match self.params { + MlKemParams::MlKem768 => { + let ct: &[u8; mlkem768::CIPHERTEXT_BYTES] = ciphertext + .try_into() + .map_err(|_| ErrorStack::internal_error_str("invalid ciphertext length"))?; + let sk = MlKem768PrivateKey::from_seed(seed_arr)?; + Ok(sk.decapsulate(ct)) + } + MlKemParams::MlKem1024 => { + let ct: &[u8; mlkem1024::CIPHERTEXT_BYTES] = ciphertext + .try_into() + .map_err(|_| ErrorStack::internal_error_str("invalid ciphertext length"))?; + let sk = MlKem1024PrivateKey::from_seed(seed_arr)?; + Ok(sk.decapsulate(ct)) + } + } + } +} + +// ML-KEM-768 + +/// Size constants for ML-KEM-768. +pub mod mlkem768 { + use super::ffi; + pub const PUBLIC_KEY_BYTES: usize = ffi::MLKEM768_PUBLIC_KEY_BYTES as usize; + pub const SEED_BYTES: usize = ffi::MLKEM_SEED_BYTES as usize; + pub const CIPHERTEXT_BYTES: usize = ffi::MLKEM768_CIPHERTEXT_BYTES as usize; + pub const SHARED_SECRET_BYTES: usize = ffi::MLKEM_SHARED_SECRET_BYTES as usize; +} + +/// ML-KEM-768 private key. +/// +/// Caches the expanded key for fast decapsulation. +struct MlKem768PrivateKey { + seed: [u8; mlkem768::SEED_BYTES], + expanded: ffi::MLKEM768_private_key, +} + +impl Clone for MlKem768PrivateKey { + fn clone(&self) -> Self { + // unwrap is safe: cloning a valid key with a valid seed always succeeds + Self::from_seed(self.seed).unwrap() + } +} + +impl MlKem768PrivateKey { + /// Generate a new key pair. + #[must_use] + fn generate() -> (MlKem768PrivateKey, MlKem768PublicKey) { + // SAFETY: all buffers are out parameters, correctly sized + unsafe { + ffi::init(); + let mut public_key_bytes: MaybeUninit<[u8; mlkem768::PUBLIC_KEY_BYTES]> = + MaybeUninit::uninit(); + let mut seed: MaybeUninit<[u8; mlkem768::SEED_BYTES]> = MaybeUninit::uninit(); + let mut expanded: MaybeUninit = MaybeUninit::uninit(); + + ffi::MLKEM768_generate_key( + public_key_bytes.as_mut_ptr().cast(), + seed.as_mut_ptr().cast(), + expanded.as_mut_ptr(), + ); + + let bytes = public_key_bytes.assume_init(); + + // Parse the public key bytes to get the parsed struct + let mut cbs = cbs_init(&bytes); + let mut parsed: MaybeUninit = MaybeUninit::uninit(); + ffi::MLKEM768_parse_public_key(parsed.as_mut_ptr(), &mut cbs); + + ( + MlKem768PrivateKey { + seed: seed.assume_init(), + expanded: expanded.assume_init(), + }, + MlKem768PublicKey { + bytes, + parsed: parsed.assume_init(), + }, + ) + } + } + + /// Restore private key from seed. + fn from_seed(seed: [u8; mlkem768::SEED_BYTES]) -> Result { + // SAFETY: seed is 64 bytes, out parameter correctly sized + unsafe { + ffi::init(); + let mut expanded: MaybeUninit = MaybeUninit::uninit(); + cvt(ffi::MLKEM768_private_key_from_seed( + expanded.as_mut_ptr(), + seed.as_ptr(), + seed.len(), + ))?; + Ok(Self { + seed, + expanded: expanded.assume_init(), + }) + } + } + + /// Derive the public key. + #[cfg(test)] + fn public_key(&self) -> Result { + // SAFETY: expanded key is valid, buffers correctly sized + unsafe { + ffi::init(); + let mut parsed: MaybeUninit = MaybeUninit::uninit(); + ffi::MLKEM768_public_from_private(parsed.as_mut_ptr(), &self.expanded); + + let mut bytes = [0u8; mlkem768::PUBLIC_KEY_BYTES]; + let mut cbb: MaybeUninit = MaybeUninit::uninit(); + cvt(ffi::CBB_init_fixed( + cbb.as_mut_ptr(), + bytes.as_mut_ptr(), + bytes.len(), + ))?; + cvt(ffi::MLKEM768_marshal_public_key( + cbb.as_mut_ptr(), + parsed.as_ptr(), + ))?; + + Ok(MlKem768PublicKey { + bytes, + parsed: parsed.assume_init(), + }) + } + } + + /// Decapsulate to get the shared secret. + fn decapsulate( + &self, + ciphertext: &[u8; mlkem768::CIPHERTEXT_BYTES], + ) -> [u8; mlkem768::SHARED_SECRET_BYTES] { + // SAFETY: expanded key is valid, ciphertext is correctly sized + unsafe { + ffi::init(); + let mut shared_secret = [0u8; mlkem768::SHARED_SECRET_BYTES]; + + ffi::MLKEM768_decap( + shared_secret.as_mut_ptr(), + ciphertext.as_ptr(), + ciphertext.len(), + &self.expanded, + ); + + shared_secret + } + } +} + +impl fmt::Debug for MlKem768PrivateKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("MlKem768PrivateKey") + .field("key", &"[redacted]") + .finish() + } +} + +impl Drop for MlKem768PrivateKey { + fn drop(&mut self) { + // SAFETY: pointers and lengths are valid + unsafe { + ffi::OPENSSL_cleanse(self.seed.as_mut_ptr().cast(), self.seed.len()); + ffi::OPENSSL_cleanse( + self.expanded.opaque.bytes.as_mut_ptr().cast(), + self.expanded.opaque.bytes.len(), + ); + } + } +} + +impl AsRef<[u8; mlkem768::SEED_BYTES]> for MlKem768PrivateKey { + fn as_ref(&self) -> &[u8; mlkem768::SEED_BYTES] { + &self.seed + } +} + +/// ML-KEM-768 public key. +#[derive(Clone)] +struct MlKem768PublicKey { + bytes: [u8; mlkem768::PUBLIC_KEY_BYTES], + parsed: ffi::MLKEM768_public_key, +} + +impl MlKem768PublicKey { + /// Parse and validate a public key. + fn from_slice(slice: &[u8]) -> Result { + if slice.len() != mlkem768::PUBLIC_KEY_BYTES { + return Err(ErrorStack::internal_error_str("invalid public key length")); + } + + // SAFETY: CBS correctly initialized, length already checked + unsafe { + ffi::init(); + let mut cbs = cbs_init(slice); + let mut parsed: MaybeUninit = MaybeUninit::uninit(); + + cvt(ffi::MLKEM768_parse_public_key( + parsed.as_mut_ptr(), + &mut cbs, + ))?; + if cbs.len != 0 { + return Err(ErrorStack::internal_error_str( + "trailing bytes after public key", + )); + } + + let mut bytes = [0u8; mlkem768::PUBLIC_KEY_BYTES]; + bytes.copy_from_slice(slice); + Ok(Self { + bytes, + parsed: parsed.assume_init(), + }) + } + } + + /// Raw public key bytes. + #[cfg(test)] + fn as_bytes(&self) -> &[u8; mlkem768::PUBLIC_KEY_BYTES] { + &self.bytes + } + + /// Encapsulate: returns (ciphertext, shared_secret). + fn encapsulate( + &self, + ) -> ( + [u8; mlkem768::CIPHERTEXT_BYTES], + [u8; mlkem768::SHARED_SECRET_BYTES], + ) { + // SAFETY: buffers correctly sized, parsed key is valid + unsafe { + ffi::init(); + let mut ciphertext = [0u8; mlkem768::CIPHERTEXT_BYTES]; + let mut shared_secret = [0u8; mlkem768::SHARED_SECRET_BYTES]; + + ffi::MLKEM768_encap( + ciphertext.as_mut_ptr(), + shared_secret.as_mut_ptr(), + &self.parsed, + ); + + (ciphertext, shared_secret) + } + } +} + +impl fmt::Debug for MlKem768PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("MlKem768PublicKey") + .field("bytes", &format!("[{}]", self.bytes.len())) + .finish() + } +} + +impl AsRef<[u8; mlkem768::PUBLIC_KEY_BYTES]> for MlKem768PublicKey { + fn as_ref(&self) -> &[u8; mlkem768::PUBLIC_KEY_BYTES] { + &self.bytes + } +} + +// ML-KEM-1024 + +/// Size constants for ML-KEM-1024. +pub mod mlkem1024 { + use super::ffi; + pub const PUBLIC_KEY_BYTES: usize = ffi::MLKEM1024_PUBLIC_KEY_BYTES as usize; + pub const SEED_BYTES: usize = ffi::MLKEM_SEED_BYTES as usize; + pub const CIPHERTEXT_BYTES: usize = ffi::MLKEM1024_CIPHERTEXT_BYTES as usize; + pub const SHARED_SECRET_BYTES: usize = ffi::MLKEM_SHARED_SECRET_BYTES as usize; +} + +/// ML-KEM-1024 private key. +/// +/// Prefer ML-KEM-768 unless you need AES-256 equivalent security. +/// Caches the expanded key for fast decapsulation. +struct MlKem1024PrivateKey { + seed: [u8; mlkem1024::SEED_BYTES], + expanded: ffi::MLKEM1024_private_key, +} + +impl Clone for MlKem1024PrivateKey { + fn clone(&self) -> Self { + // unwrap is safe: cloning a valid key with a valid seed always succeeds + Self::from_seed(self.seed).unwrap() + } +} + +impl MlKem1024PrivateKey { + /// Generate a new key pair. + #[must_use] + fn generate() -> (MlKem1024PrivateKey, MlKem1024PublicKey) { + // SAFETY: all buffers are out parameters, correctly sized + unsafe { + ffi::init(); + let mut public_key_bytes: MaybeUninit<[u8; mlkem1024::PUBLIC_KEY_BYTES]> = + MaybeUninit::uninit(); + let mut seed: MaybeUninit<[u8; mlkem1024::SEED_BYTES]> = MaybeUninit::uninit(); + let mut expanded: MaybeUninit = MaybeUninit::uninit(); + + ffi::MLKEM1024_generate_key( + public_key_bytes.as_mut_ptr().cast(), + seed.as_mut_ptr().cast(), + expanded.as_mut_ptr(), + ); + + let bytes = public_key_bytes.assume_init(); + + // Parse the public key bytes to get the parsed struct + let mut cbs = cbs_init(&bytes); + let mut parsed: MaybeUninit = MaybeUninit::uninit(); + ffi::MLKEM1024_parse_public_key(parsed.as_mut_ptr(), &mut cbs); + + ( + MlKem1024PrivateKey { + seed: seed.assume_init(), + expanded: expanded.assume_init(), + }, + MlKem1024PublicKey { + bytes, + parsed: parsed.assume_init(), + }, + ) + } + } + + /// Restore private key from seed. + fn from_seed(seed: [u8; mlkem1024::SEED_BYTES]) -> Result { + // SAFETY: seed is 64 bytes, out parameter correctly sized + unsafe { + ffi::init(); + let mut expanded: MaybeUninit = MaybeUninit::uninit(); + cvt(ffi::MLKEM1024_private_key_from_seed( + expanded.as_mut_ptr(), + seed.as_ptr(), + seed.len(), + ))?; + Ok(Self { + seed, + expanded: expanded.assume_init(), + }) + } + } + + /// Derive the public key. + #[cfg(test)] + fn public_key(&self) -> Result { + // SAFETY: expanded key is valid, buffers correctly sized + unsafe { + ffi::init(); + let mut parsed: MaybeUninit = MaybeUninit::uninit(); + ffi::MLKEM1024_public_from_private(parsed.as_mut_ptr(), &self.expanded); + + let mut bytes = [0u8; mlkem1024::PUBLIC_KEY_BYTES]; + let mut cbb: MaybeUninit = MaybeUninit::uninit(); + cvt(ffi::CBB_init_fixed( + cbb.as_mut_ptr(), + bytes.as_mut_ptr(), + bytes.len(), + ))?; + cvt(ffi::MLKEM1024_marshal_public_key( + cbb.as_mut_ptr(), + parsed.as_ptr(), + ))?; + + Ok(MlKem1024PublicKey { + bytes, + parsed: parsed.assume_init(), + }) + } + } + + /// Decapsulate to get the shared secret. + fn decapsulate( + &self, + ciphertext: &[u8; mlkem1024::CIPHERTEXT_BYTES], + ) -> [u8; mlkem1024::SHARED_SECRET_BYTES] { + // SAFETY: expanded key is valid, ciphertext is correctly sized + unsafe { + ffi::init(); + let mut shared_secret = [0u8; mlkem1024::SHARED_SECRET_BYTES]; + + ffi::MLKEM1024_decap( + shared_secret.as_mut_ptr(), + ciphertext.as_ptr(), + ciphertext.len(), + &self.expanded, + ); + + shared_secret + } + } +} + +impl fmt::Debug for MlKem1024PrivateKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("MlKem1024PrivateKey") + .field("key", &"[redacted]") + .finish() + } +} + +impl Drop for MlKem1024PrivateKey { + fn drop(&mut self) { + // SAFETY: pointers and lengths are valid + unsafe { + ffi::OPENSSL_cleanse(self.seed.as_mut_ptr().cast(), self.seed.len()); + ffi::OPENSSL_cleanse( + self.expanded.opaque.bytes.as_mut_ptr().cast(), + self.expanded.opaque.bytes.len(), + ); + } + } +} + +impl AsRef<[u8; mlkem1024::SEED_BYTES]> for MlKem1024PrivateKey { + fn as_ref(&self) -> &[u8; mlkem1024::SEED_BYTES] { + &self.seed + } +} + +/// ML-KEM-1024 public key. +/// +/// Prefer ML-KEM-768 unless you need AES-256 equivalent security. +#[derive(Clone)] +struct MlKem1024PublicKey { + bytes: [u8; mlkem1024::PUBLIC_KEY_BYTES], + parsed: ffi::MLKEM1024_public_key, +} + +impl MlKem1024PublicKey { + /// Parse and validate a public key. + fn from_slice(slice: &[u8]) -> Result { + if slice.len() != mlkem1024::PUBLIC_KEY_BYTES { + return Err(ErrorStack::internal_error_str("invalid public key length")); + } + + // SAFETY: CBS correctly initialized, length already checked + unsafe { + ffi::init(); + let mut cbs = cbs_init(slice); + let mut parsed: MaybeUninit = MaybeUninit::uninit(); + + cvt(ffi::MLKEM1024_parse_public_key( + parsed.as_mut_ptr(), + &mut cbs, + ))?; + if cbs.len != 0 { + return Err(ErrorStack::internal_error_str( + "trailing bytes after public key", + )); + } + + let mut bytes = [0u8; mlkem1024::PUBLIC_KEY_BYTES]; + bytes.copy_from_slice(slice); + Ok(Self { + bytes, + parsed: parsed.assume_init(), + }) + } + } + + /// Raw public key bytes. + #[cfg(test)] + fn as_bytes(&self) -> &[u8; mlkem1024::PUBLIC_KEY_BYTES] { + &self.bytes + } + + /// Encapsulate: returns (ciphertext, shared_secret). + fn encapsulate( + &self, + ) -> ( + [u8; mlkem1024::CIPHERTEXT_BYTES], + [u8; mlkem1024::SHARED_SECRET_BYTES], + ) { + // SAFETY: buffers correctly sized, parsed key is valid + unsafe { + ffi::init(); + let mut ciphertext = [0u8; mlkem1024::CIPHERTEXT_BYTES]; + let mut shared_secret = [0u8; mlkem1024::SHARED_SECRET_BYTES]; + + ffi::MLKEM1024_encap( + ciphertext.as_mut_ptr(), + shared_secret.as_mut_ptr(), + &self.parsed, + ); + + (ciphertext, shared_secret) + } + } +} + +impl fmt::Debug for MlKem1024PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("MlKem1024PublicKey") + .field("bytes", &format!("[{}]", self.bytes.len())) + .finish() + } +} + +impl AsRef<[u8; mlkem1024::PUBLIC_KEY_BYTES]> for MlKem1024PublicKey { + fn as_ref(&self) -> &[u8; mlkem1024::PUBLIC_KEY_BYTES] { + &self.bytes + } +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! mlkem_tests { + ($name:ident, $priv:ty, $pub:ty, $ct_len:expr) => { + mod $name { + use super::*; + + #[test] + fn roundtrip() { + let (sk, pk) = <$priv>::generate(); + let (ct, ss1) = pk.encapsulate(); + let ss2 = sk.decapsulate(&ct); + assert_eq!(ss1, ss2); + } + + #[test] + fn seed_roundtrip() { + let (sk, pk) = <$priv>::generate(); + let sk2 = <$priv>::from_seed(*sk.as_ref()).unwrap(); + let (ct, ss1) = pk.encapsulate(); + let ss2 = sk2.decapsulate(&ct); + assert_eq!(ss1, ss2); + } + + #[test] + fn derive_pubkey() { + let (sk, pk) = <$priv>::generate(); + assert_eq!(pk.as_bytes(), sk.public_key().unwrap().as_bytes()); + } + + #[test] + fn from_slice_rejects_bad_len() { + assert!(<$pub>::from_slice(&[0u8; 100]).is_err()); + assert!(<$pub>::from_slice(&[]).is_err()); + } + + #[test] + fn from_slice_roundtrip() { + let (_, pk) = <$priv>::generate(); + let pk2 = <$pub>::from_slice(pk.as_bytes()).unwrap(); + assert_eq!(pk.as_bytes(), pk2.as_bytes()); + } + + #[test] + fn implicit_rejection() { + let (sk, _) = <$priv>::generate(); + let bad_ct = [0x42u8; $ct_len]; + // bad ciphertext still "works", just returns deterministic garbage + let ss1 = sk.decapsulate(&bad_ct); + let ss2 = sk.decapsulate(&bad_ct); + assert_eq!(ss1, ss2); + } + + #[test] + fn debug_redacts_seed() { + let (sk, _) = <$priv>::generate(); + let dbg = format!("{:?}", sk); + assert!(dbg.contains("redacted")); + } + } + }; + } + + mlkem_tests!(mlkem768, MlKem768PrivateKey, MlKem768PublicKey, 1088); + mlkem_tests!(mlkem1024, MlKem1024PrivateKey, MlKem1024PublicKey, 1568); + + // Tests for unified API (MlKem struct) + mod unified_api { + use super::*; + + macro_rules! unified_tests { + ($name:ident, $params:expr, $pk_len:expr, $ct_len:expr) => { + mod $name { + use super::*; + + #[test] + fn roundtrip() { + let kem = MlKem::new($params); + let (pk, seed) = kem.generate_key().unwrap(); + let (ct, ss1) = kem.encapsulate(&pk).unwrap(); + let ss2 = kem.decapsulate(&seed, &ct).unwrap(); + assert_eq!(ss1, ss2); + } + + #[test] + fn key_sizes() { + let kem = MlKem::new($params); + assert_eq!(kem.public_key_bytes(), $pk_len); + assert_eq!(kem.ciphertext_bytes(), $ct_len); + + let (pk, private_key) = kem.generate_key().unwrap(); + assert_eq!(pk.len(), $pk_len); + assert_eq!(private_key.len(), PRIVATE_KEY_SEED_BYTES); + + let (ct, ss) = kem.encapsulate(&pk).unwrap(); + assert_eq!(ct.len(), $ct_len); + assert_eq!(ss.len(), SHARED_SECRET_BYTES); + } + + #[test] + fn invalid_public_key_length() { + let kem = MlKem::new($params); + let result = kem.encapsulate(&[0u8; 100]); + assert!(result.is_err()); + } + + #[test] + fn invalid_private_key_length() { + let kem = MlKem::new($params); + let (pk, _) = kem.generate_key().unwrap(); + let (ct, _) = kem.encapsulate(&pk).unwrap(); + let result = kem.decapsulate(&[0u8; 32], &ct); + assert!(result.is_err()); + } + + #[test] + fn invalid_ciphertext_length() { + let kem = MlKem::new($params); + let (_, private_key) = kem.generate_key().unwrap(); + let result = kem.decapsulate(&private_key, &[0u8; 100]); + assert!(result.is_err()); + } + + #[test] + fn params_accessor() { + let kem = MlKem::new($params); + assert_eq!(kem.params(), $params); + } + } + }; + } + + unified_tests!(mlkem768, MlKemParams::MlKem768, 1184, 1088); + unified_tests!(mlkem1024, MlKemParams::MlKem1024, 1568, 1568); + + #[test] + fn params_constants() { + assert_eq!(MlKemParams::MlKem768.public_key_bytes(), 1184); + assert_eq!(MlKemParams::MlKem768.ciphertext_bytes(), 1088); + assert_eq!(MlKemParams::MlKem1024.public_key_bytes(), 1568); + assert_eq!(MlKemParams::MlKem1024.ciphertext_bytes(), 1568); + } + + #[test] + fn cross_kem_incompatibility() { + // Keys from one KEM variant should not work with another + let kem768 = MlKem::new(MlKemParams::MlKem768); + let kem1024 = MlKem::new(MlKemParams::MlKem1024); + + let (pk768, _) = kem768.generate_key().unwrap(); + let (pk1024, _) = kem1024.generate_key().unwrap(); + + // 768 public key is wrong length for 1024 + assert!(kem1024.encapsulate(&pk768).is_err()); + // 1024 public key is wrong length for 768 + assert!(kem768.encapsulate(&pk1024).is_err()); + } + } +} From 88961db0642a8a707f0786a66bac03c4379b0c1c Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Fri, 30 Jan 2026 18:10:22 -0800 Subject: [PATCH 090/111] Add an init-update-finalize API for HMAC --- boring/src/hash.rs | 2 +- boring/src/hmac.rs | 98 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/boring/src/hash.rs b/boring/src/hash.rs index 5deadade2..b9dcf99e1 100644 --- a/boring/src/hash.rs +++ b/boring/src/hash.rs @@ -359,7 +359,7 @@ pub fn hmac_sha1(key: &[u8], data: &[u8]) -> Result<[u8; 20], ErrorStack> { hmac(MessageDigest::sha1(), key, data) } -fn hmac( +pub(crate) fn hmac( digest: MessageDigest, key: &[u8], data: &[u8], diff --git a/boring/src/hmac.rs b/boring/src/hmac.rs index 4821f1f07..a015409cb 100644 --- a/boring/src/hmac.rs +++ b/boring/src/hmac.rs @@ -30,3 +30,101 @@ impl HmacCtxRef { } } } + +/// Provides an init-update-finalize API for HMAC. +pub struct Hmac(*mut ffi::HMAC_CTX); + +impl Hmac { + /// Creates a new HMAC object with the given key and hash algorithm. + pub fn init(key: &[u8], md: &MessageDigest) -> Result { + ffi::init(); + + let ctx = unsafe { + let ctx = ffi::HMAC_CTX_new(); + cvt(ffi::HMAC_Init_ex( + ctx, + key.as_ptr().cast(), + key.len(), + md.as_ptr(), + // ENGINE api is deprecated + core::ptr::null_mut(), + ))?; + ctx + }; + + Ok(Hmac(ctx)) + } + + /// Updates the HMAC input. + pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::HMAC_Update(self.0, data.as_ptr().cast(), data.len())) } + } + + /// Finalizes the HMAC and returns the output. + pub fn finalize(self) -> Result, ErrorStack> { + let out_len = unsafe { ffi::HMAC_size(self.0) }; + let mut out = vec![0; out_len]; + unsafe { + cvt(ffi::HMAC_Final( + self.0, + out.as_mut_ptr().cast(), + // ENGINE api is deprecated + core::ptr::null_mut(), + ))?; + } + Ok(out) + } +} + +impl Drop for Hmac { + fn drop(&mut self) { + unsafe { ffi::HMAC_CTX_free(self.0) } + } +} + +#[cfg(test)] +mod tests { + use crate::hash; + + use super::*; + + fn test(md: MessageDigest) { + assert_eq!(N, md.size()); + let key = vec![0; N]; + let message_parts = [ + b"hello".to_vec(), + b"world!".to_vec(), + b"".to_vec(), + vec![0; 23], + b"fella guy".to_vec(), + ]; + let message = message_parts.concat(); + + let mut hmac = Hmac::init(&key, &md).unwrap(); + for part in &message_parts { + hmac.update(part).unwrap(); + } + let res = hmac.finalize().unwrap(); + assert_eq!(res, hash::hmac::(md, &key, &message).unwrap()); + } + + #[test] + fn test_sha1() { + test::<20>(MessageDigest::sha1()); + } + + #[test] + fn test_sha256() { + test::<32>(MessageDigest::sha256()); + } + + #[test] + fn test_sha384() { + test::<48>(MessageDigest::sha384()); + } + + #[test] + fn test_sha512() { + test::<64>(MessageDigest::sha512()); + } +} From 531ac086f45e99fca28a47d501b4f0d7244b9823 Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Fri, 30 Jan 2026 18:10:51 -0800 Subject: [PATCH 091/111] Expose a cipher's NID --- boring/src/symm.rs | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/boring/src/symm.rs b/boring/src/symm.rs index 877b4a1a0..74e896d9f 100644 --- a/boring/src/symm.rs +++ b/boring/src/symm.rs @@ -137,7 +137,7 @@ impl CipherCtxRef { /// See OpenSSL doc at [`EVP_EncryptInit`] for more information on each algorithms. /// /// [`EVP_EncryptInit`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_EncryptInit.html -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Cipher(*const ffi::EVP_CIPHER); impl Cipher { @@ -301,6 +301,14 @@ impl Cipher { pub fn block_size(&self) -> usize { unsafe { EVP_CIPHER_block_size(self.0) as usize } } + + /// Returns the cipher's NID. + #[corresponds(EVP_CIPHER_nid)] + pub fn nid(&self) -> Nid { + ffi::init(); + let nid = unsafe { ffi::EVP_CIPHER_nid(self.as_ptr()) }; + Nid::from_raw(nid) + } } unsafe impl Sync for Cipher {} @@ -1044,4 +1052,37 @@ mod tests { .unwrap(); assert_eq!(pt, hex::encode(out)); } + + #[test] + fn test_nid_roundtrip() { + for cipher in [ + Cipher::aes_128_ecb(), + Cipher::aes_128_cbc(), + Cipher::aes_128_ctr(), + Cipher::aes_128_ofb(), + Cipher::aes_192_ecb(), + Cipher::aes_192_cbc(), + Cipher::aes_192_ctr(), + Cipher::aes_192_ofb(), + Cipher::aes_256_ecb(), + Cipher::aes_256_cbc(), + Cipher::aes_256_ctr(), + Cipher::aes_256_ofb(), + Cipher::des_ecb(), + Cipher::des_ede3_cbc(), + Cipher::des_cbc(), + Cipher::rc4(), + ] { + assert_eq!(Cipher::from_nid(cipher.nid()), Some(cipher)); + } + + for cipher in [ + Cipher::aes_128_gcm(), + Cipher::aes_192_gcm(), + Cipher::aes_256_gcm(), + Cipher::des_ede3(), + ] { + assert_eq!(Cipher::from_nid(cipher.nid()), None); + } + } } From c35cb1bb9fc3dd27e1107a863044547d3f62bc49 Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 2 Feb 2026 15:27:41 +0000 Subject: [PATCH 092/111] Use associated constants --- boring/src/mlkem.rs | 143 ++++++++++++++++++++------------------------ 1 file changed, 65 insertions(+), 78 deletions(-) diff --git a/boring/src/mlkem.rs b/boring/src/mlkem.rs index 5bc9dcb99..5297b2632 100644 --- a/boring/src/mlkem.rs +++ b/boring/src/mlkem.rs @@ -33,9 +33,16 @@ fn cbs_init(data: &[u8]) -> ffi::CBS { } /// Private key seed size (64 bytes). -pub const PRIVATE_KEY_SEED_BYTES: usize = 64; +pub const PRIVATE_KEY_SEED_BYTES: usize = ffi::MLKEM_SEED_BYTES as usize; + /// Shared secret size (32 bytes). -pub const SHARED_SECRET_BYTES: usize = 32; +pub const SHARED_SECRET_BYTES: usize = ffi::MLKEM_SHARED_SECRET_BYTES as usize; + +/// Raw bytes of the private key seed ([`PRIVATE_KEY_SEED_BYTES`] long) +pub type MlKemPrivateKeySeed = [u8; PRIVATE_KEY_SEED_BYTES]; + +/// Raw bytes of the shared secret ([`SHARED_SECRET_BYTES`] long) +pub type MlKemSharedSecret = [u8; SHARED_SECRET_BYTES]; /// ML-KEM variant selection. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -51,8 +58,8 @@ impl MlKemParams { #[must_use] pub const fn public_key_bytes(&self) -> usize { match self { - MlKemParams::MlKem768 => mlkem768::PUBLIC_KEY_BYTES, - MlKemParams::MlKem1024 => mlkem1024::PUBLIC_KEY_BYTES, + MlKemParams::MlKem768 => MlKem768PublicKey::PUBLIC_KEY_BYTES, + MlKemParams::MlKem1024 => MlKem1024PublicKey::PUBLIC_KEY_BYTES, } } @@ -60,8 +67,8 @@ impl MlKemParams { #[must_use] pub const fn ciphertext_bytes(&self) -> usize { match self { - MlKemParams::MlKem768 => mlkem768::CIPHERTEXT_BYTES, - MlKemParams::MlKem1024 => mlkem1024::CIPHERTEXT_BYTES, + MlKemParams::MlKem768 => MlKem768PrivateKey::CIPHERTEXT_BYTES, + MlKemParams::MlKem1024 => MlKem1024PrivateKey::CIPHERTEXT_BYTES, } } } @@ -108,7 +115,7 @@ impl MlKem { /// Generates a new key pair, returning `(public_key, private_key)`. /// /// The private key is a 64-byte seed. Keep it secret. - pub fn generate_key(&self) -> Result<(Vec, [u8; PRIVATE_KEY_SEED_BYTES]), ErrorStack> { + pub fn generate_key(&self) -> Result<(Vec, MlKemPrivateKeySeed), ErrorStack> { match self.params { MlKemParams::MlKem768 => { let (sk, pk) = MlKem768PrivateKey::generate(); @@ -126,7 +133,7 @@ impl MlKem { pub fn encapsulate( &self, public_key: &[u8], - ) -> Result<(Vec, [u8; SHARED_SECRET_BYTES]), ErrorStack> { + ) -> Result<(Vec, MlKemSharedSecret), ErrorStack> { match self.params { MlKemParams::MlKem768 => { let pk = MlKem768PublicKey::from_slice(public_key)?; @@ -146,22 +153,22 @@ impl MlKem { &self, private_key: &[u8], ciphertext: &[u8], - ) -> Result<[u8; SHARED_SECRET_BYTES], ErrorStack> { + ) -> Result { if private_key.len() != PRIVATE_KEY_SEED_BYTES { return Err(ErrorStack::internal_error_str("invalid private key length")); } - let seed_arr: [u8; PRIVATE_KEY_SEED_BYTES] = private_key.try_into().unwrap(); + let seed_arr: MlKemPrivateKeySeed = private_key.try_into().unwrap(); match self.params { MlKemParams::MlKem768 => { - let ct: &[u8; mlkem768::CIPHERTEXT_BYTES] = ciphertext + let ct: &[u8; MlKem768PrivateKey::CIPHERTEXT_BYTES] = ciphertext .try_into() .map_err(|_| ErrorStack::internal_error_str("invalid ciphertext length"))?; let sk = MlKem768PrivateKey::from_seed(seed_arr)?; Ok(sk.decapsulate(ct)) } MlKemParams::MlKem1024 => { - let ct: &[u8; mlkem1024::CIPHERTEXT_BYTES] = ciphertext + let ct: &[u8; MlKem1024PrivateKey::CIPHERTEXT_BYTES] = ciphertext .try_into() .map_err(|_| ErrorStack::internal_error_str("invalid ciphertext length"))?; let sk = MlKem1024PrivateKey::from_seed(seed_arr)?; @@ -171,22 +178,11 @@ impl MlKem { } } -// ML-KEM-768 - -/// Size constants for ML-KEM-768. -pub mod mlkem768 { - use super::ffi; - pub const PUBLIC_KEY_BYTES: usize = ffi::MLKEM768_PUBLIC_KEY_BYTES as usize; - pub const SEED_BYTES: usize = ffi::MLKEM_SEED_BYTES as usize; - pub const CIPHERTEXT_BYTES: usize = ffi::MLKEM768_CIPHERTEXT_BYTES as usize; - pub const SHARED_SECRET_BYTES: usize = ffi::MLKEM_SHARED_SECRET_BYTES as usize; -} - /// ML-KEM-768 private key. /// /// Caches the expanded key for fast decapsulation. struct MlKem768PrivateKey { - seed: [u8; mlkem768::SEED_BYTES], + seed: MlKemPrivateKeySeed, expanded: ffi::MLKEM768_private_key, } @@ -198,15 +194,17 @@ impl Clone for MlKem768PrivateKey { } impl MlKem768PrivateKey { + pub const CIPHERTEXT_BYTES: usize = ffi::MLKEM768_CIPHERTEXT_BYTES as usize; + /// Generate a new key pair. #[must_use] fn generate() -> (MlKem768PrivateKey, MlKem768PublicKey) { // SAFETY: all buffers are out parameters, correctly sized unsafe { ffi::init(); - let mut public_key_bytes: MaybeUninit<[u8; mlkem768::PUBLIC_KEY_BYTES]> = + let mut public_key_bytes: MaybeUninit<[u8; MlKem768PublicKey::PUBLIC_KEY_BYTES]> = MaybeUninit::uninit(); - let mut seed: MaybeUninit<[u8; mlkem768::SEED_BYTES]> = MaybeUninit::uninit(); + let mut seed: MaybeUninit = MaybeUninit::uninit(); let mut expanded: MaybeUninit = MaybeUninit::uninit(); ffi::MLKEM768_generate_key( @@ -236,7 +234,7 @@ impl MlKem768PrivateKey { } /// Restore private key from seed. - fn from_seed(seed: [u8; mlkem768::SEED_BYTES]) -> Result { + fn from_seed(seed: MlKemPrivateKeySeed) -> Result { // SAFETY: seed is 64 bytes, out parameter correctly sized unsafe { ffi::init(); @@ -262,7 +260,7 @@ impl MlKem768PrivateKey { let mut parsed: MaybeUninit = MaybeUninit::uninit(); ffi::MLKEM768_public_from_private(parsed.as_mut_ptr(), &self.expanded); - let mut bytes = [0u8; mlkem768::PUBLIC_KEY_BYTES]; + let mut bytes = [0u8; MlKem768PublicKey::PUBLIC_KEY_BYTES]; let mut cbb: MaybeUninit = MaybeUninit::uninit(); cvt(ffi::CBB_init_fixed( cbb.as_mut_ptr(), @@ -282,14 +280,11 @@ impl MlKem768PrivateKey { } /// Decapsulate to get the shared secret. - fn decapsulate( - &self, - ciphertext: &[u8; mlkem768::CIPHERTEXT_BYTES], - ) -> [u8; mlkem768::SHARED_SECRET_BYTES] { + fn decapsulate(&self, ciphertext: &[u8; Self::CIPHERTEXT_BYTES]) -> MlKemSharedSecret { // SAFETY: expanded key is valid, ciphertext is correctly sized unsafe { ffi::init(); - let mut shared_secret = [0u8; mlkem768::SHARED_SECRET_BYTES]; + let mut shared_secret = [0u8; SHARED_SECRET_BYTES]; ffi::MLKEM768_decap( shared_secret.as_mut_ptr(), @@ -324,8 +319,8 @@ impl Drop for MlKem768PrivateKey { } } -impl AsRef<[u8; mlkem768::SEED_BYTES]> for MlKem768PrivateKey { - fn as_ref(&self) -> &[u8; mlkem768::SEED_BYTES] { +impl AsRef for MlKem768PrivateKey { + fn as_ref(&self) -> &MlKemPrivateKeySeed { &self.seed } } @@ -333,14 +328,16 @@ impl AsRef<[u8; mlkem768::SEED_BYTES]> for MlKem768PrivateKey { /// ML-KEM-768 public key. #[derive(Clone)] struct MlKem768PublicKey { - bytes: [u8; mlkem768::PUBLIC_KEY_BYTES], + bytes: [u8; Self::PUBLIC_KEY_BYTES], parsed: ffi::MLKEM768_public_key, } impl MlKem768PublicKey { + pub const PUBLIC_KEY_BYTES: usize = ffi::MLKEM768_PUBLIC_KEY_BYTES as usize; + /// Parse and validate a public key. fn from_slice(slice: &[u8]) -> Result { - if slice.len() != mlkem768::PUBLIC_KEY_BYTES { + if slice.len() != Self::PUBLIC_KEY_BYTES { return Err(ErrorStack::internal_error_str("invalid public key length")); } @@ -360,7 +357,7 @@ impl MlKem768PublicKey { )); } - let mut bytes = [0u8; mlkem768::PUBLIC_KEY_BYTES]; + let mut bytes = [0u8; Self::PUBLIC_KEY_BYTES]; bytes.copy_from_slice(slice); Ok(Self { bytes, @@ -371,7 +368,7 @@ impl MlKem768PublicKey { /// Raw public key bytes. #[cfg(test)] - fn as_bytes(&self) -> &[u8; mlkem768::PUBLIC_KEY_BYTES] { + fn as_bytes(&self) -> &[u8; Self::PUBLIC_KEY_BYTES] { &self.bytes } @@ -379,14 +376,14 @@ impl MlKem768PublicKey { fn encapsulate( &self, ) -> ( - [u8; mlkem768::CIPHERTEXT_BYTES], - [u8; mlkem768::SHARED_SECRET_BYTES], + [u8; MlKem768PrivateKey::CIPHERTEXT_BYTES], + MlKemSharedSecret, ) { // SAFETY: buffers correctly sized, parsed key is valid unsafe { ffi::init(); - let mut ciphertext = [0u8; mlkem768::CIPHERTEXT_BYTES]; - let mut shared_secret = [0u8; mlkem768::SHARED_SECRET_BYTES]; + let mut ciphertext = [0u8; MlKem768PrivateKey::CIPHERTEXT_BYTES]; + let mut shared_secret = [0u8; SHARED_SECRET_BYTES]; ffi::MLKEM768_encap( ciphertext.as_mut_ptr(), @@ -407,29 +404,18 @@ impl fmt::Debug for MlKem768PublicKey { } } -impl AsRef<[u8; mlkem768::PUBLIC_KEY_BYTES]> for MlKem768PublicKey { - fn as_ref(&self) -> &[u8; mlkem768::PUBLIC_KEY_BYTES] { +impl AsRef<[u8; Self::PUBLIC_KEY_BYTES]> for MlKem768PublicKey { + fn as_ref(&self) -> &[u8; Self::PUBLIC_KEY_BYTES] { &self.bytes } } -// ML-KEM-1024 - -/// Size constants for ML-KEM-1024. -pub mod mlkem1024 { - use super::ffi; - pub const PUBLIC_KEY_BYTES: usize = ffi::MLKEM1024_PUBLIC_KEY_BYTES as usize; - pub const SEED_BYTES: usize = ffi::MLKEM_SEED_BYTES as usize; - pub const CIPHERTEXT_BYTES: usize = ffi::MLKEM1024_CIPHERTEXT_BYTES as usize; - pub const SHARED_SECRET_BYTES: usize = ffi::MLKEM_SHARED_SECRET_BYTES as usize; -} - /// ML-KEM-1024 private key. /// /// Prefer ML-KEM-768 unless you need AES-256 equivalent security. /// Caches the expanded key for fast decapsulation. struct MlKem1024PrivateKey { - seed: [u8; mlkem1024::SEED_BYTES], + seed: MlKemPrivateKeySeed, expanded: ffi::MLKEM1024_private_key, } @@ -441,15 +427,17 @@ impl Clone for MlKem1024PrivateKey { } impl MlKem1024PrivateKey { + pub const CIPHERTEXT_BYTES: usize = ffi::MLKEM1024_CIPHERTEXT_BYTES as usize; + /// Generate a new key pair. #[must_use] fn generate() -> (MlKem1024PrivateKey, MlKem1024PublicKey) { // SAFETY: all buffers are out parameters, correctly sized unsafe { ffi::init(); - let mut public_key_bytes: MaybeUninit<[u8; mlkem1024::PUBLIC_KEY_BYTES]> = + let mut public_key_bytes: MaybeUninit<[u8; MlKem1024PublicKey::PUBLIC_KEY_BYTES]> = MaybeUninit::uninit(); - let mut seed: MaybeUninit<[u8; mlkem1024::SEED_BYTES]> = MaybeUninit::uninit(); + let mut seed: MaybeUninit = MaybeUninit::uninit(); let mut expanded: MaybeUninit = MaybeUninit::uninit(); ffi::MLKEM1024_generate_key( @@ -479,7 +467,7 @@ impl MlKem1024PrivateKey { } /// Restore private key from seed. - fn from_seed(seed: [u8; mlkem1024::SEED_BYTES]) -> Result { + fn from_seed(seed: MlKemPrivateKeySeed) -> Result { // SAFETY: seed is 64 bytes, out parameter correctly sized unsafe { ffi::init(); @@ -505,7 +493,7 @@ impl MlKem1024PrivateKey { let mut parsed: MaybeUninit = MaybeUninit::uninit(); ffi::MLKEM1024_public_from_private(parsed.as_mut_ptr(), &self.expanded); - let mut bytes = [0u8; mlkem1024::PUBLIC_KEY_BYTES]; + let mut bytes = [0u8; MlKem1024PublicKey::PUBLIC_KEY_BYTES]; let mut cbb: MaybeUninit = MaybeUninit::uninit(); cvt(ffi::CBB_init_fixed( cbb.as_mut_ptr(), @@ -525,14 +513,11 @@ impl MlKem1024PrivateKey { } /// Decapsulate to get the shared secret. - fn decapsulate( - &self, - ciphertext: &[u8; mlkem1024::CIPHERTEXT_BYTES], - ) -> [u8; mlkem1024::SHARED_SECRET_BYTES] { + fn decapsulate(&self, ciphertext: &[u8; Self::CIPHERTEXT_BYTES]) -> MlKemSharedSecret { // SAFETY: expanded key is valid, ciphertext is correctly sized unsafe { ffi::init(); - let mut shared_secret = [0u8; mlkem1024::SHARED_SECRET_BYTES]; + let mut shared_secret = [0u8; SHARED_SECRET_BYTES]; ffi::MLKEM1024_decap( shared_secret.as_mut_ptr(), @@ -567,8 +552,8 @@ impl Drop for MlKem1024PrivateKey { } } -impl AsRef<[u8; mlkem1024::SEED_BYTES]> for MlKem1024PrivateKey { - fn as_ref(&self) -> &[u8; mlkem1024::SEED_BYTES] { +impl AsRef for MlKem1024PrivateKey { + fn as_ref(&self) -> &MlKemPrivateKeySeed { &self.seed } } @@ -578,14 +563,16 @@ impl AsRef<[u8; mlkem1024::SEED_BYTES]> for MlKem1024PrivateKey { /// Prefer ML-KEM-768 unless you need AES-256 equivalent security. #[derive(Clone)] struct MlKem1024PublicKey { - bytes: [u8; mlkem1024::PUBLIC_KEY_BYTES], + bytes: [u8; Self::PUBLIC_KEY_BYTES], parsed: ffi::MLKEM1024_public_key, } impl MlKem1024PublicKey { + pub const PUBLIC_KEY_BYTES: usize = ffi::MLKEM1024_PUBLIC_KEY_BYTES as usize; + /// Parse and validate a public key. fn from_slice(slice: &[u8]) -> Result { - if slice.len() != mlkem1024::PUBLIC_KEY_BYTES { + if slice.len() != Self::PUBLIC_KEY_BYTES { return Err(ErrorStack::internal_error_str("invalid public key length")); } @@ -605,7 +592,7 @@ impl MlKem1024PublicKey { )); } - let mut bytes = [0u8; mlkem1024::PUBLIC_KEY_BYTES]; + let mut bytes = [0u8; Self::PUBLIC_KEY_BYTES]; bytes.copy_from_slice(slice); Ok(Self { bytes, @@ -616,7 +603,7 @@ impl MlKem1024PublicKey { /// Raw public key bytes. #[cfg(test)] - fn as_bytes(&self) -> &[u8; mlkem1024::PUBLIC_KEY_BYTES] { + fn as_bytes(&self) -> &[u8; Self::PUBLIC_KEY_BYTES] { &self.bytes } @@ -624,14 +611,14 @@ impl MlKem1024PublicKey { fn encapsulate( &self, ) -> ( - [u8; mlkem1024::CIPHERTEXT_BYTES], - [u8; mlkem1024::SHARED_SECRET_BYTES], + [u8; MlKem1024PrivateKey::CIPHERTEXT_BYTES], + [u8; SHARED_SECRET_BYTES], ) { // SAFETY: buffers correctly sized, parsed key is valid unsafe { ffi::init(); - let mut ciphertext = [0u8; mlkem1024::CIPHERTEXT_BYTES]; - let mut shared_secret = [0u8; mlkem1024::SHARED_SECRET_BYTES]; + let mut ciphertext = [0u8; MlKem1024PrivateKey::CIPHERTEXT_BYTES]; + let mut shared_secret = [0u8; SHARED_SECRET_BYTES]; ffi::MLKEM1024_encap( ciphertext.as_mut_ptr(), @@ -652,8 +639,8 @@ impl fmt::Debug for MlKem1024PublicKey { } } -impl AsRef<[u8; mlkem1024::PUBLIC_KEY_BYTES]> for MlKem1024PublicKey { - fn as_ref(&self) -> &[u8; mlkem1024::PUBLIC_KEY_BYTES] { +impl AsRef<[u8; Self::PUBLIC_KEY_BYTES]> for MlKem1024PublicKey { + fn as_ref(&self) -> &[u8; Self::PUBLIC_KEY_BYTES] { &self.bytes } } From 36d18367f352a9cd65f128c50e46ef70e5908c1f Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 2 Feb 2026 22:40:30 +0000 Subject: [PATCH 093/111] Tests don't need AsRef and other accessors --- boring/src/mlkem.rs | 50 ++++++++------------------------------------- 1 file changed, 9 insertions(+), 41 deletions(-) diff --git a/boring/src/mlkem.rs b/boring/src/mlkem.rs index 5297b2632..b10a388ce 100644 --- a/boring/src/mlkem.rs +++ b/boring/src/mlkem.rs @@ -319,12 +319,6 @@ impl Drop for MlKem768PrivateKey { } } -impl AsRef for MlKem768PrivateKey { - fn as_ref(&self) -> &MlKemPrivateKeySeed { - &self.seed - } -} - /// ML-KEM-768 public key. #[derive(Clone)] struct MlKem768PublicKey { @@ -336,6 +330,8 @@ impl MlKem768PublicKey { pub const PUBLIC_KEY_BYTES: usize = ffi::MLKEM768_PUBLIC_KEY_BYTES as usize; /// Parse and validate a public key. + /// + /// The slice must be [`Self::PUBLIC_KEY_BYTES`] long. fn from_slice(slice: &[u8]) -> Result { if slice.len() != Self::PUBLIC_KEY_BYTES { return Err(ErrorStack::internal_error_str("invalid public key length")); @@ -366,12 +362,6 @@ impl MlKem768PublicKey { } } - /// Raw public key bytes. - #[cfg(test)] - fn as_bytes(&self) -> &[u8; Self::PUBLIC_KEY_BYTES] { - &self.bytes - } - /// Encapsulate: returns (ciphertext, shared_secret). fn encapsulate( &self, @@ -404,12 +394,6 @@ impl fmt::Debug for MlKem768PublicKey { } } -impl AsRef<[u8; Self::PUBLIC_KEY_BYTES]> for MlKem768PublicKey { - fn as_ref(&self) -> &[u8; Self::PUBLIC_KEY_BYTES] { - &self.bytes - } -} - /// ML-KEM-1024 private key. /// /// Prefer ML-KEM-768 unless you need AES-256 equivalent security. @@ -552,12 +536,6 @@ impl Drop for MlKem1024PrivateKey { } } -impl AsRef for MlKem1024PrivateKey { - fn as_ref(&self) -> &MlKemPrivateKeySeed { - &self.seed - } -} - /// ML-KEM-1024 public key. /// /// Prefer ML-KEM-768 unless you need AES-256 equivalent security. @@ -570,7 +548,9 @@ struct MlKem1024PublicKey { impl MlKem1024PublicKey { pub const PUBLIC_KEY_BYTES: usize = ffi::MLKEM1024_PUBLIC_KEY_BYTES as usize; - /// Parse and validate a public key. + /// Parse and validate a serialized public key. + /// + /// The slice must be [`Self::PUBLIC_KEY_BYTES`] long. fn from_slice(slice: &[u8]) -> Result { if slice.len() != Self::PUBLIC_KEY_BYTES { return Err(ErrorStack::internal_error_str("invalid public key length")); @@ -601,12 +581,6 @@ impl MlKem1024PublicKey { } } - /// Raw public key bytes. - #[cfg(test)] - fn as_bytes(&self) -> &[u8; Self::PUBLIC_KEY_BYTES] { - &self.bytes - } - /// Encapsulate: returns (ciphertext, shared_secret). fn encapsulate( &self, @@ -639,12 +613,6 @@ impl fmt::Debug for MlKem1024PublicKey { } } -impl AsRef<[u8; Self::PUBLIC_KEY_BYTES]> for MlKem1024PublicKey { - fn as_ref(&self) -> &[u8; Self::PUBLIC_KEY_BYTES] { - &self.bytes - } -} - #[cfg(test)] mod tests { use super::*; @@ -665,7 +633,7 @@ mod tests { #[test] fn seed_roundtrip() { let (sk, pk) = <$priv>::generate(); - let sk2 = <$priv>::from_seed(*sk.as_ref()).unwrap(); + let sk2 = <$priv>::from_seed(sk.seed).unwrap(); let (ct, ss1) = pk.encapsulate(); let ss2 = sk2.decapsulate(&ct); assert_eq!(ss1, ss2); @@ -674,7 +642,7 @@ mod tests { #[test] fn derive_pubkey() { let (sk, pk) = <$priv>::generate(); - assert_eq!(pk.as_bytes(), sk.public_key().unwrap().as_bytes()); + assert_eq!(pk.bytes, sk.public_key().unwrap().bytes); } #[test] @@ -686,8 +654,8 @@ mod tests { #[test] fn from_slice_roundtrip() { let (_, pk) = <$priv>::generate(); - let pk2 = <$pub>::from_slice(pk.as_bytes()).unwrap(); - assert_eq!(pk.as_bytes(), pk2.as_bytes()); + let pk2 = <$pub>::from_slice(&pk.bytes).unwrap(); + assert_eq!(pk.bytes, pk2.bytes); } #[test] From 241d05aea8cc192041de07c7e5dfff964cd17af9 Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 2 Feb 2026 23:40:31 +0000 Subject: [PATCH 094/111] Skip MlKemParams --- boring/src/mlkem.rs | 124 +++++++++++++++----------------------------- 1 file changed, 43 insertions(+), 81 deletions(-) diff --git a/boring/src/mlkem.rs b/boring/src/mlkem.rs index b10a388ce..18569535f 100644 --- a/boring/src/mlkem.rs +++ b/boring/src/mlkem.rs @@ -7,12 +7,11 @@ //! Provides ML-KEM-768 (recommended) and ML-KEM-1024 variants via [`MlKem`]. //! //! ``` -//! use boring::mlkem::{MlKem, MlKemParams}; +//! use boring::mlkem::MlKem; //! -//! let kem = MlKem::new(MlKemParams::MlKem768); -//! let (public_key, private_key) = kem.generate_key().unwrap(); -//! let (ciphertext, shared_secret) = kem.encapsulate(&public_key).unwrap(); -//! let decrypted = kem.decapsulate(&private_key, &ciphertext).unwrap(); +//! let (public_key, private_key) = MlKem::MlKem768.generate_key().unwrap(); +//! let (ciphertext, shared_secret) = MlKem::MlKem768.encapsulate(&public_key).unwrap(); +//! let decrypted = MlKem::MlKem768.decapsulate(&private_key, &ciphertext).unwrap(); //! assert_eq!(shared_secret, decrypted); //! ``` @@ -44,22 +43,31 @@ pub type MlKemPrivateKeySeed = [u8; PRIVATE_KEY_SEED_BYTES]; /// Raw bytes of the shared secret ([`SHARED_SECRET_BYTES`] long) pub type MlKemSharedSecret = [u8; SHARED_SECRET_BYTES]; -/// ML-KEM variant selection. +/// ML-KEM with runtime algorithm selection. Works with byte slices. +/// +/// ``` +/// use boring::mlkem::MlKem; +/// +/// let (public_key, private_key) = MlKem::MlKem768.generate_key().unwrap(); +/// let (ciphertext, shared_secret) = MlKem::MlKem768.encapsulate(&public_key).unwrap(); +/// let decrypted = kem.decapsulate(&private_key, &ciphertext).unwrap(); +/// assert_eq!(shared_secret, decrypted); +/// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MlKemParams { +pub enum MlKem { /// Recommended. AES-192 equivalent security. MlKem768, /// AES-256 equivalent security. MlKem1024, } -impl MlKemParams { +impl MlKem { /// Returns 1184 for ML-KEM-768, 1568 for ML-KEM-1024. #[must_use] pub const fn public_key_bytes(&self) -> usize { match self { - MlKemParams::MlKem768 => MlKem768PublicKey::PUBLIC_KEY_BYTES, - MlKemParams::MlKem1024 => MlKem1024PublicKey::PUBLIC_KEY_BYTES, + Self::MlKem768 => MlKem768PublicKey::PUBLIC_KEY_BYTES, + Self::MlKem1024 => MlKem1024PublicKey::PUBLIC_KEY_BYTES, } } @@ -67,61 +75,21 @@ impl MlKemParams { #[must_use] pub const fn ciphertext_bytes(&self) -> usize { match self { - MlKemParams::MlKem768 => MlKem768PrivateKey::CIPHERTEXT_BYTES, - MlKemParams::MlKem1024 => MlKem1024PrivateKey::CIPHERTEXT_BYTES, + Self::MlKem768 => MlKem768PrivateKey::CIPHERTEXT_BYTES, + Self::MlKem1024 => MlKem1024PrivateKey::CIPHERTEXT_BYTES, } } -} - -/// ML-KEM with runtime algorithm selection. Works with byte slices. -/// -/// ``` -/// use boring::mlkem::{MlKem, MlKemParams}; -/// -/// let kem = MlKem::new(MlKemParams::MlKem768); -/// let (public_key, private_key) = kem.generate_key().unwrap(); -/// let (ciphertext, shared_secret) = kem.encapsulate(&public_key).unwrap(); -/// let decrypted = kem.decapsulate(&private_key, &ciphertext).unwrap(); -/// assert_eq!(shared_secret, decrypted); -/// ``` -#[derive(Debug, Clone, Copy)] -pub struct MlKem { - params: MlKemParams, -} - -impl MlKem { - /// Creates a new context for the given parameter set. - #[must_use] - pub fn new(params: MlKemParams) -> Self { - ffi::init(); - Self { params } - } - - #[must_use] - pub fn params(&self) -> MlKemParams { - self.params - } - - #[must_use] - pub fn public_key_bytes(&self) -> usize { - self.params.public_key_bytes() - } - - #[must_use] - pub fn ciphertext_bytes(&self) -> usize { - self.params.ciphertext_bytes() - } /// Generates a new key pair, returning `(public_key, private_key)`. /// /// The private key is a 64-byte seed. Keep it secret. pub fn generate_key(&self) -> Result<(Vec, MlKemPrivateKeySeed), ErrorStack> { - match self.params { - MlKemParams::MlKem768 => { + match self { + Self::MlKem768 => { let (sk, pk) = MlKem768PrivateKey::generate(); Ok((pk.bytes.to_vec(), sk.seed)) } - MlKemParams::MlKem1024 => { + Self::MlKem1024 => { let (sk, pk) = MlKem1024PrivateKey::generate(); Ok((pk.bytes.to_vec(), sk.seed)) } @@ -134,13 +102,13 @@ impl MlKem { &self, public_key: &[u8], ) -> Result<(Vec, MlKemSharedSecret), ErrorStack> { - match self.params { - MlKemParams::MlKem768 => { + match self { + Self::MlKem768 => { let pk = MlKem768PublicKey::from_slice(public_key)?; let (ct, ss) = pk.encapsulate(); Ok((ct.to_vec(), ss)) } - MlKemParams::MlKem1024 => { + Self::MlKem1024 => { let pk = MlKem1024PublicKey::from_slice(public_key)?; let (ct, ss) = pk.encapsulate(); Ok((ct.to_vec(), ss)) @@ -159,15 +127,15 @@ impl MlKem { } let seed_arr: MlKemPrivateKeySeed = private_key.try_into().unwrap(); - match self.params { - MlKemParams::MlKem768 => { + match self { + Self::MlKem768 => { let ct: &[u8; MlKem768PrivateKey::CIPHERTEXT_BYTES] = ciphertext .try_into() .map_err(|_| ErrorStack::internal_error_str("invalid ciphertext length"))?; let sk = MlKem768PrivateKey::from_seed(seed_arr)?; Ok(sk.decapsulate(ct)) } - MlKemParams::MlKem1024 => { + Self::MlKem1024 => { let ct: &[u8; MlKem1024PrivateKey::CIPHERTEXT_BYTES] = ciphertext .try_into() .map_err(|_| ErrorStack::internal_error_str("invalid ciphertext length"))?; @@ -686,13 +654,13 @@ mod tests { use super::*; macro_rules! unified_tests { - ($name:ident, $params:expr, $pk_len:expr, $ct_len:expr) => { + ($name:ident, $algorithm:expr, $pk_len:expr, $ct_len:expr) => { mod $name { use super::*; #[test] fn roundtrip() { - let kem = MlKem::new($params); + let kem = $algorithm; let (pk, seed) = kem.generate_key().unwrap(); let (ct, ss1) = kem.encapsulate(&pk).unwrap(); let ss2 = kem.decapsulate(&seed, &ct).unwrap(); @@ -701,7 +669,7 @@ mod tests { #[test] fn key_sizes() { - let kem = MlKem::new($params); + let kem = $algorithm; assert_eq!(kem.public_key_bytes(), $pk_len); assert_eq!(kem.ciphertext_bytes(), $ct_len); @@ -716,14 +684,14 @@ mod tests { #[test] fn invalid_public_key_length() { - let kem = MlKem::new($params); + let kem = $algorithm; let result = kem.encapsulate(&[0u8; 100]); assert!(result.is_err()); } #[test] fn invalid_private_key_length() { - let kem = MlKem::new($params); + let kem = $algorithm; let (pk, _) = kem.generate_key().unwrap(); let (ct, _) = kem.encapsulate(&pk).unwrap(); let result = kem.decapsulate(&[0u8; 32], &ct); @@ -732,37 +700,31 @@ mod tests { #[test] fn invalid_ciphertext_length() { - let kem = MlKem::new($params); + let kem = $algorithm; let (_, private_key) = kem.generate_key().unwrap(); let result = kem.decapsulate(&private_key, &[0u8; 100]); assert!(result.is_err()); } - - #[test] - fn params_accessor() { - let kem = MlKem::new($params); - assert_eq!(kem.params(), $params); - } } }; } - unified_tests!(mlkem768, MlKemParams::MlKem768, 1184, 1088); - unified_tests!(mlkem1024, MlKemParams::MlKem1024, 1568, 1568); + unified_tests!(mlkem768, MlKem::MlKem768, 1184, 1088); + unified_tests!(mlkem1024, MlKem::MlKem1024, 1568, 1568); #[test] fn params_constants() { - assert_eq!(MlKemParams::MlKem768.public_key_bytes(), 1184); - assert_eq!(MlKemParams::MlKem768.ciphertext_bytes(), 1088); - assert_eq!(MlKemParams::MlKem1024.public_key_bytes(), 1568); - assert_eq!(MlKemParams::MlKem1024.ciphertext_bytes(), 1568); + assert_eq!(MlKem::MlKem768.public_key_bytes(), 1184); + assert_eq!(MlKem::MlKem768.ciphertext_bytes(), 1088); + assert_eq!(MlKem::MlKem1024.public_key_bytes(), 1568); + assert_eq!(MlKem::MlKem1024.ciphertext_bytes(), 1568); } #[test] fn cross_kem_incompatibility() { // Keys from one KEM variant should not work with another - let kem768 = MlKem::new(MlKemParams::MlKem768); - let kem1024 = MlKem::new(MlKemParams::MlKem1024); + let kem768 = MlKem::MlKem768; + let kem1024 = MlKem::MlKem1024; let (pk768, _) = kem768.generate_key().unwrap(); let (pk1024, _) = kem1024.generate_key().unwrap(); From 1722cc7317df6b69604f132dbe8519188b31fee2 Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 3 Feb 2026 00:10:34 +0000 Subject: [PATCH 095/111] Rename MlKem to Algorithm --- boring/src/mlkem.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/boring/src/mlkem.rs b/boring/src/mlkem.rs index 18569535f..2c8bfbcc8 100644 --- a/boring/src/mlkem.rs +++ b/boring/src/mlkem.rs @@ -4,14 +4,14 @@ //! using higher-level constructions like HPKE is preferred. //! Note that it's also enabled in TLS by default, in the X25519MLKEM768 exchange. //! -//! Provides ML-KEM-768 (recommended) and ML-KEM-1024 variants via [`MlKem`]. +//! Provides ML-KEM-768 (recommended) and ML-KEM-1024 variants via [`Algorithm`]. //! //! ``` -//! use boring::mlkem::MlKem; +//! use boring::mlkem::Algorithm; //! -//! let (public_key, private_key) = MlKem::MlKem768.generate_key().unwrap(); -//! let (ciphertext, shared_secret) = MlKem::MlKem768.encapsulate(&public_key).unwrap(); -//! let decrypted = MlKem::MlKem768.decapsulate(&private_key, &ciphertext).unwrap(); +//! let (public_key, private_key) = Algorithm::MlKem768.generate_key().unwrap(); +//! let (ciphertext, shared_secret) = Algorithm::MlKem768.encapsulate(&public_key).unwrap(); +//! let decrypted = Algorithm::MlKem768.decapsulate(&private_key, &ciphertext).unwrap(); //! assert_eq!(shared_secret, decrypted); //! ``` @@ -46,22 +46,22 @@ pub type MlKemSharedSecret = [u8; SHARED_SECRET_BYTES]; /// ML-KEM with runtime algorithm selection. Works with byte slices. /// /// ``` -/// use boring::mlkem::MlKem; +/// use boring::mlkem::Algorithm; /// -/// let (public_key, private_key) = MlKem::MlKem768.generate_key().unwrap(); -/// let (ciphertext, shared_secret) = MlKem::MlKem768.encapsulate(&public_key).unwrap(); -/// let decrypted = kem.decapsulate(&private_key, &ciphertext).unwrap(); +/// let (public_key, private_key) = Algorithm::MlKem768.generate_key().unwrap(); +/// let (ciphertext, shared_secret) = Algorithm::MlKem768.encapsulate(&public_key).unwrap(); +/// let decrypted = Algorithm::MlKem768.decapsulate(&private_key, &ciphertext).unwrap(); /// assert_eq!(shared_secret, decrypted); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MlKem { +pub enum Algorithm { /// Recommended. AES-192 equivalent security. MlKem768, /// AES-256 equivalent security. MlKem1024, } -impl MlKem { +impl Algorithm { /// Returns 1184 for ML-KEM-768, 1568 for ML-KEM-1024. #[must_use] pub const fn public_key_bytes(&self) -> usize { @@ -709,22 +709,22 @@ mod tests { }; } - unified_tests!(mlkem768, MlKem::MlKem768, 1184, 1088); - unified_tests!(mlkem1024, MlKem::MlKem1024, 1568, 1568); + unified_tests!(mlkem768, Algorithm::MlKem768, 1184, 1088); + unified_tests!(mlkem1024, Algorithm::MlKem1024, 1568, 1568); #[test] fn params_constants() { - assert_eq!(MlKem::MlKem768.public_key_bytes(), 1184); - assert_eq!(MlKem::MlKem768.ciphertext_bytes(), 1088); - assert_eq!(MlKem::MlKem1024.public_key_bytes(), 1568); - assert_eq!(MlKem::MlKem1024.ciphertext_bytes(), 1568); + assert_eq!(Algorithm::MlKem768.public_key_bytes(), 1184); + assert_eq!(Algorithm::MlKem768.ciphertext_bytes(), 1088); + assert_eq!(Algorithm::MlKem1024.public_key_bytes(), 1568); + assert_eq!(Algorithm::MlKem1024.ciphertext_bytes(), 1568); } #[test] fn cross_kem_incompatibility() { // Keys from one KEM variant should not work with another - let kem768 = MlKem::MlKem768; - let kem1024 = MlKem::MlKem1024; + let kem768 = Algorithm::MlKem768; + let kem1024 = Algorithm::MlKem1024; let (pk768, _) = kem768.generate_key().unwrap(); let (pk1024, _) = kem1024.generate_key().unwrap(); From 9b098e6bb7ca45871b272a43f0510e10239e7184 Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 3 Feb 2026 00:52:04 +0000 Subject: [PATCH 096/111] Use separate MlKemPrivateKey/MlKemPublicKey instead of exposing slices --- boring/src/mlkem.rs | 257 ++++++++++++++++++++++++-------------------- 1 file changed, 143 insertions(+), 114 deletions(-) diff --git a/boring/src/mlkem.rs b/boring/src/mlkem.rs index 2c8bfbcc8..f5b84147e 100644 --- a/boring/src/mlkem.rs +++ b/boring/src/mlkem.rs @@ -7,11 +7,11 @@ //! Provides ML-KEM-768 (recommended) and ML-KEM-1024 variants via [`Algorithm`]. //! //! ``` -//! use boring::mlkem::Algorithm; +//! use boring::mlkem::{Algorithm, MlKemPrivateKey}; //! -//! let (public_key, private_key) = Algorithm::MlKem768.generate_key().unwrap(); -//! let (ciphertext, shared_secret) = Algorithm::MlKem768.encapsulate(&public_key).unwrap(); -//! let decrypted = Algorithm::MlKem768.decapsulate(&private_key, &ciphertext).unwrap(); +//! let (public_key, private_key) = MlKemPrivateKey::generate(Algorithm::MlKem768).unwrap(); +//! let (ciphertext, shared_secret) = public_key.encapsulate().unwrap(); +//! let decrypted = private_key.decapsulate(&ciphertext).unwrap(); //! assert_eq!(shared_secret, decrypted); //! ``` @@ -43,16 +43,7 @@ pub type MlKemPrivateKeySeed = [u8; PRIVATE_KEY_SEED_BYTES]; /// Raw bytes of the shared secret ([`SHARED_SECRET_BYTES`] long) pub type MlKemSharedSecret = [u8; SHARED_SECRET_BYTES]; -/// ML-KEM with runtime algorithm selection. Works with byte slices. -/// -/// ``` -/// use boring::mlkem::Algorithm; -/// -/// let (public_key, private_key) = Algorithm::MlKem768.generate_key().unwrap(); -/// let (ciphertext, shared_secret) = Algorithm::MlKem768.encapsulate(&public_key).unwrap(); -/// let decrypted = Algorithm::MlKem768.decapsulate(&private_key, &ciphertext).unwrap(); -/// assert_eq!(shared_secret, decrypted); -/// ``` +/// ML-KEM runtime algorithm selection. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Algorithm { /// Recommended. AES-192 equivalent security. @@ -79,71 +70,137 @@ impl Algorithm { Self::MlKem1024 => MlKem1024PrivateKey::CIPHERTEXT_BYTES, } } +} + +#[derive(Clone)] +pub struct MlKemPublicKey(Either, Box>); + +#[derive(Clone)] +pub struct MlKemPrivateKey(Either, Box>); +#[derive(Clone)] +enum Either { + MlKem768(T768), + MlKem1024(T1024), +} + +impl MlKemPrivateKey { /// Generates a new key pair, returning `(public_key, private_key)`. /// /// The private key is a 64-byte seed. Keep it secret. - pub fn generate_key(&self) -> Result<(Vec, MlKemPrivateKeySeed), ErrorStack> { - match self { - Self::MlKem768 => { - let (sk, pk) = MlKem768PrivateKey::generate(); - Ok((pk.bytes.to_vec(), sk.seed)) + pub fn generate(algorithm: Algorithm) -> Result<(MlKemPublicKey, MlKemPrivateKey), ErrorStack> { + match algorithm { + Algorithm::MlKem768 => { + let (pk, sk) = MlKem768PrivateKey::generate(); + Ok(( + MlKemPublicKey(Either::MlKem768(pk)), + MlKemPrivateKey(Either::MlKem768(sk)), + )) } - Self::MlKem1024 => { - let (sk, pk) = MlKem1024PrivateKey::generate(); - Ok((pk.bytes.to_vec(), sk.seed)) + Algorithm::MlKem1024 => { + let (pk, sk) = MlKem1024PrivateKey::generate(); + Ok(( + MlKemPublicKey(Either::MlKem1024(pk)), + MlKemPrivateKey(Either::MlKem1024(sk)), + )) } } } +} + +impl MlKemPublicKey { + pub fn from_slice(algorithm: Algorithm, public_key: &[u8]) -> Result { + match algorithm { + Algorithm::MlKem768 => Ok(Self(Either::MlKem768(Box::new( + MlKem768PublicKey::from_slice(public_key)?, + )))), + Algorithm::MlKem1024 => Ok(Self(Either::MlKem1024(Box::new( + MlKem1024PublicKey::from_slice(public_key)?, + )))), + } + } + + /// Serialized bytes of the public key + pub fn as_bytes(&self) -> &[u8] { + match &self.0 { + Either::MlKem768(pk) => &pk.bytes, + Either::MlKem1024(pk) => &pk.bytes, + } + } /// Encapsulates a shared secret to the given public key, returning /// `(ciphertext, shared_secret)`. - pub fn encapsulate( - &self, - public_key: &[u8], - ) -> Result<(Vec, MlKemSharedSecret), ErrorStack> { - match self { - Self::MlKem768 => { - let pk = MlKem768PublicKey::from_slice(public_key)?; + pub fn encapsulate(&self) -> Result<(Vec, MlKemSharedSecret), ErrorStack> { + match &self.0 { + Either::MlKem768(pk) => { let (ct, ss) = pk.encapsulate(); Ok((ct.to_vec(), ss)) } - Self::MlKem1024 => { - let pk = MlKem1024PublicKey::from_slice(public_key)?; + Either::MlKem1024(pk) => { let (ct, ss) = pk.encapsulate(); Ok((ct.to_vec(), ss)) } } } - /// Decapsulates a shared secret from a ciphertext using the private key. - pub fn decapsulate( - &self, - private_key: &[u8], - ciphertext: &[u8], - ) -> Result { - if private_key.len() != PRIVATE_KEY_SEED_BYTES { - return Err(ErrorStack::internal_error_str("invalid private key length")); + /// Query public key and ciphertext length + pub fn algorithm(&self) -> Algorithm { + match self.0 { + Either::MlKem768(_) => Algorithm::MlKem768, + Either::MlKem1024(_) => Algorithm::MlKem1024, } - let seed_arr: MlKemPrivateKeySeed = private_key.try_into().unwrap(); + } +} - match self { - Self::MlKem768 => { +impl MlKemPrivateKey { + /// Expand private key from the seed bytes + pub fn from_seed( + algorithm: Algorithm, + private_seed: &MlKemPrivateKeySeed, + ) -> Result { + match algorithm { + Algorithm::MlKem768 => Ok(Self(Either::MlKem768(Box::new( + MlKem768PrivateKey::from_seed(private_seed)?, + )))), + Algorithm::MlKem1024 => Ok(Self(Either::MlKem1024(Box::new( + MlKem1024PrivateKey::from_seed(private_seed)?, + )))), + } + } + + /// Secret seed bytes of this private key + pub fn seed_bytes(&self) -> &MlKemPrivateKeySeed { + match &self.0 { + Either::MlKem768(sk) => &sk.seed, + Either::MlKem1024(sk) => &sk.seed, + } + } + + /// Decapsulates a shared secret from a ciphertext using the private key. + pub fn decapsulate(&self, ciphertext: &[u8]) -> Result { + match &self.0 { + Either::MlKem768(sk) => { let ct: &[u8; MlKem768PrivateKey::CIPHERTEXT_BYTES] = ciphertext .try_into() .map_err(|_| ErrorStack::internal_error_str("invalid ciphertext length"))?; - let sk = MlKem768PrivateKey::from_seed(seed_arr)?; Ok(sk.decapsulate(ct)) } - Self::MlKem1024 => { + Either::MlKem1024(sk) => { let ct: &[u8; MlKem1024PrivateKey::CIPHERTEXT_BYTES] = ciphertext .try_into() .map_err(|_| ErrorStack::internal_error_str("invalid ciphertext length"))?; - let sk = MlKem1024PrivateKey::from_seed(seed_arr)?; Ok(sk.decapsulate(ct)) } } } + + /// Query public key and ciphertext length + pub fn algorithm(&self) -> Algorithm { + match self.0 { + Either::MlKem768(_) => Algorithm::MlKem768, + Either::MlKem1024(_) => Algorithm::MlKem1024, + } + } } /// ML-KEM-768 private key. @@ -157,7 +214,7 @@ struct MlKem768PrivateKey { impl Clone for MlKem768PrivateKey { fn clone(&self) -> Self { // unwrap is safe: cloning a valid key with a valid seed always succeeds - Self::from_seed(self.seed).unwrap() + Self::from_seed(&self.seed).unwrap() } } @@ -166,7 +223,7 @@ impl MlKem768PrivateKey { /// Generate a new key pair. #[must_use] - fn generate() -> (MlKem768PrivateKey, MlKem768PublicKey) { + fn generate() -> (Box, Box) { // SAFETY: all buffers are out parameters, correctly sized unsafe { ffi::init(); @@ -189,20 +246,20 @@ impl MlKem768PrivateKey { ffi::MLKEM768_parse_public_key(parsed.as_mut_ptr(), &mut cbs); ( - MlKem768PrivateKey { - seed: seed.assume_init(), - expanded: expanded.assume_init(), - }, - MlKem768PublicKey { + Box::new(MlKem768PublicKey { bytes, parsed: parsed.assume_init(), - }, + }), + Box::new(MlKem768PrivateKey { + seed: seed.assume_init(), + expanded: expanded.assume_init(), + }), ) } } /// Restore private key from seed. - fn from_seed(seed: MlKemPrivateKeySeed) -> Result { + fn from_seed(seed: &MlKemPrivateKeySeed) -> Result { // SAFETY: seed is 64 bytes, out parameter correctly sized unsafe { ffi::init(); @@ -213,7 +270,7 @@ impl MlKem768PrivateKey { seed.len(), ))?; Ok(Self { - seed, + seed: *seed, expanded: expanded.assume_init(), }) } @@ -357,7 +414,7 @@ impl MlKem768PublicKey { impl fmt::Debug for MlKem768PublicKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("MlKem768PublicKey") - .field("bytes", &format!("[{}]", self.bytes.len())) + .field("bytes", &format_args!("[{}]", self.bytes.len())) .finish() } } @@ -374,7 +431,7 @@ struct MlKem1024PrivateKey { impl Clone for MlKem1024PrivateKey { fn clone(&self) -> Self { // unwrap is safe: cloning a valid key with a valid seed always succeeds - Self::from_seed(self.seed).unwrap() + Self::from_seed(&self.seed).unwrap() } } @@ -383,7 +440,7 @@ impl MlKem1024PrivateKey { /// Generate a new key pair. #[must_use] - fn generate() -> (MlKem1024PrivateKey, MlKem1024PublicKey) { + fn generate() -> (Box, Box) { // SAFETY: all buffers are out parameters, correctly sized unsafe { ffi::init(); @@ -406,20 +463,20 @@ impl MlKem1024PrivateKey { ffi::MLKEM1024_parse_public_key(parsed.as_mut_ptr(), &mut cbs); ( - MlKem1024PrivateKey { - seed: seed.assume_init(), - expanded: expanded.assume_init(), - }, - MlKem1024PublicKey { + Box::new(MlKem1024PublicKey { bytes, parsed: parsed.assume_init(), - }, + }), + Box::new(MlKem1024PrivateKey { + seed: seed.assume_init(), + expanded: expanded.assume_init(), + }), ) } } /// Restore private key from seed. - fn from_seed(seed: MlKemPrivateKeySeed) -> Result { + fn from_seed(seed: &MlKemPrivateKeySeed) -> Result { // SAFETY: seed is 64 bytes, out parameter correctly sized unsafe { ffi::init(); @@ -430,7 +487,7 @@ impl MlKem1024PrivateKey { seed.len(), ))?; Ok(Self { - seed, + seed: *seed, expanded: expanded.assume_init(), }) } @@ -576,7 +633,7 @@ impl MlKem1024PublicKey { impl fmt::Debug for MlKem1024PublicKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("MlKem1024PublicKey") - .field("bytes", &format!("[{}]", self.bytes.len())) + .field("bytes", &format_args!("[{}]", self.bytes.len())) .finish() } } @@ -592,7 +649,7 @@ mod tests { #[test] fn roundtrip() { - let (sk, pk) = <$priv>::generate(); + let (pk, sk) = <$priv>::generate(); let (ct, ss1) = pk.encapsulate(); let ss2 = sk.decapsulate(&ct); assert_eq!(ss1, ss2); @@ -600,8 +657,8 @@ mod tests { #[test] fn seed_roundtrip() { - let (sk, pk) = <$priv>::generate(); - let sk2 = <$priv>::from_seed(sk.seed).unwrap(); + let (pk, sk) = <$priv>::generate(); + let sk2 = <$priv>::from_seed(&sk.seed).unwrap(); let (ct, ss1) = pk.encapsulate(); let ss2 = sk2.decapsulate(&ct); assert_eq!(ss1, ss2); @@ -609,7 +666,7 @@ mod tests { #[test] fn derive_pubkey() { - let (sk, pk) = <$priv>::generate(); + let (pk, sk) = <$priv>::generate(); assert_eq!(pk.bytes, sk.public_key().unwrap().bytes); } @@ -621,14 +678,14 @@ mod tests { #[test] fn from_slice_roundtrip() { - let (_, pk) = <$priv>::generate(); + let (pk, _) = <$priv>::generate(); let pk2 = <$pub>::from_slice(&pk.bytes).unwrap(); assert_eq!(pk.bytes, pk2.bytes); } #[test] fn implicit_rejection() { - let (sk, _) = <$priv>::generate(); + let (_, sk) = <$priv>::generate(); let bad_ct = [0x42u8; $ct_len]; // bad ciphertext still "works", just returns deterministic garbage let ss1 = sk.decapsulate(&bad_ct); @@ -638,7 +695,7 @@ mod tests { #[test] fn debug_redacts_seed() { - let (sk, _) = <$priv>::generate(); + let (_, sk) = <$priv>::generate(); let dbg = format!("{:?}", sk); assert!(dbg.contains("redacted")); } @@ -660,49 +717,36 @@ mod tests { #[test] fn roundtrip() { - let kem = $algorithm; - let (pk, seed) = kem.generate_key().unwrap(); - let (ct, ss1) = kem.encapsulate(&pk).unwrap(); - let ss2 = kem.decapsulate(&seed, &ct).unwrap(); + let (pk, sk) = MlKemPrivateKey::generate($algorithm).unwrap(); + let (ct, ss1) = pk.encapsulate().unwrap(); + let ss2 = sk.decapsulate(&ct).unwrap(); assert_eq!(ss1, ss2); } #[test] fn key_sizes() { - let kem = $algorithm; - assert_eq!(kem.public_key_bytes(), $pk_len); - assert_eq!(kem.ciphertext_bytes(), $ct_len); + assert_eq!($algorithm.public_key_bytes(), $pk_len); + assert_eq!($algorithm.ciphertext_bytes(), $ct_len); - let (pk, private_key) = kem.generate_key().unwrap(); - assert_eq!(pk.len(), $pk_len); - assert_eq!(private_key.len(), PRIVATE_KEY_SEED_BYTES); + let (pk, private_key) = MlKemPrivateKey::generate($algorithm).unwrap(); + assert_eq!(pk.as_bytes().len(), $pk_len); + assert_eq!(private_key.seed_bytes().len(), PRIVATE_KEY_SEED_BYTES); - let (ct, ss) = kem.encapsulate(&pk).unwrap(); + let (ct, ss) = pk.encapsulate().unwrap(); assert_eq!(ct.len(), $ct_len); assert_eq!(ss.len(), SHARED_SECRET_BYTES); } #[test] fn invalid_public_key_length() { - let kem = $algorithm; - let result = kem.encapsulate(&[0u8; 100]); - assert!(result.is_err()); - } - - #[test] - fn invalid_private_key_length() { - let kem = $algorithm; - let (pk, _) = kem.generate_key().unwrap(); - let (ct, _) = kem.encapsulate(&pk).unwrap(); - let result = kem.decapsulate(&[0u8; 32], &ct); + let result = MlKemPublicKey::from_slice($algorithm, &[0u8; 100]); assert!(result.is_err()); } #[test] fn invalid_ciphertext_length() { - let kem = $algorithm; - let (_, private_key) = kem.generate_key().unwrap(); - let result = kem.decapsulate(&private_key, &[0u8; 100]); + let (_, sk) = MlKemPrivateKey::generate($algorithm).unwrap(); + let result = sk.decapsulate(&[0u8; 100]); assert!(result.is_err()); } } @@ -719,20 +763,5 @@ mod tests { assert_eq!(Algorithm::MlKem1024.public_key_bytes(), 1568); assert_eq!(Algorithm::MlKem1024.ciphertext_bytes(), 1568); } - - #[test] - fn cross_kem_incompatibility() { - // Keys from one KEM variant should not work with another - let kem768 = Algorithm::MlKem768; - let kem1024 = Algorithm::MlKem1024; - - let (pk768, _) = kem768.generate_key().unwrap(); - let (pk1024, _) = kem1024.generate_key().unwrap(); - - // 768 public key is wrong length for 1024 - assert!(kem1024.encapsulate(&pk768).is_err()); - // 1024 public key is wrong length for 768 - assert!(kem768.encapsulate(&pk1024).is_err()); - } } } From 8d35c787e7346f3240fac03c1c792ea1cc7002eb Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 3 Feb 2026 01:16:10 +0000 Subject: [PATCH 097/111] Allow pq-experimental for v4 back-compat --- Cargo.toml | 8 ++++---- boring/Cargo.toml | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 230a2ccb9..5cf814453 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ resolver = "2" [workspace.package] -version = "5.0.0-alpha.2" +version = "5.0.0-alpha.3" repository = "https://github.com/cloudflare/boring" edition = "2021" @@ -19,9 +19,9 @@ tag-prefix = "" publish = false [workspace.dependencies] -boring-sys = { version = "5.0.0-alpha.2", path = "./boring-sys" } -boring = { version = "5.0.0-alpha.2", path = "./boring" } -tokio-boring = { version = "5.0.0-alpha.2", path = "./tokio-boring" } +boring-sys = { version = "5.0.0-alpha.3", path = "./boring-sys" } +boring = { version = "5.0.0-alpha.3", path = "./boring" } +tokio-boring = { version = "5.0.0-alpha.3", path = "./tokio-boring" } bindgen = { version = "0.72.0", default-features = false, features = ["runtime"] } bitflags = "2.9" diff --git a/boring/Cargo.toml b/boring/Cargo.toml index d76c92945..45cfd672d 100644 --- a/boring/Cargo.toml +++ b/boring/Cargo.toml @@ -25,6 +25,10 @@ fips = ["boring-sys/fips"] # **DO NOT USE** This will be removed without warning in future releases. legacy-compat-deprecated = [] +# **DO NOT USE** This will be removed without warning in future releases. +# PQ is always enabled. This feature is a no-op, only for backwards compatibility. +pq-experimental = [] + # Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250) # This feature is necessary in order to compile the bindings for the # default branch of boringSSL. Alternatively, a version of boringSSL that From 39e394f37fe2831f182eef753983d1a8a605acbc Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 29 Dec 2025 17:28:04 +0000 Subject: [PATCH 098/111] Avoid useless malloc for SSL_set_tlsext_status_ocsp_resp --- boring/src/ssl/mod.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 8b437dbf3..5dee12e60 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -3579,17 +3579,15 @@ impl SslRef { } /// Sets the OCSP response to be returned to the client. - #[corresponds(SSL_set_tlsext_status_ocsp_resp)] + #[corresponds(SSL_set_ocsp_response)] pub fn set_ocsp_status(&mut self, response: &[u8]) -> Result<(), ErrorStack> { unsafe { assert!(response.len() <= c_int::MAX as usize); - let p = cvt_p(ffi::OPENSSL_malloc(response.len() as _))?; - ptr::copy_nonoverlapping(response.as_ptr(), p as *mut u8, response.len()); - cvt(ffi::SSL_set_tlsext_status_ocsp_resp( + cvt(ffi::SSL_set_ocsp_response( self.as_ptr(), - p as *mut c_uchar, + response.as_ptr(), response.len(), - ) as c_int) + )) } } From bdc5e1864c813e8ec399399e6cbfe6fc7e44943a Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 29 Dec 2025 19:41:08 +0000 Subject: [PATCH 099/111] mem::forget -= 1 --- boring/src/x509/store.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/boring/src/x509/store.rs b/boring/src/x509/store.rs index c3686bc80..f4edca034 100644 --- a/boring/src/x509/store.rs +++ b/boring/src/x509/store.rs @@ -48,7 +48,7 @@ use crate::x509::{X509Object, X509}; use crate::{cvt, cvt_p}; use foreign_types::{ForeignType, ForeignTypeRef}; use openssl_macros::corresponds; -use std::mem; +use std::mem::ManuallyDrop; foreign_type_and_impl_send_sync! { type CType = ffi::X509_STORE; @@ -73,9 +73,7 @@ impl X509StoreBuilder { /// Constructs the `X509Store`. #[must_use] pub fn build(self) -> X509Store { - let store = X509Store(self.0); - mem::forget(self); - store + X509Store(ManuallyDrop::new(self).0) } } From 7298c9e0f0219a50d01e2090b21fcde0e78fd65d Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 29 Dec 2025 17:27:48 +0000 Subject: [PATCH 100/111] Use std helper methods for pointer casts --- boring/src/aes.rs | 28 +++++---- boring/src/asn1.rs | 12 ++-- boring/src/bio.rs | 7 +-- boring/src/bn.rs | 13 ++--- boring/src/derive.rs | 9 +-- boring/src/dsa.rs | 10 ++-- boring/src/ec.rs | 8 +-- boring/src/ecdsa.rs | 14 ++--- boring/src/hash.rs | 6 +- boring/src/lib.rs | 2 +- boring/src/macros.rs | 2 +- boring/src/memcmp.rs | 9 +-- boring/src/nid.rs | 6 +- boring/src/pkcs12.rs | 4 +- boring/src/pkcs5.rs | 8 +-- boring/src/pkey.rs | 8 +-- boring/src/rsa.rs | 16 +++--- boring/src/sha.rs | 12 ++-- boring/src/sign.rs | 20 +++---- boring/src/srtp.rs | 2 +- boring/src/ssl/bio.rs | 12 ++-- boring/src/ssl/callbacks.rs | 16 +++--- boring/src/ssl/mod.rs | 109 ++++++++++++++---------------------- boring/src/stack.rs | 26 ++++----- boring/src/string.rs | 4 +- boring/src/symm.rs | 9 ++- boring/src/util.rs | 8 +-- boring/src/x509/mod.rs | 52 ++++++++--------- boring/src/x509/verify.rs | 6 +- 29 files changed, 190 insertions(+), 248 deletions(-) diff --git a/boring/src/aes.rs b/boring/src/aes.rs index 8a47cbafc..2a46b6125 100644 --- a/boring/src/aes.rs +++ b/boring/src/aes.rs @@ -38,7 +38,7 @@ //! ``` //! use crate::ffi; -use libc::{c_int, c_uint, size_t}; +use libc::{c_int, c_uint}; use openssl_macros::corresponds; use std::mem::MaybeUninit; use std::ptr; @@ -63,7 +63,7 @@ impl AesKey { let mut aes_key = MaybeUninit::uninit(); let r = ffi::AES_set_encrypt_key( - key.as_ptr() as *const _, + key.as_ptr(), key.len() as c_uint * 8, aes_key.as_mut_ptr(), ); @@ -87,7 +87,7 @@ impl AesKey { let mut aes_key = MaybeUninit::uninit(); let r = ffi::AES_set_decrypt_key( - key.as_ptr() as *const _, + key.as_ptr(), key.len() as c_uint * 8, aes_key.as_mut_ptr(), ); @@ -125,12 +125,11 @@ pub fn wrap_key( assert!(out.len() >= in_.len() + 8); // Ciphertext is 64 bits longer (see 2.2.1) let written = ffi::AES_wrap_key( - &key.0 as *const _ as *mut _, // this is safe, the implementation only uses the key as a const pointer. - iv.as_ref() - .map_or(ptr::null(), |iv| iv.as_ptr() as *const _), - out.as_ptr() as *mut _, - in_.as_ptr() as *const _, - in_.len() as size_t, + std::ptr::addr_of!(key.0).cast_mut(), // this is safe, the implementation only uses the key as a const pointer. + iv.as_ref().map_or(ptr::null(), |iv| iv.as_ptr()), + out.as_mut_ptr(), + in_.as_ptr(), + in_.len(), ); if written <= 0 { Err(KeyError(())) @@ -164,12 +163,11 @@ pub fn unwrap_key( assert!(out.len() + 8 <= in_.len()); let written = ffi::AES_unwrap_key( - &key.0 as *const _ as *mut _, // this is safe, the implementation only uses the key as a const pointer. - iv.as_ref() - .map_or(ptr::null(), |iv| iv.as_ptr() as *const _), - out.as_ptr() as *mut _, - in_.as_ptr() as *const _, - in_.len() as size_t, + std::ptr::addr_of!(key.0).cast_mut(), // this is safe, the implementation only uses the key as a const pointer. + iv.as_ref().map_or(ptr::null(), |iv| iv.as_ptr().cast()), + out.as_ptr().cast_mut(), + in_.as_ptr().cast(), + in_.len(), ); if written <= 0 { diff --git a/boring/src/asn1.rs b/boring/src/asn1.rs index 6099217dd..27f375f29 100644 --- a/boring/src/asn1.rs +++ b/boring/src/asn1.rs @@ -26,7 +26,7 @@ //! ``` use crate::ffi; use foreign_types::{ForeignType, ForeignTypeRef}; -use libc::{c_char, c_int, c_long, time_t}; +use libc::{c_int, c_long, time_t}; use std::cmp::Ordering; use std::ffi::CString; use std::fmt; @@ -408,7 +408,7 @@ impl Asn1StringRef { return Err(ErrorStack::get()); } - Ok(OpensslString::from_ptr(ptr as *mut c_char)) + Ok(OpensslString::from_ptr(ptr.cast())) } } @@ -549,7 +549,7 @@ impl Asn1BitStringRef { #[corresponds(ASN1_STRING_length)] #[must_use] pub fn len(&self) -> usize { - unsafe { ffi::ASN1_STRING_length(self.as_ptr() as *const _) as usize } + unsafe { ffi::ASN1_STRING_length(self.as_ptr().cast_const()) as usize } } /// Determines if the string is empty. @@ -591,7 +591,7 @@ impl Asn1Object { unsafe { ffi::init(); let txt = CString::new(txt).map_err(ErrorStack::internal_error)?; - let obj: *mut ffi::ASN1_OBJECT = cvt_p(ffi::OBJ_txt2obj(txt.as_ptr() as *const _, 0))?; + let obj: *mut ffi::ASN1_OBJECT = cvt_p(ffi::OBJ_txt2obj(txt.as_ptr(), 0))?; Ok(Asn1Object::from_ptr(obj)) } } @@ -608,9 +608,9 @@ impl Asn1ObjectRef { impl fmt::Display for Asn1ObjectRef { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { unsafe { - let mut buf = [0; 80]; + let mut buf = [0u8; 80]; let len = ffi::OBJ_obj2txt( - buf.as_mut_ptr() as *mut _, + buf.as_mut_ptr().cast(), buf.len() as c_int, self.as_ptr(), 0, diff --git a/boring/src/bio.rs b/boring/src/bio.rs index 2e6b2572d..ff1e83e29 100644 --- a/boring/src/bio.rs +++ b/boring/src/bio.rs @@ -27,12 +27,7 @@ impl<'a> MemBioSlice<'a> { ffi::init(); assert!(buf.len() <= BufLen::MAX as usize); - let bio = unsafe { - cvt_p(BIO_new_mem_buf( - buf.as_ptr() as *const _, - buf.len() as BufLen, - ))? - }; + let bio = unsafe { cvt_p(BIO_new_mem_buf(buf.as_ptr().cast(), buf.len() as BufLen))? }; Ok(MemBioSlice(bio, PhantomData)) } diff --git a/boring/src/bn.rs b/boring/src/bn.rs index b8464e1d2..659d516e4 100644 --- a/boring/src/bn.rs +++ b/boring/src/bn.rs @@ -24,7 +24,7 @@ //! [`BIGNUM`]: https://wiki.openssl.org/index.php/Manual:Bn_internal(3) use crate::ffi; use foreign_types::{ForeignType, ForeignTypeRef}; -use libc::{c_int, size_t}; +use libc::c_int; use std::cmp::Ordering; use std::ffi::CString; use std::ops::{Add, Deref, Div, Mul, Neg, Rem, Shl, Shr, Sub}; @@ -831,7 +831,7 @@ impl BigNum { ffi::init(); let c_str = CString::new(s.as_bytes()).map_err(ErrorStack::internal_error)?; let mut bn = ptr::null_mut(); - cvt(ffi::BN_dec2bn(&mut bn, c_str.as_ptr() as *const _))?; + cvt(ffi::BN_dec2bn(&mut bn, c_str.as_ptr()))?; Ok(BigNum::from_ptr(bn)) } } @@ -843,7 +843,7 @@ impl BigNum { ffi::init(); let c_str = CString::new(s.as_bytes()).map_err(ErrorStack::internal_error)?; let mut bn = ptr::null_mut(); - cvt(ffi::BN_hex2bn(&mut bn, c_str.as_ptr() as *const _))?; + cvt(ffi::BN_hex2bn(&mut bn, c_str.as_ptr()))?; Ok(BigNum::from_ptr(bn)) } } @@ -865,12 +865,7 @@ impl BigNum { unsafe { ffi::init(); assert!(n.len() <= c_int::MAX as usize); - cvt_p(ffi::BN_bin2bn( - n.as_ptr(), - n.len() as size_t, - ptr::null_mut(), - )) - .map(|p| BigNum::from_ptr(p)) + cvt_p(ffi::BN_bin2bn(n.as_ptr(), n.len(), ptr::null_mut())).map(|p| BigNum::from_ptr(p)) } } } diff --git a/boring/src/derive.rs b/boring/src/derive.rs index a8d822d4d..a094c66d7 100644 --- a/boring/src/derive.rs +++ b/boring/src/derive.rs @@ -64,14 +64,7 @@ impl<'a> Deriver<'a> { #[corresponds(EVP_PKEY_derive)] pub fn derive(&mut self, buf: &mut [u8]) -> Result { let mut len = buf.len(); - unsafe { - cvt(ffi::EVP_PKEY_derive( - self.0, - buf.as_mut_ptr() as *mut _, - &mut len, - )) - .map(|_| len) - } + unsafe { cvt(ffi::EVP_PKEY_derive(self.0, buf.as_mut_ptr(), &mut len)).map(|_| len) } } /// A convenience function which derives a shared secret and returns it in a new buffer. diff --git a/boring/src/dsa.rs b/boring/src/dsa.rs index ca7efaf32..bc6068add 100644 --- a/boring/src/dsa.rs +++ b/boring/src/dsa.rs @@ -103,7 +103,7 @@ where unsafe { let mut pub_key = ptr::null(); DSA_get0_key(self.as_ptr(), &mut pub_key, ptr::null_mut()); - BigNumRef::from_ptr(pub_key as *mut _) + BigNumRef::from_ptr(pub_key.cast_mut()) } } } @@ -132,7 +132,7 @@ where unsafe { let mut priv_key = ptr::null(); DSA_get0_key(self.as_ptr(), ptr::null_mut(), &mut priv_key); - BigNumRef::from_ptr(priv_key as *mut _) + BigNumRef::from_ptr(priv_key.cast_mut()) } } } @@ -154,7 +154,7 @@ where unsafe { let mut p = ptr::null(); DSA_get0_pqg(self.as_ptr(), &mut p, ptr::null_mut(), ptr::null_mut()); - BigNumRef::from_ptr(p as *mut _) + BigNumRef::from_ptr(p.cast_mut()) } } @@ -164,7 +164,7 @@ where unsafe { let mut q = ptr::null(); DSA_get0_pqg(self.as_ptr(), ptr::null_mut(), &mut q, ptr::null_mut()); - BigNumRef::from_ptr(q as *mut _) + BigNumRef::from_ptr(q.cast_mut()) } } @@ -174,7 +174,7 @@ where unsafe { let mut g = ptr::null(); DSA_get0_pqg(self.as_ptr(), ptr::null_mut(), ptr::null_mut(), &mut g); - BigNumRef::from_ptr(g as *mut _) + BigNumRef::from_ptr(g.cast_mut()) } } } diff --git a/boring/src/ec.rs b/boring/src/ec.rs index 6bf3f09fb..745b81fcb 100644 --- a/boring/src/ec.rs +++ b/boring/src/ec.rs @@ -183,7 +183,7 @@ impl EcGroupRef { pub fn generator(&self) -> &EcPointRef { unsafe { let ptr = ffi::EC_GROUP_get0_generator(self.as_ptr()); - EcPointRef::from_ptr(ptr as *mut _) + EcPointRef::from_ptr(ptr.cast_mut()) } } @@ -497,7 +497,7 @@ where pub fn private_key(&self) -> &BigNumRef { unsafe { let ptr = ffi::EC_KEY_get0_private_key(self.as_ptr()); - BigNumRef::from_ptr(ptr as *mut _) + BigNumRef::from_ptr(ptr.cast_mut()) } } } @@ -512,7 +512,7 @@ where pub fn public_key(&self) -> &EcPointRef { unsafe { let ptr = ffi::EC_KEY_get0_public_key(self.as_ptr()); - EcPointRef::from_ptr(ptr as *mut _) + EcPointRef::from_ptr(ptr.cast_mut()) } } @@ -543,7 +543,7 @@ where pub fn group(&self) -> &EcGroupRef { unsafe { let ptr = ffi::EC_KEY_get0_group(self.as_ptr()); - EcGroupRef::from_ptr(ptr as *mut _) + EcGroupRef::from_ptr(ptr.cast_mut()) } } diff --git a/boring/src/ecdsa.rs b/boring/src/ecdsa.rs index 4d5bb0979..27be5c51f 100644 --- a/boring/src/ecdsa.rs +++ b/boring/src/ecdsa.rs @@ -2,7 +2,7 @@ use crate::ffi; use foreign_types::{ForeignType, ForeignTypeRef}; -use libc::{c_int, size_t}; +use libc::c_int; use openssl_macros::corresponds; use std::mem; use std::ptr; @@ -36,10 +36,10 @@ impl EcdsaSig { assert!(data.len() <= c_int::MAX as usize); let sig = cvt_p(ffi::ECDSA_do_sign( data.as_ptr(), - data.len() as size_t, + data.len(), eckey.as_ptr(), ))?; - Ok(EcdsaSig::from_ptr(sig as *mut _)) + Ok(EcdsaSig::from_ptr(sig)) } } @@ -51,7 +51,7 @@ impl EcdsaSig { let sig = cvt_p(ffi::ECDSA_SIG_new())?; ECDSA_SIG_set0(sig, r.as_ptr(), s.as_ptr()); mem::forget((r, s)); - Ok(EcdsaSig::from_ptr(sig as *mut _)) + Ok(EcdsaSig::from_ptr(sig)) } } @@ -83,7 +83,7 @@ impl EcdsaSigRef { assert!(data.len() <= c_int::MAX as usize); cvt_n(ffi::ECDSA_do_verify( data.as_ptr(), - data.len() as size_t, + data.len(), self.as_ptr(), eckey.as_ptr(), )) @@ -98,7 +98,7 @@ impl EcdsaSigRef { unsafe { let mut r = ptr::null(); ECDSA_SIG_get0(self.as_ptr(), &mut r, ptr::null_mut()); - BigNumRef::from_ptr(r as *mut _) + BigNumRef::from_ptr(r.cast_mut()) } } @@ -109,7 +109,7 @@ impl EcdsaSigRef { unsafe { let mut s = ptr::null(); ECDSA_SIG_get0(self.as_ptr(), ptr::null_mut(), &mut s); - BigNumRef::from_ptr(s as *mut _) + BigNumRef::from_ptr(s.cast_mut()) } } } diff --git a/boring/src/hash.rs b/boring/src/hash.rs index b9dcf99e1..5035edc31 100644 --- a/boring/src/hash.rs +++ b/boring/src/hash.rs @@ -1,7 +1,7 @@ use crate::ffi; use openssl_macros::corresponds; use std::convert::TryInto; -use std::ffi::{c_uint, c_void}; +use std::ffi::c_uint; use std::fmt; use std::io; use std::io::prelude::*; @@ -196,7 +196,7 @@ impl Hasher { unsafe { cvt(ffi::EVP_DigestUpdate( self.ctx, - data.as_ptr() as *mut _, + data.as_ptr().cast_mut().cast(), data.len(), ))?; } @@ -370,7 +370,7 @@ pub(crate) fn hmac( cvt_p(unsafe { ffi::HMAC( digest.as_ptr(), - key.as_ptr() as *const c_void, + key.as_ptr().cast(), key.len(), data.as_ptr(), data.len(), diff --git a/boring/src/lib.rs b/boring/src/lib.rs index fca6838b0..a93f9d692 100644 --- a/boring/src/lib.rs +++ b/boring/src/lib.rs @@ -210,6 +210,6 @@ unsafe extern "C" fn free_data_box( _argp: *mut c_void, ) { if !ptr.is_null() { - drop(Box::::from_raw(ptr as *mut T)); + drop(Box::::from_raw(ptr.cast::())); } } diff --git a/boring/src/macros.rs b/boring/src/macros.rs index 56c1d94ec..e2da938aa 100644 --- a/boring/src/macros.rs +++ b/boring/src/macros.rs @@ -28,7 +28,7 @@ macro_rules! private_key_from_pem { cvt_p($f(bio.as_ptr(), ptr::null_mut(), Some(crate::util::invoke_passwd_cb::), - &mut cb as *mut _ as *mut _)) + ptr::from_mut(&mut cb).cast())) .map(|p| ::foreign_types::ForeignType::from_ptr(p)) } } diff --git a/boring/src/memcmp.rs b/boring/src/memcmp.rs index 99cefdbee..4ac08b655 100644 --- a/boring/src/memcmp.rs +++ b/boring/src/memcmp.rs @@ -30,7 +30,6 @@ //! assert!(!eq(&a, &c)); //! ``` use crate::ffi; -use libc::size_t; /// Returns `true` iff `a` and `b` contain the same bytes. /// @@ -64,13 +63,7 @@ use libc::size_t; #[must_use] pub fn eq(a: &[u8], b: &[u8]) -> bool { assert!(a.len() == b.len()); - let ret = unsafe { - ffi::CRYPTO_memcmp( - a.as_ptr() as *const _, - b.as_ptr() as *const _, - a.len() as size_t, - ) - }; + let ret = unsafe { ffi::CRYPTO_memcmp(a.as_ptr().cast(), b.as_ptr().cast(), a.len()) }; ret == 0 } diff --git a/boring/src/nid.rs b/boring/src/nid.rs index 347b30f74..15cfe4eb9 100644 --- a/boring/src/nid.rs +++ b/boring/src/nid.rs @@ -1,6 +1,6 @@ //! A collection of numerical identifiers for OpenSSL objects. use crate::ffi; -use libc::{c_char, c_int}; +use libc::c_int; use openssl_macros::corresponds; use std::ffi::CStr; @@ -87,7 +87,7 @@ impl Nid { #[allow(clippy::trivially_copy_pass_by_ref)] pub fn long_name(&self) -> Result<&'static str, ErrorStack> { unsafe { - let nameptr = cvt_p(ffi::OBJ_nid2ln(self.0) as *mut c_char)?; + let nameptr = cvt_p(ffi::OBJ_nid2ln(self.0).cast_mut())?; CStr::from_ptr(nameptr) .to_str() .map_err(ErrorStack::internal_error) @@ -99,7 +99,7 @@ impl Nid { #[allow(clippy::trivially_copy_pass_by_ref)] pub fn short_name(&self) -> Result<&'static str, ErrorStack> { unsafe { - let nameptr = cvt_p(ffi::OBJ_nid2sn(self.0) as *mut c_char)?; + let nameptr = cvt_p(ffi::OBJ_nid2sn(self.0).cast_mut())?; CStr::from_ptr(nameptr) .to_str() .map_err(ErrorStack::internal_error) diff --git a/boring/src/pkcs12.rs b/boring/src/pkcs12.rs index e8fb7c12e..bb851421e 100644 --- a/boring/src/pkcs12.rs +++ b/boring/src/pkcs12.rs @@ -180,8 +180,8 @@ impl Pkcs12Builder { let keytype = 0; cvt_p(ffi::PKCS12_create( - pass.as_ptr() as *const _ as *mut _, - friendly_name.as_ptr() as *const _ as *mut _, + pass.as_ptr(), + friendly_name.as_ptr(), pkey, cert, ca, diff --git a/boring/src/pkcs5.rs b/boring/src/pkcs5.rs index a27181b3f..1b78c520e 100644 --- a/boring/src/pkcs5.rs +++ b/boring/src/pkcs5.rs @@ -96,7 +96,7 @@ pub fn pbkdf2_hmac( ffi::init(); cvt(ffi::PKCS5_PBKDF2_HMAC( - pass.as_ptr() as *const _, + pass.as_ptr().cast(), pass.len(), salt.as_ptr(), salt.len(), @@ -121,15 +121,15 @@ pub fn scrypt( unsafe { ffi::init(); cvt(ffi::EVP_PBE_scrypt( - pass.as_ptr() as *const _, + pass.as_ptr().cast(), pass.len(), - salt.as_ptr() as *const _, + salt.as_ptr().cast(), salt.len(), n, r, p, maxmem, - key.as_mut_ptr() as *mut _, + key.as_mut_ptr(), key.len(), )) } diff --git a/boring/src/pkey.rs b/boring/src/pkey.rs index 9245f5b64..b141f5bda 100644 --- a/boring/src/pkey.rs +++ b/boring/src/pkey.rs @@ -361,7 +361,7 @@ impl PKey { cvt(ffi::EVP_PKEY_assign( pkey.0, ffi::EVP_PKEY_RSA, - rsa.as_ptr() as *mut _, + rsa.as_ptr().cast(), ))?; mem::forget(rsa); Ok(pkey) @@ -377,7 +377,7 @@ impl PKey { cvt(ffi::EVP_PKEY_assign( pkey.0, ffi::EVP_PKEY_EC, - ec_key.as_ptr() as *mut _, + ec_key.as_ptr().cast(), ))?; mem::forget(ec_key); Ok(pkey) @@ -455,7 +455,7 @@ impl PKey { bio.as_ptr(), ptr::null_mut(), Some(invoke_passwd_cb::), - &mut cb as *mut _ as *mut _, + std::ptr::addr_of_mut!(cb).cast(), )) .map(|p| PKey::from_ptr(p)) } @@ -479,7 +479,7 @@ impl PKey { bio.as_ptr(), ptr::null_mut(), None, - passphrase.as_ptr() as *const _ as *mut _, + passphrase.as_ptr().cast_mut().cast(), )) .map(|p| PKey::from_ptr(p)) } diff --git a/boring/src/rsa.rs b/boring/src/rsa.rs index ff47e71ba..79dfa6df9 100644 --- a/boring/src/rsa.rs +++ b/boring/src/rsa.rs @@ -194,7 +194,7 @@ where unsafe { let mut d = ptr::null(); RSA_get0_key(self.as_ptr(), ptr::null_mut(), ptr::null_mut(), &mut d); - BigNumRef::from_ptr(d as *mut _) + BigNumRef::from_ptr(d.cast_mut()) } } @@ -208,7 +208,7 @@ where if p.is_null() { None } else { - Some(BigNumRef::from_ptr(p as *mut _)) + Some(BigNumRef::from_ptr(p.cast_mut())) } } } @@ -223,7 +223,7 @@ where if q.is_null() { None } else { - Some(BigNumRef::from_ptr(q as *mut _)) + Some(BigNumRef::from_ptr(q.cast_mut())) } } } @@ -238,7 +238,7 @@ where if dp.is_null() { None } else { - Some(BigNumRef::from_ptr(dp as *mut _)) + Some(BigNumRef::from_ptr(dp.cast_mut())) } } } @@ -253,7 +253,7 @@ where if dq.is_null() { None } else { - Some(BigNumRef::from_ptr(dq as *mut _)) + Some(BigNumRef::from_ptr(dq.cast_mut())) } } } @@ -268,7 +268,7 @@ where if qi.is_null() { None } else { - Some(BigNumRef::from_ptr(qi as *mut _)) + Some(BigNumRef::from_ptr(qi.cast_mut())) } } } @@ -391,7 +391,7 @@ where unsafe { let mut n = ptr::null(); RSA_get0_key(self.as_ptr(), &mut n, ptr::null_mut(), ptr::null_mut()); - BigNumRef::from_ptr(n as *mut _) + BigNumRef::from_ptr(n.cast_mut()) } } @@ -402,7 +402,7 @@ where unsafe { let mut e = ptr::null(); RSA_get0_key(self.as_ptr(), ptr::null_mut(), &mut e, ptr::null_mut()); - BigNumRef::from_ptr(e as *mut _) + BigNumRef::from_ptr(e.cast_mut()) } } } diff --git a/boring/src/sha.rs b/boring/src/sha.rs index 4bea478b9..dd0671852 100644 --- a/boring/src/sha.rs +++ b/boring/src/sha.rs @@ -152,7 +152,7 @@ impl Sha1 { #[inline] pub fn update(&mut self, buf: &[u8]) { unsafe { - ffi::SHA1_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len()); + ffi::SHA1_Update(&mut self.0, buf.as_ptr().cast::(), buf.len()); } } @@ -197,7 +197,7 @@ impl Sha224 { #[inline] pub fn update(&mut self, buf: &[u8]) { unsafe { - ffi::SHA224_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len()); + ffi::SHA224_Update(&mut self.0, buf.as_ptr().cast::(), buf.len()); } } @@ -242,7 +242,7 @@ impl Sha256 { #[inline] pub fn update(&mut self, buf: &[u8]) { unsafe { - ffi::SHA256_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len()); + ffi::SHA256_Update(&mut self.0, buf.as_ptr().cast::(), buf.len()); } } @@ -287,7 +287,7 @@ impl Sha384 { #[inline] pub fn update(&mut self, buf: &[u8]) { unsafe { - ffi::SHA384_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len()); + ffi::SHA384_Update(&mut self.0, buf.as_ptr().cast::(), buf.len()); } } @@ -332,7 +332,7 @@ impl Sha512 { #[inline] pub fn update(&mut self, buf: &[u8]) { unsafe { - ffi::SHA512_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len()); + ffi::SHA512_Update(&mut self.0, buf.as_ptr().cast::(), buf.len()); } } @@ -377,7 +377,7 @@ impl Sha512_256 { #[inline] pub fn update(&mut self, buf: &[u8]) { unsafe { - ffi::SHA512_256_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len()); + ffi::SHA512_256_Update(&mut self.0, buf.as_ptr().cast::(), buf.len()); } } diff --git a/boring/src/sign.rs b/boring/src/sign.rs index f13d9150a..eea911e67 100644 --- a/boring/src/sign.rs +++ b/boring/src/sign.rs @@ -198,7 +198,7 @@ impl<'a> Signer<'a> { unsafe { cvt(ffi::EVP_PKEY_CTX_set_rsa_mgf1_md( self.pctx, - md.as_ptr() as *mut _, + md.as_ptr().cast_mut(), )) } } @@ -212,7 +212,7 @@ impl<'a> Signer<'a> { unsafe { cvt(ffi::EVP_DigestUpdate( self.md_ctx, - buf.as_ptr() as *const _, + buf.as_ptr().cast(), buf.len(), )) } @@ -251,7 +251,7 @@ impl<'a> Signer<'a> { let mut len = buf.len(); cvt(ffi::EVP_DigestSignFinal( self.md_ctx, - buf.as_mut_ptr() as *mut _, + buf.as_mut_ptr().cast(), &mut len, ))?; Ok(len) @@ -286,9 +286,9 @@ impl<'a> Signer<'a> { let mut sig_len = sig_buf.len(); cvt(ffi::EVP_DigestSign( self.md_ctx, - sig_buf.as_mut_ptr() as *mut _, + sig_buf.as_mut_ptr(), &mut sig_len, - data_buf.as_ptr() as *const _, + data_buf.as_ptr(), data_buf.len(), ))?; Ok(sig_len) @@ -441,7 +441,7 @@ impl<'a> Verifier<'a> { unsafe { cvt(ffi::EVP_PKEY_CTX_set_rsa_mgf1_md( self.pctx, - md.as_ptr() as *mut _, + md.as_ptr().cast_mut(), )) } } @@ -455,7 +455,7 @@ impl<'a> Verifier<'a> { unsafe { cvt(ffi::EVP_DigestUpdate( self.md_ctx, - buf.as_ptr() as *const _, + buf.as_ptr().cast(), buf.len(), )) } @@ -466,7 +466,7 @@ impl<'a> Verifier<'a> { pub fn verify(&self, signature: &[u8]) -> Result { unsafe { let r = - EVP_DigestVerifyFinal(self.md_ctx, signature.as_ptr() as *mut _, signature.len()); + EVP_DigestVerifyFinal(self.md_ctx, signature.as_ptr().cast_mut(), signature.len()); match r { 1 => Ok(true), 0 => { @@ -484,9 +484,9 @@ impl<'a> Verifier<'a> { unsafe { let r = ffi::EVP_DigestVerify( self.md_ctx, - signature.as_ptr() as *const _, + signature.as_ptr().cast(), signature.len(), - buf.as_ptr() as *const _, + buf.as_ptr().cast(), buf.len(), ); match r { diff --git a/boring/src/srtp.rs b/boring/src/srtp.rs index 8d23b722c..7dffc436e 100644 --- a/boring/src/srtp.rs +++ b/boring/src/srtp.rs @@ -27,7 +27,7 @@ impl SrtpProtectionProfileRef { #[must_use] pub fn name(&self) -> &'static str { - unsafe { CStr::from_ptr((*self.as_ptr()).name as *const _) } + unsafe { CStr::from_ptr((*self.as_ptr()).name.cast()) } .to_str() .expect("should be UTF-8") } diff --git a/boring/src/ssl/bio.rs b/boring/src/ssl/bio.rs index 4b3492faf..82695853f 100644 --- a/boring/src/ssl/bio.rs +++ b/boring/src/ssl/bio.rs @@ -44,7 +44,7 @@ pub fn new(stream: S) -> Result<(*mut BIO, BioMethod), ErrorSta unsafe { let bio = cvt_p(BIO_new(method.0.get()))?; - BIO_set_data(bio, Box::into_raw(state) as *mut _); + BIO_set_data(bio, Box::into_raw(state).cast()); BIO_set_init(bio, 1); Ok((bio, method)) @@ -76,7 +76,7 @@ pub unsafe extern "C" fn take_stream(bio: *mut BIO) -> S { assert!(!data.is_null()); - let state = Box::>::from_raw(data as *mut _); + let state = Box::>::from_raw(data.cast()); BIO_set_data(bio, ptr::null_mut()); @@ -91,7 +91,7 @@ pub unsafe fn set_dtls_mtu_size(bio: *mut BIO, mtu_size: usize) { } unsafe fn state<'a, S: 'a>(bio: *mut BIO) -> &'a mut StreamState { - let data = BIO_get_data(bio) as *mut StreamState; + let data = BIO_get_data(bio).cast::>(); assert!(!data.is_null()); @@ -102,7 +102,7 @@ unsafe extern "C" fn bwrite(bio: *mut BIO, buf: *const c_char, len: c_ BIO_clear_retry_flags(bio); let state = state::(bio); - let buf = slice::from_raw_parts(buf as *const _, len as usize); + let buf = slice::from_raw_parts(buf.cast(), len as usize); match catch_unwind(AssertUnwindSafe(|| state.stream.write(buf))) { Ok(Ok(len)) => len as c_int, @@ -124,7 +124,7 @@ unsafe extern "C" fn bread(bio: *mut BIO, buf: *mut c_char, len: c_int) BIO_clear_retry_flags(bio); let state = state::(bio); - let buf = slice::from_raw_parts_mut(buf as *mut _, len as usize); + let buf = slice::from_raw_parts_mut(buf.cast(), len as usize); match catch_unwind(AssertUnwindSafe(|| state.stream.read(buf))) { Ok(Ok(len)) => len as c_int, @@ -201,7 +201,7 @@ unsafe extern "C" fn destroy(bio: *mut BIO) -> c_int { let data = BIO_get_data(bio); if !data.is_null() { - drop(Box::>::from_raw(data as *mut _)); + drop(Box::>::from_raw(data.cast())); BIO_set_data(bio, ptr::null_mut()); } diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index f08409b3c..41c08c6b9 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -41,7 +41,7 @@ where // SAFETY: The callback won't outlive the context it's associated with // because there is no `X509StoreContextRef::ssl_mut(&mut self)` method. - let verify = unsafe { &*(verify as *const F) }; + let verify = unsafe { &*std::ptr::from_ref::(verify) }; c_int::from(verify(preverify_ok != 0, ctx)) } @@ -90,7 +90,7 @@ where // SAFETY: The callback won't outlive the context it's associated with // because there is no way to get a mutable reference to the `SslContext`, // so the callback can't replace itself. - let verify = unsafe { &*(verify as *const F) }; + let verify = unsafe { &*std::ptr::from_ref::(verify) }; c_int::from(verify(ctx)) } @@ -160,7 +160,7 @@ where // Give the callback mutable slices into which it can write the identity and psk. let identity_sl = - unsafe { slice::from_raw_parts_mut(identity as *mut u8, max_identity_len as usize) }; + unsafe { slice::from_raw_parts_mut(identity.cast::(), max_identity_len as usize) }; let psk_sl = unsafe { slice::from_raw_parts_mut(psk, max_psk_len as usize) }; let ssl_context = ssl.ssl_context().to_owned(); @@ -358,7 +358,7 @@ where match callback(ssl, protos) { Ok(proto) => { - *out = proto.as_ptr() as *const c_uchar; + *out = proto.as_ptr(); *outlen = proto.len() as c_uchar; ffi::SSL_TLSEXT_ERR_OK @@ -442,7 +442,7 @@ where // SAFETY: We can make `callback` outlive `ssl` because it is a callback // stored in the session context set in `Ssl::new` so it is always // guaranteed to outlive the lifetime of this function's scope. - let callback = unsafe { &*(callback as *const F) }; + let callback = unsafe { &*std::ptr::from_ref::(callback) }; callback(ssl, session); @@ -495,7 +495,7 @@ where // SAFETY: We can make `callback` outlive `ssl` because it is a callback // stored in the session context set in `Ssl::new` so it is always // guaranteed to outlive the lifetime of this function's scope. - let callback = unsafe { &*(callback as *const F) }; + let callback = unsafe { &*std::ptr::from_ref::(callback) }; match callback(ssl, data) { Ok(Some(session)) => { @@ -515,7 +515,7 @@ where F: Fn(&SslRef, &str) + 'static + Sync + Send, { // SAFETY: boring provides valid inputs. - let ssl = unsafe { SslRef::from_ptr(ssl as *mut _) }; + let ssl = unsafe { SslRef::from_ptr(ssl.cast_mut()) }; let line = unsafe { CStr::from_ptr(line).to_string_lossy() }; let callback = ssl @@ -625,7 +625,7 @@ pub(super) unsafe extern "C" fn raw_info_callback( { // Due to FFI signature requirements we have to pass a *const SSL into this function, but // foreign-types requires a *mut SSL to get the Rust SslRef - let mut_ref = ssl as *mut ffi::SSL; + let mut_ref = ssl.cast_mut(); // SAFETY: boring provides valid inputs. let ssl = unsafe { SslRef::from_ptr(mut_ref) }; diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 5dee12e60..ff228ba9b 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -58,11 +58,11 @@ //! } //! ``` use foreign_types::{ForeignType, ForeignTypeRef, Opaque}; -use libc::{c_char, c_int, c_uchar, c_uint, c_void}; use openssl_macros::corresponds; use std::any::TypeId; use std::collections::HashMap; use std::convert::TryInto; +use std::ffi::{c_char, c_int, c_uchar, c_uint}; use std::ffi::{CStr, CString}; use std::fmt; use std::io; @@ -789,7 +789,7 @@ pub fn select_next_proto<'a>(server: &'a [u8], client: &'a [u8]) -> Option<&'a [ ); if r == ffi::OPENSSL_NPN_NEGOTIATED { - Some(slice::from_raw_parts(out as *const u8, outlen as usize)) + Some(slice::from_raw_parts(out.cast_const(), outlen as usize)) } else { None } @@ -1104,9 +1104,9 @@ impl SslContextBuilder { let callback_index = SslContext::cached_ex_index::(); self.ctx.replace_ex_data(callback_index, callback); + let callback = self.ctx.ex_data(callback_index).unwrap(); - let arg = self.ctx.ex_data(callback_index).unwrap() as *const F as *mut c_void; - + let arg = std::ptr::from_ref(callback).cast_mut().cast(); ffi::SSL_CTX_set_tlsext_servername_arg(self.as_ptr(), arg); ffi::SSL_CTX_set_tlsext_servername_callback(self.as_ptr(), Some(raw_sni::)); } @@ -1273,7 +1273,7 @@ impl SslContextBuilder { unsafe { cvt(ffi::SSL_CTX_load_verify_locations( self.as_ptr(), - file.as_ptr() as *const _, + file.as_ptr(), ptr::null(), )) } @@ -1340,7 +1340,7 @@ impl SslContextBuilder { unsafe { cvt(ffi::SSL_CTX_use_certificate_file( self.as_ptr(), - file.as_ptr() as *const _, + file.as_ptr(), file_type.as_raw(), )) } @@ -1361,7 +1361,7 @@ impl SslContextBuilder { unsafe { cvt(ffi::SSL_CTX_use_certificate_chain_file( self.as_ptr(), - file.as_ptr() as *const _, + file.as_ptr(), )) } } @@ -1400,7 +1400,7 @@ impl SslContextBuilder { unsafe { cvt(ffi::SSL_CTX_use_PrivateKey_file( self.as_ptr(), - file.as_ptr() as *const _, + file.as_ptr(), file_type.as_raw(), )) } @@ -1432,7 +1432,7 @@ impl SslContextBuilder { unsafe { cvt(ffi::SSL_CTX_set_cipher_list( self.as_ptr(), - cipher_list.as_ptr() as *const _, + cipher_list.as_ptr(), )) } } @@ -1453,7 +1453,7 @@ impl SslContextBuilder { unsafe { cvt(ffi::SSL_CTX_set_strict_cipher_list( self.as_ptr(), - cipher_list.as_ptr() as *const _, + cipher_list.as_ptr(), )) } } @@ -1962,7 +1962,7 @@ impl SslContextBuilder { unsafe { cvt_0i(ffi::SSL_CTX_set_verify_algorithm_prefs( self.as_ptr(), - prefs.as_ptr() as *const _, + prefs.as_ptr().cast(), prefs.len(), )) .map(|_| ()) @@ -1988,7 +1988,7 @@ impl SslContextBuilder { unsafe { cvt_0i(ffi::SSL_CTX_set1_curves_list( self.as_ptr(), - curves.as_ptr() as *const _, + curves.as_ptr(), )) .map(|_| ()) } @@ -2221,12 +2221,9 @@ impl SslContextRef { // this only from SslContextBuilder. #[corresponds(SSL_CTX_get_ex_data)] unsafe fn ex_data_mut(&mut self, index: Index) -> Option<&mut T> { - let data = ffi::SSL_CTX_get_ex_data(self.as_ptr(), index.as_raw()); - if data.is_null() { - None - } else { - Some(&mut *(data as *mut T)) - } + ffi::SSL_CTX_get_ex_data(self.as_ptr(), index.as_raw()) + .cast::() + .as_mut() } // Unsafe because SSL contexts are not guaranteed to be unique, we call @@ -2234,8 +2231,8 @@ impl SslContextRef { #[corresponds(SSL_CTX_set_ex_data)] unsafe fn set_ex_data(&mut self, index: Index, data: T) { unsafe { - let data = Box::into_raw(Box::new(data)) as *mut c_void; - ffi::SSL_CTX_set_ex_data(self.as_ptr(), index.as_raw(), data); + let data = Box::into_raw(Box::new(data)); + ffi::SSL_CTX_set_ex_data(self.as_ptr(), index.as_raw(), data.cast()); } } @@ -2458,7 +2455,7 @@ impl SslCipher { if ptr.is_null() { None } else { - Some(Self::from_ptr(ptr as *mut ffi::SSL_CIPHER)) + Some(Self::from_ptr(ptr.cast_mut())) } } } @@ -2541,7 +2538,7 @@ impl SslCipherRef { pub fn version(&self) -> &'static str { let version = unsafe { let ptr = ffi::SSL_CIPHER_get_version(self.as_ptr()); - CStr::from_ptr(ptr as *const _) + CStr::from_ptr(ptr) }; version.to_str().unwrap() @@ -2570,7 +2567,7 @@ impl SslCipherRef { // SSL_CIPHER_description requires a buffer of at least 128 bytes. let mut buf = [0; 128]; let ptr = ffi::SSL_CIPHER_description(self.as_ptr(), buf.as_mut_ptr(), 128); - CStr::from_ptr(ptr.cast()).to_string_lossy().into_owned() + CStr::from_ptr(ptr).to_string_lossy().into_owned() } } @@ -2900,13 +2897,7 @@ impl SslRef { #[corresponds(SSL_set1_curves_list)] pub fn set_curves_list(&mut self, curves: &str) -> Result<(), ErrorStack> { let curves = CString::new(curves).map_err(ErrorStack::internal_error)?; - unsafe { - cvt_0i(ffi::SSL_set1_curves_list( - self.as_ptr(), - curves.as_ptr() as *const _, - )) - .map(|_| ()) - } + unsafe { cvt_0i(ffi::SSL_set1_curves_list(self.as_ptr(), curves.as_ptr())).map(|_| ()) } } /// Returns the curve ID (aka group ID) used for this `SslRef`. @@ -3112,7 +3103,7 @@ impl SslRef { if ptr.is_null() { None } else { - Some(SslCipherRef::from_ptr(ptr as *mut _)) + Some(SslCipherRef::from_ptr(ptr.cast_mut())) } } } @@ -3125,7 +3116,7 @@ impl SslRef { pub fn state_string(&self) -> &'static str { let state = unsafe { let ptr = ffi::SSL_state_string(self.as_ptr()); - CStr::from_ptr(ptr as *const _) + CStr::from_ptr(ptr) }; state.to_str().unwrap_or_default() @@ -3139,7 +3130,7 @@ impl SslRef { pub fn state_string_long(&self) -> &'static str { let state = unsafe { let ptr = ffi::SSL_state_string_long(self.as_ptr()); - CStr::from_ptr(ptr as *const _) + CStr::from_ptr(ptr) }; state.to_str().unwrap_or_default() @@ -3151,9 +3142,7 @@ impl SslRef { #[corresponds(SSL_set_tlsext_host_name)] pub fn set_hostname(&mut self, hostname: &str) -> Result<(), ErrorStack> { let cstr = CString::new(hostname).map_err(ErrorStack::internal_error)?; - unsafe { - cvt(ffi::SSL_set_tlsext_host_name(self.as_ptr(), cstr.as_ptr() as *mut _) as c_int) - } + unsafe { cvt(ffi::SSL_set_tlsext_host_name(self.as_ptr(), cstr.as_ptr())) } } /// Returns the peer's certificate, if present. @@ -3248,7 +3237,7 @@ impl SslRef { pub fn version_str(&self) -> &'static str { let version = unsafe { let ptr = ffi::SSL_get_version(self.as_ptr()); - CStr::from_ptr(ptr as *const _) + CStr::from_ptr(ptr) }; version.to_str().unwrap() @@ -3356,7 +3345,7 @@ impl SslRef { if chain.is_null() { None } else { - Some(StackRef::from_ptr(chain as *mut _)) + Some(StackRef::from_ptr(chain.cast_mut())) } } } @@ -3373,7 +3362,7 @@ impl SslRef { if profile.is_null() { None } else { - Some(SrtpProtectionProfileRef::from_ptr(profile as *mut _)) + Some(SrtpProtectionProfileRef::from_ptr(profile.cast_mut())) } } } @@ -3422,7 +3411,7 @@ impl SslRef { if name.is_null() { None } else { - Some(CStr::from_ptr(name as *const _).to_bytes()) + Some(CStr::from_ptr(name).to_bytes()) } } } @@ -3492,9 +3481,7 @@ impl SslRef { /// value. #[corresponds(SSL_get_client_random)] pub fn client_random(&self, buf: &mut [u8]) -> usize { - unsafe { - ffi::SSL_get_client_random(self.as_ptr(), buf.as_mut_ptr() as *mut c_uchar, buf.len()) - } + unsafe { ffi::SSL_get_client_random(self.as_ptr(), buf.as_mut_ptr(), buf.len()) } } /// Copies the server_random value sent by the server in the TLS handshake into a buffer. @@ -3503,9 +3490,7 @@ impl SslRef { /// value. #[corresponds(SSL_get_server_random)] pub fn server_random(&self, buf: &mut [u8]) -> usize { - unsafe { - ffi::SSL_get_server_random(self.as_ptr(), buf.as_mut_ptr() as *mut c_uchar, buf.len()) - } + unsafe { ffi::SSL_get_server_random(self.as_ptr(), buf.as_mut_ptr(), buf.len()) } } /// Derives keying material for application use in accordance to RFC 5705. @@ -3518,14 +3503,14 @@ impl SslRef { ) -> Result<(), ErrorStack> { unsafe { let (context, contextlen, use_context) = match context { - Some(context) => (context.as_ptr() as *const c_uchar, context.len(), 1), + Some(context) => (context.as_ptr(), context.len(), 1), None => (ptr::null(), 0, 0), }; cvt(ffi::SSL_export_keying_material( self.as_ptr(), - out.as_mut_ptr() as *mut c_uchar, + out.as_mut_ptr(), out.len(), - label.as_ptr() as *const c_char, + label.as_ptr().cast::(), label.len(), context, contextlen, @@ -3609,17 +3594,12 @@ impl SslRef { pub fn set_ex_data(&mut self, index: Index, data: T) { if let Some(old) = self.ex_data_mut(index) { *old = data; - return; } unsafe { - let data = Box::new(data); - ffi::SSL_set_ex_data( - self.as_ptr(), - index.as_raw(), - Box::into_raw(data) as *mut c_void, - ); + let data = Box::into_raw(Box::new(data)); + ffi::SSL_set_ex_data(self.as_ptr(), index.as_raw(), data.cast()); } } @@ -3658,12 +3638,9 @@ impl SslRef { #[corresponds(SSL_get_ex_data)] pub fn ex_data_mut(&mut self, index: Index) -> Option<&mut T> { unsafe { - let data = ffi::SSL_get_ex_data(self.as_ptr(), index.as_raw()); - if data.is_null() { - None - } else { - Some(&mut *(data as *mut T)) - } + ffi::SSL_get_ex_data(self.as_ptr(), index.as_raw()) + .cast::() + .as_mut() } } @@ -3673,7 +3650,7 @@ impl SslRef { /// buffer required. #[corresponds(SSL_get_finished)] pub fn finished(&self, buf: &mut [u8]) -> usize { - unsafe { ffi::SSL_get_finished(self.as_ptr(), buf.as_mut_ptr() as *mut c_void, buf.len()) } + unsafe { ffi::SSL_get_finished(self.as_ptr(), buf.as_mut_ptr().cast(), buf.len()) } } /// Copies the contents of the last Finished message received from the peer into the provided @@ -3683,9 +3660,7 @@ impl SslRef { /// buffer required. #[corresponds(SSL_get_peer_finished)] pub fn peer_finished(&self, buf: &mut [u8]) -> usize { - unsafe { - ffi::SSL_get_peer_finished(self.as_ptr(), buf.as_mut_ptr() as *mut c_void, buf.len()) - } + unsafe { ffi::SSL_get_peer_finished(self.as_ptr(), buf.as_mut_ptr().cast(), buf.len()) } } /// Determines if the initial handshake has been completed. @@ -3812,7 +3787,7 @@ impl SslRef { if data.is_null() { None } else { - Some(slice::from_raw_parts(data as *const u8, len)) + Some(slice::from_raw_parts(data.cast::(), len)) } } } diff --git a/boring/src/stack.rs b/boring/src/stack.rs index 5a95d98c4..622c401ea 100644 --- a/boring/src/stack.rs +++ b/boring/src/stack.rs @@ -48,7 +48,7 @@ impl Drop for Stack { fn drop(&mut self) { unsafe { while self.pop().is_some() {} - OPENSSL_sk_free(self.0 as *mut _); + OPENSSL_sk_free(self.0.cast()); } } } @@ -58,7 +58,7 @@ impl Stack { unsafe { ffi::init(); let ptr = cvt_p(OPENSSL_sk_new_null())?; - Ok(Stack(ptr as *mut _)) + Ok(Stack(ptr.cast())) } } } @@ -132,7 +132,7 @@ impl Drop for IntoIter { fn drop(&mut self) { unsafe { for _ in &mut *self {} - OPENSSL_sk_free(self.stack as *mut _); + OPENSSL_sk_free(self.stack.cast()); } } } @@ -144,7 +144,7 @@ impl Iterator for IntoIter { unsafe { self.idxs .next() - .map(|i| T::from_ptr(OPENSSL_sk_value(self.stack as *mut _, i) as *mut _)) + .map(|i| T::from_ptr(OPENSSL_sk_value(self.stack.cast(), i).cast())) } } @@ -158,7 +158,7 @@ impl DoubleEndedIterator for IntoIter { unsafe { self.idxs .next_back() - .map(|i| T::from_ptr(OPENSSL_sk_value(self.stack as *mut _, i) as *mut _)) + .map(|i| T::from_ptr(OPENSSL_sk_value(self.stack.cast(), i).cast())) } } } @@ -176,7 +176,7 @@ unsafe impl ForeignTypeRef for StackRef { impl StackRef { fn as_stack(&self) -> *mut OPENSSL_STACK { - self.as_ptr() as *mut _ + self.as_ptr().cast() } /// Returns the number of items in the stack. @@ -234,7 +234,7 @@ impl StackRef { /// Pushes a value onto the top of the stack. pub fn push(&mut self, data: T) -> Result<(), ErrorStack> { unsafe { - cvt_0(OPENSSL_sk_push(self.as_stack(), data.as_ptr() as *mut _))?; + cvt_0(OPENSSL_sk_push(self.as_stack(), data.as_ptr().cast()))?; mem::forget(data); Ok(()) } @@ -247,13 +247,13 @@ impl StackRef { if ptr.is_null() { None } else { - Some(T::from_ptr(ptr as *mut _)) + Some(T::from_ptr(ptr.cast())) } } } unsafe fn _get(&self, idx: usize) -> *mut T::CType { - OPENSSL_sk_value(self.as_stack(), idx) as *mut _ + OPENSSL_sk_value(self.as_stack(), idx).cast() } } @@ -323,7 +323,7 @@ impl<'a, T: Stackable> Iterator for Iter<'a, T> { unsafe { self.idxs .next() - .map(|i| T::Ref::from_ptr(OPENSSL_sk_value(self.stack.as_stack(), i) as *mut _)) + .map(|i| T::Ref::from_ptr(OPENSSL_sk_value(self.stack.as_stack(), i).cast())) } } @@ -337,7 +337,7 @@ impl<'a, T: Stackable> DoubleEndedIterator for Iter<'a, T> { unsafe { self.idxs .next_back() - .map(|i| T::Ref::from_ptr(OPENSSL_sk_value(self.stack.as_stack(), i) as *mut _)) + .map(|i| T::Ref::from_ptr(OPENSSL_sk_value(self.stack.as_stack(), i).cast())) } } } @@ -357,7 +357,7 @@ impl<'a, T: Stackable> Iterator for IterMut<'a, T> { unsafe { self.idxs .next() - .map(|i| T::Ref::from_ptr_mut(OPENSSL_sk_value(self.stack.as_stack(), i) as *mut _)) + .map(|i| T::Ref::from_ptr_mut(OPENSSL_sk_value(self.stack.as_stack(), i).cast())) } } @@ -371,7 +371,7 @@ impl<'a, T: Stackable> DoubleEndedIterator for IterMut<'a, T> { unsafe { self.idxs .next_back() - .map(|i| T::Ref::from_ptr_mut(OPENSSL_sk_value(self.stack.as_stack(), i) as *mut _)) + .map(|i| T::Ref::from_ptr_mut(OPENSSL_sk_value(self.stack.as_stack(), i).cast())) } } } diff --git a/boring/src/string.rs b/boring/src/string.rs index 4527f0d7d..94b2d48d1 100644 --- a/boring/src/string.rs +++ b/boring/src/string.rs @@ -1,6 +1,6 @@ use crate::ffi; use foreign_types::ForeignTypeRef; -use libc::{c_char, c_void}; +use libc::c_char; use std::convert::AsRef; use std::ffi::CStr; use std::fmt; @@ -83,5 +83,5 @@ impl fmt::Debug for OpensslStringRef { } unsafe fn free(buf: *mut c_char) { - crate::ffi::OPENSSL_free(buf as *mut c_void); + crate::ffi::OPENSSL_free(buf.cast()); } diff --git a/boring/src/symm.rs b/boring/src/symm.rs index 74e896d9f..419468689 100644 --- a/boring/src/symm.rs +++ b/boring/src/symm.rs @@ -425,7 +425,6 @@ impl Crypter { key.len() as c_uint, ))?; - let key = key.as_ptr() as *mut _; let iv = match (iv, t.iv_len()) { (Some(iv), Some(len)) => { if iv.len() != len { @@ -437,7 +436,7 @@ impl Crypter { ptr::null_mut(), ))?; } - iv.as_ptr() as *mut _ + iv.as_ptr().cast_mut() } (Some(_) | None, None) => ptr::null_mut(), (None, Some(_)) => panic!("an IV is required for this cipher"), @@ -446,7 +445,7 @@ impl Crypter { crypter.ctx, ptr::null(), ptr::null_mut(), - key, + key.as_ptr().cast_mut(), iv, mode, ))?; @@ -476,7 +475,7 @@ impl Crypter { self.ctx, ffi::EVP_CTRL_GCM_SET_TAG, tag.len() as c_int, - tag.as_ptr() as *mut _, + tag.as_ptr().cast_mut().cast(), )) } } @@ -616,7 +615,7 @@ impl Crypter { self.ctx, ffi::EVP_CTRL_GCM_GET_TAG, tag.len() as c_int, - tag.as_mut_ptr() as *mut _, + tag.as_mut_ptr().cast(), )) } } diff --git a/boring/src/util.rs b/boring/src/util.rs index d34fd8984..6583f0aaf 100644 --- a/boring/src/util.rs +++ b/boring/src/util.rs @@ -46,10 +46,10 @@ pub unsafe extern "C" fn invoke_passwd_cb( where F: FnOnce(&mut [u8]) -> Result, { - let callback = &mut *(cb_state as *mut CallbackState); + let callback = &mut *cb_state.cast::>(); let result = panic::catch_unwind(AssertUnwindSafe(|| { - let pass_slice = slice::from_raw_parts_mut(buf as *mut u8, size as usize); + let pass_slice = slice::from_raw_parts_mut(buf.cast::(), size as usize); callback.cb.take().unwrap()(pass_slice) })); @@ -80,14 +80,14 @@ impl ForeignTypeExt for FT {} pub trait ForeignTypeRefExt: ForeignTypeRef { unsafe fn from_const_ptr<'a>(ptr: *const Self::CType) -> &'a Self { - Self::from_ptr(ptr as *mut Self::CType) + Self::from_ptr(ptr.cast_mut()) } unsafe fn from_const_ptr_opt<'a>(ptr: *const Self::CType) -> Option<&'a Self> { if ptr.is_null() { None } else { - Some(Self::from_const_ptr(ptr as *mut Self::CType)) + Some(Self::from_const_ptr(ptr.cast_mut())) } } } diff --git a/boring/src/x509/mod.rs b/boring/src/x509/mod.rs index bd5884332..49372e26f 100644 --- a/boring/src/x509/mod.rs +++ b/boring/src/x509/mod.rs @@ -105,12 +105,9 @@ impl X509StoreContextRef { #[must_use] pub fn ex_data(&self, index: Index) -> Option<&T> { unsafe { - let data = ffi::X509_STORE_CTX_get_ex_data(self.as_ptr(), index.as_raw()); - if data.is_null() { - None - } else { - Some(&*(data as *const T)) - } + ffi::X509_STORE_CTX_get_ex_data(self.as_ptr(), index.as_raw()) + .cast::() + .as_ref() } } @@ -118,12 +115,9 @@ impl X509StoreContextRef { #[corresponds(X509_STORE_CTX_get_ex_data)] pub fn ex_data_mut(&mut self, index: Index) -> Option<&mut T> { unsafe { - let data = ffi::X509_STORE_CTX_get_ex_data(self.as_ptr(), index.as_raw()); - if data.is_null() { - None - } else { - Some(&mut *(data as *mut T)) - } + ffi::X509_STORE_CTX_get_ex_data(self.as_ptr(), index.as_raw()) + .cast::() + .as_mut() } } @@ -145,7 +139,7 @@ impl X509StoreContextRef { ffi::X509_STORE_CTX_set_ex_data( self.as_ptr(), index.as_raw(), - Box::into_raw(data) as *mut c_void, + Box::into_raw(data).cast(), ); } } @@ -546,7 +540,7 @@ impl X509Ref { if stack.is_null() { None } else { - Some(Stack::from_ptr(stack as *mut _)) + Some(Stack::from_ptr(stack.cast())) } } } @@ -575,7 +569,7 @@ impl X509Ref { if stack.is_null() { None } else { - Some(Stack::from_ptr(stack as *mut _)) + Some(Stack::from_ptr(stack.cast())) } } } @@ -620,7 +614,7 @@ impl X509Ref { cvt(ffi::X509_digest( self.as_ptr(), hash_type.as_ptr(), - digest.buf.as_mut_ptr() as *mut _, + digest.buf.as_mut_ptr(), &mut len, ))?; digest.len = len as usize; @@ -664,7 +658,7 @@ impl X509Ref { let mut signature = ptr::null(); X509_get0_signature(&mut signature, ptr::null_mut(), self.as_ptr()); assert!(!signature.is_null()); - Asn1BitStringRef::from_ptr(signature as *mut _) + Asn1BitStringRef::from_ptr(signature.cast_mut()) } } @@ -676,7 +670,7 @@ impl X509Ref { let mut algor = ptr::null(); X509_get0_signature(ptr::null_mut(), &mut algor, self.as_ptr()); assert!(!algor.is_null()); - X509AlgorithmRef::from_ptr(algor as *mut _) + X509AlgorithmRef::from_ptr(algor.cast_mut()) } } @@ -725,7 +719,7 @@ impl X509Ref { unsafe { cvt_n(ffi::X509_check_host( self.as_ptr(), - host.as_ptr() as _, + host.as_ptr().cast(), host.len(), 0, std::ptr::null_mut(), @@ -877,7 +871,7 @@ pub struct X509v3Context<'a>(ffi::X509V3_CTX, PhantomData<(&'a X509Ref, &'a Conf impl X509v3Context<'_> { #[must_use] pub fn as_ptr(&self) -> *mut ffi::X509V3_CTX { - &self.0 as *const _ as *mut _ + std::ptr::addr_of!(self.0).cast_mut() } } @@ -932,8 +926,8 @@ impl X509Extension { &mut ctx } }; - let name = name.as_ptr() as *mut _; - let value = value.as_ptr() as *mut _; + let name = name.as_ptr().cast_mut(); + let value = value.as_ptr().cast_mut(); cvt_p(ffi::X509V3_EXT_nconf(conf, context_ptr, name, value)) .map(|p| X509Extension::from_ptr(p)) @@ -978,7 +972,7 @@ impl X509Extension { } }; let name = name.as_raw(); - let value = value.as_ptr() as *mut _; + let value = value.as_ptr().cast_mut(); cvt_p(ffi::X509V3_EXT_nconf_nid(conf, context_ptr, name, value)) .map(|p| X509Extension::from_ptr(p)) @@ -1024,7 +1018,7 @@ impl X509NameBuilder { assert!(value.len() <= ValueLen::MAX as usize); cvt(ffi::X509_NAME_add_entry_by_txt( self.0.as_ptr(), - field.as_ptr() as *mut _, + field.as_ptr().cast_mut(), ffi::MBSTRING_UTF8, value.as_ptr(), value.len() as ValueLen, @@ -1047,7 +1041,7 @@ impl X509NameBuilder { assert!(value.len() <= ValueLen::MAX as usize); cvt(ffi::X509_NAME_add_entry_by_txt( self.0.as_ptr(), - field.as_ptr() as *mut _, + field.as_ptr().cast_mut(), ty.as_raw(), value.as_ptr(), value.len() as ValueLen, @@ -1066,7 +1060,7 @@ impl X509NameBuilder { self.0.as_ptr(), field.as_raw(), ffi::MBSTRING_UTF8, - value.as_ptr() as *mut _, + value.as_ptr().cast_mut(), value.len() as ValueLen, -1, 0, @@ -1088,7 +1082,7 @@ impl X509NameBuilder { self.0.as_ptr(), field.as_raw(), ty.as_raw(), - value.as_ptr() as *mut _, + value.as_ptr().cast_mut(), value.len() as ValueLen, -1, 0, @@ -1756,7 +1750,7 @@ impl X509AlgorithmRef { let mut oid = ptr::null(); X509_ALGOR_get0(&mut oid, ptr::null_mut(), ptr::null_mut(), self.as_ptr()); assert!(!oid.is_null()); - Asn1ObjectRef::from_ptr(oid as *mut _) + Asn1ObjectRef::from_ptr(oid.cast_mut()) } } } @@ -1799,7 +1793,7 @@ use crate::ffi::X509_OBJECT_get0_X509; #[allow(bad_style)] unsafe fn X509_OBJECT_free(x: *mut ffi::X509_OBJECT) { ffi::X509_OBJECT_free_contents(x); - ffi::OPENSSL_free(x as *mut libc::c_void); + ffi::OPENSSL_free(x.cast()); } unsafe fn get_new_x509_store_ctx_idx(f: ffi::CRYPTO_EX_free) -> c_int { diff --git a/boring/src/x509/verify.rs b/boring/src/x509/verify.rs index 8f3c5942a..3ca7ca9d1 100644 --- a/boring/src/x509/verify.rs +++ b/boring/src/x509/verify.rs @@ -120,7 +120,7 @@ impl X509VerifyParamRef { let raw_host = if host.is_empty() { "\0" } else { host }; cvt(ffi::X509_VERIFY_PARAM_set1_host( self.as_ptr(), - raw_host.as_ptr() as *const _, + raw_host.as_ptr().cast(), host.len(), )) } @@ -134,7 +134,7 @@ impl X509VerifyParamRef { let raw_email = if email.is_empty() { "\0" } else { email }; cvt(ffi::X509_VERIFY_PARAM_set1_email( self.as_ptr(), - raw_email.as_ptr() as *const _, + raw_email.as_ptr().cast(), email.len(), )) } @@ -157,7 +157,7 @@ impl X509VerifyParamRef { }; cvt(ffi::X509_VERIFY_PARAM_set1_ip( self.as_ptr(), - buf.as_ptr() as *const _, + buf.as_ptr().cast(), len, )) } From 7cb4c895506952f42c9452ba420d99edc658f643 Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 30 Jan 2026 14:44:49 +0000 Subject: [PATCH 101/111] Detect bad headers in boring-sys --- boring-sys/src/lib.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/boring-sys/src/lib.rs b/boring-sys/src/lib.rs index 650b3842b..57ceffc76 100644 --- a/boring-sys/src/lib.rs +++ b/boring-sys/src/lib.rs @@ -24,6 +24,15 @@ use std::os::raw::{c_char, c_int, c_uint, c_ulong}; mod generated { include!(concat!(env!("OUT_DIR"), "/bindings.rs")); } + +// explicitly require presence of some symbols to check if the bindings worked +pub use generated::{ssl_compliance_policy_t, ERR_add_error_data, SSL_set1_groups}; // if these are missing, your include path is incorrect or has a wrong version of boringssl +pub use generated::{BIO_new, OPENSSL_free, SSL_ERROR_NONE}; // if these are missing, your include path is incorrect +#[cfg(feature = "fips")] +pub use generated::{FIPS_mode, SSL_CTX_set_compliance_policy}; // your include path is incorrect or has a version of boringssl without FIPS support +#[cfg(feature = "rpk")] +pub use generated::{SSL_CREDENTIAL_new_raw_public_key, SSL_CREDENTIAL_set1_spki}; // your include path is incorrect or has a version of boringssl without rpk support + pub use generated::*; #[cfg(target_pointer_width = "64")] From 5f4cf54cc5a7f488a924f48ea06d334ac121e5ac Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 3 Feb 2026 14:45:38 +0000 Subject: [PATCH 102/111] Bump rust-version to 1.85 --- Cargo.toml | 1 + boring-sys/Cargo.toml | 2 +- boring/Cargo.toml | 2 +- hyper-boring/Cargo.toml | 2 +- tokio-boring/Cargo.toml | 1 + 5 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5cf814453..96ec2fb3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ resolver = "2" [workspace.package] version = "5.0.0-alpha.3" +rust-version = "1.85" repository = "https://github.com/cloudflare/boring" edition = "2021" diff --git a/boring-sys/Cargo.toml b/boring-sys/Cargo.toml index 6def757f8..cecc83f51 100644 --- a/boring-sys/Cargo.toml +++ b/boring-sys/Cargo.toml @@ -13,7 +13,7 @@ build = "build/main.rs" readme = "README.md" categories = ["cryptography", "external-ffi-bindings"] edition = { workspace = true } -rust-version = "1.77" +rust-version = { workspace = true } include = [ "/*.md", "/*.toml", diff --git a/boring/Cargo.toml b/boring/Cargo.toml index 45cfd672d..465cbb294 100644 --- a/boring/Cargo.toml +++ b/boring/Cargo.toml @@ -10,7 +10,7 @@ readme = "README.md" keywords = ["crypto", "tls", "ssl", "dtls"] categories = ["cryptography", "api-bindings"] edition = { workspace = true } -rust-version = "1.80" +rust-version = { workspace = true } [package.metadata.docs.rs] features = ["rpk", "underscore-wildcards"] diff --git a/hyper-boring/Cargo.toml b/hyper-boring/Cargo.toml index d0f08aab1..9963e6ee6 100644 --- a/hyper-boring/Cargo.toml +++ b/hyper-boring/Cargo.toml @@ -9,7 +9,7 @@ repository = { workspace = true } documentation = "https://docs.rs/hyper-boring" readme = "README.md" exclude = ["test/*"] -rust-version = "1.80" +rust-version = { workspace = true } [package.metadata.docs.rs] features = [] diff --git a/tokio-boring/Cargo.toml b/tokio-boring/Cargo.toml index 151638647..18d2f2b20 100644 --- a/tokio-boring/Cargo.toml +++ b/tokio-boring/Cargo.toml @@ -4,6 +4,7 @@ version = { workspace = true } authors = ["Alex Crichton ", "Ivan Nikulin "] license = "MIT OR Apache-2.0" edition = { workspace = true } +rust-version = { workspace = true } repository = { workspace = true } homepage = "https://github.com/cloudflare/boring" documentation = "https://docs.rs/tokio-boring" From 06ca1fd7461d3626746c2cd36d1ada07c85c952d Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 29 Dec 2025 19:40:59 +0000 Subject: [PATCH 103/111] Handle overflows in FFI integer conversions --- boring/src/aes.rs | 6 +-- boring/src/bio.rs | 15 +++---- boring/src/bn.rs | 2 +- boring/src/dsa.rs | 5 ++- boring/src/ec.rs | 3 +- boring/src/hash.rs | 8 ++-- boring/src/lib.rs | 9 ++++ boring/src/macros.rs | 3 +- boring/src/pkcs5.rs | 11 ++--- boring/src/pkey.rs | 3 +- boring/src/rsa.rs | 3 +- boring/src/ssl/bio.rs | 12 +++++- boring/src/ssl/mod.rs | 97 +++++++++++++++++++++--------------------- boring/src/symm.rs | 33 +++++--------- boring/src/x509/mod.rs | 22 +++------- 15 files changed, 112 insertions(+), 120 deletions(-) diff --git a/boring/src/aes.rs b/boring/src/aes.rs index 2a46b6125..e535fa217 100644 --- a/boring/src/aes.rs +++ b/boring/src/aes.rs @@ -38,7 +38,7 @@ //! ``` //! use crate::ffi; -use libc::{c_int, c_uint}; +use libc::c_int; use openssl_macros::corresponds; use std::mem::MaybeUninit; use std::ptr; @@ -64,7 +64,7 @@ impl AesKey { let mut aes_key = MaybeUninit::uninit(); let r = ffi::AES_set_encrypt_key( key.as_ptr(), - key.len() as c_uint * 8, + (key.len() * 8).try_into().map_err(|_| KeyError(()))?, aes_key.as_mut_ptr(), ); if r == 0 { @@ -88,7 +88,7 @@ impl AesKey { let mut aes_key = MaybeUninit::uninit(); let r = ffi::AES_set_decrypt_key( key.as_ptr(), - key.len() as c_uint * 8, + (key.len() * 8).try_into().map_err(|_| KeyError(()))?, aes_key.as_mut_ptr(), ); diff --git a/boring/src/bio.rs b/boring/src/bio.rs index ff1e83e29..bb4aa1a54 100644 --- a/boring/src/bio.rs +++ b/boring/src/bio.rs @@ -1,11 +1,12 @@ -use crate::ffi; -use crate::ffi::BIO_new_mem_buf; use std::marker::PhantomData; use std::ptr; use std::slice; use crate::cvt_p; use crate::error::ErrorStack; +use crate::ffi; +use crate::ffi::BIO_new_mem_buf; +use crate::try_int; pub struct MemBioSlice<'a>(*mut ffi::BIO, PhantomData<&'a [u8]>); @@ -19,15 +20,9 @@ impl Drop for MemBioSlice<'_> { impl<'a> MemBioSlice<'a> { pub fn new(buf: &'a [u8]) -> Result, ErrorStack> { - #[cfg(not(feature = "legacy-compat-deprecated"))] - type BufLen = isize; - #[cfg(feature = "legacy-compat-deprecated")] - type BufLen = libc::c_int; - ffi::init(); - assert!(buf.len() <= BufLen::MAX as usize); - let bio = unsafe { cvt_p(BIO_new_mem_buf(buf.as_ptr().cast(), buf.len() as BufLen))? }; + let bio = unsafe { cvt_p(BIO_new_mem_buf(buf.as_ptr().cast(), try_int(buf.len())?))? }; Ok(MemBioSlice(bio, PhantomData)) } @@ -63,7 +58,7 @@ impl MemBio { unsafe { let mut ptr = ptr::null_mut(); let len = ffi::BIO_get_mem_data(self.0, &mut ptr); - if ptr.is_null() { + if ptr.is_null() || len < 0 { return &[]; } slice::from_raw_parts(ptr.cast_const().cast(), len as usize) diff --git a/boring/src/bn.rs b/boring/src/bn.rs index 659d516e4..545eb3487 100644 --- a/boring/src/bn.rs +++ b/boring/src/bn.rs @@ -390,7 +390,7 @@ impl BigNumRef { unsafe { cvt(ffi::BN_generate_prime_ex( self.as_ptr(), - bits as c_int, + c_int::from(bits), c_int::from(safe), add.map(|n| n.as_ptr()).unwrap_or(ptr::null_mut()), rem.map(|n| n.as_ptr()).unwrap_or(ptr::null_mut()), diff --git a/boring/src/dsa.rs b/boring/src/dsa.rs index bc6068add..36562699e 100644 --- a/boring/src/dsa.rs +++ b/boring/src/dsa.rs @@ -5,7 +5,6 @@ //! using the private key that can be validated with the public key but not be generated //! without the private key. -use crate::ffi; use foreign_types::{ForeignType, ForeignTypeRef}; use libc::c_uint; use openssl_macros::corresponds; @@ -15,7 +14,9 @@ use std::ptr; use crate::bn::{BigNum, BigNumRef}; use crate::error::ErrorStack; +use crate::ffi; use crate::pkey::{HasParams, HasPrivate, HasPublic, Private, Public}; +use crate::try_int; use crate::{cvt, cvt_p}; generic_foreign_type_and_impl_send_sync! { @@ -195,7 +196,7 @@ impl Dsa { let dsa = Dsa::from_ptr(cvt_p(ffi::DSA_new())?); cvt(ffi::DSA_generate_parameters_ex( dsa.0, - bits as c_uint, + c_uint::from(bits), ptr::null(), 0, ptr::null_mut(), diff --git a/boring/src/ec.rs b/boring/src/ec.rs index 745b81fcb..477f6d063 100644 --- a/boring/src/ec.rs +++ b/boring/src/ec.rs @@ -15,7 +15,6 @@ //! [`EcGroup`]: struct.EcGroup.html //! [`Nid`]: ../nid/struct.Nid.html //! [Eliptic Curve Cryptography]: https://wiki.openssl.org/index.php/Elliptic_Curve_Cryptography -use crate::ffi; use foreign_types::{ForeignType, ForeignTypeRef}; use libc::c_int; use openssl_macros::corresponds; @@ -24,8 +23,10 @@ use std::ptr; use crate::bn::{BigNumContextRef, BigNumRef}; use crate::error::ErrorStack; +use crate::ffi; use crate::nid::Nid; use crate::pkey::{HasParams, HasPrivate, HasPublic, Params, Private, Public}; +use crate::try_int; use crate::{cvt, cvt_n, cvt_p, init}; /// Compressed or Uncompressed conversion diff --git a/boring/src/hash.rs b/boring/src/hash.rs index 5035edc31..543e401fd 100644 --- a/boring/src/hash.rs +++ b/boring/src/hash.rs @@ -1,6 +1,4 @@ -use crate::ffi; use openssl_macros::corresponds; -use std::convert::TryInto; use std::ffi::c_uint; use std::fmt; use std::io; @@ -9,8 +7,10 @@ use std::ops::{Deref, DerefMut}; use std::ptr; use crate::error::ErrorStack; +use crate::ffi; use crate::ffi::{EVP_MD_CTX_free, EVP_MD_CTX_new}; use crate::nid::Nid; +use crate::try_int; use crate::{cvt, cvt_p}; #[derive(Copy, Clone, PartialEq, Eq)] @@ -210,7 +210,7 @@ impl Hasher { self.init()?; } unsafe { - let mut len = ffi::EVP_MAX_MD_SIZE.try_into().unwrap(); + let mut len = try_int(ffi::EVP_MAX_MD_SIZE)?; let mut buf = [0; ffi::EVP_MAX_MD_SIZE as usize]; cvt(ffi::EVP_DigestFinal_ex( self.ctx, @@ -220,7 +220,7 @@ impl Hasher { self.state = Finalized; Ok(DigestBytes { buf, - len: len as usize, + len: try_int(len)?, }) } } diff --git a/boring/src/lib.rs b/boring/src/lib.rs index a93f9d692..d1d87e595 100644 --- a/boring/src/lib.rs +++ b/boring/src/lib.rs @@ -201,6 +201,15 @@ fn cvt_n(r: c_int) -> Result { } } +fn try_int(from: F) -> Result +where + F: TryInto + Send + Sync + Copy + 'static, + T: Send + Sync + Copy + 'static, +{ + from.try_into() + .map_err(|_| ErrorStack::internal_error_str("int overflow")) +} + unsafe extern "C" fn free_data_box( _parent: *mut c_void, ptr: *mut c_void, diff --git a/boring/src/macros.rs b/boring/src/macros.rs index e2da938aa..f5bffae2f 100644 --- a/boring/src/macros.rs +++ b/boring/src/macros.rs @@ -60,12 +60,11 @@ macro_rules! private_key_to_pem { ) -> Result, crate::error::ErrorStack> { unsafe { let bio = crate::bio::MemBio::new()?; - assert!(passphrase.len() <= ::libc::c_int::MAX as usize); cvt($f(bio.as_ptr(), self.as_ptr(), cipher.as_ptr(), passphrase.as_ptr() as *const _ as *mut _, - passphrase.len() as ::libc::c_int, + try_int(passphrase.len())?, None, ptr::null_mut()))?; Ok(bio.get_buf().to_owned()) diff --git a/boring/src/pkcs5.rs b/boring/src/pkcs5.rs index 1b78c520e..1e1665bc5 100644 --- a/boring/src/pkcs5.rs +++ b/boring/src/pkcs5.rs @@ -1,11 +1,11 @@ use crate::ffi; -use libc::{c_int, c_uint}; +use std::ffi::c_int; use std::ptr; use crate::error::ErrorStack; use crate::hash::MessageDigest; use crate::symm::Cipher; -use crate::{cvt, cvt_nz}; +use crate::{cvt, cvt_nz, try_int}; #[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct KeyIvPair { @@ -90,17 +90,14 @@ pub fn pbkdf2_hmac( key: &mut [u8], ) -> Result<(), ErrorStack> { unsafe { - assert!(pass.len() <= c_int::MAX as usize); - assert!(salt.len() <= c_int::MAX as usize); - assert!(key.len() <= c_int::MAX as usize); - ffi::init(); + cvt(ffi::PKCS5_PBKDF2_HMAC( pass.as_ptr().cast(), pass.len(), salt.as_ptr(), salt.len(), - iter as c_uint, + try_int(iter)?, hash.as_ptr(), key.len(), key.as_mut_ptr(), diff --git a/boring/src/pkey.rs b/boring/src/pkey.rs index b141f5bda..0ab9c5288 100644 --- a/boring/src/pkey.rs +++ b/boring/src/pkey.rs @@ -40,7 +40,6 @@ //! println!("{:?}", str::from_utf8(pub_key.as_slice()).unwrap()); //! ``` -use crate::ffi; use foreign_types::{ForeignType, ForeignTypeRef}; use libc::{c_int, c_long}; use openssl_macros::corresponds; @@ -54,7 +53,9 @@ use crate::dh::Dh; use crate::dsa::Dsa; use crate::ec::EcKey; use crate::error::ErrorStack; +use crate::ffi; use crate::rsa::Rsa; +use crate::try_int; use crate::util::{invoke_passwd_cb, CallbackState}; use crate::{cvt, cvt_0i, cvt_p}; diff --git a/boring/src/rsa.rs b/boring/src/rsa.rs index 79dfa6df9..413048cc3 100644 --- a/boring/src/rsa.rs +++ b/boring/src/rsa.rs @@ -23,7 +23,6 @@ //! let mut buf = vec![0; rsa.size() as usize]; //! let encrypted_len = rsa.public_encrypt(data, &mut buf, Padding::PKCS1).unwrap(); //! ``` -use crate::ffi; use foreign_types::{ForeignType, ForeignTypeRef}; use libc::c_int; use openssl_macros::corresponds; @@ -33,7 +32,9 @@ use std::ptr; use crate::bn::{BigNum, BigNumRef}; use crate::error::ErrorStack; +use crate::ffi; use crate::pkey::{HasPrivate, HasPublic, Private, Public}; +use crate::try_int; use crate::{cvt, cvt_n, cvt_p}; pub const EVP_PKEY_OP_SIGN: c_int = 1 << 3; diff --git a/boring/src/ssl/bio.rs b/boring/src/ssl/bio.rs index 82695853f..3bae8a79b 100644 --- a/boring/src/ssl/bio.rs +++ b/boring/src/ssl/bio.rs @@ -101,8 +101,12 @@ unsafe fn state<'a, S: 'a>(bio: *mut BIO) -> &'a mut StreamState { unsafe extern "C" fn bwrite(bio: *mut BIO, buf: *const c_char, len: c_int) -> c_int { BIO_clear_retry_flags(bio); + let Ok(len) = usize::try_from(len) else { + return -1; + }; + let state = state::(bio); - let buf = slice::from_raw_parts(buf.cast(), len as usize); + let buf = slice::from_raw_parts(buf.cast(), len); match catch_unwind(AssertUnwindSafe(|| state.stream.write(buf))) { Ok(Ok(len)) => len as c_int, @@ -123,8 +127,12 @@ unsafe extern "C" fn bwrite(bio: *mut BIO, buf: *const c_char, len: c_ unsafe extern "C" fn bread(bio: *mut BIO, buf: *mut c_char, len: c_int) -> c_int { BIO_clear_retry_flags(bio); + let Ok(len) = usize::try_from(len) else { + return -1; + }; + let state = state::(bio); - let buf = slice::from_raw_parts_mut(buf.cast(), len as usize); + let buf = slice::from_raw_parts_mut(buf.cast(), len); match catch_unwind(AssertUnwindSafe(|| state.stream.read(buf))) { Ok(Ok(len)) => len as c_int, diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index ff228ba9b..802e09390 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -92,6 +92,7 @@ use crate::ssl::callbacks::*; use crate::ssl::error::InnerError; use crate::stack::{Stack, StackRef, Stackable}; use crate::symm::CipherCtxRef; +use crate::try_int; use crate::x509::store::{X509Store, X509StoreBuilder, X509StoreBuilderRef, X509StoreRef}; use crate::x509::verify::X509VerifyParamRef; use crate::x509::{ @@ -783,9 +784,9 @@ pub fn select_next_proto<'a>(server: &'a [u8], client: &'a [u8]) -> Option<&'a [ &mut out, &mut outlen, server.as_ptr(), - server.len() as c_uint, + try_int(server.len()).ok()?, client.as_ptr(), - client.len() as c_uint, + try_int(client.len()).ok()?, ); if r == ffi::OPENSSL_NPN_NEGOTIATED { @@ -1018,7 +1019,7 @@ impl SslContextBuilder { self.ctx.check_x509(); unsafe { - ffi::SSL_CTX_set_verify(self.as_ptr(), mode.bits() as c_int, None); + ffi::SSL_CTX_set_verify(self.as_ptr(), c_int::from(mode.bits()), None); } } @@ -1047,7 +1048,11 @@ impl SslContextBuilder { unsafe { self.replace_ex_data(SslContext::cached_ex_index::(), callback); - ffi::SSL_CTX_set_verify(self.as_ptr(), mode.bits() as c_int, Some(raw_verify::)); + ffi::SSL_CTX_set_verify( + self.as_ptr(), + c_int::from(mode.bits()), + Some(raw_verify::), + ); } } @@ -1074,7 +1079,7 @@ impl SslContextBuilder { self.replace_ex_data(SslContext::cached_ex_index::(), callback); ffi::SSL_CTX_set_custom_verify( self.as_ptr(), - mode.bits() as c_int, + c_int::from(mode.bits()), Some(raw_custom_verify::), ); } @@ -1175,11 +1180,10 @@ impl SslContextBuilder { self.ctx.check_x509(); unsafe { - cvt( - ffi::SSL_CTX_set0_verify_cert_store(self.as_ptr(), cert_store.into_ptr()) as c_int, - )?; - - Ok(()) + cvt(ffi::SSL_CTX_set0_verify_cert_store( + self.as_ptr(), + cert_store.into_ptr(), + )) } } @@ -1241,13 +1245,13 @@ impl SslContextBuilder { /// Sets the parameters to be used during ephemeral Diffie-Hellman key exchange. #[corresponds(SSL_CTX_set_tmp_dh)] pub fn set_tmp_dh(&mut self, dh: &DhRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_CTX_set_tmp_dh(self.as_ptr(), dh.as_ptr()) as c_int) } + unsafe { cvt(ffi::SSL_CTX_set_tmp_dh(self.as_ptr(), dh.as_ptr())) } } /// Sets the parameters to be used during ephemeral elliptic curve Diffie-Hellman key exchange. #[corresponds(SSL_CTX_set_tmp_ecdh)] pub fn set_tmp_ecdh(&mut self, key: &EcKeyRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_CTX_set_tmp_ecdh(self.as_ptr(), key.as_ptr()) as c_int) } + unsafe { cvt(ffi::SSL_CTX_set_tmp_ecdh(self.as_ptr(), key.as_ptr())) } } /// Use the default locations of trusted certificates for verification. @@ -1383,8 +1387,10 @@ impl SslContextBuilder { self.ctx.check_x509(); unsafe { - cvt(ffi::SSL_CTX_add_extra_chain_cert(self.as_ptr(), cert.into_ptr()) as c_int)?; - Ok(()) + cvt(ffi::SSL_CTX_add_extra_chain_cert( + self.as_ptr(), + cert.into_ptr(), + )) } } @@ -1558,17 +1564,10 @@ impl SslContextBuilder { #[corresponds(SSL_CTX_set_alpn_protos)] pub fn set_alpn_protos(&mut self, protocols: &[u8]) -> Result<(), ErrorStack> { unsafe { - #[cfg_attr( - not(feature = "legacy-compat-deprecated"), - allow(clippy::unnecessary_cast) - )] - { - assert!(protocols.len() <= ProtosLen::MAX as usize); - } let r = ffi::SSL_CTX_set_alpn_protos( self.as_ptr(), protocols.as_ptr(), - protocols.len() as ProtosLen, + try_int(protocols.len())?, ); // fun fact, SSL_CTX_set_alpn_protos has a reversed return code D: if r == 0 { @@ -1757,10 +1756,10 @@ impl SslContextBuilder { { unsafe { self.replace_ex_data(SslContext::cached_ex_index::(), callback); - cvt( - ffi::SSL_CTX_set_tlsext_status_cb(self.as_ptr(), Some(raw_tlsext_status::)) - as c_int, - ) + cvt(ffi::SSL_CTX_set_tlsext_status_cb( + self.as_ptr(), + Some(raw_tlsext_status::), + )) } } @@ -1938,7 +1937,12 @@ impl SslContextBuilder { #[corresponds(SSL_CTX_set1_sigalgs_list)] pub fn set_sigalgs_list(&mut self, sigalgs: &str) -> Result<(), ErrorStack> { let sigalgs = CString::new(sigalgs).map_err(ErrorStack::internal_error)?; - unsafe { cvt(ffi::SSL_CTX_set1_sigalgs_list(self.as_ptr(), sigalgs.as_ptr()) as c_int) } + unsafe { + cvt(ffi::SSL_CTX_set1_sigalgs_list( + self.as_ptr(), + sigalgs.as_ptr(), + )) + } } /// Set's whether the context should enable GREASE. @@ -2362,11 +2366,6 @@ impl SslContextRef { #[derive(Debug)] pub struct GetSessionPendingError; -#[cfg(not(feature = "legacy-compat-deprecated"))] -type ProtosLen = usize; -#[cfg(feature = "legacy-compat-deprecated")] -type ProtosLen = libc::c_uint; - /// Information about the state of a cipher. pub struct CipherBits { /// The number of secret bits used for the cipher. @@ -2941,7 +2940,7 @@ impl SslRef { pub fn set_verify(&mut self, mode: SslVerifyMode) { self.ssl_context().check_x509(); - unsafe { ffi::SSL_set_verify(self.as_ptr(), mode.bits() as c_int, None) } + unsafe { ffi::SSL_set_verify(self.as_ptr(), c_int::from(mode.bits()), None) } } /// Sets the certificate verification depth. @@ -2994,7 +2993,7 @@ impl SslRef { self.replace_ex_data(Ssl::cached_ex_index(), Arc::new(callback)); ffi::SSL_set_verify( self.as_ptr(), - mode.bits() as c_int, + c_int::from(mode.bits()), Some(ssl_raw_verify::), ); } @@ -3006,8 +3005,10 @@ impl SslRef { self.ssl_context().check_x509(); unsafe { - cvt(ffi::SSL_set0_verify_cert_store(self.as_ptr(), cert_store.into_ptr()) as c_int)?; - Ok(()) + cvt(ffi::SSL_set0_verify_cert_store( + self.as_ptr(), + cert_store.into_ptr(), + )) } } @@ -3028,7 +3029,7 @@ impl SslRef { self.replace_ex_data(Ssl::cached_ex_index(), Arc::new(callback)); ffi::SSL_set_custom_verify( self.as_ptr(), - mode.bits() as c_int, + c_int::from(mode.bits()), Some(ssl_raw_custom_verify::), ); } @@ -3039,7 +3040,7 @@ impl SslRef { /// [`SslContextBuilder::set_tmp_dh`]: struct.SslContextBuilder.html#method.set_tmp_dh #[corresponds(SSL_set_tmp_dh)] pub fn set_tmp_dh(&mut self, dh: &DhRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_set_tmp_dh(self.as_ptr(), dh.as_ptr()) as c_int) } + unsafe { cvt(ffi::SSL_set_tmp_dh(self.as_ptr(), dh.as_ptr())) } } /// Like [`SslContextBuilder::set_tmp_ecdh`]. @@ -3047,7 +3048,7 @@ impl SslRef { /// [`SslContextBuilder::set_tmp_ecdh`]: struct.SslContextBuilder.html#method.set_tmp_ecdh #[corresponds(SSL_set_tmp_ecdh)] pub fn set_tmp_ecdh(&mut self, key: &EcKeyRef) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_set_tmp_ecdh(self.as_ptr(), key.as_ptr()) as c_int) } + unsafe { cvt(ffi::SSL_set_tmp_ecdh(self.as_ptr(), key.as_ptr())) } } /// Configures whether ClientHello extensions should be permuted. @@ -3062,17 +3063,10 @@ impl SslRef { #[corresponds(SSL_set_alpn_protos)] pub fn set_alpn_protos(&mut self, protocols: &[u8]) -> Result<(), ErrorStack> { unsafe { - #[cfg_attr( - not(feature = "legacy-compat-deprecated"), - allow(clippy::unnecessary_cast) - )] - { - assert!(protocols.len() <= ProtosLen::MAX as usize); - } let r = ffi::SSL_set_alpn_protos( self.as_ptr(), protocols.as_ptr(), - protocols.len() as ProtosLen, + try_int(protocols.len())?, ); // fun fact, SSL_set_alpn_protos has a reversed return code D: if r == 0 { @@ -3544,7 +3538,12 @@ impl SslRef { /// Sets the status response a client wishes the server to reply with. #[corresponds(SSL_set_tlsext_status_type)] pub fn set_status_type(&mut self, type_: StatusType) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_set_tlsext_status_type(self.as_ptr(), type_.as_raw()) as c_int) } + unsafe { + cvt(ffi::SSL_set_tlsext_status_type( + self.as_ptr(), + type_.as_raw(), + )) + } } /// Returns the server's OCSP response, if present. @@ -3673,7 +3672,7 @@ impl SslRef { /// Sets the MTU used for DTLS connections. #[corresponds(SSL_set_mtu)] pub fn set_mtu(&mut self, mtu: u32) -> Result<(), ErrorStack> { - unsafe { cvt(ffi::SSL_set_mtu(self.as_ptr(), mtu as c_uint) as c_int) } + unsafe { cvt(ffi::SSL_set_mtu(self.as_ptr(), mtu as c_uint)) } } /// Sets the certificate. diff --git a/boring/src/symm.rs b/boring/src/symm.rs index 419468689..1604b70bd 100644 --- a/boring/src/symm.rs +++ b/boring/src/symm.rs @@ -54,14 +54,14 @@ use crate::ffi; use foreign_types::ForeignTypeRef; -use libc::{c_int, c_uint}; use openssl_macros::corresponds; use std::cmp; +use std::ffi::c_int; use std::ptr; use crate::error::ErrorStack; use crate::nid::Nid; -use crate::{cvt, cvt_p}; +use crate::{cvt, cvt_p, try_int}; #[derive(Copy, Clone)] pub enum Mode { @@ -419,20 +419,18 @@ impl Crypter { mode, ))?; - assert!(key.len() <= c_int::MAX as usize); cvt(ffi::EVP_CIPHER_CTX_set_key_length( crypter.ctx, - key.len() as c_uint, + try_int(key.len())?, ))?; let iv = match (iv, t.iv_len()) { (Some(iv), Some(len)) => { if iv.len() != len { - assert!(iv.len() <= c_int::MAX as usize); cvt(ffi::EVP_CIPHER_CTX_ctrl( crypter.ctx, ffi::EVP_CTRL_GCM_SET_IVLEN, - iv.len() as c_int, + try_int(iv.len())?, ptr::null_mut(), ))?; } @@ -469,12 +467,11 @@ impl Crypter { /// When decrypting cipher text using an AEAD cipher, this must be called before `finalize`. pub fn set_tag(&mut self, tag: &[u8]) -> Result<(), ErrorStack> { unsafe { - assert!(tag.len() <= c_int::MAX as usize); // NB: this constant is actually more general than just GCM. cvt(ffi::EVP_CIPHER_CTX_ctrl( self.ctx, ffi::EVP_CTRL_GCM_SET_TAG, - tag.len() as c_int, + try_int(tag.len())?, tag.as_ptr().cast_mut().cast(), )) } @@ -486,12 +483,11 @@ impl Crypter { /// to use a value different than the default 12 bytes. pub fn set_tag_len(&mut self, tag_len: usize) -> Result<(), ErrorStack> { unsafe { - assert!(tag_len <= c_int::MAX as usize); // NB: this constant is actually more general than just GCM. cvt(ffi::EVP_CIPHER_CTX_ctrl( self.ctx, ffi::EVP_CTRL_GCM_SET_TAG, - tag_len as c_int, + try_int(tag_len)?, ptr::null_mut(), )) } @@ -503,14 +499,13 @@ impl Crypter { /// CCM mode. pub fn set_data_len(&mut self, data_len: usize) -> Result<(), ErrorStack> { unsafe { - assert!(data_len <= c_int::MAX as usize); let mut len = 0; cvt(ffi::EVP_CipherUpdate( self.ctx, ptr::null_mut(), &mut len, ptr::null_mut(), - data_len as c_int, + try_int(data_len)?, )) } } @@ -522,14 +517,13 @@ impl Crypter { /// `update`. pub fn aad_update(&mut self, input: &[u8]) -> Result<(), ErrorStack> { unsafe { - assert!(input.len() <= c_int::MAX as usize); let mut len = 0; cvt(ffi::EVP_CipherUpdate( self.ctx, ptr::null_mut(), &mut len, input.as_ptr(), - input.len() as c_int, + try_int(input.len())?, )) } } @@ -546,8 +540,6 @@ impl Crypter { /// /// Panics for block ciphers if `output.len() < input.len() + block_size`, /// where `block_size` is the block size of the cipher (see `Cipher::block_size`). - /// - /// Panics if `output.len() > c_int::MAX`. pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result { unsafe { let block_size = if self.block_size > 1 { @@ -556,16 +548,14 @@ impl Crypter { 0 }; assert!(output.len() >= input.len() + block_size); - assert!(output.len() <= c_int::MAX as usize); - let mut outl = output.len() as c_int; - let inl = input.len() as c_int; + let mut outl = try_int(output.len())?; cvt(ffi::EVP_CipherUpdate( self.ctx, output.as_mut_ptr(), &mut outl, input.as_ptr(), - inl, + try_int(input.len())?, ))?; Ok(outl as usize) @@ -610,11 +600,10 @@ impl Crypter { /// bytes, for example. pub fn get_tag(&self, tag: &mut [u8]) -> Result<(), ErrorStack> { unsafe { - assert!(tag.len() <= c_int::MAX as usize); cvt(ffi::EVP_CIPHER_CTX_ctrl( self.ctx, ffi::EVP_CTRL_GCM_GET_TAG, - tag.len() as c_int, + try_int(tag.len())?, tag.as_mut_ptr().cast(), )) } diff --git a/boring/src/x509/mod.rs b/boring/src/x509/mod.rs index 49372e26f..d10871178 100644 --- a/boring/src/x509/mod.rs +++ b/boring/src/x509/mod.rs @@ -36,6 +36,7 @@ use crate::pkey::{HasPrivate, HasPublic, PKey, PKeyRef, Public}; use crate::ssl::SslRef; use crate::stack::{Stack, StackRef, Stackable}; use crate::string::OpensslString; +use crate::try_int; use crate::util::ForeignTypeRefExt; use crate::x509::verify::{X509VerifyParam, X509VerifyParamRef}; use crate::{cvt, cvt_n, cvt_p}; @@ -610,14 +611,14 @@ impl X509Ref { buf: [0; ffi::EVP_MAX_MD_SIZE as usize], len: ffi::EVP_MAX_MD_SIZE as usize, }; - let mut len = ffi::EVP_MAX_MD_SIZE.try_into().unwrap(); + let mut len = try_int(ffi::EVP_MAX_MD_SIZE)?; cvt(ffi::X509_digest( self.as_ptr(), hash_type.as_ptr(), digest.buf.as_mut_ptr(), &mut len, ))?; - digest.len = len as usize; + digest.len = try_int(len)?; Ok(digest) } @@ -1015,13 +1016,12 @@ impl X509NameBuilder { pub fn append_entry_by_text(&mut self, field: &str, value: &str) -> Result<(), ErrorStack> { unsafe { let field = CString::new(field).map_err(ErrorStack::internal_error)?; - assert!(value.len() <= ValueLen::MAX as usize); cvt(ffi::X509_NAME_add_entry_by_txt( self.0.as_ptr(), field.as_ptr().cast_mut(), ffi::MBSTRING_UTF8, value.as_ptr(), - value.len() as ValueLen, + try_int(value.len())?, -1, 0, )) @@ -1038,13 +1038,12 @@ impl X509NameBuilder { ) -> Result<(), ErrorStack> { unsafe { let field = CString::new(field).map_err(ErrorStack::internal_error)?; - assert!(value.len() <= ValueLen::MAX as usize); cvt(ffi::X509_NAME_add_entry_by_txt( self.0.as_ptr(), field.as_ptr().cast_mut(), ty.as_raw(), value.as_ptr(), - value.len() as ValueLen, + try_int(value.len())?, -1, 0, )) @@ -1055,13 +1054,12 @@ impl X509NameBuilder { #[corresponds(X509_NAME_add_entry_by_NID)] pub fn append_entry_by_nid(&mut self, field: Nid, value: &str) -> Result<(), ErrorStack> { unsafe { - assert!(value.len() <= ValueLen::MAX as usize); cvt(ffi::X509_NAME_add_entry_by_NID( self.0.as_ptr(), field.as_raw(), ffi::MBSTRING_UTF8, value.as_ptr().cast_mut(), - value.len() as ValueLen, + try_int(value.len())?, -1, 0, )) @@ -1077,13 +1075,12 @@ impl X509NameBuilder { ty: Asn1Type, ) -> Result<(), ErrorStack> { unsafe { - assert!(value.len() <= ValueLen::MAX as usize); cvt(ffi::X509_NAME_add_entry_by_NID( self.0.as_ptr(), field.as_raw(), ty.as_raw(), value.as_ptr().cast_mut(), - value.len() as ValueLen, + try_int(value.len())?, -1, 0, )) @@ -1100,11 +1097,6 @@ impl X509NameBuilder { } } -#[cfg(not(feature = "legacy-compat-deprecated"))] -type ValueLen = isize; -#[cfg(feature = "legacy-compat-deprecated")] -type ValueLen = i32; - foreign_type_and_impl_send_sync! { type CType = ffi::X509_NAME; fn drop = ffi::X509_NAME_free; From d60c579bfd61a2d17cd280c32964b485b34a82b2 Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 4 Feb 2026 01:14:04 +0000 Subject: [PATCH 104/111] FIXMEs --- README.md | 1 + boring/src/ec.rs | 10 ++++------ boring/src/rsa.rs | 14 +++++--------- boring/src/ssl/test/cert_compressor.rs | 6 +++--- boring/src/ssl/test/mod.rs | 8 ++++---- boring/src/ssl/test/verify.rs | 2 +- boring/src/x509/store.rs | 7 +++---- boring/src/x509/tests/mod.rs | 4 ++-- boring/src/x509/tests/trusted_first.rs | 2 +- 9 files changed, 24 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 42611afdc..dde5796ea 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ and [hyper](https://github.com/hyperium/hyper) built on top of it. * `Ssl::new_from_ref` -> `Ssl::new()`. * `X509Builder::append_extension2` -> `X509Builder::append_extension`. * `X509Store` is now cheaply cloneable, but immutable. `SslContextBuilder.cert_store_mut()` can't be used after `.set_cert_store()`. Use `.set_cert_store_builder()` if you need `.cert_store_mut()`. + * `X509StoreBuilder::add_cert` takes a reference. * `hyper` 0.x support has been removed. Use `hyper` 1.x. ## Contribution diff --git a/boring/src/ec.rs b/boring/src/ec.rs index 477f6d063..9eb7a4010 100644 --- a/boring/src/ec.rs +++ b/boring/src/ec.rs @@ -268,8 +268,7 @@ impl EcPointRef { group: &EcGroupRef, q: &EcPointRef, m: &BigNumRef, - // FIXME should be &mut - ctx: &BigNumContextRef, + ctx: &mut BigNumContextRef, ) -> Result<(), ErrorStack> { unsafe { cvt(ffi::EC_POINT_mul( @@ -288,8 +287,7 @@ impl EcPointRef { &mut self, group: &EcGroupRef, n: &BigNumRef, - // FIXME should be &mut - ctx: &BigNumContextRef, + ctx: &mut BigNumContextRef, ) -> Result<(), ErrorStack> { unsafe { cvt(ffi::EC_POINT_mul( @@ -839,7 +837,7 @@ mod test { let mut ctx = BigNumContext::new().unwrap(); let mut public_key = EcPoint::new(&group).unwrap(); public_key - .mul_generator(&group, key.private_key(), &ctx) + .mul_generator(&group, key.private_key(), &mut ctx) .unwrap(); assert!(public_key.eq(&group, key.public_key(), &mut ctx).unwrap()); } @@ -851,7 +849,7 @@ mod test { let one = BigNum::from_u32(1).unwrap(); let mut ctx = BigNumContext::new().unwrap(); let mut ecp = EcPoint::new(&group).unwrap(); - ecp.mul_generator(&group, &one, &ctx).unwrap(); + ecp.mul_generator(&group, &one, &mut ctx).unwrap(); assert!(ecp.eq(&group, gen, &mut ctx).unwrap()); } diff --git a/boring/src/rsa.rs b/boring/src/rsa.rs index 413048cc3..49f955bda 100644 --- a/boring/src/rsa.rs +++ b/boring/src/rsa.rs @@ -417,7 +417,7 @@ impl Rsa { pub fn from_public_components(n: BigNum, e: BigNum) -> Result, ErrorStack> { unsafe { let rsa = cvt_p(ffi::RSA_new())?; - RSA_set0_key(rsa, n.as_ptr(), e.as_ptr(), ptr::null_mut()); + cvt(RSA_set0_key(rsa, n.as_ptr(), e.as_ptr(), ptr::null_mut()))?; mem::forget((n, e)); Ok(Rsa::from_ptr(rsa)) } @@ -475,7 +475,7 @@ impl RsaPrivateKeyBuilder { pub fn new(n: BigNum, e: BigNum, d: BigNum) -> Result { unsafe { let rsa = cvt_p(ffi::RSA_new())?; - RSA_set0_key(rsa, n.as_ptr(), e.as_ptr(), d.as_ptr()); + cvt(RSA_set0_key(rsa, n.as_ptr(), e.as_ptr(), d.as_ptr()))?; mem::forget((n, e, d)); Ok(RsaPrivateKeyBuilder { rsa: Rsa::from_ptr(rsa), @@ -486,12 +486,10 @@ impl RsaPrivateKeyBuilder { /// Sets the factors of the Rsa key. /// /// `p` and `q` are the first and second factors of `n`. - /// - // FIXME should be infallible #[corresponds(RSA_set0_factors)] pub fn set_factors(self, p: BigNum, q: BigNum) -> Result { unsafe { - RSA_set0_factors(self.rsa.as_ptr(), p.as_ptr(), q.as_ptr()); + cvt(RSA_set0_factors(self.rsa.as_ptr(), p.as_ptr(), q.as_ptr()))?; mem::forget((p, q)); } Ok(self) @@ -501,8 +499,6 @@ impl RsaPrivateKeyBuilder { /// /// `dmp1`, `dmq1`, and `iqmp` are the exponents and coefficient for /// CRT calculations which is used to speed up RSA operations. - /// - // FIXME should be infallible #[corresponds(RSA_set0_crt_params)] pub fn set_crt_params( self, @@ -511,12 +507,12 @@ impl RsaPrivateKeyBuilder { iqmp: BigNum, ) -> Result { unsafe { - RSA_set0_crt_params( + cvt(RSA_set0_crt_params( self.rsa.as_ptr(), dmp1.as_ptr(), dmq1.as_ptr(), iqmp.as_ptr(), - ); + ))?; mem::forget((dmp1, dmq1, iqmp)); } Ok(self) diff --git a/boring/src/ssl/test/cert_compressor.rs b/boring/src/ssl/test/cert_compressor.rs index d62ffa879..78124f19a 100644 --- a/boring/src/ssl/test/cert_compressor.rs +++ b/boring/src/ssl/test/cert_compressor.rs @@ -54,7 +54,7 @@ fn server_only_cert_compression() { let mut store = X509StoreBuilder::new().unwrap(); let x509 = X509::from_pem(super::ROOT_CERT).unwrap(); - store.add_cert(x509).unwrap(); + store.add_cert(&x509).unwrap(); let client = server.client(); @@ -67,7 +67,7 @@ fn client_only_cert_compression() { let mut store = X509StoreBuilder::new().unwrap(); let x509 = X509::from_pem(super::ROOT_CERT).unwrap(); - store.add_cert(x509).unwrap(); + store.add_cert(&x509).unwrap(); let mut client = server_builder.client(); client @@ -90,7 +90,7 @@ fn client_and_server_cert_compression() { let mut store = X509StoreBuilder::new().unwrap(); let x509 = X509::from_pem(super::ROOT_CERT).unwrap(); - store.add_cert(x509).unwrap(); + store.add_cert(&x509).unwrap(); let mut client = server.client(); client diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index e66d0cc85..9317a7bae 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -317,18 +317,18 @@ fn test_mutable_store() { let cert2 = X509::from_pem(cert2).unwrap(); let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); - ctx.cert_store_mut().add_cert(cert.clone()).unwrap(); + ctx.cert_store_mut().add_cert(&cert.clone()).unwrap(); assert_eq!(1, ctx.cert_store().objects_len()); ctx.set_cert_store_builder(X509StoreBuilder::new().unwrap()); assert_eq!(0, ctx.cert_store().objects_len()); - ctx.cert_store_mut().add_cert(cert.clone()).unwrap(); + ctx.cert_store_mut().add_cert(&cert.clone()).unwrap(); assert_eq!(1, ctx.cert_store().objects_len()); let mut new_store = X509StoreBuilder::new().unwrap(); - new_store.add_cert(cert).unwrap(); - new_store.add_cert(cert2).unwrap(); + new_store.add_cert(&cert).unwrap(); + new_store.add_cert(&cert2).unwrap(); let new_store = new_store.build(); assert_eq!(2, new_store.objects_len()); diff --git a/boring/src/ssl/test/verify.rs b/boring/src/ssl/test/verify.rs index 5fed57038..b7981e68c 100644 --- a/boring/src/ssl/test/verify.rs +++ b/boring/src/ssl/test/verify.rs @@ -32,7 +32,7 @@ fn trusted_with_set_cert() { let mut store = X509StoreBuilder::new().unwrap(); let x509 = X509::from_pem(super::ROOT_CERT).unwrap(); - store.add_cert(x509).unwrap(); + store.add_cert(&x509).unwrap(); let mut client = server.client(); client.ctx().set_verify(SslVerifyMode::PEER); diff --git a/boring/src/x509/store.rs b/boring/src/x509/store.rs index f4edca034..7b9a2317b 100644 --- a/boring/src/x509/store.rs +++ b/boring/src/x509/store.rs @@ -36,7 +36,7 @@ //! //! let certificate: X509 = builder.build(); //! let mut builder = X509StoreBuilder::new().unwrap(); -//! let _ = builder.add_cert(certificate); +//! let _ = builder.add_cert(&certificate); //! let store: X509Store = builder.build(); //! ``` @@ -44,7 +44,7 @@ use crate::error::ErrorStack; use crate::ffi; use crate::stack::StackRef; use crate::x509::verify::{X509VerifyFlags, X509VerifyParamRef}; -use crate::x509::{X509Object, X509}; +use crate::x509::{X509Object, X509Ref}; use crate::{cvt, cvt_p}; use foreign_types::{ForeignType, ForeignTypeRef}; use openssl_macros::corresponds; @@ -79,9 +79,8 @@ impl X509StoreBuilder { impl X509StoreBuilderRef { /// Adds a certificate to the certificate store. - // FIXME should take an &X509Ref #[corresponds(X509_STORE_add_cert)] - pub fn add_cert(&mut self, cert: X509) -> Result<(), ErrorStack> { + pub fn add_cert(&mut self, cert: &X509Ref) -> Result<(), ErrorStack> { unsafe { cvt(ffi::X509_STORE_add_cert(self.as_ptr(), cert.as_ptr())) } } diff --git a/boring/src/x509/tests/mod.rs b/boring/src/x509/tests/mod.rs index 2a1b3fd56..90e4bf299 100644 --- a/boring/src/x509/tests/mod.rs +++ b/boring/src/x509/tests/mod.rs @@ -451,7 +451,7 @@ fn test_verify_cert() { let chain = Stack::new().unwrap(); let mut store_bldr = X509StoreBuilder::new().unwrap(); - store_bldr.add_cert(ca).unwrap(); + store_bldr.add_cert(&ca).unwrap(); let store = store_bldr.build(); let empty_store = X509StoreBuilder::new().unwrap().build(); @@ -484,7 +484,7 @@ fn test_verify_fails() { let chain = Stack::new().unwrap(); let mut store_bldr = X509StoreBuilder::new().unwrap(); - store_bldr.add_cert(ca).unwrap(); + store_bldr.add_cert(&ca).unwrap(); let store = store_bldr.build(); let mut context = X509StoreContext::new().unwrap(); diff --git a/boring/src/x509/tests/trusted_first.rs b/boring/src/x509/tests/trusted_first.rs index ad660a3b1..3755a876b 100644 --- a/boring/src/x509/tests/trusted_first.rs +++ b/boring/src/x509/tests/trusted_first.rs @@ -75,7 +75,7 @@ fn verify( let mut builder = X509StoreBuilder::new().unwrap(); for cert in trusted { - builder.add_cert((**cert).to_owned()).unwrap(); + builder.add_cert(cert).unwrap(); } builder.build() From 7888b0fb91fd160758b64cca073fbc1bb3272380 Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Tue, 10 Feb 2026 07:44:59 -0800 Subject: [PATCH 105/111] symm: Add regression test for cipher NIDs --- boring/src/symm.rs | 101 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/boring/src/symm.rs b/boring/src/symm.rs index 1604b70bd..1c6cdbb01 100644 --- a/boring/src/symm.rs +++ b/boring/src/symm.rs @@ -1061,7 +1061,8 @@ mod tests { Cipher::des_cbc(), Cipher::rc4(), ] { - assert_eq!(Cipher::from_nid(cipher.nid()), Some(cipher)); + let name = cipher.nid().short_name().unwrap_or("unknown"); + assert_eq!(Cipher::from_nid(cipher.nid()), Some(cipher), "{}", name); } for cipher in [ @@ -1070,7 +1071,103 @@ mod tests { Cipher::aes_256_gcm(), Cipher::des_ede3(), ] { - assert_eq!(Cipher::from_nid(cipher.nid()), None); + let name = cipher.nid().short_name().unwrap_or("unknown"); + assert_eq!(Cipher::from_nid(cipher.nid()), None, "{}", name); + } + } + + // Make sure the NIDs don't actually change upstream. + #[test] + fn test_nid_regression() { + struct TestCase { + cipher: Cipher, + nid: c_int, + } + + for t in [ + TestCase { + cipher: Cipher::aes_128_ecb(), + nid: 418, + }, + TestCase { + cipher: Cipher::aes_128_cbc(), + nid: 419, + }, + TestCase { + cipher: Cipher::aes_128_ctr(), + nid: 904, + }, + TestCase { + cipher: Cipher::aes_128_gcm(), + nid: 895, + }, + TestCase { + cipher: Cipher::aes_128_ofb(), + nid: 420, + }, + TestCase { + cipher: Cipher::aes_192_ecb(), + nid: 422, + }, + TestCase { + cipher: Cipher::aes_192_cbc(), + nid: 423, + }, + TestCase { + cipher: Cipher::aes_192_ctr(), + nid: 905, + }, + TestCase { + cipher: Cipher::aes_192_gcm(), + nid: 898, + }, + TestCase { + cipher: Cipher::aes_192_ofb(), + nid: 424, + }, + TestCase { + cipher: Cipher::aes_256_ecb(), + nid: 426, + }, + TestCase { + cipher: Cipher::aes_256_cbc(), + nid: 427, + }, + TestCase { + cipher: Cipher::aes_256_ctr(), + nid: 906, + }, + TestCase { + cipher: Cipher::aes_256_gcm(), + nid: 901, + }, + TestCase { + cipher: Cipher::aes_256_ofb(), + nid: 428, + }, + TestCase { + cipher: Cipher::des_ecb(), + nid: 29, + }, + TestCase { + cipher: Cipher::des_ede3_cbc(), + nid: 44, + }, + TestCase { + cipher: Cipher::des_cbc(), + nid: 31, + }, + TestCase { + cipher: Cipher::rc4(), + nid: 5, + }, + TestCase { + cipher: Cipher::des_ede3(), + nid: 33, + }, + ] { + let name = t.cipher.nid().short_name().unwrap_or("unknown"); + assert_eq!(t.cipher.nid().as_raw(), t.nid, "{}", name); } } } From 559fc27ba13a0da3c80968b7cca20c8a35b0a13f Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Tue, 10 Feb 2026 07:47:22 -0800 Subject: [PATCH 106/111] symm: Ensure `Cipher::from_nid()` handles GCM NIDs This method returns `None` for the GCM NIDs. It appears to be implemented incorrectly: It first calls `OBJ_nid2sn(nid)` to get the NID's short name, then calls `EVP_get_cipherbyname(name)`. The documentation isn't clear as to whether `name` should be the short or long name, but it appears to expect the long name. At least, changing to `OBJ_nid2sn()` to `OBJ_nid2ln()` makes the method work properly, To fix this, this commit calls `EVP_get_cipherbynid()`, which is is more direct. Note that the method still returns `None` on the 3DES NID, but we're not likely to encounter this one in practice. --- boring/src/symm.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/boring/src/symm.rs b/boring/src/symm.rs index 1c6cdbb01..7067b86b5 100644 --- a/boring/src/symm.rs +++ b/boring/src/symm.rs @@ -145,7 +145,7 @@ impl Cipher { #[corresponds(EVP_get_cipherbynid)] #[must_use] pub fn from_nid(nid: Nid) -> Option { - let ptr = unsafe { ffi::EVP_get_cipherbyname(ffi::OBJ_nid2sn(nid.as_raw())) }; + let ptr = unsafe { ffi::EVP_get_cipherbynid(nid.as_raw()) }; if ptr.is_null() { None } else { @@ -1044,6 +1044,9 @@ mod tests { #[test] fn test_nid_roundtrip() { for cipher in [ + Cipher::aes_128_gcm(), + Cipher::aes_192_gcm(), + Cipher::aes_256_gcm(), Cipher::aes_128_ecb(), Cipher::aes_128_cbc(), Cipher::aes_128_ctr(), @@ -1065,15 +1068,7 @@ mod tests { assert_eq!(Cipher::from_nid(cipher.nid()), Some(cipher), "{}", name); } - for cipher in [ - Cipher::aes_128_gcm(), - Cipher::aes_192_gcm(), - Cipher::aes_256_gcm(), - Cipher::des_ede3(), - ] { - let name = cipher.nid().short_name().unwrap_or("unknown"); - assert_eq!(Cipher::from_nid(cipher.nid()), None, "{}", name); - } + assert_eq!(Cipher::from_nid(Cipher::des_ede3().nid()), None); } // Make sure the NIDs don't actually change upstream. From 8ba06e19748a4eb33505009edbf613642d52b563 Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 3 Feb 2026 15:25:24 +0000 Subject: [PATCH 107/111] rm symlink --- boring-sys/README.md | 1 - 1 file changed, 1 deletion(-) delete mode 120000 boring-sys/README.md diff --git a/boring-sys/README.md b/boring-sys/README.md deleted file mode 120000 index 32d46ee88..000000000 --- a/boring-sys/README.md +++ /dev/null @@ -1 +0,0 @@ -../README.md \ No newline at end of file From ae4a73742648b6ff0c5a560a40df91718ac24dc4 Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 4 Feb 2026 01:25:53 +0000 Subject: [PATCH 108/111] Update README --- README.md | 10 ++++++---- boring-sys/Cargo.toml | 1 + boring-sys/README.md | 15 +++++++++++++++ boring/Cargo.toml | 2 +- 4 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 boring-sys/README.md diff --git a/README.md b/README.md index dde5796ea..9f948242b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ [![crates.io](https://img.shields.io/crates/v/boring.svg)](https://crates.io/crates/boring) -BoringSSL bindings for the Rust programming language and TLS adapters for [tokio](https://github.com/tokio-rs/tokio) +[BoringSSL](https://boringssl.googlesource.com/boringssl) is Google's fork of OpenSSL for Chrome/Chromium and Android. + +This crate provides safe bindings for the Rust programming language and TLS adapters for [tokio](https://github.com/tokio-rs/tokio) and [hyper](https://github.com/hyperium/hyper) built on top of it. ## Documentation @@ -13,14 +15,14 @@ and [hyper](https://github.com/hyperium/hyper) built on top of it. # Upgrading from `boring` v4 - * First update to boring 4.20 and ensure it builds without any deprecation warnings. + * First update to boring 4.21 and ensure it builds without any deprecation warnings. * `pq-experimental` Cargo feature is no longer needed. Post-quantum crypto is enabled by default. * `fips-precompiled` Cargo feature has been merged into `fips`. Set `BORING_BSSL_FIPS_PATH` env var to use a precompiled library. * `fips-compat` Cargo feature has been renamed to `legacy-compat-deprecated` (4cb7e260a85b7) - * `SslCurve` and `SslCurveNid` have been removed. Use `set_curves_list()`. + * `SslCurve` and `SslCurveNid` have been removed. Curve names are more stable and portable identifiers. Use `curve_name()` and `set_curves_list()`. * `Ssl::new_from_ref` -> `Ssl::new()`. * `X509Builder::append_extension2` -> `X509Builder::append_extension`. - * `X509Store` is now cheaply cloneable, but immutable. `SslContextBuilder.cert_store_mut()` can't be used after `.set_cert_store()`. Use `.set_cert_store_builder()` if you need `.cert_store_mut()`. + * `X509Store` is now cheaply cloneable, but immutable. `SslContextBuilder.cert_store_mut()` can't be used after `.set_cert_store()`. If you need `.cert_store_mut()`, either don't overwrite the default store, or use `.set_cert_store_builder()`. * `X509StoreBuilder::add_cert` takes a reference. * `hyper` 0.x support has been removed. Use `hyper` 1.x. diff --git a/boring-sys/Cargo.toml b/boring-sys/Cargo.toml index cecc83f51..72845c85e 100644 --- a/boring-sys/Cargo.toml +++ b/boring-sys/Cargo.toml @@ -12,6 +12,7 @@ links = "boringssl" build = "build/main.rs" readme = "README.md" categories = ["cryptography", "external-ffi-bindings"] +keywords = ["tls", "boringssl", "openssl", "fips", "ml-kem"] edition = { workspace = true } rust-version = { workspace = true } include = [ diff --git a/boring-sys/README.md b/boring-sys/README.md new file mode 100644 index 000000000..fe4d7db20 --- /dev/null +++ b/boring-sys/README.md @@ -0,0 +1,15 @@ +# Low-level bindings to BoringSSL + +[BoringSSL](https://boringssl.googlesource.com/boringssl) is Google's fork of OpenSSL for Chrome/Chromium and Android. + +This crate builds the BoringSSL library (or optionally links a pre-built version) and generates FFI bindings for it. +It supports FIPS-compatible builds of BoringSSL, as well as Post-Quantum crypto and Raw Public Key features. + +To use BoringSSL from Rust, prefer the [higher-level safe API](https://docs.rs/boring). + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed under the terms of both the Apache License, +Version 2.0 and the MIT license without any additional terms or conditions. diff --git a/boring/Cargo.toml b/boring/Cargo.toml index 465cbb294..b9605ff13 100644 --- a/boring/Cargo.toml +++ b/boring/Cargo.toml @@ -7,7 +7,7 @@ description = "BoringSSL bindings" repository = { workspace = true } documentation = "https://docs.rs/boring" readme = "README.md" -keywords = ["crypto", "tls", "ssl", "dtls"] +keywords = ["tls", "ssl", "dtls", "post-quantum", "fips"] categories = ["cryptography", "api-bindings"] edition = { workspace = true } rust-version = { workspace = true } From d47684d087afc47e0db38c580ec8cd05bf11c5b1 Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 3 Feb 2026 15:35:52 +0000 Subject: [PATCH 109/111] v5.0.0 --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 96ec2fb3e..80b803228 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ resolver = "2" [workspace.package] -version = "5.0.0-alpha.3" +version = "5.0.0" rust-version = "1.85" repository = "https://github.com/cloudflare/boring" edition = "2021" @@ -20,9 +20,9 @@ tag-prefix = "" publish = false [workspace.dependencies] -boring-sys = { version = "5.0.0-alpha.3", path = "./boring-sys" } -boring = { version = "5.0.0-alpha.3", path = "./boring" } -tokio-boring = { version = "5.0.0-alpha.3", path = "./tokio-boring" } +boring-sys = { version = "5.0.0", path = "./boring-sys" } +boring = { version = "5.0.0", path = "./boring" } +tokio-boring = { version = "5.0.0", path = "./tokio-boring" } bindgen = { version = "0.72.0", default-features = false, features = ["runtime"] } bitflags = "2.9" From 43c8c279f318f02777c08ba4303b23a39b475682 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Wed, 11 Feb 2026 11:38:49 -0800 Subject: [PATCH 110/111] Revert "Support TARGET_CC and CC_{target}" This reverts commit a50a39fde7967a6c25ed6ee7ec2feabad69c30bf, which interferes with CMake's own support for these variables, at least how Signal has been using them. --- .github/workflows/ci.yml | 8 ++++---- boring-sys/build/config.rs | 17 +++++++---------- boring-sys/build/main.rs | 9 --------- 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53f1dbb18..b0545fc47 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,8 +162,8 @@ jobs: apt_packages: gcc-arm-linux-gnueabi g++-arm-linux-gnueabi check_only: true custom_env: - CC_arm-unknown-linux-gnueabi: arm-linux-gnueabi-gcc - CXX_arm-unknown-linux-gnueabi: arm-linux-gnueabi-g++ + CC: arm-linux-gnueabi-gcc + CXX: arm-linux-gnueabi-g++ CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABI_LINKER: arm-linux-gnueabi-g++ - thing: aarch64-linux target: aarch64-unknown-linux-gnu @@ -172,8 +172,8 @@ jobs: apt_packages: crossbuild-essential-arm64 check_only: true custom_env: - CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc - CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++ + CC: aarch64-linux-gnu-gcc + CXX: aarch64-linux-gnu-g++ CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-g++ - thing: arm64-macos target: aarch64-apple-darwin diff --git a/boring-sys/build/config.rs b/boring-sys/build/config.rs index e344f9acb..d5f318b96 100644 --- a/boring-sys/build/config.rs +++ b/boring-sys/build/config.rs @@ -34,9 +34,6 @@ pub(crate) struct Env { pub(crate) android_ndk_home: Option, pub(crate) cmake_toolchain_file: Option, pub(crate) cpp_runtime_lib: Option, - /// C compiler (ignored if using FIPS) - pub(crate) cc: Option, - pub(crate) cxx: Option, pub(crate) docs_rs: bool, } @@ -126,15 +123,18 @@ impl Features { impl Env { fn from_env(host: &str, target: &str, is_fips_like: bool) -> Self { - let var_prefix = if host == target { "HOST" } else { "TARGET" }; let target_with_underscores = target.replace('-', "_"); - let target_only_var = |name: &str| { + // Logic stolen from cmake-rs. + let target_var = |name: &str| { + let kind = if host == target { "HOST" } else { "TARGET" }; + + // TODO(rmehra): look for just `name` first, as most people just set that var(&format!("{name}_{target}")) .or_else(|| var(&format!("{name}_{target_with_underscores}"))) - .or_else(|| var(&format!("{var_prefix}_{name}"))) + .or_else(|| var(&format!("{kind}_{name}"))) + .or_else(|| var(name)) }; - let target_var = |name: &str| target_only_var(name).or_else(|| var(name)); let boringssl_var = |name: &str| { const BORING_BSSL_PREFIX: &str = "BORING_BSSL_"; @@ -171,9 +171,6 @@ impl Env { android_ndk_home: target_var("ANDROID_NDK_HOME").map(Into::into), cmake_toolchain_file: target_var("CMAKE_TOOLCHAIN_FILE").map(Into::into), cpp_runtime_lib: target_var("BORING_BSSL_RUST_CPPLIB"), - // matches the `cc` crate - cc: target_only_var("CC"), - cxx: target_only_var("CXX"), docs_rs: var("DOCS_RS").is_some(), } } diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index abb733bed..ed1db1173 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -232,15 +232,6 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { .define("CMAKE_ASM_COMPILER_TARGET", &config.target); } - if !config.features.fips { - if let Some(cc) = &config.env.cc { - boringssl_cmake.define("CMAKE_C_COMPILER", cc); - } - if let Some(cxx) = &config.env.cxx { - boringssl_cmake.define("CMAKE_CXX_COMPILER", cxx); - } - } - if let Some(sysroot) = &config.env.sysroot { boringssl_cmake.define("CMAKE_SYSROOT", sysroot); } From 7d000369f3290faac5eda11e976add16e6a9748c Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Thu, 12 Feb 2026 10:51:36 -0800 Subject: [PATCH 111/111] boring-sys: Support static MSVC runtime --- boring-sys/build/config.rs | 8 ++++++++ boring-sys/build/main.rs | 6 +++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/boring-sys/build/config.rs b/boring-sys/build/config.rs index d5f318b96..a95416b31 100644 --- a/boring-sys/build/config.rs +++ b/boring-sys/build/config.rs @@ -12,6 +12,7 @@ pub(crate) struct Config { pub(crate) target_os: String, pub(crate) unix: bool, pub(crate) target_env: String, + pub(crate) target_features: Vec, pub(crate) features: Features, pub(crate) env: Env, } @@ -48,6 +49,12 @@ impl Config { let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap(); let unix = env::var("CARGO_CFG_UNIX").is_ok(); + let target_features = env::var("CARGO_CFG_TARGET_FEATURE") + .unwrap() + .split(',') + .map(|s| s.to_owned()) + .collect(); + let features = Features::from_env(); let env = Env::from_env(&host, &target, features.is_fips_like()); @@ -66,6 +73,7 @@ impl Config { target_os, unix, target_env, + target_features, features, env, }; diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index ed1db1173..c034fb931 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -217,7 +217,11 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { // This is required now because newest BoringSSL requires CMake 3.22 which // uses the new logic with CMAKE_MSVC_RUNTIME_LIBRARY introduced in CMake 3.15. // https://github.com/rust-lang/cmake-rs/pull/30#issuecomment-2969758499 - boringssl_cmake.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreadedDLL"); + if config.target_features.iter().any(|f| f == "crt-static") { + boringssl_cmake.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded"); + } else { + boringssl_cmake.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreadedDLL"); + } } if config.host == config.target {