Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/actions/setup-boringssl/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: "Setup BoringSSL"
description: "Clone, build, and cache BoringSSL with Rust bindings"
runs:
using: "composite"
steps:
- name: Cache BoringSSL build
uses: actions/cache@v4
id: bssl-cache
with:
path: ${{ runner.temp }}/boringssl/build
key: boringssl-v2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('Cargo.lock') }}

- name: Install bindgen-cli
if: steps.bssl-cache.outputs.cache-hit != 'true'
uses: taiki-e/cache-cargo-install-action@v2
with:
tool: bindgen-cli

- name: Build BoringSSL
if: steps.bssl-cache.outputs.cache-hit != 'true'
shell: bash
run: |
set -euo pipefail
BUILD_DIR="${{ runner.temp }}/boringssl/build"
SRC_DIR="${{ runner.temp }}/boringssl/src"
mkdir -p "${{ runner.temp }}/boringssl"
if [ ! -d "$SRC_DIR/.git" ]; then
git clone https://boringssl.googlesource.com/boringssl "$SRC_DIR"
fi
REV=$(grep -A 2 'name = "bssl-sys"' Cargo.lock | grep -o 'boringssl#[a-f0-9]*' | cut -d'#' -f2 | head -n1 | tr -d '\r' || true)
TARGET=$(rustc -vV | sed -n 's|host: ||p' | tr -d '\r')
cd "$SRC_DIR"
if [ -n "$REV" ]; then
git checkout "$REV"
fi
cmake -GNinja -B "$BUILD_DIR" -S "$SRC_DIR" -DBUILD_TESTING=OFF -DRUST_BINDINGS="$TARGET"
ninja -C "$BUILD_DIR" crypto ssl bssl_sys

- name: Export BORINGSSL_BUILD_DIR and C++ runtime library
shell: bash
run: |
echo "BORINGSSL_BUILD_DIR=${{ runner.temp }}/boringssl/build" >> "$GITHUB_ENV"
if [ "${{ runner.os }}" = "Windows" ]; then
echo "BORINGSSL_RUST_CPPLIB=stdc++" >> "$GITHUB_ENV"
fi
10 changes: 10 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ jobs:
- uses: hecrj/setup-rust-action@v2
with:
components: clippy
- uses: ./.github/actions/setup-boringssl
- uses: actions/cache@v4
with:
path: protoc-cache
Expand Down Expand Up @@ -116,6 +117,7 @@ jobs:
toolchain: nightly-2026-02-22
- uses: taiki-e/install-action@cargo-hack
- uses: taiki-e/install-action@cargo-udeps
- uses: ./.github/actions/setup-boringssl
- uses: actions/cache@v4
with:
path: protoc-cache
Expand Down Expand Up @@ -147,6 +149,7 @@ jobs:
- uses: actions/checkout@v6
- uses: hecrj/setup-rust-action@v2
- uses: taiki-e/install-action@cargo-hack
- uses: ./.github/actions/setup-boringssl
- uses: actions/cache@v4
with:
path: protoc-cache
Expand Down Expand Up @@ -184,6 +187,7 @@ jobs:
with:
rust-version: ${{ steps.msrv.outputs.version }}
- uses: taiki-e/install-action@cargo-no-dev-deps
- uses: ./.github/actions/setup-boringssl
- uses: actions/cache@v4
with:
path: protoc-cache
Expand All @@ -203,6 +207,7 @@ jobs:
- uses: dtolnay/rust-toolchain@nightly
- uses: dtolnay/install@cargo-docs-rs
- uses: taiki-e/install-action@cargo-hack
- uses: ./.github/actions/setup-boringssl
- uses: actions/cache@v4
with:
path: protoc-cache
Expand All @@ -228,6 +233,7 @@ jobs:
- uses: hecrj/setup-rust-action@v2
- uses: taiki-e/install-action@cargo-hack
- uses: taiki-e/install-action@cargo-nextest
- uses: ./.github/actions/setup-boringssl
- uses: actions/cache@v4
with:
path: protoc-cache
Expand All @@ -249,6 +255,7 @@ jobs:
- uses: actions/checkout@v6
- uses: hecrj/setup-rust-action@v2
- uses: taiki-e/install-action@cargo-hack
- uses: ./.github/actions/setup-boringssl
- uses: actions/cache@v4
with:
path: protoc-cache
Expand Down Expand Up @@ -293,6 +300,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: hecrj/setup-rust-action@v2
- uses: ./.github/actions/setup-boringssl
- uses: obi1kenobi/cargo-semver-checks-action@v2
with:
feature-group: all-features
Expand All @@ -309,6 +318,7 @@ jobs:
with:
tool: cargo-check-external-types@0.4.0
- uses: taiki-e/install-action@cargo-hack
- uses: ./.github/actions/setup-boringssl
- uses: actions/cache@v4
with:
path: protoc-cache
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ members = [
"codegen",
"grpc",
"grpc-benchmark",
"grpc-bssl",
"grpc-google",
"grpc-protobuf",
"grpc-protobuf-build",
Expand Down
32 changes: 32 additions & 0 deletions grpc-bssl/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[package]
name = "grpc-bssl"
version = "0.10.0"
authors = ["gRPC authors"]
license = "MIT"
edition = "2024"
rust-version = { workspace = true }
publish = false
description = "BoringSSL TLS credentials provider for gRPC in Rust"
homepage = "https://grpc.io"
repository = "https://github.com/grpc/grpc-rust"
readme = "README.md"
keywords = ["rpc", "grpc", "async", "tls", "boringssl"]
categories = ["web-programming", "network-programming", "asynchronous"]

[package.metadata.cargo_check_external_types]
allowed_external_types = [
"bssl_tls::*",
"bssl_tls_tokio::*",
"grpc::*",
]

[dependencies]
bssl-tls = { git = "https://boringssl.googlesource.com/boringssl", rev = "281fb6d844f8ef70c95ce9d3d599652cc2448292", package = "bssl-tls" }
bssl-tls-tokio = { git = "https://boringssl.googlesource.com/boringssl", rev = "281fb6d844f8ef70c95ce9d3d599652cc2448292", package = "bssl-tls-tokio" }
grpc = { path = "../grpc", default-features = false, features = ["__unstable"] }
tokio = { version = "1", features = ["io-util", "net"] }
tonic = { version = "0.14.6", path = "../tonic", default-features = false }

[dev-dependencies]
bssl-x509 = { git = "https://boringssl.googlesource.com/boringssl", rev = "281fb6d844f8ef70c95ce9d3d599652cc2448292", package = "bssl-x509" }
tokio = { version = "1", features = ["macros", "rt-multi-thread", "io-util", "net"] }
5 changes: 5 additions & 0 deletions grpc-bssl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# grpc-bssl

BoringSSL TLS transport credentials provider for [`grpc`](../grpc).

This crate provides `BsslChannelCredentials` and `BsslServerCredentials`, which integrate BoringSSL (`bssl-tls` and `bssl-tls-tokio`) transport security with the `grpc` crate.
158 changes: 158 additions & 0 deletions grpc-bssl/src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
*
* Copyright 2026 gRPC authors.
*
* 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.
*
*/

use std::fmt;
use std::sync::Arc;

use bssl_tls::alpn::H2;
use bssl_tls::context::TlsContextBuilder;
use bssl_tls_tokio::{TlsConnector, TokioTlsExt};
use grpc::__unstable::Internal;
use grpc::__unstable::credentials::client::{
ClientHandshakeInfo, HandshakeOutput, ValidateAuthority,
};
use grpc::__unstable::credentials::common::Authority;
use grpc::__unstable::rt::{BoxEndpoint, EndpointIoStream, GrpcRuntime, StreamEndpoint};
use grpc::credentials::call::CallCredentials;
use grpc::credentials::{ChannelCredentials, ProtocolInfo, SecurityInfo, SecurityLevel};
use tonic::async_trait;

/// Authority validator for BoringSSL-based channel credentials.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BsslAuthorityValidator {
expected_host: String,
}

impl BsslAuthorityValidator {
/// Creates a new validator with the expected server authority host.
pub fn new(expected_host: impl Into<String>) -> Self {
Self {
expected_host: expected_host.into(),
}
}
}

impl ValidateAuthority for BsslAuthorityValidator {
fn validate_authority(&self, authority: &Authority) -> bool {
self.expected_host == authority.host()
}
}

/// Client-side transport security credentials powered by BoringSSL (`bssl-tls`).
pub struct BsslChannelCredentials {
connector: TlsConnector,
protocol_info: ProtocolInfo,
}

impl fmt::Debug for BsslChannelCredentials {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BsslChannelCredentials")
.field("protocol_info", &self.protocol_info.security_protocol())
.finish()
}
}

impl BsslChannelCredentials {
/// Creates credentials with an existing [`TlsConnector`].
pub fn new(connector: TlsConnector) -> Self {
Self {
connector,
protocol_info: ProtocolInfo::new("tls"),
}
}

/// Creates credentials from a [`TlsContextBuilder`], automatically configuring ALPN for HTTP/2 (`h2`).
pub fn from_context_builder(
mut builder: TlsContextBuilder<bssl_tls::context::TlsMode>,
) -> Result<Self, bssl_tls::errors::Error> {
builder.set_alpn_protocols([H2])?;
Ok(Self::new(builder.build_tokio_connector()))
}
}

#[async_trait]
impl ChannelCredentials for BsslChannelCredentials {
fn info(&self) -> &ProtocolInfo {
&self.protocol_info
}

fn get_call_credentials(&self, _token: Internal) -> Option<&Arc<dyn CallCredentials>> {
None
}

async fn connect(
&self,
authority: &Authority,
source: BoxEndpoint,
_info: &ClientHandshakeInfo,
_runtime: &GrpcRuntime,
_token: Internal,
) -> Result<HandshakeOutput, String> {
let local_addr: Box<str> = source.get_local_address().into();
let peer_addr: Box<str> = source.get_peer_address().into();
let network_type = source.get_network_type();

let host = authority.host();
let input_io = EndpointIoStream::new(source);

let mut tls_stream = self
.connector
.connect(host, input_io)
.await
.map_err(|e| format!("BoringSSL TLS client handshake failed: {e}"))?;

let alpn = if let Some(est) = tls_stream.get_mut().established() {
est.get_selected_alpn().map(|s| s.to_vec())
} else {
None
};

if let Some(selected) = alpn {
if selected != H2 {
return Err(format!(
"Server negotiated unexpected ALPN protocol: {:?}",
String::from_utf8_lossy(&selected)
));
}
} else {
return Err("Server did not negotiate ALPN (h2 required)".into());
}

let endpoint: BoxEndpoint = Box::new(StreamEndpoint::new(
tls_stream,
local_addr,
peer_addr,
network_type,
));

let security_info =
SecurityInfo::new("tls").with_security_level(SecurityLevel::PrivacyAndIntegrity);

Ok(HandshakeOutput {
endpoint,
security_info,
authority_validator: Box::new(BsslAuthorityValidator::new(host)),
})
}
}
35 changes: 35 additions & 0 deletions grpc-bssl/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
*
* Copyright 2026 gRPC authors.
*
* 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.
*
*/

//! BoringSSL TLS transport credentials for gRPC in Rust.
//!
//! This crate provides [`BsslChannelCredentials`] and [`BsslServerCredentials`],
//! which integrate Google's `bssl-tls` / `bssl-tls-tokio` stack with the `grpc` crate.

pub mod client;
pub mod server;

pub use client::BsslAuthorityValidator;
pub use client::BsslChannelCredentials;
pub use server::BsslServerCredentials;
Loading
Loading