diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index be87dfbcd217..6230ed84977c 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -133,13 +133,16 @@ jobs: - name: Set Rust toolchain override run: rustup override set ${{ steps.toolchain.outputs.name }} + - name: Install valgrind + run: sudo apt-get update && sudo apt-get install -y valgrind + - name: Install cargo-udeps run: cargo install cargo-udeps - name: Run cargo udeps - # we only use openssl when the openssl-benchmarks feature is enabled. - # openssl is a dev-dependency so it can't be optional. - run: cargo udeps --workspace --all-targets --features openssl-benchmarks + # ring and openssl are optional dependencies in aws-lc-rs-testing, + # gated by ring-benchmarks and openssl-benchmarks features respectively. + run: cargo udeps --workspace --all-targets --features ring-benchmarks,openssl-benchmarks env: RUSTC_WRAPPER: "" @@ -404,7 +407,7 @@ jobs: - name: Install readelf run: sudo apt-get update && sudo apt-get install -y binutils - name: Build project - run: cargo build --workspace --all-targets --release + run: cargo build -p aws-lc-rs -p aws-lc-sys -p aws-lc-fips-sys --all-targets --release - name: Check for GNU-stack section in object files run: | echo "Checking for .note.GNU-stack section in all object files..." diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml new file mode 100644 index 000000000000..3fef4a70a6c2 --- /dev/null +++ b/.github/workflows/benchmarks.yml @@ -0,0 +1,266 @@ +name: Benchmarks + +on: + pull_request: + branches: [main] + push: + branches: [main] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + RUST_BACKTRACE: 1 + +permissions: + contents: read + pull-requests: write + +jobs: + benchmark: + name: Benchmark (${{ matrix.target }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + target: x86_64-unknown-linux-gnu + - os: ubuntu-22.04-arm + target: aarch64-unknown-linux-gnu + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Install valgrind + run: | + sudo apt-get update + sudo apt-get install -y valgrind + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-${{ matrix.target }}-cargo-bench-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.target }}-cargo-bench- + + - name: Build benchmark tool (candidate) + run: cargo build --release -p aws-lc-rs-bench + + - name: Checkout baseline (main branch) + if: github.event_name == 'pull_request' + run: | + git worktree add ../baseline origin/${{ github.base_ref }} + cd ../baseline + git submodule update --init --recursive + + - name: Build benchmark tool (baseline) + if: github.event_name == 'pull_request' + run: | + cd ../baseline + cargo build --release -p aws-lc-rs-bench + + - name: Run baseline benchmarks + if: github.event_name == 'pull_request' + run: | + cd ../baseline + ./target/release/aws-lc-rs-bench run-all --output-dir baseline-results + + - name: Run candidate benchmarks + run: | + ./target/release/aws-lc-rs-bench run-all --output-dir candidate-results + + - name: Compare results + if: github.event_name == 'pull_request' + id: compare + run: | + ./target/release/aws-lc-rs-bench compare ../baseline/baseline-results candidate-results > benchmark-report-${{ matrix.target }}.md + cat benchmark-report-${{ matrix.target }}.md >> $GITHUB_STEP_SUMMARY + + - name: Upload benchmark results + uses: actions/upload-artifact@v4 + with: + name: benchmark-results-${{ matrix.target }} + path: | + candidate-results/ + benchmark-report-${{ matrix.target }}.md + retention-days: 30 + + - name: Store main branch results + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: actions/upload-artifact@v4 + with: + name: main-benchmark-results-${{ matrix.target }} + path: candidate-results/ + retention-days: 90 + + benchmark-fips: + name: Benchmark FIPS (${{ matrix.target }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + target: x86_64-unknown-linux-gnu + - os: ubuntu-22.04-arm + target: aarch64-unknown-linux-gnu + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Install valgrind + run: | + sudo apt-get update + sudo apt-get install -y valgrind + + - name: Install Go (required for FIPS build) + uses: actions/setup-go@v5 + with: + go-version: '1.22' + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-${{ matrix.target }}-cargo-bench-fips-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.target }}-cargo-bench-fips- + + - name: Build benchmark tool with FIPS (candidate) + run: cargo build --release -p aws-lc-rs-bench --features fips + + - name: Checkout baseline (main branch) + if: github.event_name == 'pull_request' + run: | + git worktree add ../baseline origin/${{ github.base_ref }} + cd ../baseline + git submodule update --init --recursive + + - name: Build benchmark tool with FIPS (baseline) + if: github.event_name == 'pull_request' + run: | + cd ../baseline + cargo build --release -p aws-lc-rs-bench --features fips + + - name: Run baseline FIPS benchmarks + if: github.event_name == 'pull_request' + run: | + cd ../baseline + ./target/release/aws-lc-rs-bench run-all --output-dir baseline-fips-results + + - name: Run candidate FIPS benchmarks + run: | + ./target/release/aws-lc-rs-bench run-all --output-dir candidate-fips-results + + - name: Compare FIPS results + if: github.event_name == 'pull_request' + id: compare + run: | + ./target/release/aws-lc-rs-bench compare ../baseline/baseline-fips-results candidate-fips-results > benchmark-fips-report-${{ matrix.target }}.md + cat benchmark-fips-report-${{ matrix.target }}.md >> $GITHUB_STEP_SUMMARY + + - name: Upload FIPS benchmark results + uses: actions/upload-artifact@v4 + with: + name: benchmark-fips-results-${{ matrix.target }} + path: | + candidate-fips-results/ + benchmark-fips-report-${{ matrix.target }}.md + retention-days: 30 + + - name: Store main branch FIPS results + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: actions/upload-artifact@v4 + with: + name: main-benchmark-fips-results-${{ matrix.target }} + path: candidate-fips-results/ + retention-days: 90 + + post-comment: + name: Post PR Comment + needs: [benchmark, benchmark-fips] + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Download all benchmark reports + uses: actions/download-artifact@v4 + with: + pattern: benchmark-*-results-* + merge-multiple: true + + - name: Combine reports + run: | + echo "" > comment-body.md + echo "" >> comment-body.md + echo "# Benchmark Results" >> comment-body.md + echo "" >> comment-body.md + echo "## Standard Build" >> comment-body.md + echo "" >> comment-body.md + for f in benchmark-report-*.md; do + if [ -f "$f" ]; then + target=$(echo "$f" | sed 's/benchmark-report-\(.*\)\.md/\1/') + echo "### Target: \`$target\`" >> comment-body.md + echo "" >> comment-body.md + cat "$f" >> comment-body.md + echo "" >> comment-body.md + fi + done + echo "## FIPS Build" >> comment-body.md + echo "" >> comment-body.md + for f in benchmark-fips-report-*.md; do + if [ -f "$f" ]; then + target=$(echo "$f" | sed 's/benchmark-fips-report-\(.*\)\.md/\1/') + echo "### Target: \`$target\`" >> comment-body.md + echo "" >> comment-body.md + cat "$f" >> comment-body.md + echo "" >> comment-body.md + fi + done + echo "" >> comment-body.md + echo "---" >> comment-body.md + echo "> **Note**: Benchmark results are measured using CPU instruction counts via Valgrind's callgrind tool." >> comment-body.md + echo "> Changes greater than 2% are considered significant." >> comment-body.md + echo ">" >> comment-body.md + echo "> ✅ = improvement, ⚠️ = regression" >> comment-body.md + + - name: Find existing comment + uses: peter-evans/find-comment@v3 + id: find-comment + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: '' + + - name: Create or update comment + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body-path: comment-body.md + edit-mode: replace diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 770e1ee310c7..0464d715488e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -55,7 +55,7 @@ jobs: run: cargo test ${{ matrix.args }} - name: Run extra tests working-directory: ./aws-lc-rs-testing - run: cargo test --all-targets + run: cargo test --all-targets --features ring-benchmarks,openssl-benchmarks bindgen-test: if: github.repository_owner == 'aws' diff --git a/.gitignore b/.gitignore index 45a15a3dd7f6..14e107e9441c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ Cargo.lock deps/aws-lc-sys/src/bindings.rs /flamegraph.svg +/results/ # These are backup files generated by rustfmt **/*.rs.bk diff --git a/Cargo.toml b/Cargo.toml index 05acaefa2d80..c0fd00ad5406 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "aws-lc-sys", "aws-lc-fips-sys", "aws-lc-rs-testing", + "aws-lc-rs-bench", "links-testing", ] exclude = [ @@ -28,6 +29,7 @@ zeroize = "1.8.1" # The dependencies below do not affect our MSRV. They are only used as # a dev-dependency or only used outside of our public crates. # These can be updated to the latest versions. +anyhow = "1.0" clap = "4.4" criterion = "0.8.2" hex = "0.4.3" @@ -35,6 +37,8 @@ lazy_static = "1.5.0" openssl = "0.10.73" paste = "1.0.15" ring = "0.17.14" +serde = "1.0" +serde_json = "1.0" toml_edit = "0.25.0" [profile.bench] diff --git a/aws-lc-fips-sys/builder/main.rs b/aws-lc-fips-sys/builder/main.rs index 4cd8ac2b247d..3810cf66fc1d 100644 --- a/aws-lc-fips-sys/builder/main.rs +++ b/aws-lc-fips-sys/builder/main.rs @@ -696,6 +696,7 @@ const PRELUDE: &str = r" clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::default_trait_access, + clippy::doc_markdown, clippy::missing_safety_doc, clippy::must_use_candidate, clippy::not_unsafe_ptr_arg_deref, diff --git a/aws-lc-fips-sys/src/lib.rs b/aws-lc-fips-sys/src/lib.rs index 779e14479b43..fd5577c6f79f 100644 --- a/aws-lc-fips-sys/src/lib.rs +++ b/aws-lc-fips-sys/src/lib.rs @@ -60,6 +60,7 @@ platform_binding!( clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::default_trait_access, + clippy::doc_markdown, clippy::missing_safety_doc, clippy::must_use_candidate, clippy::not_unsafe_ptr_arg_deref, diff --git a/aws-lc-rs-bench/Cargo.toml b/aws-lc-rs-bench/Cargo.toml new file mode 100644 index 000000000000..5ba10ea767a3 --- /dev/null +++ b/aws-lc-rs-bench/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "aws-lc-rs-bench" +version = "0.1.0" +edition = "2021" +rust-version = "1.75.0" +publish = false +license = "Apache-2.0 OR ISC" +description = "CI benchmarking tool for aws-lc-rs" + +[features] +default = [] +fips = ["aws-lc-rs/fips"] + +[dependencies] +aws-lc-rs = { path = "../aws-lc-rs", default-features = true } +clap = { workspace = true, features = ["derive"] } +anyhow = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } + +[target.'cfg(target_os = "linux")'.dependencies] +# For instruction counting with valgrind/callgrind +crabgrind = "0.1" diff --git a/aws-lc-rs-bench/README.md b/aws-lc-rs-bench/README.md new file mode 100644 index 000000000000..bb40203e9b2b --- /dev/null +++ b/aws-lc-rs-bench/README.md @@ -0,0 +1,131 @@ +# aws-lc-rs-bench + +CI benchmarking tool for aws-lc-rs that measures CPU instruction counts using Valgrind's callgrind tool. + +## Overview + +This tool provides deterministic and reproducible benchmark results suitable for CI environments. It measures instruction counts rather than wall-clock time, which eliminates noise from varying system loads and makes it ideal for detecting performance regressions in pull requests. + +## Requirements + +- **Linux only**: The instruction counting feature requires Valgrind, which is only available on Linux. +- **Valgrind**: Install via `sudo apt-get install valgrind` on Debian/Ubuntu. + +## Usage + +### List available benchmarks + +```bash +cargo run --release -p aws-lc-rs-bench -- list +``` + +### Run all benchmarks (instruction counting mode) + +This runs all benchmarks under Valgrind's callgrind tool and outputs instruction counts: + +```bash +cargo run --release -p aws-lc-rs-bench -- run-all --output-dir results +``` + +The output directory will contain: +- `results.csv` - CSV file with benchmark names and instruction counts +- `callgrind/` - Directory with callgrind output files for detailed analysis + +### Run a single benchmark (instruction counting mode) + +Run a specific benchmark under Valgrind's callgrind tool: + +```bash +cargo run --release -p aws-lc-rs-bench -- run-single digest_sha256_1kb --output-dir results +``` + +This produces the same output structure as `run-all` (a `results.csv` and `callgrind/` directory), making it +compatible with the `compare` command for analyzing individual benchmark performance. + +> **Note**: `run-single` appends results to the CSV file. If you run the same benchmark multiple +> times, duplicate entries will appear. The `compare` command uses the last entry for each +> benchmark name. + +### Run benchmarks in wall-time mode (for local testing) + +For quick local testing without Valgrind: + +```bash +cargo run --release -p aws-lc-rs-bench -- walltime --iterations 10 +``` + +### Compare two benchmark runs + +Generate a comparison report between a baseline and candidate run: + +```bash +cargo run --release -p aws-lc-rs-bench -- compare baseline-results candidate-results +``` + +This outputs a Markdown report showing: +- Summary of changes +- Significant regressions (⚠️) and improvements (✅) +- Full results table + +For JSON output: + +```bash +cargo run --release -p aws-lc-rs-bench -- compare baseline-results candidate-results --format json +``` + +## FIPS Mode + +To benchmark with FIPS support enabled: + +```bash +cargo run --release -p aws-lc-rs-bench --features fips -- run-all --output-dir fips-results +``` + +## Benchmark Categories + +The tool includes benchmarks for: + +| Category | Operations | +|----------|------------| +| **AEAD** | AES-128-GCM, AES-256-GCM, ChaCha20-Poly1305 (seal/open, various sizes) | +| **Digest** | SHA-256, SHA-384, SHA-512 (various sizes, incremental) | +| **HMAC** | HMAC-SHA256, HMAC-SHA384, HMAC-SHA512 (various sizes, verify) | +| **HKDF** | HKDF-SHA256, HKDF-SHA384 (key derivation) | +| **Agreement** | X25519, ECDH P-256, ECDH P-384 (keygen, agree) | +| **Signatures** | Ed25519, ECDSA P-256/P-384, RSA-2048 (keygen, sign, verify) | + +## Significance Threshold + +The default significance threshold is **2%**. Changes below this threshold are not flagged as significant in the comparison report. + +## CI Integration + +This tool is integrated into the GitHub Actions workflow (`.github/workflows/benchmarks.yml`) which: + +1. Runs on every pull request and push to main +2. Benchmarks both x86_64 and aarch64 Linux targets +3. Runs both standard and FIPS builds +4. Posts comparison results as PR comments +5. Archives results for historical tracking + +## Technical Details + +### Why instruction counts? + +Measuring CPU instructions provides several advantages over wall-clock time: + +- **Deterministic**: Same code produces the same instruction count regardless of system load +- **Reproducible**: Results are consistent across runs +- **Low noise**: No interference from other processes or CPU frequency scaling +- **Sensitive**: Can detect small changes that would be lost in timing noise + +### How it works + +1. The tool spawns itself under Valgrind's callgrind tool +2. Callgrind is started with instrumentation disabled (`--instr-atstart=no`) +3. Each benchmark uses crabgrind client requests to start/stop instrumentation +4. Only the actual cryptographic operation is measured + +## License + +Apache-2.0 OR ISC diff --git a/aws-lc-rs-bench/src/benchmarks.rs b/aws-lc-rs-bench/src/benchmarks.rs new file mode 100644 index 000000000000..f819127e0beb --- /dev/null +++ b/aws-lc-rs-bench/src/benchmarks.rs @@ -0,0 +1,962 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +//! Benchmark definitions for aws-lc-rs cryptographic operations. +//! +//! Each benchmark measures a specific cryptographic operation that represents +//! typical usage patterns. The benchmarks are designed to be deterministic +//! when run under Valgrind's callgrind tool. +//! +//! Setup steps (key creation, RNG initialization, etc.) are performed outside +//! the benchmark closure wherever possible, so that only the target operation +//! is measured. Dedicated key-creation benchmarks are provided for operations +//! whose setup cost was extracted. + +use std::hint::black_box; + +use aws_lc_rs::aead::{ + Aad, BoundKey, Nonce, NonceSequence, OpeningKey, SealingKey, UnboundKey, AES_128_GCM, + AES_256_GCM, CHACHA20_POLY1305, NONCE_LEN, +}; +use aws_lc_rs::agreement::{self, EphemeralPrivateKey, UnparsedPublicKey, X25519}; +use aws_lc_rs::digest::{self, Context as DigestContext}; +use aws_lc_rs::error::Unspecified; +use aws_lc_rs::hkdf::{Salt, HKDF_SHA256, HKDF_SHA384}; +use aws_lc_rs::hmac::{self, Context as HmacContext}; +use aws_lc_rs::rand::SystemRandom; +use aws_lc_rs::signature::{ + self, EcdsaKeyPair, Ed25519KeyPair, KeyPair, RsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING, + ECDSA_P384_SHA384_FIXED_SIGNING, ED25519, +}; + +/// A benchmark definition +pub struct Benchmark { + pub name: String, + pub description: String, + pub func: Box, +} + +impl Benchmark { + pub fn new(name: &str, description: &str, func: F) -> Self + where + F: FnMut() + Send + 'static, + { + Self { + name: name.to_string(), + description: description.to_string(), + func: Box::new(func), + } + } +} + +/// A simple nonce sequence for benchmarking +struct BenchNonceSequence { + counter: u64, +} + +impl BenchNonceSequence { + fn new() -> Self { + Self { counter: 0 } + } +} + +impl NonceSequence for BenchNonceSequence { + fn advance(&mut self) -> Result { + let mut nonce_bytes = [0u8; NONCE_LEN]; + nonce_bytes[..8].copy_from_slice(&self.counter.to_le_bytes()); + self.counter += 1; + Nonce::try_assume_unique_for_key(&nonce_bytes) + } +} + +/// Returns all benchmarks to run +pub fn all_benchmarks() -> Vec { + let mut benchmarks = Vec::new(); + + // AEAD benchmarks + benchmarks.extend(aead_benchmarks()); + + // Digest benchmarks + benchmarks.extend(digest_benchmarks()); + + // HMAC benchmarks + benchmarks.extend(hmac_benchmarks()); + + // HKDF benchmarks + benchmarks.extend(hkdf_benchmarks()); + + // Agreement (key exchange) benchmarks + benchmarks.extend(agreement_benchmarks()); + + // Signature benchmarks + benchmarks.extend(signature_benchmarks()); + + benchmarks +} + +/// AEAD (Authenticated Encryption) benchmarks +fn aead_benchmarks() -> Vec { + let mut benchmarks = Vec::new(); + + // --- Key creation benchmarks --- + + benchmarks.push(Benchmark::new( + "aead_aes_128_gcm_key_create", + "AES-128-GCM key creation", + || { + let key = UnboundKey::new(&AES_128_GCM, &[0u8; 16]).unwrap(); + black_box(SealingKey::new(key, BenchNonceSequence::new())); + }, + )); + + benchmarks.push(Benchmark::new( + "aead_aes_256_gcm_key_create", + "AES-256-GCM key creation", + || { + let key = UnboundKey::new(&AES_256_GCM, &[0u8; 32]).unwrap(); + black_box(SealingKey::new(key, BenchNonceSequence::new())); + }, + )); + + benchmarks.push(Benchmark::new( + "aead_chacha20_poly1305_key_create", + "ChaCha20-Poly1305 key creation", + || { + let key = UnboundKey::new(&CHACHA20_POLY1305, &[0u8; 32]).unwrap(); + black_box(SealingKey::new(key, BenchNonceSequence::new())); + }, + )); + + // --- AES-128-GCM seal --- + + { + let key = UnboundKey::new(&AES_128_GCM, &[0u8; 16]).unwrap(); + let mut sealing_key = SealingKey::new(key, BenchNonceSequence::new()); + let mut data = [0u8; 16]; + benchmarks.push(Benchmark::new( + "aead_aes_128_gcm_seal_16b", + "AES-128-GCM seal 16 bytes", + move || { + let _ = black_box( + sealing_key + .seal_in_place_separate_tag(Aad::from(&[]), &mut data) + .unwrap(), + ); + }, + )); + } + + { + let key = UnboundKey::new(&AES_128_GCM, &[0u8; 16]).unwrap(); + let mut sealing_key = SealingKey::new(key, BenchNonceSequence::new()); + let mut data = vec![0u8; 1024]; + benchmarks.push(Benchmark::new( + "aead_aes_128_gcm_seal_1kb", + "AES-128-GCM seal 1 KB", + move || { + let _ = black_box( + sealing_key + .seal_in_place_separate_tag(Aad::from(&[]), &mut data) + .unwrap(), + ); + }, + )); + } + + { + let key = UnboundKey::new(&AES_128_GCM, &[0u8; 16]).unwrap(); + let mut sealing_key = SealingKey::new(key, BenchNonceSequence::new()); + let mut data = vec![0u8; 8192]; + benchmarks.push(Benchmark::new( + "aead_aes_128_gcm_seal_8kb", + "AES-128-GCM seal 8 KB", + move || { + let _ = black_box( + sealing_key + .seal_in_place_separate_tag(Aad::from(&[]), &mut data) + .unwrap(), + ); + }, + )); + } + + // --- AES-128-GCM open --- + + { + // Pre-seal the data outside the benchmark closure + let key_bytes = [0u8; 16]; + let seal_key = UnboundKey::new(&AES_128_GCM, &key_bytes).unwrap(); + let mut sk = SealingKey::new(seal_key, BenchNonceSequence::new()); + let mut sealed_data = vec![0u8; 1024]; + sk.seal_in_place_append_tag(Aad::from(&[]), &mut sealed_data) + .unwrap(); + + // Key creation remains inside the closure because OpeningKey requires &mut self + // and the nonce must match the sealed data. The key creation cost is measured + // separately by the aead_aes_128_gcm_key_create benchmark. + benchmarks.push(Benchmark::new( + "aead_aes_128_gcm_open_1kb", + "AES-128-GCM open 1 KB (includes key creation; see aead_aes_128_gcm_key_create)", + move || { + let mut data = sealed_data.clone(); + let key = UnboundKey::new(&AES_128_GCM, &key_bytes).unwrap(); + let mut opening_key = OpeningKey::new(key, BenchNonceSequence::new()); + black_box( + opening_key + .open_in_place(Aad::from(&[]), &mut data) + .unwrap(), + ); + }, + )); + } + + // --- AES-256-GCM seal --- + + { + let key = UnboundKey::new(&AES_256_GCM, &[0u8; 32]).unwrap(); + let mut sealing_key = SealingKey::new(key, BenchNonceSequence::new()); + let mut data = vec![0u8; 1024]; + benchmarks.push(Benchmark::new( + "aead_aes_256_gcm_seal_1kb", + "AES-256-GCM seal 1 KB", + move || { + let _ = black_box( + sealing_key + .seal_in_place_separate_tag(Aad::from(&[]), &mut data) + .unwrap(), + ); + }, + )); + } + + { + let key = UnboundKey::new(&AES_256_GCM, &[0u8; 32]).unwrap(); + let mut sealing_key = SealingKey::new(key, BenchNonceSequence::new()); + let mut data = vec![0u8; 8192]; + benchmarks.push(Benchmark::new( + "aead_aes_256_gcm_seal_8kb", + "AES-256-GCM seal 8 KB", + move || { + let _ = black_box( + sealing_key + .seal_in_place_separate_tag(Aad::from(&[]), &mut data) + .unwrap(), + ); + }, + )); + } + + // --- ChaCha20-Poly1305 seal --- + + { + let key = UnboundKey::new(&CHACHA20_POLY1305, &[0u8; 32]).unwrap(); + let mut sealing_key = SealingKey::new(key, BenchNonceSequence::new()); + let mut data = vec![0u8; 1024]; + benchmarks.push(Benchmark::new( + "aead_chacha20_poly1305_seal_1kb", + "ChaCha20-Poly1305 seal 1 KB", + move || { + let _ = black_box( + sealing_key + .seal_in_place_separate_tag(Aad::from(&[]), &mut data) + .unwrap(), + ); + }, + )); + } + + { + let key = UnboundKey::new(&CHACHA20_POLY1305, &[0u8; 32]).unwrap(); + let mut sealing_key = SealingKey::new(key, BenchNonceSequence::new()); + let mut data = vec![0u8; 8192]; + benchmarks.push(Benchmark::new( + "aead_chacha20_poly1305_seal_8kb", + "ChaCha20-Poly1305 seal 8 KB", + move || { + let _ = black_box( + sealing_key + .seal_in_place_separate_tag(Aad::from(&[]), &mut data) + .unwrap(), + ); + }, + )); + } + + benchmarks +} + +/// Digest (hashing) benchmarks +fn digest_benchmarks() -> Vec { + let mut benchmarks = Vec::new(); + + { + let data = [0u8; 16]; + benchmarks.push(Benchmark::new( + "digest_sha256_16b", + "SHA-256 hash 16 bytes", + move || { + black_box(digest::digest(&digest::SHA256, &data)); + }, + )); + } + + { + let data = [0u8; 256]; + benchmarks.push(Benchmark::new( + "digest_sha256_256b", + "SHA-256 hash 256 bytes", + move || { + black_box(digest::digest(&digest::SHA256, &data)); + }, + )); + } + + { + let data = [0u8; 1024]; + benchmarks.push(Benchmark::new( + "digest_sha256_1kb", + "SHA-256 hash 1 KB", + move || { + black_box(digest::digest(&digest::SHA256, &data)); + }, + )); + } + + { + let data = vec![0u8; 8192]; + benchmarks.push(Benchmark::new( + "digest_sha256_8kb", + "SHA-256 hash 8 KB", + move || { + black_box(digest::digest(&digest::SHA256, &data)); + }, + )); + } + + { + let data = vec![0u8; 1024 * 1024]; + benchmarks.push(Benchmark::new( + "digest_sha256_1mb", + "SHA-256 hash 1 MB", + move || { + black_box(digest::digest(&digest::SHA256, &data)); + }, + )); + } + + { + let data = [0u8; 1024]; + benchmarks.push(Benchmark::new( + "digest_sha384_1kb", + "SHA-384 hash 1 KB", + move || { + black_box(digest::digest(&digest::SHA384, &data)); + }, + )); + } + + { + let data = [0u8; 1024]; + benchmarks.push(Benchmark::new( + "digest_sha512_1kb", + "SHA-512 hash 1 KB", + move || { + black_box(digest::digest(&digest::SHA512, &data)); + }, + )); + } + + // Incremental hashing + { + let data = [0u8; 256]; + benchmarks.push(Benchmark::new( + "digest_sha256_incremental_1kb", + "SHA-256 incremental hash 1 KB (4 chunks)", + move || { + let mut ctx = DigestContext::new(&digest::SHA256); + ctx.update(&data); + ctx.update(&data); + ctx.update(&data); + ctx.update(&data); + black_box(ctx.finish()); + }, + )); + } + + benchmarks +} + +/// HMAC benchmarks +fn hmac_benchmarks() -> Vec { + let mut benchmarks = Vec::new(); + + // --- Key creation benchmarks --- + + benchmarks.push(Benchmark::new( + "hmac_sha256_key_create", + "HMAC-SHA256 key creation", + || { + black_box(hmac::Key::new(hmac::HMAC_SHA256, &[0u8; 32])); + }, + )); + + benchmarks.push(Benchmark::new( + "hmac_sha384_key_create", + "HMAC-SHA384 key creation", + || { + black_box(hmac::Key::new(hmac::HMAC_SHA384, &[0u8; 48])); + }, + )); + + benchmarks.push(Benchmark::new( + "hmac_sha512_key_create", + "HMAC-SHA512 key creation", + || { + black_box(hmac::Key::new(hmac::HMAC_SHA512, &[0u8; 64])); + }, + )); + + // --- HMAC sign --- + + { + let key = hmac::Key::new(hmac::HMAC_SHA256, &[0u8; 32]); + benchmarks.push(Benchmark::new( + "hmac_sha256_16b", + "HMAC-SHA256 16 bytes", + move || { + black_box(hmac::sign(&key, &[0u8; 16])); + }, + )); + } + + { + let key = hmac::Key::new(hmac::HMAC_SHA256, &[0u8; 32]); + benchmarks.push(Benchmark::new( + "hmac_sha256_256b", + "HMAC-SHA256 256 bytes", + move || { + black_box(hmac::sign(&key, &[0u8; 256])); + }, + )); + } + + { + let key = hmac::Key::new(hmac::HMAC_SHA256, &[0u8; 32]); + benchmarks.push(Benchmark::new( + "hmac_sha256_1kb", + "HMAC-SHA256 1 KB", + move || { + black_box(hmac::sign(&key, &[0u8; 1024])); + }, + )); + } + + { + let key = hmac::Key::new(hmac::HMAC_SHA384, &[0u8; 48]); + benchmarks.push(Benchmark::new( + "hmac_sha384_1kb", + "HMAC-SHA384 1 KB", + move || { + black_box(hmac::sign(&key, &[0u8; 1024])); + }, + )); + } + + { + let key = hmac::Key::new(hmac::HMAC_SHA512, &[0u8; 64]); + benchmarks.push(Benchmark::new( + "hmac_sha512_1kb", + "HMAC-SHA512 1 KB", + move || { + black_box(hmac::sign(&key, &[0u8; 1024])); + }, + )); + } + + // --- Incremental HMAC --- + + { + let key = hmac::Key::new(hmac::HMAC_SHA256, &[0u8; 32]); + benchmarks.push(Benchmark::new( + "hmac_sha256_incremental_1kb", + "HMAC-SHA256 incremental 1 KB (4 chunks)", + move || { + let data = [0u8; 256]; + let mut ctx = HmacContext::with_key(&key); + ctx.update(&data); + ctx.update(&data); + ctx.update(&data); + ctx.update(&data); + black_box(ctx.sign()); + }, + )); + } + + // --- HMAC verify --- + + { + let key = hmac::Key::new(hmac::HMAC_SHA256, &[0u8; 32]); + let data = [0u8; 1024]; + let tag = hmac::sign(&key, &data); + let tag_bytes: Vec = tag.as_ref().to_vec(); + benchmarks.push(Benchmark::new( + "hmac_sha256_verify_1kb", + "HMAC-SHA256 verify 1 KB", + move || { + black_box(hmac::verify(&key, &data, tag_bytes.as_ref()).unwrap()); + }, + )); + } + + benchmarks +} + +/// HKDF benchmarks — measures full derivation pipeline (salt → extract → expand → fill) +fn hkdf_benchmarks() -> Vec { + vec![ + Benchmark::new( + "hkdf_sha256_derive_32b", + "HKDF-SHA256 derive 32 bytes", + || { + let salt = Salt::new(HKDF_SHA256, &[0u8; 32]); + let prk = salt.extract(&[0u8; 32]); + let okm = prk.expand(&[b"info"], HkdfOutputLen(32)).unwrap(); + let mut out = [0u8; 32]; + black_box(okm.fill(&mut out).unwrap()); + }, + ), + Benchmark::new( + "hkdf_sha256_derive_64b", + "HKDF-SHA256 derive 64 bytes", + || { + let salt = Salt::new(HKDF_SHA256, &[0u8; 32]); + let prk = salt.extract(&[0u8; 32]); + let okm = prk.expand(&[b"info"], HkdfOutputLen(64)).unwrap(); + let mut out = [0u8; 64]; + black_box(okm.fill(&mut out).unwrap()); + }, + ), + Benchmark::new( + "hkdf_sha384_derive_48b", + "HKDF-SHA384 derive 48 bytes", + || { + let salt = Salt::new(HKDF_SHA384, &[0u8; 48]); + let prk = salt.extract(&[0u8; 48]); + let okm = prk.expand(&[b"info"], HkdfOutputLen(48)).unwrap(); + let mut out = [0u8; 48]; + black_box(okm.fill(&mut out).unwrap()); + }, + ), + ] +} + +/// HKDF output length wrapper +struct HkdfOutputLen(usize); + +impl aws_lc_rs::hkdf::KeyType for HkdfOutputLen { + fn len(&self) -> usize { + self.0 + } +} + +/// Agreement (key exchange) benchmarks +fn agreement_benchmarks() -> Vec { + let mut benchmarks = Vec::new(); + + // --- X25519 --- + + { + let rng = SystemRandom::new(); + benchmarks.push(Benchmark::new( + "agreement_x25519_keygen", + "X25519 key generation", + move || { + black_box(EphemeralPrivateKey::generate(&X25519, &rng).unwrap()); + }, + )); + } + + { + let rng = SystemRandom::new(); + // Pre-generate peer public key outside the closure + let peer_private = EphemeralPrivateKey::generate(&X25519, &rng).unwrap(); + let peer_public_bytes: Vec = + peer_private.compute_public_key().unwrap().as_ref().to_vec(); + let peer_public = UnparsedPublicKey::new(&X25519, peer_public_bytes); + + benchmarks.push(Benchmark::new( + "agreement_x25519_agree", + "X25519 key agreement (includes keygen; see agreement_x25519_keygen)", + move || { + // Own keygen is required because agree_ephemeral consumes the private key. + // The keygen cost is measured separately by agreement_x25519_keygen. + let my_private = EphemeralPrivateKey::generate(&X25519, &rng).unwrap(); + black_box( + agreement::agree_ephemeral(my_private, &peer_public, (), |key| { + Ok(Vec::from(key)) + }) + .unwrap(), + ); + }, + )); + } + + // --- ECDH P-256 --- + + { + let rng = SystemRandom::new(); + benchmarks.push(Benchmark::new( + "agreement_ecdh_p256_keygen", + "ECDH P-256 key generation", + move || { + black_box(EphemeralPrivateKey::generate(&agreement::ECDH_P256, &rng).unwrap()); + }, + )); + } + + { + let rng = SystemRandom::new(); + let peer_private = EphemeralPrivateKey::generate(&agreement::ECDH_P256, &rng).unwrap(); + let peer_public_bytes: Vec = + peer_private.compute_public_key().unwrap().as_ref().to_vec(); + let peer_public = UnparsedPublicKey::new(&agreement::ECDH_P256, peer_public_bytes); + + benchmarks.push(Benchmark::new( + "agreement_ecdh_p256_agree", + "ECDH P-256 key agreement (includes keygen; see agreement_ecdh_p256_keygen)", + move || { + // Own keygen is required because agree_ephemeral consumes the private key. + // The keygen cost is measured separately by agreement_ecdh_p256_keygen. + let my_private = + EphemeralPrivateKey::generate(&agreement::ECDH_P256, &rng).unwrap(); + black_box( + agreement::agree_ephemeral(my_private, &peer_public, (), |key| { + Ok(Vec::from(key)) + }) + .unwrap(), + ); + }, + )); + } + + // --- ECDH P-384 --- + + { + let rng = SystemRandom::new(); + benchmarks.push(Benchmark::new( + "agreement_ecdh_p384_keygen", + "ECDH P-384 key generation", + move || { + black_box(EphemeralPrivateKey::generate(&agreement::ECDH_P384, &rng).unwrap()); + }, + )); + } + + { + let rng = SystemRandom::new(); + let peer_private = EphemeralPrivateKey::generate(&agreement::ECDH_P384, &rng).unwrap(); + let peer_public_bytes: Vec = + peer_private.compute_public_key().unwrap().as_ref().to_vec(); + let peer_public = UnparsedPublicKey::new(&agreement::ECDH_P384, peer_public_bytes); + + benchmarks.push(Benchmark::new( + "agreement_ecdh_p384_agree", + "ECDH P-384 key agreement (includes keygen; see agreement_ecdh_p384_keygen)", + move || { + // Own keygen is required because agree_ephemeral consumes the private key. + // The keygen cost is measured separately by agreement_ecdh_p384_keygen. + let my_private = + EphemeralPrivateKey::generate(&agreement::ECDH_P384, &rng).unwrap(); + black_box( + agreement::agree_ephemeral(my_private, &peer_public, (), |key| { + Ok(Vec::from(key)) + }) + .unwrap(), + ); + }, + )); + } + + benchmarks +} + +/// Signature benchmarks +fn signature_benchmarks() -> Vec { + // Pre-generated test keys + const ED25519_PKCS8_V1: &[u8] = include_bytes!("test_data/ed25519_pkcs8_v1.der"); + const ECDSA_P256_PKCS8: &[u8] = include_bytes!("test_data/ecdsa_p256_pkcs8.der"); + const RSA_2048_PKCS8: &[u8] = include_bytes!("test_data/rsa_2048_pkcs8.der"); + + let mut benchmarks = Vec::new(); + + let rng = SystemRandom::new(); + let message = [0u8; 32]; + + // --- Ed25519 setup --- + let ed25519_sign_key = Ed25519KeyPair::from_pkcs8(ED25519_PKCS8_V1).unwrap(); + let ed25519_verify_setup = Ed25519KeyPair::from_pkcs8(ED25519_PKCS8_V1).unwrap(); + let ed25519_sig_bytes: Vec = ed25519_verify_setup.sign(&message).as_ref().to_vec(); + let ed25519_pub_bytes: Vec = ed25519_verify_setup.public_key().as_ref().to_vec(); + + // --- ECDSA P-256 setup --- + let ecdsa_p256_sign_key = + EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P256_PKCS8).unwrap(); + let ecdsa_p256_verify_setup = + EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P256_PKCS8).unwrap(); + let ecdsa_p256_sig_bytes: Vec = ecdsa_p256_verify_setup + .sign(&rng, &message) + .unwrap() + .as_ref() + .to_vec(); + let ecdsa_p256_pub_bytes: Vec = ecdsa_p256_verify_setup.public_key().as_ref().to_vec(); + + // --- ECDSA P-384 setup (generate key at setup time) --- + let p384_pkcs8 = EcdsaKeyPair::generate_pkcs8(&ECDSA_P384_SHA384_FIXED_SIGNING, &rng).unwrap(); + let p384_pkcs8_bytes: Vec = p384_pkcs8.as_ref().to_vec(); + let ecdsa_p384_sign_key = + EcdsaKeyPair::from_pkcs8(&ECDSA_P384_SHA384_FIXED_SIGNING, &p384_pkcs8_bytes).unwrap(); + let ecdsa_p384_verify_setup = + EcdsaKeyPair::from_pkcs8(&ECDSA_P384_SHA384_FIXED_SIGNING, &p384_pkcs8_bytes).unwrap(); + let ecdsa_p384_sig_bytes: Vec = ecdsa_p384_verify_setup + .sign(&rng, &message) + .unwrap() + .as_ref() + .to_vec(); + let ecdsa_p384_pub_bytes: Vec = ecdsa_p384_verify_setup.public_key().as_ref().to_vec(); + let p384_pkcs8_for_bench = p384_pkcs8_bytes; + + // --- RSA-2048 setup --- + let rsa_pkcs1_sign_key = RsaKeyPair::from_pkcs8(RSA_2048_PKCS8).unwrap(); + let rsa_modulus_len = rsa_pkcs1_sign_key.public_modulus_len(); + + let rsa_pkcs1_verify_setup = RsaKeyPair::from_pkcs8(RSA_2048_PKCS8).unwrap(); + let mut rsa_pkcs1_sig_buf = vec![0u8; rsa_modulus_len]; + rsa_pkcs1_verify_setup + .sign( + &signature::RSA_PKCS1_SHA256, + &rng, + &message, + &mut rsa_pkcs1_sig_buf, + ) + .unwrap(); + let rsa_pkcs1_sig_bytes = rsa_pkcs1_sig_buf; + let rsa_pkcs1_pub_bytes: Vec = rsa_pkcs1_verify_setup.public_key().as_ref().to_vec(); + + let rsa_pss_sign_key = RsaKeyPair::from_pkcs8(RSA_2048_PKCS8).unwrap(); + let rsa_pss_verify_setup = RsaKeyPair::from_pkcs8(RSA_2048_PKCS8).unwrap(); + let mut rsa_pss_sig_buf = vec![0u8; rsa_modulus_len]; + rsa_pss_verify_setup + .sign( + &signature::RSA_PSS_SHA256, + &rng, + &message, + &mut rsa_pss_sig_buf, + ) + .unwrap(); + let rsa_pss_sig_bytes = rsa_pss_sig_buf; + let rsa_pss_pub_bytes: Vec = rsa_pss_verify_setup.public_key().as_ref().to_vec(); + + // === Ed25519 === + + { + let rng = SystemRandom::new(); + benchmarks.push(Benchmark::new( + "signature_ed25519_keygen", + "Ed25519 key pair generation (PKCS#8)", + move || { + black_box(Ed25519KeyPair::generate_pkcs8(&rng).unwrap()); + }, + )); + } + + benchmarks.push(Benchmark::new( + "signature_ed25519_from_pkcs8", + "Ed25519 parse key from PKCS#8", + || { + black_box(Ed25519KeyPair::from_pkcs8(ED25519_PKCS8_V1).unwrap()); + }, + )); + + benchmarks.push(Benchmark::new( + "signature_ed25519_sign", + "Ed25519 sign 32 bytes", + move || { + black_box(ed25519_sign_key.sign(&message)); + }, + )); + + benchmarks.push(Benchmark::new( + "signature_ed25519_verify", + "Ed25519 verify 32 bytes", + move || { + let public_key = signature::UnparsedPublicKey::new(&ED25519, &ed25519_pub_bytes); + black_box(public_key.verify(&message, &ed25519_sig_bytes).unwrap()); + }, + )); + + // === ECDSA P-256 === + + { + let rng = SystemRandom::new(); + benchmarks.push(Benchmark::new( + "signature_ecdsa_p256_keygen", + "ECDSA P-256 key pair generation (PKCS#8)", + move || { + black_box( + EcdsaKeyPair::generate_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, &rng).unwrap(), + ); + }, + )); + } + + benchmarks.push(Benchmark::new( + "signature_ecdsa_p256_from_pkcs8", + "ECDSA P-256 parse key from PKCS#8", + || { + black_box( + EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P256_PKCS8) + .unwrap(), + ); + }, + )); + + { + let rng = SystemRandom::new(); + benchmarks.push(Benchmark::new( + "signature_ecdsa_p256_sign", + "ECDSA P-256 sign 32 bytes", + move || { + black_box(ecdsa_p256_sign_key.sign(&rng, &message).unwrap()); + }, + )); + } + + benchmarks.push(Benchmark::new( + "signature_ecdsa_p256_verify", + "ECDSA P-256 verify 32 bytes", + move || { + let public_key = signature::UnparsedPublicKey::new( + &signature::ECDSA_P256_SHA256_FIXED, + &ecdsa_p256_pub_bytes, + ); + black_box(public_key.verify(&message, &ecdsa_p256_sig_bytes).unwrap()); + }, + )); + + // === ECDSA P-384 === + + { + let rng = SystemRandom::new(); + benchmarks.push(Benchmark::new( + "signature_ecdsa_p384_keygen", + "ECDSA P-384 key pair generation (PKCS#8)", + move || { + black_box( + EcdsaKeyPair::generate_pkcs8(&ECDSA_P384_SHA384_FIXED_SIGNING, &rng).unwrap(), + ); + }, + )); + } + + benchmarks.push(Benchmark::new( + "signature_ecdsa_p384_from_pkcs8", + "ECDSA P-384 parse key from PKCS#8", + move || { + black_box( + EcdsaKeyPair::from_pkcs8(&ECDSA_P384_SHA384_FIXED_SIGNING, &p384_pkcs8_for_bench) + .unwrap(), + ); + }, + )); + + { + let rng = SystemRandom::new(); + benchmarks.push(Benchmark::new( + "signature_ecdsa_p384_sign", + "ECDSA P-384 sign 32 bytes", + move || { + black_box(ecdsa_p384_sign_key.sign(&rng, &message).unwrap()); + }, + )); + } + + benchmarks.push(Benchmark::new( + "signature_ecdsa_p384_verify", + "ECDSA P-384 verify 32 bytes", + move || { + let public_key = signature::UnparsedPublicKey::new( + &signature::ECDSA_P384_SHA384_FIXED, + &ecdsa_p384_pub_bytes, + ); + black_box(public_key.verify(&message, &ecdsa_p384_sig_bytes).unwrap()); + }, + )); + + // === RSA-2048 === + + benchmarks.push(Benchmark::new( + "signature_rsa_2048_from_pkcs8", + "RSA-2048 parse key from PKCS#8", + || { + black_box(RsaKeyPair::from_pkcs8(RSA_2048_PKCS8).unwrap()); + }, + )); + + { + let rng = SystemRandom::new(); + let mut sig = vec![0u8; rsa_modulus_len]; + benchmarks.push(Benchmark::new( + "signature_rsa_2048_pkcs1_sign", + "RSA-2048 PKCS#1 sign 32 bytes", + move || { + black_box( + rsa_pkcs1_sign_key + .sign(&signature::RSA_PKCS1_SHA256, &rng, &message, &mut sig) + .unwrap(), + ); + }, + )); + } + + benchmarks.push(Benchmark::new( + "signature_rsa_2048_pkcs1_verify", + "RSA-2048 PKCS#1 verify 32 bytes", + move || { + let public_key = signature::UnparsedPublicKey::new( + &signature::RSA_PKCS1_2048_8192_SHA256, + &rsa_pkcs1_pub_bytes, + ); + black_box(public_key.verify(&message, &rsa_pkcs1_sig_bytes).unwrap()); + }, + )); + + { + let rng = SystemRandom::new(); + let mut sig = vec![0u8; rsa_modulus_len]; + benchmarks.push(Benchmark::new( + "signature_rsa_2048_pss_sign", + "RSA-2048 PSS sign 32 bytes", + move || { + black_box( + rsa_pss_sign_key + .sign(&signature::RSA_PSS_SHA256, &rng, &message, &mut sig) + .unwrap(), + ); + }, + )); + } + + benchmarks.push(Benchmark::new( + "signature_rsa_2048_pss_verify", + "RSA-2048 PSS verify 32 bytes", + move || { + let public_key = signature::UnparsedPublicKey::new( + &signature::RSA_PSS_2048_8192_SHA256, + &rsa_pss_pub_bytes, + ); + black_box(public_key.verify(&message, &rsa_pss_sig_bytes).unwrap()); + }, + )); + + benchmarks +} diff --git a/aws-lc-rs-bench/src/main.rs b/aws-lc-rs-bench/src/main.rs new file mode 100644 index 000000000000..79f9a0d81099 --- /dev/null +++ b/aws-lc-rs-bench/src/main.rs @@ -0,0 +1,562 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +//! CI Benchmarking tool for aws-lc-rs +//! +//! This tool measures CPU instruction counts using Valgrind's callgrind tool, +//! providing deterministic and reproducible benchmark results suitable for CI. +//! +//! ## Usage +//! +//! Run all benchmarks: +//! ```bash +//! cargo run --release -p aws-lc-rs-bench -- run-all --output-dir results +//! ``` +//! +//! Run a single benchmark: +//! ```bash +//! cargo run --release -p aws-lc-rs-bench -- run-single digest_sha256_1kb --output-dir results +//! ``` +//! +//! Compare two benchmark runs: +//! ```bash +//! cargo run --release -p aws-lc-rs-bench -- compare baseline-results candidate-results +//! ``` + +use std::collections::BTreeMap; +use std::fs::{self, File, OpenOptions}; +use std::io::{BufRead, BufReader, Write}; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; +use std::time::Instant; + +use anyhow::{Context, Result}; +use clap::{Parser, Subcommand}; + +mod benchmarks; + +/// The significance threshold for reporting benchmark differences (2%) +const SIGNIFICANCE_THRESHOLD: f64 = 0.02; + +#[derive(Parser)] +#[command(name = "aws-lc-rs-bench")] +#[command(about = "CI benchmarking tool for aws-lc-rs")] +#[command(version)] +pub struct Cli { + #[command(subcommand)] + pub command: Commands, +} + +#[derive(Subcommand)] +pub enum Commands { + /// Run all benchmarks and output results to a directory + RunAll { + /// Output directory for benchmark results + #[arg(short, long, default_value = "target/aws-lc-rs-bench")] + output_dir: PathBuf, + }, + + /// Run a single benchmark by name and report its instruction count + RunSingle { + /// Name of the benchmark to run + name: String, + /// Output directory for benchmark results + #[arg(short, long, default_value = "target/aws-lc-rs-bench")] + output_dir: PathBuf, + }, + + /// Execute a benchmark function (used internally under valgrind) + #[command(hide = true)] + Execute { + /// Name of the benchmark to execute + name: String, + }, + + /// List all available benchmarks + List, + + /// Compare results from two benchmark runs + Compare { + /// Path to baseline results directory + baseline_dir: PathBuf, + /// Path to candidate results directory + candidate_dir: PathBuf, + /// Output format (markdown or json) + #[arg(short, long, default_value = "markdown")] + format: OutputFormat, + }, + + /// Run benchmarks in wall-time mode (for local testing) + Walltime { + /// Number of iterations per benchmark + #[arg(short, long, default_value = "10")] + iterations: usize, + }, +} + +#[derive(Clone, Debug, Default, clap::ValueEnum)] +pub enum OutputFormat { + #[default] + Markdown, + Json, +} + +/// Result of a single benchmark +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct BenchmarkResult { + pub name: String, + pub instructions: u64, +} + +/// Comparison between baseline and candidate results +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct BenchmarkComparison { + pub name: String, + pub baseline: u64, + pub candidate: u64, + pub diff: i64, + pub diff_percent: f64, + pub significant: bool, +} + +fn main() -> Result<()> { + let cli = Cli::parse(); + + match cli.command { + Commands::RunAll { output_dir } => run_all_benchmarks(&output_dir), + Commands::RunSingle { name, output_dir } => run_single_benchmark(&name, &output_dir), + Commands::Execute { name } => execute_benchmark(&name), + Commands::List => list_benchmarks(), + Commands::Compare { + baseline_dir, + candidate_dir, + format, + } => compare_results(&baseline_dir, &candidate_dir, format), + Commands::Walltime { iterations } => run_walltime_benchmarks(iterations), + } +} + +/// Verify that the current platform supports instruction counting via Valgrind. +fn ensure_linux_with_valgrind() -> Result<()> { + if !cfg!(target_os = "linux") { + anyhow::bail!( + "Instruction counting requires Linux with Valgrind installed.\n\ + The run-all and run-single commands use Valgrind's callgrind tool,\n\ + which is only available on Linux.\n\ + \n\ + For local testing on other platforms, use the 'walltime' subcommand instead:\n\ + \n\ + cargo run --release -p aws-lc-rs-bench -- walltime" + ); + } + + // Check that valgrind is installed + if Command::new("valgrind").arg("--version").output().is_err() { + anyhow::bail!( + "Valgrind is not installed or not found in PATH.\n\ + Install it with: sudo apt-get install valgrind" + ); + } + + Ok(()) +} + +/// Run all benchmarks using callgrind for instruction counting +fn run_all_benchmarks(output_dir: &Path) -> Result<()> { + ensure_linux_with_valgrind()?; + fs::create_dir_all(output_dir).context("Failed to create output directory")?; + + let benchmarks = benchmarks::all_benchmarks(); + let executable = std::env::current_exe().context("Failed to get current executable path")?; + + let mut results: BTreeMap = BTreeMap::new(); + let callgrind_dir = output_dir.join("callgrind"); + fs::create_dir_all(&callgrind_dir).context("Failed to create callgrind output directory")?; + + println!("Running {} benchmarks...", benchmarks.len()); + + for bench in &benchmarks { + print!(" {} ... ", bench.name); + std::io::stdout().flush()?; + + match run_benchmark_under_callgrind(&bench.name, &callgrind_dir, &executable) { + Ok(instr_count) => { + results.insert(bench.name.clone(), instr_count); + println!("{} instructions", format_number(instr_count)); + } + Err(e) => { + println!("FAILED"); + eprintln!("Benchmark {} failed: {}", bench.name, e); + } + } + } + + // Write results to CSV + let csv_path = output_dir.join("results.csv"); + let mut csv_file = File::create(&csv_path).context("Failed to create results CSV")?; + for (name, count) in &results { + writeln!(csv_file, "{},{}", name, count)?; + } + + println!("\nResults written to {}", csv_path.display()); + Ok(()) +} + +/// Run a single benchmark under callgrind and report the result. +/// +/// Results are appended to the CSV file in the output directory. If the same benchmark +/// is run multiple times, duplicate entries will appear in the file; the `compare` command +/// uses the last entry for each benchmark name. +fn run_single_benchmark(name: &str, output_dir: &Path) -> Result<()> { + ensure_linux_with_valgrind()?; + let benchmarks = benchmarks::all_benchmarks(); + let bench = benchmarks + .iter() + .find(|b| b.name == name) + .ok_or_else(|| anyhow::anyhow!("Benchmark not found: {}", name))?; + + let executable = std::env::current_exe().context("Failed to get current executable path")?; + let callgrind_dir = output_dir.join("callgrind"); + fs::create_dir_all(&callgrind_dir).context("Failed to create callgrind output directory")?; + + println!("Running benchmark: {} ({})", bench.name, bench.description); + + let instr_count = run_benchmark_under_callgrind(&bench.name, &callgrind_dir, &executable)?; + println!( + "{}: {} instructions", + bench.name, + format_number(instr_count) + ); + + // Append result to CSV (preserves results from previous runs in the same output directory) + let csv_path = output_dir.join("results.csv"); + let mut csv_file = OpenOptions::new() + .create(true) + .append(true) + .open(&csv_path) + .context("Failed to open results CSV")?; + writeln!(csv_file, "{},{}", bench.name, instr_count)?; + println!("Results written to {}", csv_path.display()); + + Ok(()) +} + +/// Execute a benchmark function (called internally under valgrind) +fn execute_benchmark(name: &str) -> Result<()> { + let mut benchmarks = benchmarks::all_benchmarks(); + let bench = benchmarks + .iter_mut() + .find(|b| b.name == name) + .ok_or_else(|| anyhow::anyhow!("Benchmark not found: {}", name))?; + + eprintln!( + "Executing benchmark: {} ({})", + bench.name, bench.description + ); + + // Start instruction counting + #[cfg(target_os = "linux")] + crabgrind::callgrind::start_instrumentation(); + + // Run the benchmark + (bench.func)(); + + // Stop instruction counting + #[cfg(target_os = "linux")] + crabgrind::callgrind::stop_instrumentation(); + + eprintln!("Benchmark complete: {}", bench.name); + + Ok(()) +} + +/// Run a single benchmark under valgrind/callgrind and return the instruction count +fn run_benchmark_under_callgrind( + name: &str, + callgrind_dir: &Path, + executable: &Path, +) -> Result { + let callgrind_out = callgrind_dir.join(format!("{}.callgrind", name)); + let log_file = callgrind_dir.join(format!("{}.log", name)); + + let callgrind_out_flag = format!("--callgrind-out-file={}", callgrind_out.to_str().unwrap()); + let output = Command::new("valgrind") + .args([ + "--tool=callgrind", + callgrind_out_flag.as_str(), + "--instr-atstart=no", + "--cache-sim=no", + "--branch-sim=no", + ]) + .arg(executable) + .arg("execute") + .arg(name) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .context("Failed to execute valgrind. Ensure Valgrind is installed and in PATH.")?; + + // Save the log file + fs::write(&log_file, &output.stderr).context("Failed to write log file")?; + + if !output.status.success() { + anyhow::bail!("{}", String::from_utf8_lossy(&output.stderr)); + } + + parse_callgrind_output(&callgrind_out) +} + +/// List all available benchmarks +fn list_benchmarks() -> Result<()> { + let benchmarks = benchmarks::all_benchmarks(); + println!("Available benchmarks ({}):", benchmarks.len()); + for bench in benchmarks { + println!(" {}: {}", bench.name, bench.description); + } + Ok(()) +} + +/// Compare results from two benchmark runs +fn compare_results(baseline_dir: &Path, candidate_dir: &Path, format: OutputFormat) -> Result<()> { + let baseline = read_results(&baseline_dir.join("results.csv"))?; + let candidate = read_results(&candidate_dir.join("results.csv"))?; + + let mut comparisons: Vec = Vec::new(); + + for (name, &candidate_count) in &candidate { + if let Some(&baseline_count) = baseline.get(name) { + let diff = candidate_count as i64 - baseline_count as i64; + let diff_percent = if baseline_count > 0 { + (diff as f64 / baseline_count as f64) * 100.0 + } else { + 0.0 + }; + let significant = diff_percent.abs() > SIGNIFICANCE_THRESHOLD * 100.0; + + comparisons.push(BenchmarkComparison { + name: name.clone(), + baseline: baseline_count, + candidate: candidate_count, + diff, + diff_percent, + significant, + }); + } + } + + // Sort by absolute difference percentage (largest first) + comparisons.sort_by(|a, b| { + b.diff_percent + .abs() + .partial_cmp(&a.diff_percent.abs()) + .unwrap() + }); + + match format { + OutputFormat::Markdown => print_markdown_report(&comparisons, &baseline, &candidate), + OutputFormat::Json => print_json_report(&comparisons)?, + } + + Ok(()) +} + +/// Run benchmarks in wall-time mode (for local testing) +fn run_walltime_benchmarks(iterations: usize) -> Result<()> { + let mut benchmarks = benchmarks::all_benchmarks(); + + println!( + "Running {} benchmarks with {} iterations each...\n", + benchmarks.len(), + iterations + ); + + for bench in &mut benchmarks { + let mut times: Vec = Vec::with_capacity(iterations); + + // Warm-up run + (bench.func)(); + + for _ in 0..iterations { + let start = Instant::now(); + (bench.func)(); + times.push(start.elapsed().as_nanos()); + } + + let min = times.iter().min().unwrap(); + let max = times.iter().max().unwrap(); + let avg: u128 = times.iter().sum::() / iterations as u128; + + println!( + "{}: min={} avg={} max={} ns", + bench.name, + format_number(*min as u64), + format_number(avg as u64), + format_number(*max as u64) + ); + } + + Ok(()) +} + +/// Parse instruction count from callgrind output file +fn parse_callgrind_output(path: &Path) -> Result { + let file = File::open(path).context("Failed to open callgrind output")?; + let reader = BufReader::new(file); + + for line in reader.lines() { + let line = line?; + if line.starts_with("totals:") { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 2 { + return parts[1] + .parse() + .context("Failed to parse instruction count"); + } + } + } + + anyhow::bail!("Could not find instruction count in callgrind output") +} + +/// Read benchmark results from CSV file +fn read_results(path: &Path) -> Result> { + let file = + File::open(path).context(format!("Failed to open results file: {}", path.display()))?; + let reader = BufReader::new(file); + let mut results = BTreeMap::new(); + + for line in reader.lines() { + let line = line?; + let parts: Vec<&str> = line.split(',').collect(); + if parts.len() >= 2 { + let name = parts[0].to_string(); + let count: u64 = parts[1] + .parse() + .context("Failed to parse instruction count")?; + results.insert(name, count); + } + } + + Ok(results) +} + +/// Print comparison report in Markdown format +fn print_markdown_report( + comparisons: &[BenchmarkComparison], + baseline: &BTreeMap, + candidate: &BTreeMap, +) { + println!("# Benchmark Results\n"); + + // Summary + let significant_regressions: Vec<_> = comparisons + .iter() + .filter(|c| c.significant && c.diff > 0) + .collect(); + let significant_improvements: Vec<_> = comparisons + .iter() + .filter(|c| c.significant && c.diff < 0) + .collect(); + let unchanged: Vec<_> = comparisons.iter().filter(|c| !c.significant).collect(); + + println!("## Summary\n"); + println!( + "- {} benchmarks with no significant change (< {}%)", + unchanged.len(), + SIGNIFICANCE_THRESHOLD * 100.0 + ); + if !significant_improvements.is_empty() { + println!( + "- ✅ {} benchmarks improved", + significant_improvements.len() + ); + } + if !significant_regressions.is_empty() { + println!( + "- ⚠️ {} benchmarks regressed", + significant_regressions.len() + ); + } + + // Check for missing benchmarks + let missing_in_candidate: Vec<_> = baseline + .keys() + .filter(|k| !candidate.contains_key(*k)) + .collect(); + let new_in_candidate: Vec<_> = candidate + .keys() + .filter(|k| !baseline.contains_key(*k)) + .collect(); + + if !missing_in_candidate.is_empty() { + println!("\n### ⚠️ Benchmarks removed\n"); + for name in &missing_in_candidate { + println!("- {}", name); + } + } + + if !new_in_candidate.is_empty() { + println!("\n### ℹ️ New benchmarks\n"); + for name in &new_in_candidate { + println!("- {}", name); + } + } + + // Significant changes table + let significant: Vec<_> = comparisons.iter().filter(|c| c.significant).collect(); + if !significant.is_empty() { + println!("\n## Significant Changes\n"); + println!("| Benchmark | Baseline | Candidate | Diff |"); + println!("| --------- | -------: | --------: | ---: |"); + for comp in &significant { + let emoji = if comp.diff > 0 { "⚠️" } else { "✅" }; + println!( + "| {} | {} | {} | {} {:+.2}% |", + comp.name, + format_number(comp.baseline), + format_number(comp.candidate), + emoji, + comp.diff_percent + ); + } + } + + // Full results (collapsed) + println!("\n
"); + println!( + "Full Results ({} benchmarks)\n", + comparisons.len() + ); + println!("| Benchmark | Baseline | Candidate | Diff |"); + println!("| --------- | -------: | --------: | ---: |"); + for comp in comparisons { + println!( + "| {} | {} | {} | {:+.2}% |", + comp.name, + format_number(comp.baseline), + format_number(comp.candidate), + comp.diff_percent + ); + } + println!("\n
"); +} + +/// Print comparison report in JSON format +fn print_json_report(comparisons: &[BenchmarkComparison]) -> Result<()> { + let json = serde_json::to_string_pretty(comparisons)?; + println!("{}", json); + Ok(()) +} + +/// Format a number with thousand separators +fn format_number(n: u64) -> String { + let s = n.to_string(); + let mut result = String::new(); + for (i, c) in s.chars().rev().enumerate() { + if i > 0 && i % 3 == 0 { + result.push(','); + } + result.push(c); + } + result.chars().rev().collect() +} diff --git a/aws-lc-rs-bench/src/test_data/ecdsa_p256_pkcs8.der b/aws-lc-rs-bench/src/test_data/ecdsa_p256_pkcs8.der new file mode 100644 index 000000000000..bc118842bb1a Binary files /dev/null and b/aws-lc-rs-bench/src/test_data/ecdsa_p256_pkcs8.der differ diff --git a/aws-lc-rs-bench/src/test_data/ed25519_pkcs8_v1.der b/aws-lc-rs-bench/src/test_data/ed25519_pkcs8_v1.der new file mode 100644 index 000000000000..744c4b9f2fa2 Binary files /dev/null and b/aws-lc-rs-bench/src/test_data/ed25519_pkcs8_v1.der differ diff --git a/aws-lc-rs-bench/src/test_data/rsa_2048_pkcs8.der b/aws-lc-rs-bench/src/test_data/rsa_2048_pkcs8.der new file mode 100644 index 000000000000..26c480b150b7 Binary files /dev/null and b/aws-lc-rs-bench/src/test_data/rsa_2048_pkcs8.der differ diff --git a/aws-lc-rs-testing/Cargo.toml b/aws-lc-rs-testing/Cargo.toml index 746e269738d1..d2f47276335d 100644 --- a/aws-lc-rs-testing/Cargo.toml +++ b/aws-lc-rs-testing/Cargo.toml @@ -7,20 +7,22 @@ rust-version = "1.86.0" publish = false [features] -ring-benchmarks = [] -openssl-benchmarks = [] +ring-benchmarks = ["ring"] +openssl-benchmarks = ["openssl"] ring-sig-verify = ["aws-lc-rs/ring-sig-verify"] bindgen = ["aws-lc-rs/bindgen"] fips = ["aws-lc-rs/fips"] asan = ["aws-lc-rs/asan"] +[dependencies] +ring = { workspace = true, optional = true } +openssl = { workspace = true, optional = true, features = ["vendored"] } + [dev-dependencies] aws-lc-rs = { version = "1.0", path = "../aws-lc-rs", features = ["ring-sig-verify", "unstable"] } untrusted.workspace = true paste.workspace = true criterion = { workspace = true, features = ["csv_output"] } -ring.workspace = true -openssl = { workspace = true, features = ["vendored"] } [[bench]] name = "aead_benchmark" diff --git a/aws-lc-rs-testing/benches/agreement_benchmark.rs b/aws-lc-rs-testing/benches/agreement_benchmark.rs index 048668235d35..1506e8965767 100644 --- a/aws-lc-rs-testing/benches/agreement_benchmark.rs +++ b/aws-lc-rs-testing/benches/agreement_benchmark.rs @@ -45,38 +45,31 @@ macro_rules! benchmark_agreement { use $pkg::{agreement, test}; - use crate::{AgreementConfig, Curve}; - use agreement::{ - agree_ephemeral, Algorithm, EphemeralPrivateKey, UnparsedPublicKey, ECDH_P256, ECDH_P384, - X25519, - }; - - fn algorithm(config: &AgreementConfig) -> &'static Algorithm { - match config.curve { - Curve::X25519 => &X25519, - Curve::P256 => &ECDH_P256, - Curve::P384 => &ECDH_P384, + use crate::{AgreementConfig, Curve}; + use agreement::{ + Algorithm, EphemeralPrivateKey, UnparsedPublicKey, ECDH_P256, ECDH_P384, + X25519, + }; + + fn algorithm(config: &AgreementConfig) -> &'static Algorithm { + match config.curve { + Curve::X25519 => &X25519, + Curve::P256 => &ECDH_P256, + Curve::P384 => &ECDH_P384, + } + } + + pub fn private_key(config: &AgreementConfig) -> EphemeralPrivateKey { + let rng = test::rand::FixedSliceRandom { + bytes: &config.private, + }; + EphemeralPrivateKey::generate(algorithm(config), &rng).unwrap() + } + + pub fn peer_public_key(config: &AgreementConfig) -> UnparsedPublicKey> { + UnparsedPublicKey::new(algorithm(config), config.peer_pub.clone()) + } } - } - - pub fn private_key(config: &AgreementConfig) -> EphemeralPrivateKey { - let rng = test::rand::FixedSliceRandom { - bytes: &config.private, - }; - EphemeralPrivateKey::generate(algorithm(config), &rng).unwrap() - } - - pub fn peer_public_key(config: &AgreementConfig) -> UnparsedPublicKey> { - UnparsedPublicKey::new(algorithm(config), config.peer_pub.clone()) - } - - pub fn agreement( - private_key: EphemeralPrivateKey, - peer_public_key: &UnparsedPublicKey>, - ) { - agree_ephemeral(private_key, peer_public_key, (), |val| Ok(Vec::from(val))).unwrap(); - } -} } }; } @@ -94,7 +87,10 @@ fn test_agree_ephemeral(c: &mut Criterion, config: &AgreementConfig) { group.bench_function("AWS-LC", |b| { b.iter(|| { let private_key = aws_lc_rs_benchmarks::private_key(config); - aws_lc_rs_benchmarks::agreement(private_key, &aws_peer_public_key); + aws_lc_rs::agreement::agree_ephemeral(private_key, &aws_peer_public_key, (), |val| { + Ok(Vec::from(val)) + }) + .unwrap(); }); }); #[cfg(feature = "ring-benchmarks")] @@ -103,7 +99,10 @@ fn test_agree_ephemeral(c: &mut Criterion, config: &AgreementConfig) { group.bench_function("Ring", |b| { b.iter(|| { let private_key = ring_benchmarks::private_key(config); - ring_benchmarks::agreement(private_key, &ring_peer_public_key); + ring::agreement::agree_ephemeral(private_key, &ring_peer_public_key, |val| { + Vec::from(val) + }) + .unwrap(); }); }); } diff --git a/aws-lc-rs-testing/benches/rsa_benchmark.rs b/aws-lc-rs-testing/benches/rsa_benchmark.rs index 235a3f4a4366..5afff62ee860 100644 --- a/aws-lc-rs-testing/benches/rsa_benchmark.rs +++ b/aws-lc-rs-testing/benches/rsa_benchmark.rs @@ -76,7 +76,7 @@ macro_rules! benchmark_rsa { use $pkg::{signature, rand}; use super::{RsaDigest, RsaPadding}; - use signature::{RsaKeyPair, RsaParameters, VerificationAlgorithm}; + use signature::{RsaKeyPair, RsaParameters}; pub fn create_key_pair(key: &[u8]) -> RsaKeyPair { RsaKeyPair::from_der(key).expect("Unable to parse key") @@ -126,11 +126,9 @@ macro_rules! benchmark_rsa { .expect("signing failed"); } - pub fn verify(rsa_params: &RsaParameters, public_key: &[u8], msg: &[u8], signature: &[u8]) { - let public_key = untrusted::Input::from(public_key); - let msg = untrusted::Input::from(msg); - let signature = untrusted::Input::from(signature); - rsa_params.verify(public_key, msg, signature).unwrap() + pub fn verify(rsa_params: &'static RsaParameters, public_key: &[u8], msg: &[u8], sig: &[u8]) { + let public_key = signature::UnparsedPublicKey::new(rsa_params, public_key); + public_key.verify(msg, sig).unwrap() } } diff --git a/aws-lc-rs-testing/tests/aead_test.rs b/aws-lc-rs-testing/tests/aead_test.rs index 12921a43052f..68fdcfb64bb6 100644 --- a/aws-lc-rs-testing/tests/aead_test.rs +++ b/aws-lc-rs-testing/tests/aead_test.rs @@ -1,5 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC +#![cfg(feature = "ring")] use aws_lc_rs::aead; diff --git a/aws-lc-rs-testing/tests/agreement_tests.rs b/aws-lc-rs-testing/tests/agreement_tests.rs index 3aefd65a5397..2903b0fb57b7 100644 --- a/aws-lc-rs-testing/tests/agreement_tests.rs +++ b/aws-lc-rs-testing/tests/agreement_tests.rs @@ -3,6 +3,8 @@ // Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC +#![cfg(feature = "ring")] + extern crate alloc; use aws_lc_rs::{agreement, rand}; diff --git a/aws-lc-rs-testing/tests/hkdf_test.rs b/aws-lc-rs-testing/tests/hkdf_test.rs index 7beaddd88674..b066690342a0 100644 --- a/aws-lc-rs-testing/tests/hkdf_test.rs +++ b/aws-lc-rs-testing/tests/hkdf_test.rs @@ -1,6 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC +#![cfg(feature = "ring")] + use aws_lc_rs::hkdf::{Prk, Salt, HKDF_SHA256}; #[test] diff --git a/aws-lc-rs-testing/tests/quic_test.rs b/aws-lc-rs-testing/tests/quic_test.rs index 93a56c576836..15cf052df87e 100644 --- a/aws-lc-rs-testing/tests/quic_test.rs +++ b/aws-lc-rs-testing/tests/quic_test.rs @@ -1,6 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC +#![cfg(feature = "ring")] + use aws_lc_rs::aead::quic::{HeaderProtectionKey, AES_128, AES_256, CHACHA20}; use aws_lc_rs::{hkdf, test}; diff --git a/aws-lc-rs-testing/tests/rsa_test.rs b/aws-lc-rs-testing/tests/rsa_test.rs index 1d009b9d23e7..9d9e8bec4467 100644 --- a/aws-lc-rs-testing/tests/rsa_test.rs +++ b/aws-lc-rs-testing/tests/rsa_test.rs @@ -1,6 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC +#![cfg(feature = "openssl")] + use aws_lc_rs::rsa::{ Pkcs1PrivateDecryptingKey, Pkcs1PublicEncryptingKey, PrivateDecryptingKey, PublicEncryptingKey, }; diff --git a/aws-lc-rs/src/cipher.rs b/aws-lc-rs/src/cipher.rs index 017637212b38..03f8808fc968 100644 --- a/aws-lc-rs/src/cipher.rs +++ b/aws-lc-rs/src/cipher.rs @@ -783,10 +783,9 @@ fn encrypt( let block_len = algorithm.block_len(); match mode { - OperatingMode::CBC | OperatingMode::ECB - if in_out.len() % block_len != 0 => { - return Err(Unspecified); - } + OperatingMode::CBC | OperatingMode::ECB if in_out.len() % block_len != 0 => { + return Err(Unspecified); + } _ => {} } @@ -825,10 +824,9 @@ fn decrypt<'in_out>( let block_len = algorithm.block_len(); match mode { - OperatingMode::CBC | OperatingMode::ECB - if in_out.len() % block_len != 0 => { - return Err(Unspecified); - } + OperatingMode::CBC | OperatingMode::ECB if in_out.len() % block_len != 0 => { + return Err(Unspecified); + } _ => {} } diff --git a/aws-lc-sys/builder/main.rs b/aws-lc-sys/builder/main.rs index 76dd44a4dd01..0bb5ddb21a77 100644 --- a/aws-lc-sys/builder/main.rs +++ b/aws-lc-sys/builder/main.rs @@ -961,6 +961,7 @@ const PRELUDE: &str = r" clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::default_trait_access, + clippy::doc_markdown, clippy::missing_safety_doc, clippy::must_use_candidate, clippy::not_unsafe_ptr_arg_deref, diff --git a/aws-lc-sys/src/lib.rs b/aws-lc-sys/src/lib.rs index fe58c7e98c96..0312d98fdb18 100644 --- a/aws-lc-sys/src/lib.rs +++ b/aws-lc-sys/src/lib.rs @@ -50,6 +50,7 @@ platform_binding!(x86_64_unknown_linux_musl, x86_64_unknown_linux_musl_crypto); clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::default_trait_access, + clippy::doc_markdown, clippy::missing_safety_doc, clippy::must_use_candidate, clippy::not_unsafe_ptr_arg_deref, diff --git a/docker/linux-cross/Dockerfile b/docker/linux-cross/Dockerfile index fcab98e42040..89b4e43c249b 100644 --- a/docker/linux-cross/Dockerfile +++ b/docker/linux-cross/Dockerfile @@ -2,20 +2,21 @@ ARG CROSS_BASE_IMAGE=ubuntu:20.04 FROM $CROSS_BASE_IMAGE ARG DEBIAN_FRONTEND=noninteractive +ARG GO_VERSION=1.23.8 RUN apt-get update && \ - apt-get install --assume-yes --no-install-recommends gpg-agent software-properties-common dirmngr && \ - gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys F6BC817356A3D45E C631127F87FA12D1 && \ - add-apt-repository --yes ppa:longsleep/golang-backports && \ - apt-get update && \ - apt-get install --assume-yes --no-install-recommends build-essential cmake golang-go clang && \ + apt-get install --assume-yes --no-install-recommends build-essential cmake clang ca-certificates curl && \ + ARCH=$(dpkg --print-architecture) && \ + curl -sL "https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz" | tar -C /usr/local -xz && \ git config --global --add safe.directory '*' && \ rm -rf /tmp/* +ENV PATH="/usr/local/go/bin:${PATH}" + RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable && \ . $HOME/.cargo/env && \ cargo install --force --locked bindgen-cli && \ mv $HOME/.cargo/bin/bindgen /usr/bin && \ rm -rf $HOME/.cargo -ENV GOCACHE=/tmp +ENV GOCACHE=/tmp \ No newline at end of file