Skip to content

Commit da9f0bd

Browse files
authored
[FIPS 4.0 CHERRY PICK] Generate Rust Bindings (#2999) (#3048)
Originally: #2999 * Cherry-picked from main: 167015c * Minor conflicts resolved in CMakeLists.txt and aws-lc-rs.yml. ------ ### Issues: * Addresses: V2090958423 ### Description of changes: This adds CMake support for generating Rust FFI bindings from the AWS-LC public C headers using `bindgen-cli`. This is intended for use by downstream Rust consumers so that bindings can be generated directly from the CMake build system. **New CMake option:** `-DGENERATE_RUST_BINDINGS=ON` (off by default) When enabled, the build: 1. Validates that `bindgen-cli` (≥0.69.5) and `rustfmt` are available 2. Auto-discovers all public OpenSSL headers and generates a `rust_wrapper.h` aggregation header 3. Invokes `bindgen` to produce `aws_lc_bindings.rs` in the build directory 4. Supports the existing `BORINGSSL_PREFIX` mechanism — uses `--prefix-link-name` so Rust-side function names stay unprefixed while `#[link_name]` attributes carry the prefix for the linker 5. During install, the bindings are placed at `$INSTALL_DIR/share/rust/aws_lc_bindings.rs` **Files added:** - `cmake/rust_bindings.cmake` — Module containing the header discovery, prefix formatting, and bindgen invocation logic - `cmake/rust_wrapper.h.in` — Template header that aggregates all public headers for bindgen input ### Call-outs: - The prefix symbols header (`boringssl_prefix_symbols.h`) is intentionally excluded from bindgen's input. The rationale is documented in the module-level comment in `cmake/rust_bindings.cmake`. - The `rust_bindings` target is not part of `ALL`. It is built on-demand when explicitly requested (`--target rust_bindings`) or automatically during `cmake --install`. - Platform-specific symbol prefix formatting (Apple/Win32 leading underscore) is handled by `get_symbol_prefix_format()`. ### Testing: CI workflow added in `.github/workflows/aws-lc-rs.yml` (`cmake-rust-bindings` job) covering: - Linux (no prefix) - Linux with `BORINGSSL_PREFIX=AWSLC` - macOS (no prefix) - macOS with `BORINGSSL_PREFIX=AWSLC` - Windows (no prefix) Each matrix entry verifies that bindings are generated, contain expected symbols, have correct prefix behavior, and compile as valid Rust. ### Call-outs: Point out areas that need special attention or support during the review process. Discuss architecture or design changes. ### Testing: How is this change tested (unit tests, fuzz tests, etc.)? Are there any testing steps to be verified by the reviewer? By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license.
1 parent d6d2288 commit da9f0bd

File tree

4 files changed

+538
-0
lines changed

4 files changed

+538
-0
lines changed

.github/workflows/aws-lc-rs.yml

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,181 @@ jobs:
8888
if: runner.os != 'Windows'
8989
working-directory: ./aws-lc-rs
9090
run: ./scripts/build/collect_build_src.sh
91+
92+
# CMake Rust bindings generation tests
93+
cmake-rust-bindings:
94+
if: github.repository_owner == 'aws'
95+
strategy:
96+
fail-fast: false
97+
matrix:
98+
include:
99+
- os: ubuntu-latest
100+
name: linux
101+
- os: ubuntu-latest
102+
name: linux-prefix
103+
prefix: AWSLC_PREFIX
104+
- os: macos-latest
105+
name: macos
106+
- os: macos-latest
107+
name: macos-prefix
108+
prefix: AWSLC_PREFIX
109+
- os: windows-latest
110+
name: windows
111+
- os: ubuntu-latest
112+
name: linux-no-ssl
113+
build_libssl: OFF
114+
runs-on: ${{ matrix.os }}
115+
name: cmake-rust-bindings (${{ matrix.name }})
116+
steps:
117+
- uses: actions/checkout@v4
118+
- uses: dtolnay/rust-toolchain@stable
119+
with:
120+
components: 'rustfmt'
121+
- uses: ilammy/setup-nasm@v1
122+
if: runner.os == 'Windows'
123+
- uses: actions/setup-go@v4
124+
with:
125+
go-version: '>=1.20'
126+
- name: Install bindgen-cli
127+
run: cargo install --force --locked bindgen-cli
128+
129+
# Prefix builds need a non-prefixed build first to collect symbols
130+
- name: Generate prefix symbols file
131+
if: matrix.prefix
132+
shell: bash
133+
run: |
134+
cmake -B build-noprefix -DBUILD_TESTING=OFF
135+
cmake --build build-noprefix
136+
go run ./util/read_symbols.go build-noprefix/crypto/libcrypto.a > symbols.txt
137+
go run ./util/read_symbols.go build-noprefix/ssl/libssl.a >> symbols.txt
138+
echo "Collected $(wc -l < symbols.txt) symbols"
139+
140+
- name: Configure with Rust bindings generation
141+
shell: bash
142+
run: |
143+
cmake_args="-DGENERATE_RUST_BINDINGS=ON -DBUILD_TESTING=OFF"
144+
if [ -n "${{ matrix.prefix }}" ]; then
145+
cmake_args="$cmake_args -DBORINGSSL_PREFIX=${{ matrix.prefix }}"
146+
cmake_args="$cmake_args -DBORINGSSL_PREFIX_SYMBOLS=$(pwd)/symbols.txt"
147+
fi
148+
if [ "${{ matrix.build_libssl }}" = "OFF" ]; then
149+
cmake_args="$cmake_args -DBUILD_LIBSSL=OFF"
150+
fi
151+
cmake -B build $cmake_args
152+
153+
- name: Build libraries
154+
run: cmake --build build --config Release
155+
156+
- name: Generate bindings
157+
run: cmake --build build --target rust_bindings --config Release --verbose
158+
159+
- name: Verify bindings file exists
160+
shell: bash
161+
run: |
162+
if [ ! -f build/rust/aws_lc_bindings.rs ]; then
163+
echo "ERROR: Rust bindings file was not generated"
164+
exit 1
165+
fi
166+
echo "Generated bindings file size: $(wc -c < build/rust/aws_lc_bindings.rs) bytes"
167+
echo "Generated bindings line count: $(wc -l < build/rust/aws_lc_bindings.rs) lines"
168+
169+
- name: Verify bindings content
170+
shell: bash
171+
run: |
172+
# Verify SSL bindings based on BUILD_LIBSSL setting (defaults to ON)
173+
if [ "${{ matrix.build_libssl }}" != "OFF" ]; then
174+
if ! grep -q "pub fn SSL_new" build/rust/aws_lc_bindings.rs; then
175+
echo "ERROR: Expected SSL_new function not found"
176+
exit 1
177+
fi
178+
else
179+
if grep -q "pub fn SSL_new" build/rust/aws_lc_bindings.rs; then
180+
echo "ERROR: Unexpected SSL_new found in BUILD_LIBSSL=OFF build"
181+
exit 1
182+
fi
183+
echo "Confirmed: SSL bindings correctly excluded"
184+
fi
185+
if [ -n "${{ matrix.prefix }}" ]; then
186+
# Prefix builds: link_name attributes should contain the prefix.
187+
# The exact format varies by platform (e.g., _PREFIX_ on macOS vs PREFIX_ on Linux).
188+
if ! grep -q 'link_name.*${{ matrix.prefix }}_' build/rust/aws_lc_bindings.rs; then
189+
echo "ERROR: Expected prefixed link_name attributes not found"
190+
exit 1
191+
fi
192+
if ! grep -B1 "pub fn SSL_new" build/rust/aws_lc_bindings.rs | grep -q 'link_name.*${{ matrix.prefix }}_'; then
193+
echo "ERROR: SSL_new should have ${{ matrix.prefix }}_ prefixed link_name"
194+
exit 1
195+
fi
196+
else
197+
# Non-prefix builds should not have link_name attributes
198+
if grep -q '#\[link_name' build/rust/aws_lc_bindings.rs; then
199+
echo "ERROR: Unexpected link_name attributes found in no-prefix build"
200+
exit 1
201+
fi
202+
fi
203+
echo "Bindings content verification passed"
204+
205+
206+
- name: Verify bindings build and link
207+
shell: bash
208+
run: |
209+
mkdir -p "${RUNNER_TEMP}/test-bindings/src"
210+
cat > "${RUNNER_TEMP}/test-bindings/Cargo.toml" << 'EOF'
211+
[package]
212+
name = "test-bindings"
213+
version = "0.1.0"
214+
edition = "2021"
215+
216+
[[bin]]
217+
name = "test-bindings"
218+
path = "src/main.rs"
219+
EOF
220+
cat > "${RUNNER_TEMP}/test-bindings/build.rs" << 'EOF'
221+
use std::env;
222+
fn main() {
223+
let build_dir = env::var("CMAKE_BUILD_DIR").expect("CMAKE_BUILD_DIR must be set");
224+
// Library search paths for single-config generators (Unix Makefiles, Ninja)
225+
println!("cargo:rustc-link-search=native={}/crypto", build_dir);
226+
println!("cargo:rustc-link-search=native={}/ssl", build_dir);
227+
// Library search paths for multi-config generators (Visual Studio)
228+
println!("cargo:rustc-link-search=native={}/crypto/Release", build_dir);
229+
println!("cargo:rustc-link-search=native={}/ssl/Release", build_dir);
230+
println!("cargo:rustc-link-lib=static=crypto");
231+
if env::var("INCLUDE_SSL").unwrap_or_default() == "1" {
232+
println!("cargo:rustc-link-lib=static=ssl");
233+
}
234+
// Platform-specific system library dependencies required by aws-lc
235+
let target_family = env::var("CARGO_CFG_TARGET_FAMILY").unwrap_or_default();
236+
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
237+
if target_family == "unix" {
238+
println!("cargo:rustc-link-lib=dylib=pthread");
239+
}
240+
if target_os == "windows" {
241+
println!("cargo:rustc-link-lib=dylib=ws2_32");
242+
}
243+
}
244+
EOF
245+
cat > "${RUNNER_TEMP}/test-bindings/src/main.rs" << 'EOF'
246+
#![allow(clippy::all)]
247+
#![allow(non_upper_case_globals)]
248+
#![allow(non_camel_case_types)]
249+
#![allow(non_snake_case)]
250+
#![allow(dead_code)]
251+
#![allow(improper_ctypes)]
252+
#![allow(unpredictable_function_pointer_comparisons)]
253+
include!(concat!(env!("BINDINGS_PATH"), "/aws_lc_bindings.rs"));
254+
fn main() {
255+
unsafe { CRYPTO_library_init(); }
256+
println!("Bindings link test passed");
257+
}
258+
EOF
259+
cd "${RUNNER_TEMP}/test-bindings"
260+
include_ssl="0"
261+
if [ "${{ matrix.build_libssl }}" != "OFF" ]; then
262+
include_ssl="1"
263+
fi
264+
export CMAKE_BUILD_DIR="${GITHUB_WORKSPACE}/build"
265+
export BINDINGS_PATH="${GITHUB_WORKSPACE}/build/rust"
266+
export INCLUDE_SSL="${include_ssl}"
267+
cargo run
268+
echo "Bindings build, link, and run test passed"

CMakeLists.txt

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ option(ENABLE_FIPS_ENTROPY_CPU_JITTER "Enable FIPS entropy source: CPU Jitter" O
9494
option(ENABLE_DATA_INDEPENDENT_TIMING "Enable automatic setting/resetting Data-Independent Timing
9595
(DIT) flag in cryptographic functions. Currently only applicable to Arm64 (except on Windows)" OFF)
9696
option(ENABLE_PRE_SONAME_BUILD "Build AWS-LC without SONAME configuration for shared library builds" ON)
97+
option(GENERATE_RUST_BINDINGS "Generate Rust bindings using bindgen-cli" OFF)
98+
set(RUST_BINDINGS_TARGET_VERSION "1.70" CACHE STRING "Minimum Rust version for generated bindings")
99+
97100
include(cmake/go.cmake)
98101

99102
if(NOT ENABLE_PRE_SONAME_BUILD AND BUILD_SHARED_LIBS AND UNIX AND NOT APPLE)
@@ -113,6 +116,60 @@ enable_language(C)
113116
# Entropy by default; because it's the root of the Tree DRBG.
114117
message(STATUS "Entropy source configured: Dynamic (default: CPU Jitter)")
115118

119+
# Validate Rust bindings prerequisites
120+
if(GENERATE_RUST_BINDINGS)
121+
find_program(BINDGEN_EXECUTABLE NAMES bindgen)
122+
if(NOT BINDGEN_EXECUTABLE)
123+
message(FATAL_ERROR "GENERATE_RUST_BINDINGS is enabled but bindgen-cli was not found. "
124+
"Install it with: cargo install --force --locked bindgen-cli")
125+
endif()
126+
127+
# Verify minimum version (0.69.5)
128+
execute_process(
129+
COMMAND ${BINDGEN_EXECUTABLE} --version
130+
OUTPUT_VARIABLE BINDGEN_VERSION_OUTPUT
131+
OUTPUT_STRIP_TRAILING_WHITESPACE
132+
RESULT_VARIABLE BINDGEN_VERSION_RESULT
133+
)
134+
135+
if(NOT BINDGEN_VERSION_RESULT EQUAL 0)
136+
message(FATAL_ERROR "Failed to get bindgen version")
137+
endif()
138+
139+
# Extract version number (format: "bindgen X.Y.Z")
140+
string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" BINDGEN_VERSION "${BINDGEN_VERSION_OUTPUT}")
141+
142+
if(BINDGEN_VERSION VERSION_LESS "0.69.5")
143+
message(FATAL_ERROR "bindgen version ${BINDGEN_VERSION} is too old. "
144+
"Minimum required version is 0.69.5. "
145+
"Upgrade with: cargo install --force --locked bindgen-cli")
146+
endif()
147+
148+
message(STATUS "Found bindgen-cli: ${BINDGEN_EXECUTABLE} (version ${BINDGEN_VERSION})")
149+
message(STATUS "Rust bindings target version: ${RUST_BINDINGS_TARGET_VERSION}")
150+
151+
# Check for rustfmt (required for --formatter rustfmt option)
152+
find_program(RUSTFMT_EXECUTABLE NAMES rustfmt)
153+
if(NOT RUSTFMT_EXECUTABLE)
154+
message(FATAL_ERROR "GENERATE_RUST_BINDINGS requires rustfmt but it was not found. "
155+
"Install it with: rustup component add rustfmt")
156+
endif()
157+
message(STATUS "Found rustfmt: ${RUSTFMT_EXECUTABLE}")
158+
endif()
159+
160+
if(DISABLE_CPU_JITTER_ENTROPY)
161+
if(FIPS)
162+
message(FATAL_ERROR "Cannot opt-out of CPU Jitter for the FIPS build")
163+
endif()
164+
add_definitions(-DDISABLE_CPU_JITTER_ENTROPY)
165+
message(STATUS "Entropy source configuration: CPU Jitter opt-out")
166+
message(STATUS "Entropy source configured: Dynamic (default: Operating system)")
167+
else()
168+
# The validated entropy source will always be configured to be CPU Jitter
169+
# Entropy by default; because it's the root of the Tree DRBG.
170+
message(STATUS "Entropy source configured: Dynamic (default: CPU Jitter)")
171+
endif()
172+
116173
if(${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
117174
# OpenBSD by defaults links with --execute-only this is problematic for two reasons:
118175
# 1. The FIPS shared and static builds need to compute the module signature hash by reading the .text section
@@ -347,6 +404,60 @@ else()
347404
)
348405
endif()
349406

407+
# Rust bindings generation
408+
if(GENERATE_RUST_BINDINGS)
409+
include(cmake/rust_bindings.cmake)
410+
411+
# Generate the rust_wrapper.h header from template
412+
generate_rust_wrapper_header()
413+
414+
set(RUST_BINDINGS_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rust/aws_lc_bindings.rs")
415+
416+
# Build list of include directories for bindgen.
417+
# Prefix symbols header is intentionally excluded; see cmake/rust_bindings.cmake.
418+
set(RUST_BINDINGS_INCLUDE_DIRS
419+
"${CMAKE_CURRENT_BINARY_DIR}/include" # Generated rust_wrapper.h location
420+
"${AWSLC_SOURCE_DIR}/include" # Main AWS-LC headers (unprefixed)
421+
)
422+
423+
# Conditionally set optional arguments for generate_rust_bindings
424+
if(BORINGSSL_PREFIX)
425+
set(_rust_prefix_arg PREFIX "${BORINGSSL_PREFIX}")
426+
else()
427+
set(_rust_prefix_arg "")
428+
endif()
429+
430+
if(BUILD_LIBSSL)
431+
set(_rust_ssl_arg INCLUDE_SSL)
432+
else()
433+
set(_rust_ssl_arg "")
434+
endif()
435+
436+
generate_rust_bindings(
437+
OUTPUT_FILE "${RUST_BINDINGS_OUTPUT}"
438+
INCLUDE_DIRS ${RUST_BINDINGS_INCLUDE_DIRS}
439+
${_rust_prefix_arg}
440+
${_rust_ssl_arg}
441+
)
442+
443+
# Install the generated Rust bindings.
444+
# The rust_bindings target is not part of ALL, so we build it on-demand during install.
445+
install(CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" --build \"${CMAKE_BINARY_DIR}\" --target rust_bindings)")
446+
447+
install(
448+
FILES "${CMAKE_CURRENT_BINARY_DIR}/rust/aws_lc_bindings.rs"
449+
DESTINATION "share/rust"
450+
COMPONENT Development
451+
)
452+
453+
# Install the generated rust_wrapper.h header
454+
install(
455+
FILES "${CMAKE_CURRENT_BINARY_DIR}/include/rust_wrapper.h"
456+
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
457+
COMPONENT Development
458+
)
459+
endif()
460+
350461

351462
if("${CMAKE_SYSTEM_NAME}" STREQUAL "Emscripten")
352463
set(EMSCRIPTEN 1)

0 commit comments

Comments
 (0)