Compare commits
251 Commits
test/signa
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c157897fe | ||
|
|
e71b24328f | ||
|
|
1f8a1041df | ||
|
|
fa3a4ca915 | ||
|
|
434585d90d | ||
|
|
ee95e2060a | ||
|
|
f19374dfad | ||
|
|
5bd645077b | ||
|
|
c740bd7715 | ||
|
|
144b67bb10 | ||
|
|
484afea507 | ||
|
|
a40cba6cd6 | ||
|
|
9cc97c87c7 | ||
|
|
d0973d7617 | ||
|
|
e65f394509 | ||
|
|
fc02d4a47c | ||
|
|
532003e437 | ||
|
|
c3f96c9cb0 | ||
|
|
d47684d087 | ||
|
|
ae4a737426 | ||
|
|
8ba06e1974 | ||
|
|
559fc27ba1 | ||
|
|
7888b0fb91 | ||
|
|
d60c579bfd | ||
|
|
06ca1fd746 | ||
|
|
5f4cf54cc5 | ||
|
|
7cb4c89550 | ||
|
|
7298c9e0f0 | ||
|
|
bdc5e1864c | ||
|
|
39e394f37f | ||
|
|
8d35c787e7 | ||
|
|
9b098e6bb7 | ||
|
|
1722cc7317 | ||
|
|
241d05aea8 | ||
|
|
36d18367f3 | ||
|
|
c35cb1bb9f | ||
|
|
a721d89d08 | ||
|
|
531ac086f4 | ||
|
|
88961db064 | ||
|
|
884819622a | ||
|
|
f4dba99cd3 | ||
|
|
13b2db754d | ||
|
|
48e27ae5a3 | ||
|
|
f76cdc7502 | ||
|
|
cc1881c71e | ||
|
|
6124273c4d | ||
|
|
395f57810b | ||
|
|
64b3e14976 | ||
|
|
6413deb356 | ||
|
|
3c33edfd43 | ||
|
|
c13c69be3d | ||
|
|
f10b98672a | ||
|
|
f4a7f8d345 | ||
|
|
3751abeeca | ||
|
|
33b36ab7a1 | ||
|
|
99dbbb3437 | ||
|
|
077f134c75 | ||
|
|
80c3a8911e | ||
|
|
f10414d927 | ||
|
|
98215dbdb0 | ||
|
|
05e3aafd0b | ||
|
|
35774a0116 | ||
|
|
16290eb11b | ||
|
|
97aa4a987e | ||
|
|
ed768854a4 | ||
|
|
b65a064e76 | ||
|
|
4ca7589c2f | ||
|
|
d322d3a380 | ||
|
|
96b7d5ff21 | ||
|
|
13ef735d35 | ||
|
|
59483981d2 | ||
|
|
4deec23722 | ||
|
|
1ba39985e1 | ||
|
|
968999cf46 | ||
|
|
b32059ee88 | ||
|
|
a2c57ecc0f | ||
|
|
c2f063cf47 | ||
|
|
93d9018774 | ||
|
|
11fec56d55 | ||
|
|
acd8cbaf02 | ||
|
|
3ac364abc4 | ||
|
|
c299b1476b | ||
|
|
e3483004e5 | ||
|
|
c5ed1d1319 | ||
|
|
3cf9b4f943 | ||
|
|
7cb075cc6f | ||
|
|
a88541930f | ||
|
|
9bb20132ed | ||
|
|
41b4d6b77e | ||
|
|
fc4ccbee1d | ||
|
|
04114a8868 | ||
|
|
2d2052ee32 | ||
|
|
e98f4289e2 | ||
|
|
2f53153128 | ||
|
|
9fcdba89f7 | ||
|
|
1999540f7d | ||
|
|
5a3d1ae4b2 | ||
|
|
8dfa471c86 | ||
|
|
a54b351cc0 | ||
|
|
794d4d5e2e | ||
|
|
2c7c9b7672 | ||
|
|
36a2a1ca44 | ||
|
|
456836aea1 | ||
|
|
76f47a794c | ||
|
|
feb62b627b | ||
|
|
7efa9e1dd0 | ||
|
|
ff3d69c075 | ||
|
|
f2f4871a39 | ||
|
|
237c82d394 | ||
|
|
327162be0d | ||
|
|
7c8dd2678a | ||
|
|
7a0021e169 | ||
|
|
230f167b80 | ||
|
|
0c4062ed54 | ||
|
|
a095d95238 | ||
|
|
47c33f6428 | ||
|
|
410a96752b | ||
|
|
e23d2d16d4 | ||
|
|
5cd912df1d | ||
|
|
77f612c16c | ||
|
|
75ef523230 | ||
|
|
5957ce94cc | ||
|
|
e3998212ed | ||
|
|
353ea62c17 | ||
|
|
8773f0e1fa | ||
|
|
ab8513ef8f | ||
|
|
ac1d71cb54 | ||
|
|
5cb35db989 | ||
|
|
b9af0ef176 | ||
|
|
ba85fbb7ad | ||
|
|
f526b57daa | ||
|
|
ae783f8273 | ||
|
|
ea1d120912 | ||
|
|
c49282f112 | ||
|
|
b3521e5523 | ||
|
|
4ce1308e1c | ||
|
|
1c51c7ee3b | ||
|
|
7078f61077 | ||
|
|
b46d77087e | ||
|
|
21735accf8 | ||
|
|
72dabe1d85 | ||
|
|
646ae33c61 | ||
|
|
8abba360d3 | ||
|
|
0fc992bd76 | ||
|
|
4cb7e260a8 | ||
|
|
78b8ceaf10 | ||
|
|
974c3d2db0 | ||
|
|
b4bf601394 | ||
|
|
c3f33f0ea1 | ||
|
|
3116032a83 | ||
|
|
9bad96e48b | ||
|
|
fa9df8081d | ||
|
|
4814eb8547 | ||
|
|
a50a39fde7 | ||
|
|
21f2885be3 | ||
|
|
79338a99ea | ||
|
|
330bf825d4 | ||
|
|
963425eb82 | ||
|
|
50fa2e672f | ||
|
|
a91bfdc67d | ||
|
|
8d77a5d40e | ||
|
|
c5045fb6b4 | ||
|
|
8966ca27b7 | ||
|
|
3de1385660 | ||
|
|
81d4aa19ac | ||
|
|
404a753921 | ||
|
|
a264df22fa | ||
|
|
26ac58b2bd | ||
|
|
0ca11b5680 | ||
|
|
8d5fba3767 | ||
|
|
b01510d050 | ||
|
|
c596d7d47c | ||
|
|
17d137e33b | ||
|
|
5fa9c81c88 | ||
|
|
5d57b3a057 | ||
|
|
4d178a7f9f | ||
|
|
bcec9462af | ||
|
|
29c05d41cd | ||
|
|
05f798adc4 | ||
|
|
56e9fef055 | ||
|
|
45f8589d48 | ||
|
|
15975ddde4 | ||
|
|
6789a72fc0 | ||
|
|
7a52fbbe99 | ||
|
|
2bc82e8d1c | ||
|
|
e99d162891 | ||
|
|
5e8aaf63f0 | ||
|
|
560925293b | ||
|
|
4ea82a2e1b | ||
|
|
eefc7b7265 | ||
|
|
6e35abb2cd | ||
|
|
15281c77e2 | ||
|
|
eb48ab9a26 | ||
|
|
3ab8b53532 | ||
|
|
0327dd03c6 | ||
|
|
23863ffd1b | ||
|
|
9b34d3524b | ||
|
|
9c4ea22f72 | ||
|
|
b29537e08f | ||
|
|
220bedf239 | ||
|
|
49a8d0906a | ||
|
|
20ad2665b2 | ||
|
|
721b6fca2e | ||
|
|
d5bd85b3e5 | ||
|
|
11630058f0 | ||
|
|
d8975dc413 | ||
|
|
867f2b3b99 | ||
|
|
57307d739e | ||
|
|
c774afc859 | ||
|
|
bb42da53b3 | ||
|
|
dde4b9ccde | ||
|
|
221efdfea9 | ||
|
|
ae1851ba03 | ||
|
|
615af5aafb | ||
|
|
1af143f509 | ||
|
|
f7b6f5dfa1 | ||
|
|
3d4180b232 | ||
|
|
889ad0f41e | ||
|
|
59883d7e23 | ||
|
|
fbea17998a | ||
|
|
6c4a02f131 | ||
|
|
b9a75167dc | ||
|
|
7c88181568 | ||
|
|
e17586365c | ||
|
|
e8e6122545 | ||
|
|
2f63b8affd | ||
|
|
7f4dca3405 | ||
|
|
8245063ae6 | ||
|
|
8d4822be72 | ||
|
|
02e4a3f9aa | ||
|
|
3c50b28a61 | ||
|
|
3809a7e1ca | ||
|
|
9534656269 | ||
|
|
bdf9d2ac06 | ||
|
|
25e1dd8fa5 | ||
|
|
73ee9d7085 | ||
|
|
81eeaab77a | ||
|
|
b95cb545b9 | ||
|
|
e1c719c096 | ||
|
|
8a2d686286 | ||
|
|
8e6f19aa9a | ||
|
|
879ffef1b5 | ||
|
|
a04d27b0c4 | ||
|
|
7451d82bb2 | ||
|
|
b73043e51b | ||
|
|
7afa7798cf | ||
|
|
29b7ed4cbd | ||
|
|
1e2df380d9 | ||
|
|
2dbdb97351 | ||
|
|
ea59de3542 | ||
|
|
ceb140105b |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.toml text eol=lf
|
||||
255
.github/workflows/ci.yml
vendored
255
.github/workflows/ci.yml
vendored
@ -3,14 +3,16 @@ name: CI
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_BACKTRACE: 1
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_PROFILE_DEV_DEBUG: 0
|
||||
|
||||
jobs:
|
||||
rustfmt:
|
||||
@ -18,46 +20,59 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust
|
||||
run: rustup update stable && rustup default stable
|
||||
- name: Install Rustfmt
|
||||
run: rustup default stable && rustup component add rustfmt
|
||||
- name: Check formatting
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
clippy:
|
||||
name: clippy
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CARGO_HOME: ${{ github.workspace }}/.cache/cargo
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- name: Install Rust
|
||||
run: rustup update stable && rustup default stable
|
||||
run: rustup toolchain add stable --no-self-update --component clippy && rustup default stable
|
||||
- name: Get rust version
|
||||
id: rust-version
|
||||
run: echo "::set-output name=version::$(rustc --version)"
|
||||
shell: bash
|
||||
run: |
|
||||
echo "version=$(rustc --version)" >> $GITHUB_OUTPUT
|
||||
- name: Cache cargo index
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cargo/registry/index
|
||||
key: index-${{ runner.os }}-${{ github.run_number }}
|
||||
restore-keys: |
|
||||
index-${{ runner.os }}-
|
||||
- name: Create lockfile
|
||||
run: cargo generate-lockfile
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cargo/registry/cache
|
||||
key: registry-${{ runner.os }}-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }}
|
||||
path: |
|
||||
.cache/cargo/registry/index
|
||||
.cache/cargo/registry/cache
|
||||
key: index-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.toml') }}
|
||||
enableCrossOsArchive: true
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch
|
||||
- name: Cache target directory
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: target
|
||||
key: clippy-target-${{ runner.os }}-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }}
|
||||
key: clippy-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }}
|
||||
- name: Run clippy
|
||||
run: cargo clippy --all --all-targets
|
||||
run: cargo clippy --all --all-targets --features rpk,mlkem
|
||||
- name: Check docs
|
||||
run: cargo doc --no-deps -p boring -p boring-sys -p hyper-boring -p tokio-boring --features rpk,underscore-wildcards
|
||||
env:
|
||||
CARGO_BUILD_RUSTDOCFLAGS: "--cfg=docsrs"
|
||||
RUSTC_BOOTSTRAP: 1
|
||||
DOCS_RS: 1
|
||||
- name: Cargo.toml boring versions consistency
|
||||
shell: bash
|
||||
run: |
|
||||
WORKSPACE_VERSION=$(grep -F '[workspace.package]' -A1 Cargo.toml | grep -F version | grep -Eo '".*"')
|
||||
if [[ -z "$WORKSPACE_VERSION" ]]; then echo 2>&1 "error: can't find boring version"; exit 1; fi
|
||||
if grep -E 'boring.* =' Cargo.toml | grep -vF "$WORKSPACE_VERSION"; then
|
||||
echo 2>&1 "error: boring dependencies must match workspace version $WORKSPACE_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ${{ matrix.os }}
|
||||
@ -65,6 +80,7 @@ jobs:
|
||||
matrix:
|
||||
thing:
|
||||
- stable
|
||||
- i686-mingw
|
||||
- arm-android
|
||||
- arm64-android
|
||||
- i686-android
|
||||
@ -104,6 +120,8 @@ jobs:
|
||||
rust: stable
|
||||
os: ubuntu-latest
|
||||
check_only: true
|
||||
custom_env:
|
||||
CXXFLAGS: -msse2
|
||||
- thing: x86_64-android
|
||||
target: x86_64-linux-android
|
||||
rust: stable
|
||||
@ -134,6 +152,8 @@ jobs:
|
||||
rust: stable
|
||||
os: ubuntu-latest
|
||||
apt_packages: gcc-multilib g++-multilib
|
||||
custom_env:
|
||||
CXXFLAGS: -msse2
|
||||
- thing: arm-linux
|
||||
target: arm-unknown-linux-gnueabi
|
||||
rust: stable
|
||||
@ -174,29 +194,73 @@ jobs:
|
||||
C_INCLUDE_PATH: "C:\\msys64\\usr\\include"
|
||||
CPLUS_INCLUDE_PATH: "C:\\msys64\\usr\\include"
|
||||
LIBRARY_PATH: "C:\\msys64\\usr\\lib"
|
||||
RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness
|
||||
# CI's Windows doesn't have required root certs
|
||||
extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring
|
||||
extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness
|
||||
- thing: i686-mingw
|
||||
target: i686-pc-windows-gnu
|
||||
rust: stable
|
||||
os: windows-latest
|
||||
check_only: true
|
||||
custom_env:
|
||||
RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness
|
||||
CMAKE_GENERATOR: "MinGW Makefiles"
|
||||
COLLECT_GCC: null
|
||||
# CI's Windows doesn't have required root certs
|
||||
extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness
|
||||
- thing: i686-msvc
|
||||
target: i686-pc-windows-msvc
|
||||
rust: stable-x86_64-msvc
|
||||
os: windows-latest
|
||||
custom_env:
|
||||
RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness
|
||||
CXXFLAGS: -msse2
|
||||
# CI's Windows doesn't have required root certs
|
||||
extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring
|
||||
extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness
|
||||
- thing: x86_64-msvc
|
||||
target: x86_64-pc-windows-msvc
|
||||
rust: stable-x86_64-msvc
|
||||
os: windows-latest
|
||||
custom_env:
|
||||
RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness
|
||||
# CI's Windows doesn't have required root certs
|
||||
extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring
|
||||
|
||||
extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness
|
||||
- thing: x86_64-msvc-static
|
||||
target: x86_64-pc-windows-msvc
|
||||
rust: stable-x86_64-msvc
|
||||
os: windows-latest
|
||||
custom_env:
|
||||
RUSTC_BOOTSTRAP: 1 # for -Z checksum-freshness
|
||||
RUSTFLAGS: -Dwarnings -C target-feature=+crt-static
|
||||
# CI's Windows doesn't have required root certs
|
||||
extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring -Z checksum-freshness
|
||||
env:
|
||||
CARGO_HOME: ${{ github.workspace }}/.cache/cargo
|
||||
CARGO_BUILD_BUILD_DIR: ${{ github.workspace }}/.cache/build-dir
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- name: Install Rust (rustup)
|
||||
run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }}
|
||||
run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} && rustup target add ${{ matrix.target }}
|
||||
shell: bash
|
||||
- run: rustup target add ${{ matrix.target }}
|
||||
- name: Get rust version
|
||||
id: rust-version
|
||||
shell: bash
|
||||
run: |
|
||||
echo "version=$(rustc --version)" >> $GITHUB_OUTPUT
|
||||
- name: Prepopulate cargo index
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
.cache/cargo/registry/index
|
||||
.cache/cargo/registry/cache
|
||||
key: index-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.toml') }}
|
||||
enableCrossOsArchive: true
|
||||
- name: Install golang
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '>=1.22.0'
|
||||
- name: Install target-specific APT dependencies
|
||||
if: "matrix.apt_packages != ''"
|
||||
run: sudo apt update && sudo apt install -y ${{ matrix.apt_packages }}
|
||||
@ -205,6 +269,32 @@ jobs:
|
||||
if: startsWith(matrix.os, 'windows')
|
||||
run: choco install nasm
|
||||
shell: cmd
|
||||
- name: Setup 32-bit MSYS2
|
||||
if: matrix.thing == 'i686-mingw'
|
||||
uses: msys2/setup-msys2@v2
|
||||
id: msys2
|
||||
with:
|
||||
msystem: MINGW32
|
||||
path-type: inherit
|
||||
install: >-
|
||||
mingw-w64-i686-gcc
|
||||
mingw-w64-i686-cmake
|
||||
- name: Setup 32-bit MSYS2 Env vars
|
||||
if: matrix.thing == 'i686-mingw'
|
||||
shell: bash
|
||||
run: |
|
||||
MSYS_ROOT='${{ steps.msys2.outputs.msys2-location }}'
|
||||
test -d "$MSYS_ROOT\\mingw32\\bin"
|
||||
echo >> $GITHUB_PATH "$MSYS_ROOT\\mingw32\\bin"
|
||||
echo >> $GITHUB_PATH "$MSYS_ROOT\\usr\\bin"
|
||||
echo >> $GITHUB_ENV CC="$MSYS_ROOT\\mingw32\\bin\\gcc"
|
||||
echo >> $GITHUB_ENV CXX="$MSYS_ROOT\\mingw32\\bin\\g++"
|
||||
echo >> $GITHUB_ENV AR="$MSYS_ROOT\\mingw32\\bin\\ar"
|
||||
echo >> $GITHUB_ENV CFLAGS="-mlong-double-64 -I$MSYS_ROOT\\mingw32\\include"
|
||||
echo >> $GITHUB_ENV CXXFLAGS="-mlong-double-64 -I$MSYS_ROOT\\mingw32\\include"
|
||||
echo >> $GITHUB_ENV BINDGEN_EXTRA_CLANG_ARGS="-mlong-double-64 -I$MSYS_ROOT\\mingw32\\include"
|
||||
echo >> $GITHUB_ENV LIBRARY_PATH="$MSYS_ROOT\\mingw32\\lib"
|
||||
echo >> $GITHUB_ENV LDFLAGS="-L$MSYS_ROOT\\mingw32\\lib"
|
||||
- name: Install LLVM and Clang
|
||||
if: startsWith(matrix.os, 'windows')
|
||||
uses: KyleMayes/install-llvm-action@v1
|
||||
@ -217,12 +307,31 @@ jobs:
|
||||
- name: Set Android Linker path
|
||||
if: endsWith(matrix.thing, '-android')
|
||||
run: echo "CARGO_TARGET_$(echo ${{ matrix.target }} | tr \\-a-z _A-Z)_LINKER=$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/$(echo ${{ matrix.target }} | sed s/armv7/armv7a/)21-clang++" >> "$GITHUB_ENV"
|
||||
- name: Build tests
|
||||
# We `build` because we want the linker to verify we are cross-compiling correctly for check-only targets.
|
||||
run: cargo build --target ${{ matrix.target }} --tests ${{ matrix.extra_test_args }}
|
||||
- name: Fetch deps
|
||||
run: cargo fetch --target ${{ matrix.target }}
|
||||
shell: bash
|
||||
env: ${{ matrix.custom_env }}
|
||||
- name: Run tests
|
||||
# Windows builds are the slowest
|
||||
- name: Cache deps in Windows tests
|
||||
if: startsWith(matrix.os, 'windows')
|
||||
uses: actions/cache/restore@v4
|
||||
id: test-cache-restore
|
||||
with:
|
||||
path: .cache/build-dir
|
||||
key: wintest-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }}
|
||||
- name: Build tests
|
||||
# We `build` because we want the linker to verify we are cross-compiling correctly for check-only targets.
|
||||
run: cargo build -vv --target ${{ matrix.target }} --tests ${{ matrix.extra_test_args }}
|
||||
shell: bash
|
||||
env: ${{ matrix.custom_env }}
|
||||
# By default it'd be saved after later cargo calls, which already invalidated the cache
|
||||
- name: Cache deps in Windows tests
|
||||
if: startsWith(matrix.os, 'windows')
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: .cache/build-dir
|
||||
key: ${{ steps.test-cache-restore.outputs.cache-primary-key }}
|
||||
- name: Run tests (skip=${{ matrix.check_only }})
|
||||
if: "!matrix.check_only"
|
||||
run: cargo test --target ${{ matrix.target }} ${{ matrix.extra_test_args }}
|
||||
shell: bash
|
||||
@ -238,11 +347,13 @@ jobs:
|
||||
#
|
||||
# Both of these may no longer be the case after updating the BoringSSL
|
||||
# submodules to a new revision, so it's important to test this on CI.
|
||||
run: cargo publish --dry-run -p boring-sys
|
||||
run: cargo publish --dry-run --target ${{ matrix.target }} -p boring-sys
|
||||
shell: bash
|
||||
env: ${{ matrix.custom_env }}
|
||||
|
||||
test-fips:
|
||||
name: Test FIPS integration
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@ -250,18 +361,10 @@ jobs:
|
||||
- name: Install Rust (rustup)
|
||||
run: rustup update stable --no-self-update && rustup default stable
|
||||
shell: bash
|
||||
- name: Install Clang-12
|
||||
uses: KyleMayes/install-llvm-action@v1
|
||||
with:
|
||||
version: "12.0.0"
|
||||
directory: ${{ runner.temp }}/llvm
|
||||
- name: Install golang
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '>=1.22.0'
|
||||
- name: Add clang++-12 link
|
||||
working-directory: ${{ runner.temp }}/llvm/bin
|
||||
run: ln -s clang clang++-12
|
||||
- name: Run tests
|
||||
run: cargo test --features fips
|
||||
- name: Test boring-sys cargo publish (FIPS)
|
||||
@ -289,8 +392,12 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- name: Install Rust (rustup)
|
||||
run: rustup update stable --no-self-update && rustup default stable && rustup target add ${{ matrix.target }}
|
||||
run: rustup toolchain install stable --no-self-update --profile minimal --target ${{ matrix.target }} && rustup default stable
|
||||
shell: bash
|
||||
- name: Install golang
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '>=1.22.0'
|
||||
- name: Install ${{ matrix.target }} toolchain
|
||||
run: brew tap messense/macos-cross-toolchains && brew install ${{ matrix.target }}
|
||||
- name: Set BORING_BSSL_SYSROOT
|
||||
@ -298,51 +405,17 @@ jobs:
|
||||
shell: bash
|
||||
- name: Set CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER
|
||||
run: echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=${{ matrix.target }}-gcc" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
- name: Set CXXFLAGS
|
||||
run: echo "CXXFLAGS=-D__STDC_FORMAT_MACROS" >> $GITHUB_ENV
|
||||
- name: Build for ${{ matrix.target }}
|
||||
run: cargo build --target ${{ matrix.target }} --all-targets
|
||||
|
||||
cross-build-fips:
|
||||
name: Cross build from macOS to Linux (FIPS)
|
||||
runs-on: macos-13 # Need an Intel (x86_64) runner for Clang 12.0.0
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- name: Install Rust (rustup)
|
||||
run: rustup update stable --no-self-update && rustup default stable && rustup target add ${{ matrix.target }}
|
||||
shell: bash
|
||||
- name: Install golang
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '>=1.22.0'
|
||||
- name: Install ${{ matrix.target }} toolchain
|
||||
run: brew tap messense/macos-cross-toolchains && brew install ${{ matrix.target }} && brew link x86_64-unknown-linux-gnu
|
||||
- name: Install Clang-12
|
||||
uses: KyleMayes/install-llvm-action@v1
|
||||
with:
|
||||
version: "12.0.0"
|
||||
directory: ${{ runner.temp }}/llvm
|
||||
- name: Add clang++-12 link
|
||||
working-directory: ${{ runner.temp }}/llvm/bin
|
||||
run: ln -s clang++ clang++-12
|
||||
- name: Set BORING_BSSL_FIPS_COMPILER_EXTERNAL_TOOLCHAIN
|
||||
run: echo "BORING_BSSL_FIPS_COMPILER_EXTERNAL_TOOLCHAIN=$(brew --prefix ${{ matrix.target }})/toolchain" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
- name: Set BORING_BSSL_FIPS_SYSROOT
|
||||
run: echo "BORING_BSSL_FIPS_SYSROOT=$BORING_BSSL_FIPS_COMPILER_EXTERNAL_TOOLCHAIN/${{ matrix.target }}/sysroot" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
- name: Set CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER
|
||||
run: echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=${{ matrix.target }}-gcc" >> $GITHUB_ENV
|
||||
- name: Build for ${{ matrix.target }}
|
||||
run: cargo build --target ${{ matrix.target }} --all-targets --features fips
|
||||
|
||||
test-features:
|
||||
name: Test features
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CARGO_INCREMENTAL: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@ -350,21 +423,15 @@ jobs:
|
||||
- name: Install Rust (rustup)
|
||||
run: rustup update stable --no-self-update && rustup default stable
|
||||
shell: bash
|
||||
- run: cargo check --no-default-features
|
||||
name: Check `--no-default-features`
|
||||
- run: cargo check --features mlkem,credential
|
||||
name: Check `mlkem,credential`
|
||||
- run: cargo test --features rpk
|
||||
name: Run `rpk` tests
|
||||
- run: cargo test --features pq-experimental
|
||||
name: Run `pq-experimental` tests
|
||||
- run: cargo test --features underscore-wildcards
|
||||
name: Run `underscore-wildcards` tests
|
||||
- run: cargo test --features pq-experimental,rpk
|
||||
name: Run `pq-experimental,rpk` tests
|
||||
- run: cargo test --features kx-safe-default,pq-experimental
|
||||
name: Run `kx-safe-default` tests
|
||||
- run: cargo test --features pq-experimental,underscore-wildcards
|
||||
name: Run `pq-experimental,underscore-wildcards` tests
|
||||
- run: cargo test --features rpk,underscore-wildcards
|
||||
name: Run `rpk,underscore-wildcards` tests
|
||||
- run: cargo test --features pq-experimental,rpk,underscore-wildcards
|
||||
name: Run `pq-experimental,rpk,underscore-wildcards` tests
|
||||
- run: cargo test -p hyper-boring --features hyper1
|
||||
name: Run hyper 1.0 tests for hyper-boring
|
||||
- run: cargo test --features rpk,underscore-wildcards,mlkem
|
||||
name: Run `rpk,underscore-wildcards` tests
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -2,6 +2,3 @@
|
||||
path = boring-sys/deps/boringssl
|
||||
url = https://github.com/google/boringssl.git
|
||||
ignore = dirty
|
||||
[submodule "boring-sys/deps/boringssl-fips"]
|
||||
path = boring-sys/deps/boringssl-fips
|
||||
url = https://github.com/google/boringssl.git
|
||||
|
||||
20
Cargo.toml
20
Cargo.toml
@ -8,7 +8,8 @@ members = [
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "4.15.0"
|
||||
version = "5.0.2"
|
||||
rust-version = "1.85"
|
||||
repository = "https://github.com/cloudflare/boring"
|
||||
edition = "2021"
|
||||
|
||||
@ -19,16 +20,17 @@ tag-prefix = ""
|
||||
publish = false
|
||||
|
||||
[workspace.dependencies]
|
||||
boring-sys = { version = "4.15.0", path = "./boring-sys" }
|
||||
boring = { version = "4.15.0", path = "./boring" }
|
||||
tokio-boring = { version = "4.15.0", path = "./tokio-boring" }
|
||||
boring-sys = { version = "5.0.2", path = "./boring-sys", default-features = false }
|
||||
boring = { version = "5.0.2", path = "./boring", default-features = false }
|
||||
tokio-boring = { version = "5.0.2", path = "./tokio-boring", default-features = false }
|
||||
|
||||
bindgen = { version = "0.70.1", default-features = false, features = ["runtime"] }
|
||||
bindgen = { version = "0.72.0", default-features = false, features = ["runtime"] }
|
||||
bitflags = "2.9"
|
||||
brotli = "8.0"
|
||||
bytes = "1"
|
||||
cmake = "0.1.18"
|
||||
cmake = "0.1.54"
|
||||
fs_extra = "1.3.0"
|
||||
fslock = "0.2"
|
||||
bitflags = "2.4"
|
||||
foreign-types = "0.5"
|
||||
libc = "0.2"
|
||||
hex = "0.4"
|
||||
@ -39,14 +41,10 @@ anyhow = "1"
|
||||
antidote = "1.0.0"
|
||||
http = "1"
|
||||
http-body-util = "0.1.2"
|
||||
http_old = { package = "http", version = "0.2" }
|
||||
hyper = "1"
|
||||
hyper-util = "0.1.6"
|
||||
hyper_old = { package = "hyper", version = "0.14", default-features = false }
|
||||
linked_hash_set = "0.1"
|
||||
openssl-macros = "0.1.1"
|
||||
tower = "0.4"
|
||||
tower-layer = "0.3"
|
||||
tower-service = "0.3"
|
||||
autocfg = "1.3.0"
|
||||
brotli = "6.0"
|
||||
|
||||
21
README.md
21
README.md
@ -2,15 +2,34 @@
|
||||
|
||||
[](https://crates.io/crates/boring)
|
||||
|
||||
BoringSSL bindings for the Rust programming language and TLS adapters for [tokio](https://github.com/tokio-rs/tokio)
|
||||
[BoringSSL](https://boringssl.googlesource.com/boringssl) is Google's fork of OpenSSL for Chrome/Chromium and Android.
|
||||
|
||||
This crate provides safe bindings for the Rust programming language and TLS adapters for [tokio](https://github.com/tokio-rs/tokio)
|
||||
and [hyper](https://github.com/hyperium/hyper) built on top of it.
|
||||
|
||||
It supports [FIPS-compatible builds of BoringSSL](https://boringssl.googlesource.com/boringssl/+/master/crypto/fipsmodule/FIPS.md),
|
||||
as well as [Post-Quantum crypto](https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/)
|
||||
and [Raw Public Key](https://docs.rs/boring/latest/boring/ssl/struct.SslRef.html#method.peer_pubkey) extensions.
|
||||
|
||||
## Documentation
|
||||
- Boring API: <https://docs.rs/boring>
|
||||
- tokio TLS adapters: <https://docs.rs/tokio-boring>
|
||||
- hyper HTTPS connector: <https://docs.rs/hyper-boring>
|
||||
- FFI bindings: <https://docs.rs/boring-sys>
|
||||
|
||||
# Upgrading from `boring` v4
|
||||
|
||||
* First update to boring 4.21 and ensure it builds without any deprecation warnings.
|
||||
* `pq-experimental` Cargo feature is no longer needed. Post-quantum crypto is enabled by default.
|
||||
* `fips-precompiled` Cargo feature has been merged into `fips`. Set `BORING_BSSL_FIPS_PATH` env var to use a precompiled library.
|
||||
* `fips-compat` Cargo feature has been renamed to `legacy-compat-deprecated` (4cb7e260a85b7)
|
||||
* `SslCurve` and `SslCurveNid` have been removed. Curve names are more stable and portable identifiers. Use `curve_name()` and `set_curves_list()`.
|
||||
* `Ssl::new_from_ref` -> `Ssl::new()`.
|
||||
* `X509Builder::append_extension2` -> `X509Builder::append_extension`.
|
||||
* `X509Store` is now cheaply cloneable, but immutable. `SslContextBuilder.cert_store_mut()` can't be used after `.set_cert_store()`. If you need `.cert_store_mut()`, either don't overwrite the default store, or use `.set_cert_store_builder()`.
|
||||
* `X509StoreBuilder::add_cert` takes a reference.
|
||||
* `hyper` 0.x support has been removed. Use `hyper` 1.x.
|
||||
|
||||
## Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally
|
||||
|
||||
100
RELEASE_NOTES
100
RELEASE_NOTES
@ -1,5 +1,103 @@
|
||||
5.0.0
|
||||
- 2025-12-19 Update vendored boring to a newer version (2023.11 to 2025.11)
|
||||
- 2025-12-20 Rework RPK/SslMethod (c2f063cf4711f15b8b417b6926496fbf1c2a03ac)
|
||||
- 2025-09-29 Remove `SslCurve` API
|
||||
- 2025-09-30 Remove the "kx-*" features
|
||||
- 2025-09-25 Remove legacy FIPS options (they're controlled via `BORING_BSSL_` env vars instead)
|
||||
- 2026-01-05 Remove deprecated X509CheckFlags flag
|
||||
- 2025-09-30 Remove "pq-experimental" Cargo feature, apply PQ patch by default + P256Kyber768Draft00
|
||||
- 2026-01-05 Safe clone for X509Store
|
||||
- 2025-03-08 Add set_ticket_key_callback (SSL_CTX_set_tlsext_ticket_key_cb)
|
||||
- 2025-09-30 Add SslRef::curve_name()
|
||||
- 2025-09-30 Expose a safe Rust interface for the session resumption callback
|
||||
- 2026-01-05 Fix leaky set_ex_data() API
|
||||
- 2025-12-12 Add boring specific api set_strict_cipher_list to SslContextBuilder
|
||||
- 2025-11-20 Introduce SslCipherRef::protocol_id
|
||||
- 2023-05-11 fix: BIO_set_retry_write when BIO_CTRL_FLUSH to allow writer returns WouldBlock on flush
|
||||
- 2025-11-14 Remove blanket Eq from FFI types
|
||||
- 2025-12-20 Never use the debug CRT on Windows
|
||||
- 2025-02-19 X509Builder::append_extension2 -> X509Builder::append_extension
|
||||
- 2025-02-19 `Ssl::new_from_ref` -> `Ssl::new()`
|
||||
- 2025-02-19 Align SslStream APIs with upstream
|
||||
- 2025-09-26 Remove support for Hyper v0
|
||||
|
||||
4.21.0
|
||||
- 2026-01-05 Warn about set_curves() removal
|
||||
- 2026-01-05 Deprecate set_ex_data()
|
||||
- 2026-01-05 Fix build with --no-default-features
|
||||
- 2026-01-05 Make set_curves_list always available
|
||||
- 2026-01-19 Use fips-build-compatible ERR_add_error_data
|
||||
|
||||
4.20.0
|
||||
- 2025-08-26 Support TARGET_CC and CC_{target}
|
||||
- 2025-08-26 Fix swapped host/target args
|
||||
- 2025-06-13 CStr UTF-8 improvements
|
||||
- 2025-09-26 Skip Rust version detection for bindgen
|
||||
- 2025-09-26 Upgrade deps
|
||||
- 2025-06-13 Ensure that ERR_LIB type can be named
|
||||
- 2025-06-13 Add more reliable library_reason()
|
||||
- 2025-09-30 pq: fix MSVC C4146 warning
|
||||
- 2025-10-14 Freebsd build
|
||||
- 2025-10-01 Fix string data conversion in ErrorStack::put()
|
||||
|
||||
4.19.0
|
||||
- 2025-09-03 Add binding for X509_check_ip_asc
|
||||
- 2025-06-13 Use ERR_clear_error
|
||||
- 2025-06-13 Error descriptions and docs
|
||||
- 2025-06-13 Boring doesn't use function codes
|
||||
- 2025-09-03 Fix patched docs.rs builds
|
||||
- 2025-09-03 Test docs.rs docs
|
||||
- 2025-09-03 Fix doc links
|
||||
|
||||
4.18.0
|
||||
- 2025-05-29 Add set_verify_param
|
||||
- 2025-05-28 Add support for X509_STORE_CTX_get0_untrusted
|
||||
- 2025-06-02 Add X509VerifyParamRef::copy_from (#361)
|
||||
- 2025-06-02 Fix X509VerifyContextRef::set_verify_param (#358)
|
||||
- 2025-06-02 Ensure we call X509_STORE_CTX_cleanup on error path too (#360)
|
||||
- 2025-06-02 Add mutable ex_data APIs for X509StoreContext
|
||||
- 2025-06-02 Add X509StoreContextRef::init_without_cleanup
|
||||
- 2025-06-04 Rename to reset_with_context_data
|
||||
- 2025-06-05 Avoid panicking in error handling
|
||||
- 2025-06-05 Don't unwrap when Result can be returned instead
|
||||
- 2025-06-04 Make X509Store shareable between contexts
|
||||
- 2025-06-05 Sprinkle #[must_use] (#368)
|
||||
- 2025-06-05 Expose SSL_set1_groups to Efficiently Set Curves on SSL Session (#346)
|
||||
- 2025-06-09 Upgrade bindgen to v0.72.0
|
||||
- 2025-06-13 Expose PKey::raw_{private,public}_key (#364)
|
||||
- 2025-06-10 Don't link binaries on docs.rs
|
||||
- 2025-06-11 Use cargo:warning for warnings
|
||||
|
||||
4.17.0
|
||||
- 2025-05-27 Revert "feat(x509): Implement `Clone` for `X509Store` (#339)" (#353)
|
||||
- 2025-05-14 Update bindgen from 0.70.1 -> 0.71.1.
|
||||
- 2025-05-19 Add `X509_STORE_CTX_get0_cert` interface
|
||||
- 2025-05-18 boring(ssl): use `corresponds` macro in `add_certificate_compression_algorithm`
|
||||
- 2025-02-14 Update Cargo.toml
|
||||
- 2025-02-13 build: Fix the build for 32-bit Linux platform
|
||||
- 2025-05-20 rustfmt ;(
|
||||
- 2025-05-20 Fix linking SystemFunction036 from advapi32 in Rust 1.87
|
||||
- 2025-05-20 Clippy
|
||||
- 2025-05-01 add SslCurve::X25519_MLKEM768 constant
|
||||
- 2025-04-17 Use ubuntu-latest for all ci jobs
|
||||
- 2025-04-16 fix clippy error
|
||||
- 2025-04-15 expose SSL_set_compliance_policy
|
||||
- 2025-04-07 feat(x509): Implement `Clone` for `X509Store` (#339)
|
||||
|
||||
4.16.0
|
||||
- 2025-03-31 Add fips-precompiled feature to support newer versions of FIPS (#338)
|
||||
- 2025-03-18 Document linking to C++ standard library (#335)
|
||||
- 2025-03-18 Revert "Remove "fips-no-compat", decouple "fips-compat" from "fips"" (#334)
|
||||
- 2025-03-11 boring: Disable `SslCurve` API with "fips" feature
|
||||
- 2025-03-11 boring-sys: Ignore patches when boringSSL is precompiled
|
||||
- 2025-03-13 Remove "fips-no-compat", decouple "fips-compat" from "fips"
|
||||
- 2025-03-14 Add feature "fips-no-compat"
|
||||
- 2025-03-10 Advertise X25519MLKEM768 with "kx-client-pq-preferred" (#329)
|
||||
- 2025-03-10 Update to actions/cache@v4 (#328)
|
||||
- 2025-02-28 Add missing release notes entry (#324)
|
||||
|
||||
4.15.0
|
||||
- 2025-02-27 Expose API to enable certificate compression. (#241)
|
||||
- 2025-02-23 Fix lifetimes in ssl::select_next_proto
|
||||
- 2025-02-23 Revert cmake bump (for now) as it is overly restrictive (#321)
|
||||
- 2025-02-21 Introduce a builder pattern for SslEchKeys + make set_ech_keys take a reference (#320)
|
||||
@ -516,7 +614,7 @@
|
||||
- 2019-12-01 Change *const to *mut to try if it fixes tests
|
||||
- 2019-12-01 move EVP_PKCS82PKEY into evp module
|
||||
- 2019-12-01 Support for PKCS#8 unencrypted private key deserialization
|
||||
- 2019-11-23 Update openssl/src/hash.rs
|
||||
- 2019-11-23 Update openssl/src/hash.rs
|
||||
- 2019-11-22 Add EVP_md_null() and MessageDigest::md_null()
|
||||
- 2019-11-22 Fix up base64 docs
|
||||
- 2019-11-22 Cleanup
|
||||
|
||||
@ -12,74 +12,64 @@ links = "boringssl"
|
||||
build = "build/main.rs"
|
||||
readme = "README.md"
|
||||
categories = ["cryptography", "external-ffi-bindings"]
|
||||
keywords = ["tls", "boringssl", "openssl", "fips", "ml-kem"]
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
include = [
|
||||
"/*.md",
|
||||
"/*.toml",
|
||||
"/LICENSE-MIT",
|
||||
"/cmake/*.cmake",
|
||||
# boringssl (non-FIPS)
|
||||
"/deps/boringssl/src/util/32-bit-toolchain.cmake",
|
||||
"/deps/boringssl/**/*.[chS]",
|
||||
"/deps/boringssl/**/*.inc",
|
||||
"/deps/boringssl/**/*.asm",
|
||||
"/deps/boringssl/sources.json",
|
||||
"/deps/boringssl/src/crypto/obj/obj_mac.num",
|
||||
"/deps/boringssl/src/crypto/obj/objects.txt",
|
||||
"/deps/boringssl/**/*.pl",
|
||||
"/deps/boringssl/**/*.go",
|
||||
"/deps/boringssl/**/*.cmake",
|
||||
"/deps/boringssl/**/go.mod",
|
||||
"/deps/boringssl/**/go.sum",
|
||||
"/deps/boringssl/crypto/obj/obj_mac.num",
|
||||
"/deps/boringssl/crypto/obj/objects.txt",
|
||||
"/deps/boringssl/crypto/err/*.errordata",
|
||||
"/deps/boringssl/**/*.bzl",
|
||||
"/deps/boringssl/src/**/*.cc",
|
||||
"/deps/boringssl/**/*.cc",
|
||||
"/deps/boringssl/**/CMakeLists.txt",
|
||||
"/deps/boringssl/**/sources.cmake",
|
||||
"/deps/boringssl/**/util/go_tests.txt",
|
||||
"/deps/boringssl/LICENSE",
|
||||
# boringssl (FIPS)
|
||||
"/deps/boringssl-fips/src/util/32-bit-toolchain.cmake",
|
||||
"/deps/boringssl-fips/**/*.[chS]",
|
||||
"/deps/boringssl-fips/**/*.asm",
|
||||
"/deps/boringssl-fips/**/*.pl",
|
||||
"/deps/boringssl-fips/**/*.go",
|
||||
"/deps/boringssl-fips/**/go.mod",
|
||||
"/deps/boringssl-fips/**/go.sum",
|
||||
"/deps/boringssl-fips/sources.json",
|
||||
"/deps/boringssl-fips/crypto/obj/obj_mac.num",
|
||||
"/deps/boringssl-fips/crypto/obj/objects.txt",
|
||||
"/deps/boringssl-fips/crypto/err/*.errordata",
|
||||
"/deps/boringssl-fips/**/*.bzl",
|
||||
"/deps/boringssl-fips/**/*.cc",
|
||||
"/deps/boringssl-fips/**/CMakeLists.txt",
|
||||
"/deps/boringssl-fips/**/sources.cmake",
|
||||
"/deps/boringssl-fips/LICENSE",
|
||||
"/build/*",
|
||||
"/src",
|
||||
"/patches",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["rpk", "pq-experimental", "underscore-wildcards"]
|
||||
features = ["rpk", "underscore-wildcards", "mlkem"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
# Use a FIPS-validated version of boringssl.
|
||||
# Compile boringssl using the FIPS build flag if building boringssl from
|
||||
# scratch.
|
||||
#
|
||||
# See
|
||||
# https://boringssl.googlesource.com/boringssl/+/master/crypto/fipsmodule/FIPS.md
|
||||
# for instructions and more details on the boringssl FIPS flag.
|
||||
fips = []
|
||||
|
||||
# Link with precompiled FIPS-validated `bcm.o` module.
|
||||
fips-link-precompiled = []
|
||||
|
||||
# Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250)
|
||||
rpk = []
|
||||
|
||||
# Applies a patch (`patches/boring-pq.patch`) to the boringSSL source code that
|
||||
# enables support for PQ key exchange. This feature is necessary in order to
|
||||
# compile the bindings for the default branch of boringSSL (`deps/boringssl`).
|
||||
# Alternatively, a version of boringSSL that implements the same feature set
|
||||
# can be provided by setting `BORING_BSSL{,_FIPS}_SOURCE_PATH`.
|
||||
pq-experimental = []
|
||||
# Require mlkem.h
|
||||
mlkem = []
|
||||
|
||||
# Applies a patch (`patches/underscore-wildcards.patch`) to enable
|
||||
# `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. Same caveats as
|
||||
# those for `pq-experimental` feature apply.
|
||||
# `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. This feature is necessary in
|
||||
# order to compile the bindings for the default branch of boringSSL
|
||||
# (`deps/boringssl`). Alternatively, a version of boringSSL that implements the
|
||||
# same feature set can be provided by setting
|
||||
# `BORING_BSSL{,_FIPS}_SOURCE_PATH`.
|
||||
underscore-wildcards = []
|
||||
|
||||
[build-dependencies]
|
||||
autocfg = { workspace = true }
|
||||
bindgen = { workspace = true }
|
||||
cmake = { workspace = true }
|
||||
fs_extra = { workspace = true }
|
||||
|
||||
@ -1 +0,0 @@
|
||||
../README.md
|
||||
17
boring-sys/README.md
Normal file
17
boring-sys/README.md
Normal file
@ -0,0 +1,17 @@
|
||||
# Low-level bindings to BoringSSL
|
||||
|
||||
[BoringSSL](https://boringssl.googlesource.com/boringssl) is Google's fork of OpenSSL for Chrome/Chromium and Android.
|
||||
|
||||
This crate builds the BoringSSL library (or optionally links a pre-built version) and generates FFI bindings for it.
|
||||
It supports [FIPS-compatible builds of BoringSSL](https://boringssl.googlesource.com/boringssl/+/master/crypto/fipsmodule/FIPS.md),
|
||||
as well as [Post-Quantum crypto](https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/)
|
||||
and [Raw Public Key](https://docs.rs/boring/latest/boring/ssl/struct.SslRef.html#method.peer_pubkey) extensions.
|
||||
|
||||
To use BoringSSL from Rust, prefer the [higher-level safe API](https://docs.rs/boring).
|
||||
|
||||
## Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally
|
||||
submitted for inclusion in the work by you, as defined in the Apache-2.0
|
||||
license, shall be dual licensed under the terms of both the Apache License,
|
||||
Version 2.0 and the MIT license without any additional terms or conditions.
|
||||
@ -10,14 +10,15 @@ pub(crate) struct Config {
|
||||
pub(crate) target: String,
|
||||
pub(crate) target_arch: String,
|
||||
pub(crate) target_os: String,
|
||||
pub(crate) unix: bool,
|
||||
pub(crate) target_env: String,
|
||||
pub(crate) target_features: Vec<String>,
|
||||
pub(crate) features: Features,
|
||||
pub(crate) env: Env,
|
||||
}
|
||||
|
||||
pub(crate) struct Features {
|
||||
pub(crate) fips: bool,
|
||||
pub(crate) fips_link_precompiled: bool,
|
||||
pub(crate) pq_experimental: bool,
|
||||
pub(crate) rpk: bool,
|
||||
pub(crate) underscore_wildcards: bool,
|
||||
}
|
||||
@ -26,7 +27,6 @@ pub(crate) struct Env {
|
||||
pub(crate) path: Option<PathBuf>,
|
||||
pub(crate) include_path: Option<PathBuf>,
|
||||
pub(crate) source_path: Option<PathBuf>,
|
||||
pub(crate) precompiled_bcm_o: Option<PathBuf>,
|
||||
pub(crate) assume_patched: bool,
|
||||
pub(crate) sysroot: Option<PathBuf>,
|
||||
pub(crate) compiler_external_toolchain: Option<PathBuf>,
|
||||
@ -35,6 +35,7 @@ pub(crate) struct Env {
|
||||
pub(crate) android_ndk_home: Option<PathBuf>,
|
||||
pub(crate) cmake_toolchain_file: Option<PathBuf>,
|
||||
pub(crate) cpp_runtime_lib: Option<OsString>,
|
||||
pub(crate) docs_rs: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@ -45,18 +46,22 @@ impl Config {
|
||||
let target = env::var("TARGET").unwrap();
|
||||
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap();
|
||||
let unix = env::var("CARGO_CFG_UNIX").is_ok();
|
||||
|
||||
let target_features = env::var("CARGO_CFG_TARGET_FEATURE")
|
||||
.unwrap_or_default()
|
||||
.split(',')
|
||||
.map(|s| s.to_owned())
|
||||
.collect();
|
||||
|
||||
let features = Features::from_env();
|
||||
let env = Env::from_env(
|
||||
&host,
|
||||
&target,
|
||||
features.fips || features.fips_link_precompiled,
|
||||
);
|
||||
let env = Env::from_env(&host, &target, features.is_fips_like());
|
||||
|
||||
let mut is_bazel = false;
|
||||
if let Some(src_path) = &env.source_path {
|
||||
is_bazel = src_path.join("src").exists();
|
||||
}
|
||||
let is_bazel = env
|
||||
.source_path
|
||||
.as_ref()
|
||||
.is_some_and(|path| path.join("src").exists());
|
||||
|
||||
let config = Self {
|
||||
manifest_dir,
|
||||
@ -66,6 +71,9 @@ impl Config {
|
||||
target,
|
||||
target_arch,
|
||||
target_os,
|
||||
unix,
|
||||
target_env,
|
||||
target_features,
|
||||
features,
|
||||
env,
|
||||
};
|
||||
@ -91,15 +99,14 @@ impl Config {
|
||||
);
|
||||
}
|
||||
|
||||
let features_with_patches_enabled = self.features.rpk
|
||||
|| self.features.pq_experimental
|
||||
|| self.features.underscore_wildcards;
|
||||
let features_with_patches_enabled = self.features.rpk || self.features.underscore_wildcards;
|
||||
|
||||
let patches_required = features_with_patches_enabled && !self.env.assume_patched;
|
||||
let build_from_sources_required = self.features.fips_link_precompiled || patches_required;
|
||||
|
||||
if is_precompiled_native_lib && build_from_sources_required {
|
||||
panic!("precompiled BoringSSL was provided, so FIPS configuration or optional patches can't be applied");
|
||||
if is_precompiled_native_lib && patches_required {
|
||||
println!(
|
||||
"cargo:warning=precompiled BoringSSL was provided, so patches will be ignored"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -107,65 +114,72 @@ impl Config {
|
||||
impl Features {
|
||||
fn from_env() -> Self {
|
||||
let fips = env::var_os("CARGO_FEATURE_FIPS").is_some();
|
||||
let fips_link_precompiled = env::var_os("CARGO_FEATURE_FIPS_LINK_PRECOMPILED").is_some();
|
||||
let pq_experimental = env::var_os("CARGO_FEATURE_PQ_EXPERIMENTAL").is_some();
|
||||
let rpk = env::var_os("CARGO_FEATURE_RPK").is_some();
|
||||
let underscore_wildcards = env::var_os("CARGO_FEATURE_UNDERSCORE_WILDCARDS").is_some();
|
||||
|
||||
Self {
|
||||
fips,
|
||||
fips_link_precompiled,
|
||||
pq_experimental,
|
||||
rpk,
|
||||
underscore_wildcards,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_fips_like(&self) -> bool {
|
||||
self.fips
|
||||
}
|
||||
}
|
||||
|
||||
impl Env {
|
||||
fn from_env(target: &str, host: &str, is_fips_like: bool) -> Self {
|
||||
const NORMAL_PREFIX: &str = "BORING_BSSL";
|
||||
const FIPS_PREFIX: &str = "BORING_BSSL_FIPS";
|
||||
|
||||
fn from_env(host: &str, target: &str, is_fips_like: bool) -> Self {
|
||||
let target_with_underscores = target.replace('-', "_");
|
||||
|
||||
// Logic stolen from cmake-rs.
|
||||
let target_var = |name: &str| {
|
||||
let kind = if host == target { "HOST" } else { "TARGET" };
|
||||
|
||||
var(&format!("{}_{}", name, target))
|
||||
.or_else(|| var(&format!("{}_{}", name, target_with_underscores)))
|
||||
.or_else(|| var(&format!("{}_{}", kind, name)))
|
||||
// TODO(rmehra): look for just `name` first, as most people just set that
|
||||
var(&format!("{name}_{target}"))
|
||||
.or_else(|| var(&format!("{name}_{target_with_underscores}")))
|
||||
.or_else(|| var(&format!("{kind}_{name}")))
|
||||
.or_else(|| var(name))
|
||||
};
|
||||
|
||||
let boringssl_var = |name: &str| {
|
||||
const BORING_BSSL_PREFIX: &str = "BORING_BSSL_";
|
||||
const BORING_BSSL_FIPS_PREFIX: &str = "BORING_BSSL_FIPS_";
|
||||
|
||||
// The passed name is the non-fips version of the environment variable,
|
||||
// to help look for them in the repository.
|
||||
assert!(name.starts_with(NORMAL_PREFIX));
|
||||
assert!(name.starts_with(BORING_BSSL_PREFIX));
|
||||
|
||||
let non_fips = target_var(name);
|
||||
if is_fips_like {
|
||||
target_var(&name.replace(NORMAL_PREFIX, FIPS_PREFIX))
|
||||
let fips_name = name.replace(BORING_BSSL_PREFIX, BORING_BSSL_FIPS_PREFIX);
|
||||
let fips = target_var(&fips_name);
|
||||
if fips.is_none() && non_fips.is_some() {
|
||||
println!("cargo:warning=env var {name} ignored, because FIPS is enabled. Set {fips_name} instead.");
|
||||
}
|
||||
fips
|
||||
} else {
|
||||
target_var(name)
|
||||
non_fips
|
||||
}
|
||||
};
|
||||
|
||||
Self {
|
||||
path: boringssl_var("BORING_BSSL_PATH").map(PathBuf::from),
|
||||
include_path: boringssl_var("BORING_BSSL_INCLUDE_PATH").map(PathBuf::from),
|
||||
source_path: boringssl_var("BORING_BSSL_SOURCE_PATH").map(PathBuf::from),
|
||||
precompiled_bcm_o: boringssl_var("BORING_BSSL_PRECOMPILED_BCM_O").map(PathBuf::from),
|
||||
assume_patched: boringssl_var("BORING_BSSL_ASSUME_PATCHED")
|
||||
path: boringssl_var("BORING_BSSL_PATH").map(PathBuf::from), // gets BORING_BSSL_FIPS_PATH if fips is enabled
|
||||
include_path: boringssl_var("BORING_BSSL_INCLUDE_PATH").map(PathBuf::from), // gets BORING_BSSL_FIPS_INCLUDE_PATH if fips is enabled
|
||||
source_path: boringssl_var("BORING_BSSL_SOURCE_PATH").map(PathBuf::from), // gets BORING_BSSL_FIPS_SOURCE_PATH if fips is enabled
|
||||
assume_patched: boringssl_var("BORING_BSSL_ASSUME_PATCHED") // gets BORING_BSSL_FIPS_ASSUME_PATCHED if fips is enabled
|
||||
.is_some_and(|v| !v.is_empty()),
|
||||
sysroot: boringssl_var("BORING_BSSL_SYSROOT").map(PathBuf::from),
|
||||
compiler_external_toolchain: boringssl_var("BORING_BSSL_COMPILER_EXTERNAL_TOOLCHAIN")
|
||||
sysroot: boringssl_var("BORING_BSSL_SYSROOT").map(PathBuf::from), // gets BORING_BSSL_FIPS_SYSROOT if fips is enabled
|
||||
compiler_external_toolchain: boringssl_var("BORING_BSSL_COMPILER_EXTERNAL_TOOLCHAIN") // gets BORING_BSSL_FIPS_COMPILER_EXTERNAL_TOOLCHAIN if fips is enabled
|
||||
.map(PathBuf::from),
|
||||
debug: target_var("DEBUG"),
|
||||
opt_level: target_var("OPT_LEVEL"),
|
||||
android_ndk_home: target_var("ANDROID_NDK_HOME").map(Into::into),
|
||||
cmake_toolchain_file: target_var("CMAKE_TOOLCHAIN_FILE").map(Into::into),
|
||||
cpp_runtime_lib: target_var("BORING_BSSL_RUST_CPPLIB"),
|
||||
docs_rs: var("DOCS_RS").is_some(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
use fslock::LockFile;
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
@ -50,6 +49,7 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[
|
||||
&[
|
||||
("CMAKE_OSX_ARCHITECTURES", "arm64"),
|
||||
("CMAKE_OSX_SYSROOT", "iphoneos"),
|
||||
("CMAKE_MACOSX_BUNDLE", "OFF"),
|
||||
],
|
||||
),
|
||||
(
|
||||
@ -57,6 +57,7 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[
|
||||
&[
|
||||
("CMAKE_OSX_ARCHITECTURES", "arm64"),
|
||||
("CMAKE_OSX_SYSROOT", "iphonesimulator"),
|
||||
("CMAKE_MACOSX_BUNDLE", "OFF"),
|
||||
],
|
||||
),
|
||||
(
|
||||
@ -64,6 +65,7 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[
|
||||
&[
|
||||
("CMAKE_OSX_ARCHITECTURES", "x86_64"),
|
||||
("CMAKE_OSX_SYSROOT", "iphonesimulator"),
|
||||
("CMAKE_MACOSX_BUNDLE", "OFF"),
|
||||
],
|
||||
),
|
||||
// macOS
|
||||
@ -106,19 +108,21 @@ fn get_apple_sdk_name(config: &Config) -> &'static str {
|
||||
}
|
||||
|
||||
/// Returns an absolute path to the BoringSSL source.
|
||||
fn get_boringssl_source_path(config: &Config) -> &PathBuf {
|
||||
if let Some(src_path) = &config.env.source_path {
|
||||
return src_path;
|
||||
}
|
||||
|
||||
fn get_boringssl_source_path(config: &Config) -> &Path {
|
||||
static SOURCE_PATH: OnceLock<PathBuf> = OnceLock::new();
|
||||
|
||||
SOURCE_PATH.get_or_init(|| {
|
||||
let submodule_dir = if config.features.fips {
|
||||
"boringssl-fips"
|
||||
} else {
|
||||
"boringssl"
|
||||
};
|
||||
if let Some(src_path) = &config.env.source_path {
|
||||
if !src_path.exists() {
|
||||
println!(
|
||||
"cargo:warning=boringssl source path doesn't exist: {}",
|
||||
src_path.display()
|
||||
);
|
||||
}
|
||||
return src_path.into();
|
||||
}
|
||||
|
||||
let submodule_dir = "boringssl";
|
||||
|
||||
let src_path = config.out_dir.join(submodule_dir);
|
||||
|
||||
@ -132,11 +136,15 @@ fn get_boringssl_source_path(config: &Config) -> &PathBuf {
|
||||
.args(["submodule", "update", "--init", "--recursive"])
|
||||
.arg(&submodule_path),
|
||||
)
|
||||
.unwrap();
|
||||
.expect("git submodule update");
|
||||
}
|
||||
|
||||
let _ = fs::remove_dir_all(&src_path);
|
||||
fs_extra::dir::copy(submodule_path, &config.out_dir, &Default::default()).unwrap();
|
||||
fs_extra::dir::copy(submodule_path, &config.out_dir, &Default::default())
|
||||
.inspect_err(|_| {
|
||||
let _ = fs::remove_dir_all(&config.out_dir);
|
||||
})
|
||||
.expect("copying failed. Try running `cargo clean`");
|
||||
|
||||
// NOTE: .git can be both file and dir, depening on whether it was copied from a submodule
|
||||
// or created by the patches code.
|
||||
@ -152,8 +160,8 @@ fn get_boringssl_source_path(config: &Config) -> &PathBuf {
|
||||
///
|
||||
/// MSVC generator on Windows place static libs in a target sub-folder,
|
||||
/// so adjust library location based on platform and build target.
|
||||
/// See issue: https://github.com/alexcrichton/cmake-rs/issues/18
|
||||
fn get_boringssl_platform_output_path(config: &Config) -> String {
|
||||
/// See issue: <https://github.com/alexcrichton/cmake-rs/issues/18>
|
||||
fn msvc_lib_subdir(config: &Config) -> Option<&'static str> {
|
||||
if config.target.ends_with("-msvc") {
|
||||
// Code under this branch should match the logic in cmake-rs
|
||||
let debug_env_var = config
|
||||
@ -165,7 +173,7 @@ fn get_boringssl_platform_output_path(config: &Config) -> String {
|
||||
let deb_info = match debug_env_var.to_str() {
|
||||
Some("false") => false,
|
||||
Some("true") => true,
|
||||
_ => panic!("Unknown DEBUG={:?} env var.", debug_env_var),
|
||||
_ => panic!("Unknown DEBUG={debug_env_var:?} env var."),
|
||||
};
|
||||
|
||||
let opt_env_var = config
|
||||
@ -184,27 +192,39 @@ fn get_boringssl_platform_output_path(config: &Config) -> String {
|
||||
}
|
||||
}
|
||||
Some("s" | "z") => "MinSizeRel",
|
||||
_ => panic!("Unknown OPT_LEVEL={:?} env var.", opt_env_var),
|
||||
_ => panic!("Unknown OPT_LEVEL={opt_env_var:?} env var."),
|
||||
};
|
||||
|
||||
subdir.to_string()
|
||||
Some(subdir)
|
||||
} else {
|
||||
"".to_string()
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new cmake::Config for building BoringSSL.
|
||||
/// Returns a new `cmake::Config` for building BoringSSL.
|
||||
///
|
||||
/// It will add platform-specific parameters if needed.
|
||||
fn get_boringssl_cmake_config(config: &Config) -> cmake::Config {
|
||||
let src_path = get_boringssl_source_path(config);
|
||||
let mut boringssl_cmake = cmake::Config::new(src_path);
|
||||
|
||||
if config.host == config.target {
|
||||
if config.env.cmake_toolchain_file.is_some() {
|
||||
return boringssl_cmake;
|
||||
}
|
||||
|
||||
if config.env.cmake_toolchain_file.is_some() {
|
||||
if config.target_os == "windows" {
|
||||
// Explicitly use the non-debug CRT.
|
||||
// This is required now because newest BoringSSL requires CMake 3.22 which
|
||||
// uses the new logic with CMAKE_MSVC_RUNTIME_LIBRARY introduced in CMake 3.15.
|
||||
// https://github.com/rust-lang/cmake-rs/pull/30#issuecomment-2969758499
|
||||
if config.target_features.iter().any(|f| f == "crt-static") {
|
||||
boringssl_cmake.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded");
|
||||
} else {
|
||||
boringssl_cmake.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreadedDLL");
|
||||
}
|
||||
}
|
||||
|
||||
if config.host == config.target {
|
||||
return boringssl_cmake;
|
||||
}
|
||||
|
||||
@ -242,12 +262,12 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config {
|
||||
}
|
||||
let toolchain_file = android_ndk_home.join("build/cmake/android.toolchain.cmake");
|
||||
let toolchain_file = toolchain_file.to_str().unwrap();
|
||||
eprintln!("android toolchain={}", toolchain_file);
|
||||
eprintln!("android toolchain={toolchain_file}");
|
||||
boringssl_cmake.define("CMAKE_TOOLCHAIN_FILE", toolchain_file);
|
||||
|
||||
// 21 is the minimum level tested. You can give higher value.
|
||||
boringssl_cmake.define("ANDROID_NATIVE_API_LEVEL", "21");
|
||||
boringssl_cmake.define("ANDROID_STL", "c++_shared");
|
||||
boringssl_cmake.define("CMAKE_SYSTEM_VERSION", "21");
|
||||
boringssl_cmake.define("CMAKE_ANDROID_STL_TYPE", "c++_shared");
|
||||
}
|
||||
|
||||
"macos" => {
|
||||
@ -273,7 +293,7 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config {
|
||||
""
|
||||
};
|
||||
|
||||
let cflag = format!("{} {}", bitcode_cflag, target_cflag);
|
||||
let cflag = format!("{bitcode_cflag} {target_cflag}");
|
||||
boringssl_cmake.define("CMAKE_ASM_FLAGS", &cflag);
|
||||
boringssl_cmake.cflag(&cflag);
|
||||
}
|
||||
@ -295,7 +315,7 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config {
|
||||
config
|
||||
.manifest_dir
|
||||
.join(src_path)
|
||||
.join("src/util/32-bit-toolchain.cmake")
|
||||
.join("util/32-bit-toolchain.cmake")
|
||||
.as_os_str(),
|
||||
);
|
||||
}
|
||||
@ -318,8 +338,8 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config {
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
eprintln!(
|
||||
"warning: no toolchain file configured by boring-sys for {}",
|
||||
println!(
|
||||
"cargo:warning=no toolchain file configured by boring-sys for {}",
|
||||
config.target
|
||||
);
|
||||
}
|
||||
@ -331,57 +351,6 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config {
|
||||
boringssl_cmake
|
||||
}
|
||||
|
||||
/// Verify that the toolchains match https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp3678.pdf
|
||||
/// See "Installation Instructions" under section 12.1.
|
||||
// TODO: maybe this should also verify the Go and Ninja versions? But those haven't been an issue in practice ...
|
||||
fn verify_fips_clang_version() -> (&'static str, &'static str) {
|
||||
fn version(tool: &str) -> Option<String> {
|
||||
let output = match Command::new(tool).arg("--version").output() {
|
||||
Ok(o) => o,
|
||||
Err(e) => {
|
||||
eprintln!("warning: missing {}, trying other compilers: {}", tool, e);
|
||||
// NOTE: hard-codes that the loop below checks the version
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if !output.status.success() {
|
||||
return Some(String::new());
|
||||
}
|
||||
let output = std::str::from_utf8(&output.stdout).expect("invalid utf8 output");
|
||||
Some(output.lines().next().expect("empty output").to_string())
|
||||
}
|
||||
|
||||
const REQUIRED_CLANG_VERSION: &str = "12.0.0";
|
||||
for (cc, cxx) in [
|
||||
("clang-12", "clang++-12"),
|
||||
("clang", "clang++"),
|
||||
("cc", "c++"),
|
||||
] {
|
||||
let (Some(cc_version), Some(cxx_version)) = (version(cc), version(cxx)) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if cc_version.contains(REQUIRED_CLANG_VERSION) {
|
||||
assert!(
|
||||
cxx_version.contains(REQUIRED_CLANG_VERSION),
|
||||
"mismatched versions of cc and c++"
|
||||
);
|
||||
return (cc, cxx);
|
||||
} else if cc == "cc" {
|
||||
panic!(
|
||||
"unsupported clang version \"{}\": FIPS requires clang {}",
|
||||
cc_version, REQUIRED_CLANG_VERSION
|
||||
);
|
||||
} else if !cc_version.is_empty() {
|
||||
eprintln!(
|
||||
"warning: FIPS requires clang version {}, skipping incompatible version \"{}\"",
|
||||
REQUIRED_CLANG_VERSION, cc_version
|
||||
);
|
||||
}
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn pick_best_android_ndk_toolchain(toolchains_dir: &Path) -> std::io::Result<OsString> {
|
||||
let toolchains = std::fs::read_dir(toolchains_dir)?.collect::<Result<Vec<_>, _>>()?;
|
||||
// First look for one of the toolchains that Google has documented.
|
||||
@ -413,31 +382,23 @@ fn get_extra_clang_args_for_bindgen(config: &Config) -> Vec<String> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
// Add platform-specific parameters.
|
||||
#[allow(clippy::single_match)]
|
||||
match &*config.target_os {
|
||||
"ios" | "macos" => {
|
||||
// When cross-compiling for Apple targets, tell bindgen to use SDK sysroot,
|
||||
// and *don't* use system headers of the host macOS.
|
||||
let sdk = get_apple_sdk_name(config);
|
||||
let output = std::process::Command::new("xcrun")
|
||||
.args(["--show-sdk-path", "--sdk", sdk])
|
||||
.output()
|
||||
.unwrap();
|
||||
if !output.status.success() {
|
||||
if let Some(exit_code) = output.status.code() {
|
||||
eprintln!("xcrun failed: exit code {}", exit_code);
|
||||
} else {
|
||||
eprintln!("xcrun failed: killed");
|
||||
match run_command(Command::new("xcrun").args(["--show-sdk-path", "--sdk", sdk])) {
|
||||
Ok(output) => {
|
||||
let sysroot = std::str::from_utf8(&output.stdout).expect("xcrun output");
|
||||
params.push("-isysroot".to_string());
|
||||
// There is typically a newline at the end which confuses clang.
|
||||
params.push(sysroot.trim_end().to_string());
|
||||
}
|
||||
Err(e) => {
|
||||
println!("cargo:warning={e}");
|
||||
// Uh... let's try anyway, I guess?
|
||||
}
|
||||
std::io::stderr().write_all(&output.stderr).unwrap();
|
||||
// Uh... let's try anyway, I guess?
|
||||
return params;
|
||||
}
|
||||
let mut sysroot = String::from_utf8(output.stdout).unwrap();
|
||||
// There is typically a newline at the end which confuses clang.
|
||||
sysroot.truncate(sysroot.trim_end().len());
|
||||
params.push("-isysroot".to_string());
|
||||
params.push(sysroot);
|
||||
}
|
||||
"android" => {
|
||||
let mut android_sysroot = config
|
||||
@ -448,21 +409,18 @@ fn get_extra_clang_args_for_bindgen(config: &Config) -> Vec<String> {
|
||||
|
||||
android_sysroot.extend(["toolchains", "llvm", "prebuilt"]);
|
||||
|
||||
let toolchain = match pick_best_android_ndk_toolchain(&android_sysroot) {
|
||||
Ok(toolchain) => toolchain,
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"warning: failed to find prebuilt Android NDK toolchain for bindgen: {}",
|
||||
e
|
||||
);
|
||||
// Uh... let's try anyway, I guess?
|
||||
return params;
|
||||
match pick_best_android_ndk_toolchain(&android_sysroot) {
|
||||
Ok(toolchain) => {
|
||||
android_sysroot.push(toolchain);
|
||||
android_sysroot.push("sysroot");
|
||||
params.push("--sysroot".to_string());
|
||||
params.push(android_sysroot.into_os_string().into_string().unwrap());
|
||||
}
|
||||
};
|
||||
android_sysroot.push(toolchain);
|
||||
android_sysroot.push("sysroot");
|
||||
params.push("--sysroot".to_string());
|
||||
params.push(android_sysroot.into_os_string().into_string().unwrap());
|
||||
Err(e) => {
|
||||
println!("cargo:warning=failed to find prebuilt Android NDK toolchain for bindgen: {e}");
|
||||
// Uh... let's try anyway, I guess?
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@ -471,6 +429,22 @@ fn get_extra_clang_args_for_bindgen(config: &Config) -> Vec<String> {
|
||||
}
|
||||
|
||||
fn ensure_patches_applied(config: &Config) -> io::Result<()> {
|
||||
if config.env.assume_patched || config.env.path.is_some() {
|
||||
println!(
|
||||
"cargo:warning=skipping git patches application, provided\
|
||||
native BoringSSL is expected to have the patches included"
|
||||
);
|
||||
return Ok(());
|
||||
} else if config.env.source_path.is_some()
|
||||
&& (config.features.rpk || config.features.underscore_wildcards)
|
||||
{
|
||||
panic!(
|
||||
"BORING_BSSL_ASSUME_PATCHED must be set when setting
|
||||
BORING_BSSL_SOURCE_PATH and using any of the following
|
||||
features: rpk, underscore-wildcards"
|
||||
);
|
||||
}
|
||||
|
||||
let mut lock_file = LockFile::open(&config.out_dir.join(".patch_lock"))?;
|
||||
let src_path = get_boringssl_source_path(config);
|
||||
let has_git = src_path.join(".git").exists();
|
||||
@ -482,10 +456,8 @@ fn ensure_patches_applied(config: &Config) -> io::Result<()> {
|
||||
run_command(Command::new("git").arg("init").current_dir(src_path))?;
|
||||
}
|
||||
|
||||
if config.features.pq_experimental {
|
||||
println!("cargo:warning=applying experimental post quantum crypto patch to boringssl");
|
||||
apply_patch(config, "boring-pq.patch")?;
|
||||
}
|
||||
println!("cargo:warning=applying post quantum crypto patch to boringssl");
|
||||
apply_patch(config, "boring-pq.patch")?;
|
||||
|
||||
if config.features.rpk {
|
||||
println!("cargo:warning=applying RPK patch to boringssl");
|
||||
@ -530,201 +502,164 @@ fn apply_patch(config: &Config, patch_name: &str) -> io::Result<()> {
|
||||
}
|
||||
|
||||
fn run_command(command: &mut Command) -> io::Result<Output> {
|
||||
let out = command.output()?;
|
||||
let out = command.output().map_err(|e| {
|
||||
io::Error::new(
|
||||
e.kind(),
|
||||
format!(
|
||||
"can't run {}: {e}\n{command:?} failed",
|
||||
command.get_program().to_string_lossy(),
|
||||
),
|
||||
)
|
||||
})?;
|
||||
|
||||
println!("{}", std::str::from_utf8(&out.stdout).unwrap());
|
||||
eprintln!("{}", std::str::from_utf8(&out.stderr).unwrap());
|
||||
std::io::stderr().write_all(&out.stderr)?;
|
||||
std::io::stdout().write_all(&out.stdout)?;
|
||||
|
||||
if !out.status.success() {
|
||||
let err = match out.status.code() {
|
||||
Some(code) => format!("{:?} exited with status: {}", command, code),
|
||||
None => format!("{:?} was terminated by signal", command),
|
||||
Some(code) => format!("{command:?} exited with status: {code}"),
|
||||
None => format!("{command:?} was terminated by signal"),
|
||||
};
|
||||
|
||||
return Err(io::Error::new(io::ErrorKind::Other, err));
|
||||
return Err(io::Error::other(err));
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn built_boring_source_path(config: &Config) -> &PathBuf {
|
||||
if let Some(path) = &config.env.path {
|
||||
return path;
|
||||
}
|
||||
|
||||
fn build_boringssl_or_get_prebuilt(config: &Config) -> &Path {
|
||||
static BUILD_SOURCE_PATH: OnceLock<PathBuf> = OnceLock::new();
|
||||
|
||||
BUILD_SOURCE_PATH.get_or_init(|| {
|
||||
if config.env.assume_patched {
|
||||
println!(
|
||||
"cargo:warning=skipping git patches application, provided\
|
||||
native BoringSSL is expected to have the patches included"
|
||||
);
|
||||
} else if config.env.source_path.is_some()
|
||||
&& (config.features.rpk
|
||||
|| config.features.pq_experimental
|
||||
|| config.features.underscore_wildcards)
|
||||
{
|
||||
panic!(
|
||||
"BORING_BSSL_ASSUME_PATCHED must be set when setting
|
||||
BORING_BSSL_SOURCE_PATH and using any of the following
|
||||
features: rpk, pq-experimental, underscore-wildcards"
|
||||
);
|
||||
} else {
|
||||
ensure_patches_applied(config).unwrap();
|
||||
if let Some(path) = &config.env.path {
|
||||
if !path.exists() {
|
||||
println!("cargo:warning=built path doesn't exist: {}", path.display());
|
||||
}
|
||||
return path.into();
|
||||
}
|
||||
|
||||
let mut cfg = get_boringssl_cmake_config(config);
|
||||
|
||||
if let Ok(threads) = std::thread::available_parallelism() {
|
||||
cfg.env("CMAKE_BUILD_PARALLEL_LEVEL", threads.to_string());
|
||||
let num_jobs = std::env::var("NUM_JOBS").ok().or_else(|| {
|
||||
std::thread::available_parallelism()
|
||||
.ok()
|
||||
.map(|t| t.to_string())
|
||||
});
|
||||
if let Some(num_jobs) = num_jobs {
|
||||
cfg.env("CMAKE_BUILD_PARALLEL_LEVEL", num_jobs);
|
||||
}
|
||||
|
||||
if config.features.fips {
|
||||
let (clang, clangxx) = verify_fips_clang_version();
|
||||
cfg.define("CMAKE_C_COMPILER", clang)
|
||||
.define("CMAKE_CXX_COMPILER", clangxx)
|
||||
.define("CMAKE_ASM_COMPILER", clang)
|
||||
cfg.define("CMAKE_C_COMPILER", "clang")
|
||||
.define("CMAKE_CXX_COMPILER", "clang++")
|
||||
.define("CMAKE_ASM_COMPILER", "clang")
|
||||
.define("FIPS", "1");
|
||||
}
|
||||
|
||||
if config.features.fips_link_precompiled {
|
||||
cfg.define("FIPS", "1");
|
||||
}
|
||||
|
||||
cfg.build_target("ssl").build();
|
||||
cfg.build_target("crypto").build()
|
||||
let path = cfg.build_target("crypto").build();
|
||||
let build_dir = path.join("build");
|
||||
if build_dir.exists() {
|
||||
build_dir
|
||||
} else {
|
||||
path
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn link_in_precompiled_bcm_o(config: &Config) {
|
||||
println!("cargo:warning=linking in precompiled `bcm.o` module");
|
||||
|
||||
let bssl_dir = built_boring_source_path(config);
|
||||
let bcm_o_src_path = config.env.precompiled_bcm_o.as_ref()
|
||||
.expect("`fips-link-precompiled` requires `BORING_BSSL_FIPS_PRECOMPILED_BCM_O` env variable to be specified");
|
||||
|
||||
let libcrypto_path = bssl_dir
|
||||
.join("build/crypto/libcrypto.a")
|
||||
.canonicalize()
|
||||
.unwrap();
|
||||
|
||||
let bcm_o_dst_path = bssl_dir.join("build/bcm-fips.o");
|
||||
|
||||
fs::copy(bcm_o_src_path, &bcm_o_dst_path).unwrap();
|
||||
|
||||
// check that fips module is named as expected
|
||||
let out = run_command(
|
||||
Command::new("ar")
|
||||
.arg("t")
|
||||
.arg(&libcrypto_path)
|
||||
.arg("bcm.o"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
String::from_utf8(out.stdout).unwrap().trim(),
|
||||
"bcm.o",
|
||||
"failed to verify FIPS module name"
|
||||
);
|
||||
|
||||
// insert fips bcm.o before bcm.o into libcrypto.a,
|
||||
// so for all duplicate symbols the older fips bcm.o is used
|
||||
// (this causes the need for extra linker flags to deal with duplicate symbols)
|
||||
// (as long as the newer module does not define new symbols, one may also remove it,
|
||||
// but once there are new symbols it would cause missing symbols at linking stage)
|
||||
run_command(
|
||||
Command::new("ar")
|
||||
.args(["rb", "bcm.o"])
|
||||
.args([&libcrypto_path, &bcm_o_dst_path]),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn get_cpp_runtime_lib(config: &Config) -> Option<String> {
|
||||
if let Some(ref cpp_lib) = config.env.cpp_runtime_lib {
|
||||
return cpp_lib.clone().into_string().ok();
|
||||
}
|
||||
|
||||
// TODO(rmehra): figure out how to do this for windows
|
||||
if env::var_os("CARGO_CFG_UNIX").is_some() {
|
||||
match env::var("CARGO_CFG_TARGET_OS").unwrap().as_ref() {
|
||||
"macos" | "ios" => Some("c++".into()),
|
||||
_ => Some("stdc++".into()),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
match &*config.target_os {
|
||||
"macos" | "ios" | "freebsd" | "openbsd" | "android" => Some("c++".into()),
|
||||
_ if config.unix || config.target_env == "gnu" => Some("stdc++".into()),
|
||||
// TODO(rmehra): figure out how to do this for windows
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let config = Config::from_env();
|
||||
let bssl_dir = built_boring_source_path(&config);
|
||||
let build_path = get_boringssl_platform_output_path(&config);
|
||||
ensure_patches_applied(&config).unwrap();
|
||||
if !config.env.docs_rs {
|
||||
emit_link_directives(&config);
|
||||
}
|
||||
generate_bindings(&config);
|
||||
}
|
||||
|
||||
if config.is_bazel || (config.features.fips && config.env.path.is_some()) {
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}/lib/{}",
|
||||
bssl_dir.display(),
|
||||
build_path
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}/build/crypto/{}",
|
||||
bssl_dir.display(),
|
||||
build_path
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}/build/ssl/{}",
|
||||
bssl_dir.display(),
|
||||
build_path
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}/build/{}",
|
||||
bssl_dir.display(),
|
||||
build_path
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}/build",
|
||||
bssl_dir.display(),
|
||||
);
|
||||
fn emit_link_directives(config: &Config) {
|
||||
let bssl_dir = build_boringssl_or_get_prebuilt(config);
|
||||
let msvc_lib_subdir = msvc_lib_subdir(config);
|
||||
|
||||
let subdirs =
|
||||
if config.is_bazel || (config.features.is_fips_like() && config.env.path.is_some()) {
|
||||
&["lib"][..]
|
||||
} else {
|
||||
&["lib", "crypto", "ssl", ""][..]
|
||||
};
|
||||
|
||||
for subdir in subdirs {
|
||||
let dir = bssl_dir.join(subdir);
|
||||
let dir = msvc_lib_subdir
|
||||
.map(|s| dir.join(s))
|
||||
.filter(|d| d.exists())
|
||||
.unwrap_or(dir);
|
||||
println!("cargo:rustc-link-search=native={}", dir.display());
|
||||
}
|
||||
|
||||
if config.features.fips_link_precompiled {
|
||||
link_in_precompiled_bcm_o(&config);
|
||||
}
|
||||
|
||||
if let Some(cpp_lib) = get_cpp_runtime_lib(&config) {
|
||||
println!("cargo:rustc-link-lib={}", cpp_lib);
|
||||
if let Some(cpp_lib) = get_cpp_runtime_lib(config) {
|
||||
println!("cargo:rustc-link-lib={cpp_lib}");
|
||||
}
|
||||
println!("cargo:rustc-link-lib=static=crypto");
|
||||
println!("cargo:rustc-link-lib=static=ssl");
|
||||
|
||||
if config.target_os == "windows" {
|
||||
// Rust 1.87.0 compat - https://github.com/rust-lang/rust/pull/138233
|
||||
println!("cargo:rustc-link-lib=advapi32");
|
||||
}
|
||||
}
|
||||
|
||||
fn check_include_path(path: PathBuf) -> Result<PathBuf, String> {
|
||||
if path.join("openssl").join("x509v3.h").exists() {
|
||||
Ok(path)
|
||||
} else {
|
||||
Err(format!(
|
||||
"Include path {} {}",
|
||||
path.display(),
|
||||
if !path.exists() {
|
||||
"does not exist"
|
||||
} else {
|
||||
"does not have expected openssl/x509v3.h"
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_bindings(config: &Config) {
|
||||
let include_path = config.env.include_path.clone().unwrap_or_else(|| {
|
||||
if let Some(bssl_path) = &config.env.path {
|
||||
return bssl_path.join("include");
|
||||
return check_include_path(bssl_path.join("include"))
|
||||
.expect("config has invalid include path");
|
||||
}
|
||||
|
||||
let src_path = get_boringssl_source_path(&config);
|
||||
let candidate = src_path.join("include");
|
||||
|
||||
if candidate.exists() {
|
||||
candidate
|
||||
} else {
|
||||
src_path.join("src").join("include")
|
||||
}
|
||||
let src_path = get_boringssl_source_path(config);
|
||||
check_include_path(src_path.join("include"))
|
||||
.or_else(|_| check_include_path(src_path.join("src").join("include")))
|
||||
.expect("can't find usable include path")
|
||||
});
|
||||
|
||||
// bindgen 0.70 replaced the run-time layout tests with compile-time ones,
|
||||
// but they depend on std::mem::offset_of, stabilized in 1.77.
|
||||
let supports_layout_tests = autocfg::new().probe_rustc_version(1, 77);
|
||||
let target_rust_version =
|
||||
bindgen::RustTarget::stable(77, 0).expect("bindgen does not recognize target rust version");
|
||||
|
||||
let mut builder = bindgen::Builder::default()
|
||||
.rust_target(bindgen::RustTarget::Stable_1_68) // bindgen MSRV is 1.70, so this is enough
|
||||
.rust_target(target_rust_version) // bindgen MSRV is 1.70, so this is enough
|
||||
.derive_copy(true)
|
||||
.derive_debug(true)
|
||||
.derive_default(true)
|
||||
.derive_eq(true)
|
||||
.derive_eq(false)
|
||||
.derive_partialeq(false)
|
||||
.default_enum_style(bindgen::EnumVariation::NewType {
|
||||
is_bitfield: false,
|
||||
is_global: false,
|
||||
@ -733,10 +668,10 @@ fn main() {
|
||||
.generate_comments(true)
|
||||
.fit_macro_constants(false)
|
||||
.size_t_is_usize(true)
|
||||
.layout_tests(supports_layout_tests)
|
||||
.layout_tests(config.env.debug.is_some())
|
||||
.prepend_enum_name(true)
|
||||
.blocklist_type("max_align_t") // Not supported by bindgen on all targets, not used by BoringSSL
|
||||
.clang_args(get_extra_clang_args_for_bindgen(&config))
|
||||
.clang_args(get_extra_clang_args_for_bindgen(config))
|
||||
.clang_arg("-I")
|
||||
.clang_arg(include_path.display().to_string());
|
||||
|
||||
@ -744,9 +679,20 @@ fn main() {
|
||||
builder = builder
|
||||
.clang_arg("--sysroot")
|
||||
.clang_arg(sysroot.display().to_string());
|
||||
|
||||
// we need to add special platform header file with env for support cross building
|
||||
let target_include_dir = sysroot.join(format!(
|
||||
"usr/include/{}-{}-{}",
|
||||
config.target_arch, config.target_os, config.target_env
|
||||
));
|
||||
if target_include_dir.is_dir() {
|
||||
builder = builder
|
||||
.clang_arg("-I")
|
||||
.clang_arg(target_include_dir.display().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let headers = [
|
||||
let must_have_headers = [
|
||||
"aes.h",
|
||||
"asn1_mac.h",
|
||||
"asn1t.h",
|
||||
@ -759,33 +705,63 @@ fn main() {
|
||||
"curve25519.h",
|
||||
"des.h",
|
||||
"dtls1.h",
|
||||
"err.h",
|
||||
"hkdf.h",
|
||||
#[cfg(not(feature = "fips"))]
|
||||
"hpke.h",
|
||||
"ossl_typ.h",
|
||||
"pkcs12.h",
|
||||
"poly1305.h",
|
||||
"x509v3.h",
|
||||
];
|
||||
let headers = [
|
||||
"hmac.h",
|
||||
"hrss.h",
|
||||
"md4.h",
|
||||
"md5.h",
|
||||
"mlkem.h",
|
||||
"obj_mac.h",
|
||||
"objects.h",
|
||||
"opensslv.h",
|
||||
"ossl_typ.h",
|
||||
"pkcs12.h",
|
||||
"poly1305.h",
|
||||
"rand.h",
|
||||
"rc4.h",
|
||||
"ripemd.h",
|
||||
"siphash.h",
|
||||
"srtp.h",
|
||||
"trust_token.h",
|
||||
"x509v3.h",
|
||||
];
|
||||
for header in &headers {
|
||||
builder = builder.header(include_path.join("openssl").join(header).to_str().unwrap());
|
||||
for (i, header) in must_have_headers.into_iter().chain(headers).enumerate() {
|
||||
let header_path = include_path.join("openssl").join(header);
|
||||
if header_path.exists() {
|
||||
builder = builder.header(header_path.to_str().unwrap());
|
||||
} else {
|
||||
let err = format!("'openssl/{header}' is missing from '{}'. The include path may be incorrect or contain an outdated version of OpenSSL/BoringSSL", include_path.display());
|
||||
let required = i < must_have_headers.len();
|
||||
println!(
|
||||
"cargo::{}={err}",
|
||||
if required { "error" } else { "warning" }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let bindings = builder.generate().expect("Unable to generate bindings");
|
||||
let mut source_code = Vec::new();
|
||||
bindings
|
||||
.write_to_file(config.out_dir.join("bindings.rs"))
|
||||
.expect("Couldn't write bindings!");
|
||||
.write(Box::new(&mut source_code))
|
||||
.expect("Couldn't serialize bindings!");
|
||||
ensure_err_lib_enum_is_named(&mut source_code);
|
||||
fs::write(config.out_dir.join("bindings.rs"), source_code).expect("Couldn't write bindings!");
|
||||
}
|
||||
|
||||
/// err.h has anonymous `enum { ERR_LIB_NONE = 1 }`, which makes a dodgy `_bindgen_ty_1` name
|
||||
fn ensure_err_lib_enum_is_named(source_code: &mut Vec<u8>) {
|
||||
let src = String::from_utf8_lossy(source_code);
|
||||
let enum_type = src
|
||||
.split_once("ERR_LIB_SSL:")
|
||||
.and_then(|(_, def)| Some(def.split_once('=')?.0))
|
||||
.unwrap_or("_bindgen_ty_1");
|
||||
|
||||
source_code.extend_from_slice(
|
||||
format!("\n/// Newtype for [`ERR_LIB_SSL`] constants\npub use {enum_type} as ErrLib;\n")
|
||||
.as_bytes(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 44b3df6f03d85c901767250329c571db405122d5
|
||||
Subproject commit 91a66a59b6c1435120ff83e245d7719411294386
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 853ca1ea1168dff08011e5d42d94609cc0ca2e27
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,19 @@
|
||||
https://github.com/google/boringssl/compare/master...cloudflare:boringssl:underscore-wildcards
|
||||
From 2128aa4382ba668e2c4f77bf18da719b2ad0087e Mon Sep 17 00:00:00 2001
|
||||
From: Anthony Ramine <aramine@cloudflare.com>
|
||||
Date: Fri, 5 Dec 2025 08:19:56 +0100
|
||||
Subject: [PATCH] Introduce X509_CHECK_FLAG_UNDERSCORE_WILDCARDS
|
||||
|
||||
--- a/src/crypto/x509v3/v3_utl.c
|
||||
+++ b/src/crypto/x509v3/v3_utl.c
|
||||
@@ -790,7 +790,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len,
|
||||
---
|
||||
crypto/x509/v3_utl.cc | 4 +++-
|
||||
crypto/x509/x509_test.cc | 25 +++++++++++++++++++++++++
|
||||
include/openssl/x509.h | 3 +++
|
||||
3 files changed, 31 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/crypto/x509/v3_utl.cc b/crypto/x509/v3_utl.cc
|
||||
index 015bbcad2..2b9b63430 100644
|
||||
--- a/crypto/x509/v3_utl.cc
|
||||
+++ b/crypto/x509/v3_utl.cc
|
||||
@@ -740,7 +740,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len,
|
||||
// Check that the part matched by the wildcard contains only
|
||||
// permitted characters and only matches a single label.
|
||||
for (p = wildcard_start; p != wildcard_end; ++p) {
|
||||
@ -13,9 +24,11 @@ https://github.com/google/boringssl/compare/master...cloudflare:boringssl:unders
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
--- a/src/crypto/x509/x509_test.cc
|
||||
+++ b/src/crypto/x509/x509_test.cc
|
||||
@@ -4500,6 +4500,31 @@ TEST(X509Test, Names) {
|
||||
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
|
||||
index c6ce62dd1..f284f421f 100644
|
||||
--- a/crypto/x509/x509_test.cc
|
||||
+++ b/crypto/x509/x509_test.cc
|
||||
@@ -5209,6 +5209,31 @@ TEST(X509Test, Names) {
|
||||
/*invalid_emails=*/{},
|
||||
/*flags=*/0,
|
||||
},
|
||||
@ -47,15 +60,20 @@ https://github.com/google/boringssl/compare/master...cloudflare:boringssl:unders
|
||||
};
|
||||
|
||||
size_t i = 0;
|
||||
--- a/src/include/openssl/x509c3.h
|
||||
+++ b/src/include/openssl/x509v3.h
|
||||
@@ -4497,6 +4497,8 @@ OPENSSL_EXPORT int X509_PURPOSE_get_id(const X509_PURPOSE *);
|
||||
#define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS 0
|
||||
// Skip the subject common name fallback if subjectAltNames is missing.
|
||||
diff --git a/include/openssl/x509.h b/include/openssl/x509.h
|
||||
index 926f365f4..cc538cceb 100644
|
||||
--- a/include/openssl/x509.h
|
||||
+++ b/include/openssl/x509.h
|
||||
@@ -3359,6 +3359,9 @@ OPENSSL_EXPORT int X509_VERIFY_PARAM_add1_host(X509_VERIFY_PARAM *param,
|
||||
// enabled when subjectAltNames is missing.
|
||||
#define X509_CHECK_FLAG_NEVER_CHECK_SUBJECT 0x20
|
||||
+// Allow underscores in DNS wildcard matches.
|
||||
+#define X509_CHECK_FLAG_UNDERSCORE_WILDCARDS 0x40
|
||||
|
||||
OPENSSL_EXPORT int X509_check_host(X509 *x, const char *chk, size_t chklen,
|
||||
unsigned int flags, char **peername);
|
||||
+// X509_CHECK_FLAG_UNDERSCORE_WILDCARDS allows underscores in DNS wildcard matches.
|
||||
+#define X509_CHECK_FLAG_UNDERSCORE_WILDCARDS 0x40
|
||||
+
|
||||
// X509_VERIFY_PARAM_set_hostflags sets the name-checking flags on |param| to
|
||||
// |flags|. |flags| should be a combination of |X509_CHECK_FLAG_*| constants.
|
||||
OPENSSL_EXPORT void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *param,
|
||||
--
|
||||
2.40.0
|
||||
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
non_upper_case_globals,
|
||||
unused_imports
|
||||
)]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::c_void;
|
||||
@ -19,11 +18,23 @@ use std::os::raw::{c_char, c_int, c_uint, c_ulong};
|
||||
#[allow(
|
||||
clippy::useless_transmute,
|
||||
clippy::derive_partial_eq_without_eq,
|
||||
clippy::ptr_offset_with_cast,
|
||||
dead_code
|
||||
)]
|
||||
mod generated {
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
}
|
||||
|
||||
// explicitly require presence of some symbols to check if the bindings worked
|
||||
pub use generated::{ssl_compliance_policy_t, ERR_add_error_data, SSL_set1_groups}; // if these are missing, your include path is incorrect or has a wrong version of boringssl
|
||||
pub use generated::{BIO_new, OPENSSL_free, SSL_ERROR_NONE}; // if these are missing, your include path is incorrect
|
||||
#[cfg(feature = "fips")]
|
||||
pub use generated::{FIPS_mode, SSL_CTX_set_compliance_policy}; // your include path is incorrect or has a version of boringssl without FIPS support
|
||||
#[cfg(feature = "mlkem")]
|
||||
pub use generated::{MLKEM768_encap, MLKEM768_private_key_from_seed}; // your include path is incorrect or has a version of boringssl without mlkem support
|
||||
#[cfg(feature = "rpk")]
|
||||
pub use generated::{SSL_CREDENTIAL_new_raw_public_key, SSL_CREDENTIAL_set1_spki}; // your include path is incorrect or has a version of boringssl without rpk support
|
||||
|
||||
pub use generated::*;
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
@ -31,18 +42,22 @@ pub type BN_ULONG = u64;
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
pub type BN_ULONG = u32;
|
||||
|
||||
#[must_use]
|
||||
pub const fn ERR_PACK(l: c_int, f: c_int, r: c_int) -> c_ulong {
|
||||
((l as c_ulong & 0x0FF) << 24) | ((f as c_ulong & 0xFFF) << 12) | (r as c_ulong & 0xFFF)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn ERR_GET_LIB(l: c_uint) -> c_int {
|
||||
((l >> 24) & 0x0FF) as c_int
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn ERR_GET_FUNC(l: c_uint) -> c_int {
|
||||
((l >> 12) & 0xFFF) as c_int
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn ERR_GET_REASON(l: c_uint) -> c_int {
|
||||
(l & 0xFFF) as c_int
|
||||
}
|
||||
|
||||
@ -7,69 +7,51 @@ description = "BoringSSL bindings"
|
||||
repository = { workspace = true }
|
||||
documentation = "https://docs.rs/boring"
|
||||
readme = "README.md"
|
||||
keywords = ["crypto", "tls", "ssl", "dtls"]
|
||||
keywords = ["tls", "ssl", "dtls", "post-quantum", "fips"]
|
||||
categories = ["cryptography", "api-bindings"]
|
||||
edition = { workspace = true }
|
||||
rust-version = "1.80"
|
||||
rust-version = { workspace = true }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["rpk", "pq-experimental", "underscore-wildcards"]
|
||||
features = ["rpk", "underscore-wildcards"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
# Controlling the build
|
||||
default = []
|
||||
|
||||
# Use a FIPS-validated version of boringssl.
|
||||
fips = ["fips-compat", "boring-sys/fips"]
|
||||
# Use a FIPS-validated version of BoringSSL.
|
||||
fips = ["boring-sys/fips"]
|
||||
|
||||
# Build with compatibility for the BoringSSL FIPS version, without enabling the
|
||||
# `fips` feature itself (useful e.g. if `fips-link-precompiled` is used with an
|
||||
# older BoringSSL version).
|
||||
fips-compat = []
|
||||
# **DO NOT USE** This will be removed without warning in future releases.
|
||||
legacy-compat-deprecated = []
|
||||
|
||||
# Link with precompiled FIPS-validated `bcm.o` module.
|
||||
fips-link-precompiled = ["boring-sys/fips-link-precompiled"]
|
||||
# **DO NOT USE** This will be removed without warning in future releases.
|
||||
# PQ is always enabled. This feature is a no-op, only for backwards compatibility.
|
||||
pq-experimental = []
|
||||
|
||||
# Interface for ML-KEM (FIPS 203) post-quantum key encapsulation. Does not affect ciphers used in TLS.
|
||||
mlkem = []
|
||||
|
||||
# SslCredential API
|
||||
credential = []
|
||||
|
||||
# Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250)
|
||||
# This feature is necessary in order to compile the bindings for the
|
||||
# default branch of boringSSL. Alternatively, a version of boringSSL that
|
||||
# implements the same feature set can be provided by setting
|
||||
# `BORING_BSSL{,_FIPS}_SOURCE_PATH` and `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`.
|
||||
rpk = ["boring-sys/rpk"]
|
||||
rpk = ["credential", "boring-sys/rpk"]
|
||||
|
||||
# Applies a patch to the boringSSL source code that enables support for PQ key
|
||||
# exchange. This feature is necessary in order to compile the bindings for the
|
||||
# default branch of boringSSL. Alternatively, a version of boringSSL that
|
||||
# implements the same feature set can be provided by setting
|
||||
# `BORING_BSSL{,_FIPS}_SOURCE_PATH` and `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`.
|
||||
pq-experimental = ["boring-sys/pq-experimental"]
|
||||
|
||||
# Applies a patch to enable
|
||||
# `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. Same caveats as
|
||||
# those for `pq-experimental` feature apply.
|
||||
# Applies a patch to enable `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. This
|
||||
# feature is necessary in order to compile the bindings for the default branch
|
||||
# of boringSSL. Alternatively, a version of boringSSL that implements the same
|
||||
# feature set can be provided by setting `BORING_BSSL{,_FIPS}_SOURCE_PATH` and
|
||||
# `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`.
|
||||
underscore-wildcards = ["boring-sys/underscore-wildcards"]
|
||||
|
||||
# Controlling key exchange preferences at compile time
|
||||
|
||||
# Choose key exchange preferences at compile time. This prevents the user from
|
||||
# choosing their own preferences.
|
||||
kx-safe-default = []
|
||||
|
||||
# Support PQ key exchange. The client will prefer classical key exchange, but
|
||||
# will upgrade to PQ key exchange if requested by the server. This is the
|
||||
# safest option if you don't know if the peer supports PQ key exchange. This
|
||||
# feature implies "kx-safe-default".
|
||||
kx-client-pq-supported = ["kx-safe-default"]
|
||||
|
||||
# Prefer PQ key exchange. The client will prefer PQ exchange, but fallback to
|
||||
# classical key exchange if requested by the server. This is the best option if
|
||||
# you know the peer supports PQ key exchange. This feature implies
|
||||
# "kx-safe-default" and "kx-client-pq-supported".
|
||||
kx-client-pq-preferred = ["kx-safe-default", "kx-client-pq-supported"]
|
||||
|
||||
# Disable key exchange involving non-NIST key exchange on the client side.
|
||||
# Implies "kx-safe-default".
|
||||
kx-client-nist-required = ["kx-safe-default"]
|
||||
# **DO NOT USE** This will be removed without warning in future releases.
|
||||
# Alias for 'fips', only for backwards compatibility.
|
||||
fips-precompiled = ["fips"]
|
||||
|
||||
[dependencies]
|
||||
bitflags = { workspace = true }
|
||||
|
||||
@ -43,18 +43,19 @@ fn mk_ca_cert() -> Result<(X509, PKey<Private>), ErrorStack> {
|
||||
let not_after = Asn1Time::days_from_now(365)?;
|
||||
cert_builder.set_not_after(¬_after)?;
|
||||
|
||||
cert_builder.append_extension(BasicConstraints::new().critical().ca().build()?)?;
|
||||
cert_builder.append_extension(BasicConstraints::new().critical().ca().build()?.as_ref())?;
|
||||
cert_builder.append_extension(
|
||||
KeyUsage::new()
|
||||
.critical()
|
||||
.key_cert_sign()
|
||||
.crl_sign()
|
||||
.build()?,
|
||||
.build()?
|
||||
.as_ref(),
|
||||
)?;
|
||||
|
||||
let subject_key_identifier =
|
||||
SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(None, None))?;
|
||||
cert_builder.append_extension(subject_key_identifier)?;
|
||||
cert_builder.append_extension(&subject_key_identifier)?;
|
||||
|
||||
cert_builder.sign(&privkey, MessageDigest::sha256())?;
|
||||
let cert = cert_builder.build();
|
||||
@ -106,7 +107,7 @@ fn mk_ca_signed_cert(
|
||||
let not_after = Asn1Time::days_from_now(365)?;
|
||||
cert_builder.set_not_after(¬_after)?;
|
||||
|
||||
cert_builder.append_extension(BasicConstraints::new().build()?)?;
|
||||
cert_builder.append_extension(BasicConstraints::new().build()?.as_ref())?;
|
||||
|
||||
cert_builder.append_extension(
|
||||
KeyUsage::new()
|
||||
@ -114,24 +115,25 @@ fn mk_ca_signed_cert(
|
||||
.non_repudiation()
|
||||
.digital_signature()
|
||||
.key_encipherment()
|
||||
.build()?,
|
||||
.build()?
|
||||
.as_ref(),
|
||||
)?;
|
||||
|
||||
let subject_key_identifier =
|
||||
SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(Some(ca_cert), None))?;
|
||||
cert_builder.append_extension(subject_key_identifier)?;
|
||||
cert_builder.append_extension(&subject_key_identifier)?;
|
||||
|
||||
let auth_key_identifier = AuthorityKeyIdentifier::new()
|
||||
.keyid(false)
|
||||
.issuer(false)
|
||||
.build(&cert_builder.x509v3_context(Some(ca_cert), None))?;
|
||||
cert_builder.append_extension(auth_key_identifier)?;
|
||||
cert_builder.append_extension(&auth_key_identifier)?;
|
||||
|
||||
let subject_alt_name = SubjectAlternativeName::new()
|
||||
.dns("*.example.com")
|
||||
.dns("hello.com")
|
||||
.build(&cert_builder.x509v3_context(Some(ca_cert), None))?;
|
||||
cert_builder.append_extension(subject_alt_name)?;
|
||||
cert_builder.append_extension(&subject_alt_name)?;
|
||||
|
||||
cert_builder.sign(ca_privkey, MessageDigest::sha256())?;
|
||||
let cert = cert_builder.build();
|
||||
@ -146,8 +148,8 @@ fn real_main() -> Result<(), ErrorStack> {
|
||||
// Verify that this cert was issued by this ca
|
||||
match ca_cert.issued(&cert) {
|
||||
Ok(()) => println!("Certificate verified!"),
|
||||
Err(ver_err) => println!("Failed to verify certificate: {}", ver_err),
|
||||
};
|
||||
Err(ver_err) => println!("Failed to verify certificate: {ver_err}"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -155,6 +157,6 @@ fn real_main() -> Result<(), ErrorStack> {
|
||||
fn main() {
|
||||
match real_main() {
|
||||
Ok(()) => println!("Finished."),
|
||||
Err(e) => println!("Error: {}", e),
|
||||
};
|
||||
Err(e) => println!("Error: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
//! ```
|
||||
//!
|
||||
use crate::ffi;
|
||||
use libc::{c_int, c_uint, size_t};
|
||||
use libc::c_int;
|
||||
use openssl_macros::corresponds;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr;
|
||||
@ -63,8 +63,8 @@ impl AesKey {
|
||||
|
||||
let mut aes_key = MaybeUninit::uninit();
|
||||
let r = ffi::AES_set_encrypt_key(
|
||||
key.as_ptr() as *const _,
|
||||
key.len() as c_uint * 8,
|
||||
key.as_ptr(),
|
||||
(key.len() * 8).try_into().map_err(|_| KeyError(()))?,
|
||||
aes_key.as_mut_ptr(),
|
||||
);
|
||||
if r == 0 {
|
||||
@ -87,8 +87,8 @@ impl AesKey {
|
||||
|
||||
let mut aes_key = MaybeUninit::uninit();
|
||||
let r = ffi::AES_set_decrypt_key(
|
||||
key.as_ptr() as *const _,
|
||||
key.len() as c_uint * 8,
|
||||
key.as_ptr(),
|
||||
(key.len() * 8).try_into().map_err(|_| KeyError(()))?,
|
||||
aes_key.as_mut_ptr(),
|
||||
);
|
||||
|
||||
@ -125,12 +125,11 @@ pub fn wrap_key(
|
||||
assert!(out.len() >= in_.len() + 8); // Ciphertext is 64 bits longer (see 2.2.1)
|
||||
|
||||
let written = ffi::AES_wrap_key(
|
||||
&key.0 as *const _ as *mut _, // this is safe, the implementation only uses the key as a const pointer.
|
||||
iv.as_ref()
|
||||
.map_or(ptr::null(), |iv| iv.as_ptr() as *const _),
|
||||
out.as_ptr() as *mut _,
|
||||
in_.as_ptr() as *const _,
|
||||
in_.len() as size_t,
|
||||
std::ptr::addr_of!(key.0).cast_mut(), // this is safe, the implementation only uses the key as a const pointer.
|
||||
iv.as_ref().map_or(ptr::null(), |iv| iv.as_ptr()),
|
||||
out.as_mut_ptr(),
|
||||
in_.as_ptr(),
|
||||
in_.len(),
|
||||
);
|
||||
if written <= 0 {
|
||||
Err(KeyError(()))
|
||||
@ -164,12 +163,11 @@ pub fn unwrap_key(
|
||||
assert!(out.len() + 8 <= in_.len());
|
||||
|
||||
let written = ffi::AES_unwrap_key(
|
||||
&key.0 as *const _ as *mut _, // this is safe, the implementation only uses the key as a const pointer.
|
||||
iv.as_ref()
|
||||
.map_or(ptr::null(), |iv| iv.as_ptr() as *const _),
|
||||
out.as_ptr() as *mut _,
|
||||
in_.as_ptr() as *const _,
|
||||
in_.len() as size_t,
|
||||
std::ptr::addr_of!(key.0).cast_mut(), // this is safe, the implementation only uses the key as a const pointer.
|
||||
iv.as_ref().map_or(ptr::null(), |iv| iv.as_ptr().cast()),
|
||||
out.as_ptr().cast_mut(),
|
||||
in_.as_ptr().cast(),
|
||||
in_.len(),
|
||||
);
|
||||
|
||||
if written <= 0 {
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
//! ```
|
||||
use crate::ffi;
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use libc::{c_char, c_int, c_long, time_t};
|
||||
use libc::{c_int, c_long, time_t};
|
||||
use std::cmp::Ordering;
|
||||
use std::ffi::CString;
|
||||
use std::fmt;
|
||||
@ -63,20 +63,19 @@ foreign_type_and_impl_send_sync! {
|
||||
|
||||
impl fmt::Display for Asn1GeneralizedTimeRef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
unsafe {
|
||||
let mem_bio = match MemBio::new() {
|
||||
Err(_) => return f.write_str("error"),
|
||||
Ok(m) => m,
|
||||
};
|
||||
let print_result = cvt(ffi::ASN1_GENERALIZEDTIME_print(
|
||||
mem_bio.as_ptr(),
|
||||
self.as_ptr(),
|
||||
));
|
||||
match print_result {
|
||||
Err(_) => f.write_str("error"),
|
||||
Ok(_) => f.write_str(str::from_utf8_unchecked(mem_bio.get_buf())),
|
||||
}
|
||||
}
|
||||
let bio = MemBio::new().ok();
|
||||
let msg = bio
|
||||
.as_ref()
|
||||
.and_then(|mem_bio| unsafe {
|
||||
cvt(ffi::ASN1_GENERALIZEDTIME_print(
|
||||
mem_bio.as_ptr(),
|
||||
self.as_ptr(),
|
||||
))
|
||||
.ok()?;
|
||||
str::from_utf8(mem_bio.get_buf()).ok()
|
||||
})
|
||||
.unwrap_or("error");
|
||||
f.write_str(msg)
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,11 +142,13 @@ impl Asn1Type {
|
||||
pub const BMPSTRING: Asn1Type = Asn1Type(ffi::V_ASN1_BMPSTRING);
|
||||
|
||||
/// Constructs an `Asn1Type` from a raw OpenSSL value.
|
||||
#[must_use]
|
||||
pub fn from_raw(value: c_int) -> Self {
|
||||
Asn1Type(value)
|
||||
}
|
||||
|
||||
/// Returns the raw OpenSSL value represented by this type.
|
||||
#[must_use]
|
||||
pub fn as_raw(&self) -> c_int {
|
||||
self.0
|
||||
}
|
||||
@ -304,7 +305,8 @@ impl Asn1Time {
|
||||
|
||||
/// Creates a new time on specified interval in days from now
|
||||
pub fn days_from_now(days: u32) -> Result<Asn1Time, ErrorStack> {
|
||||
Asn1Time::from_period(days as c_long * 60 * 60 * 24)
|
||||
// the type varies between platforms, so both into() and try_into() trigger Clippy lints
|
||||
Self::from_period((days * 60 * 60 * 24) as _)
|
||||
}
|
||||
|
||||
/// Creates a new time from the specified `time_t` value
|
||||
@ -313,7 +315,10 @@ impl Asn1Time {
|
||||
ffi::init();
|
||||
|
||||
unsafe {
|
||||
let handle = cvt_p(ffi::ASN1_TIME_set(ptr::null_mut(), time))?;
|
||||
// for higher musl version, need to convert i32 to i64
|
||||
// https://github.com/rust-lang/libc/issues/1848
|
||||
#[allow(clippy::useless_conversion)]
|
||||
let handle = cvt_p(ffi::ASN1_TIME_set(ptr::null_mut(), time.into()))?;
|
||||
Ok(Asn1Time::from_ptr(handle))
|
||||
}
|
||||
}
|
||||
@ -323,7 +328,7 @@ impl Asn1Time {
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn from_str(s: &str) -> Result<Asn1Time, ErrorStack> {
|
||||
unsafe {
|
||||
let s = CString::new(s).unwrap();
|
||||
let s = CString::new(s).map_err(ErrorStack::internal_error)?;
|
||||
|
||||
let time = Asn1Time::new()?;
|
||||
cvt(ffi::ASN1_TIME_set_string(time.as_ptr(), s.as_ptr()))?;
|
||||
@ -403,7 +408,7 @@ impl Asn1StringRef {
|
||||
return Err(ErrorStack::get());
|
||||
}
|
||||
|
||||
Ok(OpensslString::from_ptr(ptr as *mut c_char))
|
||||
Ok(OpensslString::from_ptr(ptr.cast()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -414,17 +419,20 @@ impl Asn1StringRef {
|
||||
///
|
||||
/// [`as_utf8`]: struct.Asn1String.html#method.as_utf8
|
||||
#[corresponds(ASN1_STRING_get0_data)]
|
||||
#[must_use]
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
unsafe { slice::from_raw_parts(ASN1_STRING_get0_data(self.as_ptr()), self.len()) }
|
||||
}
|
||||
|
||||
/// Returns the number of bytes in the string.
|
||||
#[corresponds(ASN1_STRING_length)]
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
unsafe { ffi::ASN1_STRING_length(self.as_ptr()) as usize }
|
||||
}
|
||||
|
||||
/// Determines if the string is empty.
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
@ -472,6 +480,7 @@ impl Asn1IntegerRef {
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
#[allow(missing_docs)]
|
||||
#[deprecated(since = "0.10.6", note = "use to_bn instead")]
|
||||
#[must_use]
|
||||
pub fn get(&self) -> i64 {
|
||||
unsafe { crate::ffi::ASN1_INTEGER_get(self.as_ptr()) as i64 }
|
||||
}
|
||||
@ -494,7 +503,12 @@ impl Asn1IntegerRef {
|
||||
/// [`bn`]: ../bn/struct.BigNumRef.html#method.to_asn1_integer
|
||||
#[corresponds(ASN1_INTEGER_set)]
|
||||
pub fn set(&mut self, value: i32) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(crate::ffi::ASN1_INTEGER_set(self.as_ptr(), value as c_long)).map(|_| ()) }
|
||||
unsafe {
|
||||
cvt(crate::ffi::ASN1_INTEGER_set(
|
||||
self.as_ptr(),
|
||||
c_long::from(value),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -513,17 +527,33 @@ foreign_type_and_impl_send_sync! {
|
||||
impl Asn1BitStringRef {
|
||||
/// Returns the Asn1BitString as a slice.
|
||||
#[corresponds(ASN1_STRING_get0_data)]
|
||||
#[must_use]
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
unsafe { slice::from_raw_parts(ASN1_STRING_get0_data(self.as_ptr() as *mut _), self.len()) }
|
||||
unsafe {
|
||||
let ptr = ASN1_STRING_get0_data(self.as_ptr().cast());
|
||||
if ptr.is_null() {
|
||||
return &[];
|
||||
}
|
||||
slice::from_raw_parts(ptr, self.len())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the Asn1BitString as a str, if possible.
|
||||
#[corresponds(ASN1_STRING_get0_data)]
|
||||
#[must_use]
|
||||
pub fn to_str(&self) -> Option<&str> {
|
||||
str::from_utf8(self.as_slice()).ok()
|
||||
}
|
||||
|
||||
/// Returns the number of bytes in the string.
|
||||
#[corresponds(ASN1_STRING_length)]
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
unsafe { ffi::ASN1_STRING_length(self.as_ptr() as *const _) as usize }
|
||||
unsafe { ffi::ASN1_STRING_length(self.as_ptr().cast_const()) as usize }
|
||||
}
|
||||
|
||||
/// Determines if the string is empty.
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
@ -560,8 +590,8 @@ impl Asn1Object {
|
||||
pub fn from_str(txt: &str) -> Result<Asn1Object, ErrorStack> {
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let txt = CString::new(txt).unwrap();
|
||||
let obj: *mut ffi::ASN1_OBJECT = cvt_p(ffi::OBJ_txt2obj(txt.as_ptr() as *const _, 0))?;
|
||||
let txt = CString::new(txt).map_err(ErrorStack::internal_error)?;
|
||||
let obj: *mut ffi::ASN1_OBJECT = cvt_p(ffi::OBJ_txt2obj(txt.as_ptr(), 0))?;
|
||||
Ok(Asn1Object::from_ptr(obj))
|
||||
}
|
||||
}
|
||||
@ -569,26 +599,38 @@ impl Asn1Object {
|
||||
|
||||
impl Asn1ObjectRef {
|
||||
/// Returns the NID associated with this OID.
|
||||
#[must_use]
|
||||
pub fn nid(&self) -> Nid {
|
||||
unsafe { Nid::from_raw(ffi::OBJ_obj2nid(self.as_ptr())) }
|
||||
}
|
||||
|
||||
/// Returns the numerical string OID of this object.
|
||||
///
|
||||
/// This corresponds to [`OBJ_obj2txt`] with `no_name = 1`.
|
||||
///
|
||||
/// [`OBJ_obj2txt`]: https://www.openssl.org/docs/man1.1.1/man3/OBJ_obj2txt.html
|
||||
pub fn oid_string(&self) -> String {
|
||||
self.to_text(true)
|
||||
}
|
||||
|
||||
// To promote this to `pub`, the call-site parameter meaning ought to be clearer
|
||||
fn to_text(&self, no_name: bool) -> String {
|
||||
unsafe {
|
||||
let mut buf = [0u8; 80];
|
||||
let len = ffi::OBJ_obj2txt(
|
||||
buf.as_mut_ptr().cast(),
|
||||
buf.len() as c_int,
|
||||
self.as_ptr(),
|
||||
no_name as c_int,
|
||||
);
|
||||
String::from_utf8_lossy(&buf[..len as usize]).into_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Asn1ObjectRef {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
unsafe {
|
||||
let mut buf = [0; 80];
|
||||
let len = ffi::OBJ_obj2txt(
|
||||
buf.as_mut_ptr() as *mut _,
|
||||
buf.len() as c_int,
|
||||
self.as_ptr(),
|
||||
0,
|
||||
);
|
||||
match str::from_utf8(&buf[..len as usize]) {
|
||||
Err(_) => fmt.write_str("error"),
|
||||
Ok(s) => fmt.write_str(s),
|
||||
}
|
||||
}
|
||||
fmt.write_str(&self.to_text(false))
|
||||
}
|
||||
}
|
||||
|
||||
@ -691,4 +733,13 @@ mod tests {
|
||||
.map(|object| object.to_string())
|
||||
.expect_err("parsing invalid OID should fail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_to_text() {
|
||||
let oid = "2.16.840.1.101.3.4.2.1";
|
||||
let object = Asn1Object::from_str(oid).unwrap();
|
||||
assert_eq!(object.to_text(false), Nid::SHA256.long_name().unwrap());
|
||||
assert_eq!(object.to_text(true), oid.to_string());
|
||||
assert_eq!(object.oid_string(), oid.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ use openssl_macros::corresponds;
|
||||
///
|
||||
/// Panics if the input length or computed output length overflow a signed C integer.
|
||||
#[corresponds(EVP_EncodeBlock)]
|
||||
#[must_use]
|
||||
pub fn encode_block(src: &[u8]) -> String {
|
||||
assert!(src.len() <= c_int::MAX as usize);
|
||||
let src_len = src.len();
|
||||
@ -101,7 +102,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_encode_block() {
|
||||
assert_eq!("".to_string(), encode_block(b""));
|
||||
assert_eq!(String::new(), encode_block(b""));
|
||||
assert_eq!("Zg==".to_string(), encode_block(b"f"));
|
||||
assert_eq!("Zm8=".to_string(), encode_block(b"fo"));
|
||||
assert_eq!("Zm9v".to_string(), encode_block(b"foo"));
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
use crate::ffi;
|
||||
use crate::ffi::BIO_new_mem_buf;
|
||||
use std::marker::PhantomData;
|
||||
use std::ptr;
|
||||
use std::slice;
|
||||
|
||||
use crate::cvt_p;
|
||||
use crate::error::ErrorStack;
|
||||
use crate::ffi;
|
||||
use crate::ffi::BIO_new_mem_buf;
|
||||
use crate::try_int;
|
||||
|
||||
pub struct MemBioSlice<'a>(*mut ffi::BIO, PhantomData<&'a [u8]>);
|
||||
|
||||
@ -19,20 +20,9 @@ impl Drop for MemBioSlice<'_> {
|
||||
|
||||
impl<'a> MemBioSlice<'a> {
|
||||
pub fn new(buf: &'a [u8]) -> Result<MemBioSlice<'a>, ErrorStack> {
|
||||
#[cfg(not(feature = "fips-compat"))]
|
||||
type BufLen = isize;
|
||||
#[cfg(feature = "fips-compat")]
|
||||
type BufLen = libc::c_int;
|
||||
|
||||
ffi::init();
|
||||
|
||||
assert!(buf.len() <= BufLen::MAX as usize);
|
||||
let bio = unsafe {
|
||||
cvt_p(BIO_new_mem_buf(
|
||||
buf.as_ptr() as *const _,
|
||||
buf.len() as BufLen,
|
||||
))?
|
||||
};
|
||||
let bio = unsafe { cvt_p(BIO_new_mem_buf(buf.as_ptr().cast(), try_int(buf.len())?))? };
|
||||
|
||||
Ok(MemBioSlice(bio, PhantomData))
|
||||
}
|
||||
@ -68,7 +58,10 @@ impl MemBio {
|
||||
unsafe {
|
||||
let mut ptr = ptr::null_mut();
|
||||
let len = ffi::BIO_get_mem_data(self.0, &mut ptr);
|
||||
slice::from_raw_parts(ptr as *const _ as *const _, len as usize)
|
||||
if ptr.is_null() || len < 0 {
|
||||
return &[];
|
||||
}
|
||||
slice::from_raw_parts(ptr.cast_const().cast(), len as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
//! [`BIGNUM`]: https://wiki.openssl.org/index.php/Manual:Bn_internal(3)
|
||||
use crate::ffi;
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use libc::{c_int, size_t};
|
||||
use libc::c_int;
|
||||
use std::cmp::Ordering;
|
||||
use std::ffi::CString;
|
||||
use std::ops::{Add, Deref, Div, Mul, Neg, Rem, Shl, Shr, Sub};
|
||||
@ -121,19 +121,19 @@ impl BigNumRef {
|
||||
/// Adds a `u32` to `self`.
|
||||
#[corresponds(BN_add_word)]
|
||||
pub fn add_word(&mut self, w: u32) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::BN_add_word(self.as_ptr(), w as ffi::BN_ULONG)).map(|_| ()) }
|
||||
unsafe { cvt(ffi::BN_add_word(self.as_ptr(), ffi::BN_ULONG::from(w))) }
|
||||
}
|
||||
|
||||
/// Subtracts a `u32` from `self`.
|
||||
#[corresponds(BN_sub_word)]
|
||||
pub fn sub_word(&mut self, w: u32) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::BN_sub_word(self.as_ptr(), w as ffi::BN_ULONG)).map(|_| ()) }
|
||||
unsafe { cvt(ffi::BN_sub_word(self.as_ptr(), ffi::BN_ULONG::from(w))) }
|
||||
}
|
||||
|
||||
/// Multiplies a `u32` by `self`.
|
||||
#[corresponds(BN_mul_word)]
|
||||
pub fn mul_word(&mut self, w: u32) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::BN_mul_word(self.as_ptr(), w as ffi::BN_ULONG)).map(|_| ()) }
|
||||
unsafe { cvt(ffi::BN_mul_word(self.as_ptr(), ffi::BN_ULONG::from(w))) }
|
||||
}
|
||||
|
||||
/// Divides `self` by a `u32`, returning the remainder.
|
||||
@ -168,13 +168,13 @@ impl BigNumRef {
|
||||
/// number less than `self` in `rnd`.
|
||||
#[corresponds(BN_rand_range)]
|
||||
pub fn rand_range(&self, rnd: &mut BigNumRef) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::BN_rand_range(rnd.as_ptr(), self.as_ptr())).map(|_| ()) }
|
||||
unsafe { cvt(ffi::BN_rand_range(rnd.as_ptr(), self.as_ptr())) }
|
||||
}
|
||||
|
||||
/// The cryptographically weak counterpart to `rand_in_range`.
|
||||
#[corresponds(BN_pseudo_rand_range)]
|
||||
pub fn pseudo_rand_range(&self, rnd: &mut BigNumRef) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::BN_pseudo_rand_range(rnd.as_ptr(), self.as_ptr())).map(|_| ()) }
|
||||
unsafe { cvt(ffi::BN_pseudo_rand_range(rnd.as_ptr(), self.as_ptr())) }
|
||||
}
|
||||
|
||||
/// Sets bit `n`. Equivalent to `self |= (1 << n)`.
|
||||
@ -183,7 +183,7 @@ impl BigNumRef {
|
||||
#[corresponds(BN_set_bit)]
|
||||
#[allow(clippy::useless_conversion)]
|
||||
pub fn set_bit(&mut self, n: i32) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::BN_set_bit(self.as_ptr(), n.into())).map(|_| ()) }
|
||||
unsafe { cvt(ffi::BN_set_bit(self.as_ptr(), n.into())) }
|
||||
}
|
||||
|
||||
/// Clears bit `n`, setting it to 0. Equivalent to `self &= ~(1 << n)`.
|
||||
@ -192,12 +192,13 @@ impl BigNumRef {
|
||||
#[corresponds(BN_clear_bit)]
|
||||
#[allow(clippy::useless_conversion)]
|
||||
pub fn clear_bit(&mut self, n: i32) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::BN_clear_bit(self.as_ptr(), n.into())).map(|_| ()) }
|
||||
unsafe { cvt(ffi::BN_clear_bit(self.as_ptr(), n.into())) }
|
||||
}
|
||||
|
||||
/// Returns `true` if the `n`th bit of `self` is set to 1, `false` otherwise.
|
||||
#[corresponds(BN_is_bit_set)]
|
||||
#[allow(clippy::useless_conversion)]
|
||||
#[must_use]
|
||||
pub fn is_bit_set(&self, n: i32) -> bool {
|
||||
unsafe { ffi::BN_is_bit_set(self.as_ptr(), n.into()) == 1 }
|
||||
}
|
||||
@ -208,19 +209,19 @@ impl BigNumRef {
|
||||
#[corresponds(BN_mask_bits)]
|
||||
#[allow(clippy::useless_conversion)]
|
||||
pub fn mask_bits(&mut self, n: i32) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::BN_mask_bits(self.as_ptr(), n.into())).map(|_| ()) }
|
||||
unsafe { cvt(ffi::BN_mask_bits(self.as_ptr(), n.into())) }
|
||||
}
|
||||
|
||||
/// Places `a << 1` in `self`. Equivalent to `self * 2`.
|
||||
#[corresponds(BN_lshift1)]
|
||||
pub fn lshift1(&mut self, a: &BigNumRef) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::BN_lshift1(self.as_ptr(), a.as_ptr())).map(|_| ()) }
|
||||
unsafe { cvt(ffi::BN_lshift1(self.as_ptr(), a.as_ptr())) }
|
||||
}
|
||||
|
||||
/// Places `a >> 1` in `self`. Equivalent to `self / 2`.
|
||||
#[corresponds(BN_rshift1)]
|
||||
pub fn rshift1(&mut self, a: &BigNumRef) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::BN_rshift1(self.as_ptr(), a.as_ptr())).map(|_| ()) }
|
||||
unsafe { cvt(ffi::BN_rshift1(self.as_ptr(), a.as_ptr())) }
|
||||
}
|
||||
|
||||
/// Places `a + b` in `self`. [`core::ops::Add`] is also implemented for `BigNumRef`.
|
||||
@ -228,7 +229,7 @@ impl BigNumRef {
|
||||
/// [`core::ops::Add`]: struct.BigNumRef.html#method.add
|
||||
#[corresponds(BN_add)]
|
||||
pub fn checked_add(&mut self, a: &BigNumRef, b: &BigNumRef) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::BN_add(self.as_ptr(), a.as_ptr(), b.as_ptr())).map(|_| ()) }
|
||||
unsafe { cvt(ffi::BN_add(self.as_ptr(), a.as_ptr(), b.as_ptr())) }
|
||||
}
|
||||
|
||||
/// Places `a - b` in `self`. [`core::ops::Sub`] is also implemented for `BigNumRef`.
|
||||
@ -236,21 +237,21 @@ impl BigNumRef {
|
||||
/// [`core::ops::Sub`]: struct.BigNumRef.html#method.sub
|
||||
#[corresponds(BN_sub)]
|
||||
pub fn checked_sub(&mut self, a: &BigNumRef, b: &BigNumRef) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::BN_sub(self.as_ptr(), a.as_ptr(), b.as_ptr())).map(|_| ()) }
|
||||
unsafe { cvt(ffi::BN_sub(self.as_ptr(), a.as_ptr(), b.as_ptr())) }
|
||||
}
|
||||
|
||||
/// Places `a << n` in `self`. Equivalent to `a * 2 ^ n`.
|
||||
#[corresponds(BN_lshift)]
|
||||
#[allow(clippy::useless_conversion)]
|
||||
pub fn lshift(&mut self, a: &BigNumRef, n: i32) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::BN_lshift(self.as_ptr(), a.as_ptr(), n.into())).map(|_| ()) }
|
||||
unsafe { cvt(ffi::BN_lshift(self.as_ptr(), a.as_ptr(), n.into())) }
|
||||
}
|
||||
|
||||
/// Places `a >> n` in `self`. Equivalent to `a / 2 ^ n`.
|
||||
#[corresponds(BN_rshift)]
|
||||
#[allow(clippy::useless_conversion)]
|
||||
pub fn rshift(&mut self, a: &BigNumRef, n: i32) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::BN_rshift(self.as_ptr(), a.as_ptr(), n.into())).map(|_| ()) }
|
||||
unsafe { cvt(ffi::BN_rshift(self.as_ptr(), a.as_ptr(), n.into())) }
|
||||
}
|
||||
|
||||
/// Creates a new BigNum with the same value.
|
||||
@ -263,7 +264,7 @@ impl BigNumRef {
|
||||
/// `self` positive.
|
||||
#[corresponds(BN_set_negative)]
|
||||
pub fn set_negative(&mut self, negative: bool) {
|
||||
unsafe { ffi::BN_set_negative(self.as_ptr(), negative as c_int) }
|
||||
unsafe { ffi::BN_set_negative(self.as_ptr(), c_int::from(negative)) }
|
||||
}
|
||||
|
||||
/// Compare the absolute values of `self` and `oth`.
|
||||
@ -279,23 +280,27 @@ impl BigNumRef {
|
||||
/// assert_eq!(s.ucmp(&o), Ordering::Equal);
|
||||
/// ```
|
||||
#[corresponds(BN_ucmp)]
|
||||
#[must_use]
|
||||
pub fn ucmp(&self, oth: &BigNumRef) -> Ordering {
|
||||
unsafe { ffi::BN_ucmp(self.as_ptr(), oth.as_ptr()).cmp(&0) }
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is negative.
|
||||
#[corresponds(BN_is_negative)]
|
||||
#[must_use]
|
||||
pub fn is_negative(&self) -> bool {
|
||||
unsafe { BN_is_negative(self.as_ptr()) == 1 }
|
||||
}
|
||||
|
||||
/// Returns the number of significant bits in `self`.
|
||||
#[corresponds(BN_num_bits)]
|
||||
#[must_use]
|
||||
pub fn num_bits(&self) -> i32 {
|
||||
unsafe { ffi::BN_num_bits(self.as_ptr()) as i32 }
|
||||
}
|
||||
|
||||
/// Returns the size of `self` in bytes. Implemented natively.
|
||||
#[must_use]
|
||||
pub fn num_bytes(&self) -> i32 {
|
||||
(self.num_bits() + 7) / 8
|
||||
}
|
||||
@ -332,9 +337,8 @@ impl BigNumRef {
|
||||
self.as_ptr(),
|
||||
bits.into(),
|
||||
msb.0,
|
||||
odd as c_int,
|
||||
c_int::from(odd),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -347,9 +351,8 @@ impl BigNumRef {
|
||||
self.as_ptr(),
|
||||
bits.into(),
|
||||
msb.0,
|
||||
odd as c_int,
|
||||
c_int::from(odd),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -387,13 +390,12 @@ impl BigNumRef {
|
||||
unsafe {
|
||||
cvt(ffi::BN_generate_prime_ex(
|
||||
self.as_ptr(),
|
||||
bits as c_int,
|
||||
safe as c_int,
|
||||
c_int::from(bits),
|
||||
c_int::from(safe),
|
||||
add.map(|n| n.as_ptr()).unwrap_or(ptr::null_mut()),
|
||||
rem.map(|n| n.as_ptr()).unwrap_or(ptr::null_mut()),
|
||||
ptr::null_mut(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -415,7 +417,6 @@ impl BigNumRef {
|
||||
b.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -438,7 +439,6 @@ impl BigNumRef {
|
||||
b.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -458,7 +458,6 @@ impl BigNumRef {
|
||||
b.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -479,14 +478,13 @@ impl BigNumRef {
|
||||
b.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Places the result of `a²` in `self`.
|
||||
#[corresponds(BN_sqr)]
|
||||
pub fn sqr(&mut self, a: &BigNumRef, ctx: &mut BigNumContextRef) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::BN_sqr(self.as_ptr(), a.as_ptr(), ctx.as_ptr())).map(|_| ()) }
|
||||
unsafe { cvt(ffi::BN_sqr(self.as_ptr(), a.as_ptr(), ctx.as_ptr())) }
|
||||
}
|
||||
|
||||
/// Places the result of `a mod m` in `self`. As opposed to `div_rem`
|
||||
@ -505,7 +503,6 @@ impl BigNumRef {
|
||||
m.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -526,7 +523,6 @@ impl BigNumRef {
|
||||
m.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -547,7 +543,6 @@ impl BigNumRef {
|
||||
m.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -568,7 +563,6 @@ impl BigNumRef {
|
||||
m.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -587,7 +581,6 @@ impl BigNumRef {
|
||||
m.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -606,7 +599,6 @@ impl BigNumRef {
|
||||
p.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -627,7 +619,6 @@ impl BigNumRef {
|
||||
m.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -665,7 +656,6 @@ impl BigNumRef {
|
||||
b.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -712,7 +702,7 @@ impl BigNumRef {
|
||||
self.as_ptr(),
|
||||
checks.into(),
|
||||
ctx.as_ptr(),
|
||||
do_trial_division as c_int,
|
||||
c_int::from(do_trial_division),
|
||||
ptr::null_mut(),
|
||||
))
|
||||
.map(|r| r != 0)
|
||||
@ -732,6 +722,7 @@ impl BigNumRef {
|
||||
/// assert_eq!(BigNum::from_slice(&s_vec).unwrap(), r);
|
||||
/// ```
|
||||
#[corresponds(BN_bn2bin)]
|
||||
#[must_use]
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
let size = self.num_bytes() as usize;
|
||||
let mut v = Vec::with_capacity(size);
|
||||
@ -829,7 +820,7 @@ impl BigNum {
|
||||
#[corresponds(BN_set_word)]
|
||||
pub fn from_u32(n: u32) -> Result<BigNum, ErrorStack> {
|
||||
BigNum::new().and_then(|v| unsafe {
|
||||
cvt(ffi::BN_set_word(v.as_ptr(), n as ffi::BN_ULONG)).map(|_| v)
|
||||
cvt(ffi::BN_set_word(v.as_ptr(), ffi::BN_ULONG::from(n))).map(|_| v)
|
||||
})
|
||||
}
|
||||
|
||||
@ -838,9 +829,9 @@ impl BigNum {
|
||||
pub fn from_dec_str(s: &str) -> Result<BigNum, ErrorStack> {
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let c_str = CString::new(s.as_bytes()).unwrap();
|
||||
let c_str = CString::new(s.as_bytes()).map_err(ErrorStack::internal_error)?;
|
||||
let mut bn = ptr::null_mut();
|
||||
cvt(ffi::BN_dec2bn(&mut bn, c_str.as_ptr() as *const _))?;
|
||||
cvt(ffi::BN_dec2bn(&mut bn, c_str.as_ptr()))?;
|
||||
Ok(BigNum::from_ptr(bn))
|
||||
}
|
||||
}
|
||||
@ -850,9 +841,9 @@ impl BigNum {
|
||||
pub fn from_hex_str(s: &str) -> Result<BigNum, ErrorStack> {
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let c_str = CString::new(s.as_bytes()).unwrap();
|
||||
let c_str = CString::new(s.as_bytes()).map_err(ErrorStack::internal_error)?;
|
||||
let mut bn = ptr::null_mut();
|
||||
cvt(ffi::BN_hex2bn(&mut bn, c_str.as_ptr() as *const _))?;
|
||||
cvt(ffi::BN_hex2bn(&mut bn, c_str.as_ptr()))?;
|
||||
Ok(BigNum::from_ptr(bn))
|
||||
}
|
||||
}
|
||||
@ -874,12 +865,7 @@ impl BigNum {
|
||||
unsafe {
|
||||
ffi::init();
|
||||
assert!(n.len() <= c_int::MAX as usize);
|
||||
cvt_p(ffi::BN_bin2bn(
|
||||
n.as_ptr(),
|
||||
n.len() as size_t,
|
||||
ptr::null_mut(),
|
||||
))
|
||||
.map(|p| BigNum::from_ptr(p))
|
||||
cvt_p(ffi::BN_bin2bn(n.as_ptr(), n.len(), ptr::null_mut())).map(|p| BigNum::from_ptr(p))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -928,7 +914,7 @@ impl PartialEq<BigNumRef> for BigNumRef {
|
||||
|
||||
impl PartialEq<BigNum> for BigNumRef {
|
||||
fn eq(&self, oth: &BigNum) -> bool {
|
||||
self.eq(oth.deref())
|
||||
self.eq(&**oth)
|
||||
}
|
||||
}
|
||||
|
||||
@ -956,7 +942,7 @@ impl PartialOrd<BigNumRef> for BigNumRef {
|
||||
|
||||
impl PartialOrd<BigNum> for BigNumRef {
|
||||
fn partial_cmp(&self, oth: &BigNum) -> Option<Ordering> {
|
||||
Some(self.cmp(oth.deref()))
|
||||
Some(self.cmp(&**oth))
|
||||
}
|
||||
}
|
||||
|
||||
@ -980,7 +966,7 @@ impl PartialOrd<BigNumRef> for BigNum {
|
||||
|
||||
impl Ord for BigNum {
|
||||
fn cmp(&self, oth: &BigNum) -> Ordering {
|
||||
self.deref().cmp(oth.deref())
|
||||
self.deref().cmp(&**oth)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ impl ConfMethod {
|
||||
}
|
||||
|
||||
/// Convert to raw pointer.
|
||||
#[must_use]
|
||||
pub fn as_ptr(&self) -> *mut c_void {
|
||||
self.0
|
||||
}
|
||||
|
||||
@ -44,14 +44,13 @@ impl<'a> Deriver<'a> {
|
||||
where
|
||||
T: HasPublic,
|
||||
{
|
||||
unsafe { cvt(ffi::EVP_PKEY_derive_set_peer(self.0, key.as_ptr())).map(|_| ()) }
|
||||
unsafe { cvt(ffi::EVP_PKEY_derive_set_peer(self.0, key.as_ptr())) }
|
||||
}
|
||||
|
||||
/// Returns the size of the shared secret.
|
||||
///
|
||||
/// It can be used to size the buffer passed to [`Deriver::derive`].
|
||||
#[corresponds(EVP_PKEY_derive)]
|
||||
/// [`EVP_PKEY_derive`]: https://www.openssl.org/docs/man1.0.2/crypto/EVP_PKEY_derive_init.html
|
||||
pub fn len(&mut self) -> Result<usize, ErrorStack> {
|
||||
unsafe {
|
||||
let mut len = 0;
|
||||
@ -65,14 +64,7 @@ impl<'a> Deriver<'a> {
|
||||
#[corresponds(EVP_PKEY_derive)]
|
||||
pub fn derive(&mut self, buf: &mut [u8]) -> Result<usize, ErrorStack> {
|
||||
let mut len = buf.len();
|
||||
unsafe {
|
||||
cvt(ffi::EVP_PKEY_derive(
|
||||
self.0,
|
||||
buf.as_mut_ptr() as *mut _,
|
||||
&mut len,
|
||||
))
|
||||
.map(|_| len)
|
||||
}
|
||||
unsafe { cvt(ffi::EVP_PKEY_derive(self.0, buf.as_mut_ptr(), &mut len)).map(|_| len) }
|
||||
}
|
||||
|
||||
/// A convenience function which derives a shared secret and returns it in a new buffer.
|
||||
|
||||
@ -32,7 +32,7 @@ where
|
||||
}
|
||||
|
||||
to_der! {
|
||||
/// Serializes the parameters into a DER-encoded PKCS#3 DHparameter structure.
|
||||
/// Serializes the parameters into a DER-encoded PKCS#3 `DHparameter` structure.
|
||||
#[corresponds(i2d_DHparams)]
|
||||
params_to_der,
|
||||
ffi::i2d_DHparams
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
//! using the private key that can be validated with the public key but not be generated
|
||||
//! without the private key.
|
||||
|
||||
use crate::ffi;
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use libc::c_uint;
|
||||
use openssl_macros::corresponds;
|
||||
@ -15,7 +14,9 @@ use std::ptr;
|
||||
|
||||
use crate::bn::{BigNum, BigNumRef};
|
||||
use crate::error::ErrorStack;
|
||||
use crate::ffi;
|
||||
use crate::pkey::{HasParams, HasPrivate, HasPublic, Private, Public};
|
||||
use crate::try_int;
|
||||
use crate::{cvt, cvt_p};
|
||||
|
||||
generic_foreign_type_and_impl_send_sync! {
|
||||
@ -98,11 +99,12 @@ where
|
||||
}
|
||||
|
||||
/// Returns a reference to the public key component of `self`.
|
||||
#[must_use]
|
||||
pub fn pub_key(&self) -> &BigNumRef {
|
||||
unsafe {
|
||||
let mut pub_key = ptr::null();
|
||||
DSA_get0_key(self.as_ptr(), &mut pub_key, ptr::null_mut());
|
||||
BigNumRef::from_ptr(pub_key as *mut _)
|
||||
BigNumRef::from_ptr(pub_key.cast_mut())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -126,11 +128,12 @@ where
|
||||
}
|
||||
|
||||
/// Returns a reference to the private key component of `self`.
|
||||
#[must_use]
|
||||
pub fn priv_key(&self) -> &BigNumRef {
|
||||
unsafe {
|
||||
let mut priv_key = ptr::null();
|
||||
DSA_get0_key(self.as_ptr(), ptr::null_mut(), &mut priv_key);
|
||||
BigNumRef::from_ptr(priv_key as *mut _)
|
||||
BigNumRef::from_ptr(priv_key.cast_mut())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -141,34 +144,38 @@ where
|
||||
{
|
||||
/// Returns the maximum size of the signature output by `self` in bytes.
|
||||
#[corresponds(DSA_size)]
|
||||
#[must_use]
|
||||
pub fn size(&self) -> u32 {
|
||||
unsafe { ffi::DSA_size(self.as_ptr()) as u32 }
|
||||
}
|
||||
|
||||
/// Returns the DSA prime parameter of `self`.
|
||||
#[must_use]
|
||||
pub fn p(&self) -> &BigNumRef {
|
||||
unsafe {
|
||||
let mut p = ptr::null();
|
||||
DSA_get0_pqg(self.as_ptr(), &mut p, ptr::null_mut(), ptr::null_mut());
|
||||
BigNumRef::from_ptr(p as *mut _)
|
||||
BigNumRef::from_ptr(p.cast_mut())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the DSA sub-prime parameter of `self`.
|
||||
#[must_use]
|
||||
pub fn q(&self) -> &BigNumRef {
|
||||
unsafe {
|
||||
let mut q = ptr::null();
|
||||
DSA_get0_pqg(self.as_ptr(), ptr::null_mut(), &mut q, ptr::null_mut());
|
||||
BigNumRef::from_ptr(q as *mut _)
|
||||
BigNumRef::from_ptr(q.cast_mut())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the DSA base parameter of `self`.
|
||||
#[must_use]
|
||||
pub fn g(&self) -> &BigNumRef {
|
||||
unsafe {
|
||||
let mut g = ptr::null();
|
||||
DSA_get0_pqg(self.as_ptr(), ptr::null_mut(), ptr::null_mut(), &mut g);
|
||||
BigNumRef::from_ptr(g as *mut _)
|
||||
BigNumRef::from_ptr(g.cast_mut())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -189,7 +196,7 @@ impl Dsa<Private> {
|
||||
let dsa = Dsa::from_ptr(cvt_p(ffi::DSA_new())?);
|
||||
cvt(ffi::DSA_generate_parameters_ex(
|
||||
dsa.0,
|
||||
bits as c_uint,
|
||||
c_uint::from(bits),
|
||||
ptr::null(),
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
@ -294,7 +301,7 @@ mod test {
|
||||
let mut ctx = BigNumContext::new().unwrap();
|
||||
let mut calc = BigNum::new().unwrap();
|
||||
calc.mod_exp(g, priv_key, p, &mut ctx).unwrap();
|
||||
assert_eq!(&calc, pub_key)
|
||||
assert_eq!(&calc, pub_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
//! [`EcGroup`]: struct.EcGroup.html
|
||||
//! [`Nid`]: ../nid/struct.Nid.html
|
||||
//! [Eliptic Curve Cryptography]: https://wiki.openssl.org/index.php/Elliptic_Curve_Cryptography
|
||||
use crate::ffi;
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use libc::c_int;
|
||||
use openssl_macros::corresponds;
|
||||
@ -24,8 +23,10 @@ use std::ptr;
|
||||
|
||||
use crate::bn::{BigNumContextRef, BigNumRef};
|
||||
use crate::error::ErrorStack;
|
||||
use crate::ffi;
|
||||
use crate::nid::Nid;
|
||||
use crate::pkey::{HasParams, HasPrivate, HasPublic, Params, Private, Public};
|
||||
use crate::try_int;
|
||||
use crate::{cvt, cvt_n, cvt_p, init};
|
||||
|
||||
/// Compressed or Uncompressed conversion
|
||||
@ -143,7 +144,6 @@ impl EcGroupRef {
|
||||
b.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,29 +160,31 @@ impl EcGroupRef {
|
||||
cofactor.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the degree of the curve.
|
||||
#[corresponds(EC_GROUP_get_degree)]
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
#[must_use]
|
||||
pub fn degree(&self) -> u32 {
|
||||
unsafe { ffi::EC_GROUP_get_degree(self.as_ptr()) as u32 }
|
||||
}
|
||||
|
||||
/// Returns the number of bits in the group order.
|
||||
#[corresponds(EC_GROUP_order_bits)]
|
||||
#[must_use]
|
||||
pub fn order_bits(&self) -> u32 {
|
||||
unsafe { ffi::EC_GROUP_order_bits(self.as_ptr()) as u32 }
|
||||
}
|
||||
|
||||
/// Returns the generator for the given curve as a [`EcPoint`].
|
||||
#[corresponds(EC_GROUP_get0_generator)]
|
||||
#[must_use]
|
||||
pub fn generator(&self) -> &EcPointRef {
|
||||
unsafe {
|
||||
let ptr = ffi::EC_GROUP_get0_generator(self.as_ptr());
|
||||
EcPointRef::from_ptr(ptr as *mut _)
|
||||
EcPointRef::from_ptr(ptr.cast_mut())
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,7 +201,6 @@ impl EcGroupRef {
|
||||
order.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,6 +217,7 @@ impl EcGroupRef {
|
||||
|
||||
/// Returns the name of the curve, if a name is associated.
|
||||
#[corresponds(EC_GROUP_get_curve_name)]
|
||||
#[must_use]
|
||||
pub fn curve_name(&self) -> Option<Nid> {
|
||||
let nid = unsafe { ffi::EC_GROUP_get_curve_name(self.as_ptr()) };
|
||||
if nid > 0 {
|
||||
@ -256,7 +258,6 @@ impl EcPointRef {
|
||||
b.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -267,8 +268,7 @@ impl EcPointRef {
|
||||
group: &EcGroupRef,
|
||||
q: &EcPointRef,
|
||||
m: &BigNumRef,
|
||||
// FIXME should be &mut
|
||||
ctx: &BigNumContextRef,
|
||||
ctx: &mut BigNumContextRef,
|
||||
) -> Result<(), ErrorStack> {
|
||||
unsafe {
|
||||
cvt(ffi::EC_POINT_mul(
|
||||
@ -279,7 +279,6 @@ impl EcPointRef {
|
||||
m.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,8 +287,7 @@ impl EcPointRef {
|
||||
&mut self,
|
||||
group: &EcGroupRef,
|
||||
n: &BigNumRef,
|
||||
// FIXME should be &mut
|
||||
ctx: &BigNumContextRef,
|
||||
ctx: &mut BigNumContextRef,
|
||||
) -> Result<(), ErrorStack> {
|
||||
unsafe {
|
||||
cvt(ffi::EC_POINT_mul(
|
||||
@ -300,7 +298,6 @@ impl EcPointRef {
|
||||
ptr::null(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -322,7 +319,6 @@ impl EcPointRef {
|
||||
m.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -335,7 +331,6 @@ impl EcPointRef {
|
||||
self.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -424,7 +419,6 @@ impl EcPointRef {
|
||||
y.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -498,10 +492,11 @@ where
|
||||
|
||||
/// Return [`EcPoint`] associated with the private key
|
||||
#[corresponds(EC_KEY_get0_private_key)]
|
||||
#[must_use]
|
||||
pub fn private_key(&self) -> &BigNumRef {
|
||||
unsafe {
|
||||
let ptr = ffi::EC_KEY_get0_private_key(self.as_ptr());
|
||||
BigNumRef::from_ptr(ptr as *mut _)
|
||||
BigNumRef::from_ptr(ptr.cast_mut())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -512,10 +507,11 @@ where
|
||||
{
|
||||
/// Returns the public key.
|
||||
#[corresponds(EC_KEY_get0_public_key)]
|
||||
#[must_use]
|
||||
pub fn public_key(&self) -> &EcPointRef {
|
||||
unsafe {
|
||||
let ptr = ffi::EC_KEY_get0_public_key(self.as_ptr());
|
||||
EcPointRef::from_ptr(ptr as *mut _)
|
||||
EcPointRef::from_ptr(ptr.cast_mut())
|
||||
}
|
||||
}
|
||||
|
||||
@ -542,17 +538,18 @@ where
|
||||
{
|
||||
/// Return [`EcGroup`] of the `EcKey`
|
||||
#[corresponds(EC_KEY_get0_group)]
|
||||
#[must_use]
|
||||
pub fn group(&self) -> &EcGroupRef {
|
||||
unsafe {
|
||||
let ptr = ffi::EC_KEY_get0_group(self.as_ptr());
|
||||
EcGroupRef::from_ptr(ptr as *mut _)
|
||||
EcGroupRef::from_ptr(ptr.cast_mut())
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks the key for validity.
|
||||
#[corresponds(EC_KEY_check_key)]
|
||||
pub fn check_key(&self) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::EC_KEY_check_key(self.as_ptr())).map(|_| ()) }
|
||||
unsafe { cvt(ffi::EC_KEY_check_key(self.as_ptr())) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -840,7 +837,7 @@ mod test {
|
||||
let mut ctx = BigNumContext::new().unwrap();
|
||||
let mut public_key = EcPoint::new(&group).unwrap();
|
||||
public_key
|
||||
.mul_generator(&group, key.private_key(), &ctx)
|
||||
.mul_generator(&group, key.private_key(), &mut ctx)
|
||||
.unwrap();
|
||||
assert!(public_key.eq(&group, key.public_key(), &mut ctx).unwrap());
|
||||
}
|
||||
@ -852,7 +849,7 @@ mod test {
|
||||
let one = BigNum::from_u32(1).unwrap();
|
||||
let mut ctx = BigNumContext::new().unwrap();
|
||||
let mut ecp = EcPoint::new(&group).unwrap();
|
||||
ecp.mul_generator(&group, &one, &ctx).unwrap();
|
||||
ecp.mul_generator(&group, &one, &mut ctx).unwrap();
|
||||
assert!(ecp.eq(&group, gen, &mut ctx).unwrap());
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
use crate::ffi;
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use libc::{c_int, size_t};
|
||||
use libc::c_int;
|
||||
use openssl_macros::corresponds;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
@ -36,10 +36,10 @@ impl EcdsaSig {
|
||||
assert!(data.len() <= c_int::MAX as usize);
|
||||
let sig = cvt_p(ffi::ECDSA_do_sign(
|
||||
data.as_ptr(),
|
||||
data.len() as size_t,
|
||||
data.len(),
|
||||
eckey.as_ptr(),
|
||||
))?;
|
||||
Ok(EcdsaSig::from_ptr(sig as *mut _))
|
||||
Ok(EcdsaSig::from_ptr(sig))
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ impl EcdsaSig {
|
||||
let sig = cvt_p(ffi::ECDSA_SIG_new())?;
|
||||
ECDSA_SIG_set0(sig, r.as_ptr(), s.as_ptr());
|
||||
mem::forget((r, s));
|
||||
Ok(EcdsaSig::from_ptr(sig as *mut _))
|
||||
Ok(EcdsaSig::from_ptr(sig))
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ impl EcdsaSigRef {
|
||||
assert!(data.len() <= c_int::MAX as usize);
|
||||
cvt_n(ffi::ECDSA_do_verify(
|
||||
data.as_ptr(),
|
||||
data.len() as size_t,
|
||||
data.len(),
|
||||
self.as_ptr(),
|
||||
eckey.as_ptr(),
|
||||
))
|
||||
@ -93,21 +93,23 @@ impl EcdsaSigRef {
|
||||
|
||||
/// Returns internal component: `r` of an `EcdsaSig`. (See X9.62 or FIPS 186-2)
|
||||
#[corresponds(ECDSA_SIG_get0)]
|
||||
#[must_use]
|
||||
pub fn r(&self) -> &BigNumRef {
|
||||
unsafe {
|
||||
let mut r = ptr::null();
|
||||
ECDSA_SIG_get0(self.as_ptr(), &mut r, ptr::null_mut());
|
||||
BigNumRef::from_ptr(r as *mut _)
|
||||
BigNumRef::from_ptr(r.cast_mut())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns internal components: `s` of an `EcdsaSig`. (See X9.62 or FIPS 186-2)
|
||||
#[corresponds(ECDSA_SIG_get0)]
|
||||
#[must_use]
|
||||
pub fn s(&self) -> &BigNumRef {
|
||||
unsafe {
|
||||
let mut s = ptr::null();
|
||||
ECDSA_SIG_get0(self.as_ptr(), ptr::null_mut(), &mut s);
|
||||
BigNumRef::from_ptr(s as *mut _)
|
||||
BigNumRef::from_ptr(s.cast_mut())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,10 +15,12 @@
|
||||
//! Err(e) => println!("Parsing Error: {:?}", e),
|
||||
//! }
|
||||
//! ```
|
||||
use libc::{c_char, c_uint};
|
||||
use libc::{c_char, c_int, c_uint};
|
||||
use openssl_macros::corresponds;
|
||||
use std::borrow::Cow;
|
||||
use std::error;
|
||||
use std::ffi::CStr;
|
||||
use std::ffi::CString;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::ptr;
|
||||
@ -26,6 +28,8 @@ use std::str;
|
||||
|
||||
use crate::ffi;
|
||||
|
||||
pub use crate::ffi::ErrLib;
|
||||
|
||||
/// Collection of [`Error`]s from OpenSSL.
|
||||
///
|
||||
/// [`Error`]: struct.Error.html
|
||||
@ -33,7 +37,12 @@ use crate::ffi;
|
||||
pub struct ErrorStack(Vec<Error>);
|
||||
|
||||
impl ErrorStack {
|
||||
/// Returns the contents of the OpenSSL error stack.
|
||||
/// Pops the contents of the OpenSSL error stack, and returns it.
|
||||
///
|
||||
/// This should be used only immediately after calling Boring FFI functions,
|
||||
/// otherwise the stack may be empty or a leftover from unrelated calls.
|
||||
#[corresponds(ERR_get_error_line_data)]
|
||||
#[must_use = "Use ErrorStack::clear() to drop the error stack"]
|
||||
pub fn get() -> ErrorStack {
|
||||
let mut vec = vec![];
|
||||
while let Some(err) = Error::get() {
|
||||
@ -43,15 +52,37 @@ impl ErrorStack {
|
||||
}
|
||||
|
||||
/// Pushes the errors back onto the OpenSSL error stack.
|
||||
#[corresponds(ERR_put_error)]
|
||||
pub fn put(&self) {
|
||||
for error in self.errors() {
|
||||
error.put();
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to report errors from the Rust crate
|
||||
#[cold]
|
||||
pub(crate) fn internal_error(err: impl error::Error) -> Self {
|
||||
Self(vec![Error::new_internal(Data::String(err.to_string()))])
|
||||
}
|
||||
|
||||
/// Used to report errors from the Rust crate
|
||||
#[cold]
|
||||
pub(crate) fn internal_error_str(message: &'static str) -> Self {
|
||||
Self(vec![Error::new_internal(Data::Static(message))])
|
||||
}
|
||||
|
||||
/// Empties the current thread's error queue.
|
||||
#[corresponds(ERR_clear_error)]
|
||||
pub(crate) fn clear() {
|
||||
unsafe {
|
||||
ffi::ERR_clear_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ErrorStack {
|
||||
/// Returns the errors in the stack.
|
||||
#[must_use]
|
||||
pub fn errors(&self) -> &[Error] {
|
||||
&self.0
|
||||
}
|
||||
@ -69,7 +100,13 @@ impl fmt::Display for ErrorStack {
|
||||
fmt.write_str(" ")?;
|
||||
}
|
||||
first = false;
|
||||
write!(fmt, "[{}]", err.reason().unwrap_or("unknown reason"))?;
|
||||
write!(
|
||||
fmt,
|
||||
"[{}]",
|
||||
err.reason()
|
||||
.or_else(|| err.library())
|
||||
.unwrap_or("unknown reason")
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -79,7 +116,7 @@ impl error::Error for ErrorStack {}
|
||||
|
||||
impl From<ErrorStack> for io::Error {
|
||||
fn from(e: ErrorStack) -> io::Error {
|
||||
io::Error::new(io::ErrorKind::Other, e)
|
||||
io::Error::other(e)
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,20 +126,32 @@ impl From<ErrorStack> for fmt::Error {
|
||||
}
|
||||
}
|
||||
|
||||
/// An error reported from OpenSSL.
|
||||
/// A detailed error reported as part of an [`ErrorStack`].
|
||||
#[derive(Clone)]
|
||||
pub struct Error {
|
||||
code: c_uint,
|
||||
file: *const c_char,
|
||||
line: c_uint,
|
||||
data: Option<Cow<'static, str>>,
|
||||
data: Data,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Data {
|
||||
None,
|
||||
CString(CString),
|
||||
String(String),
|
||||
Static(&'static str),
|
||||
}
|
||||
|
||||
unsafe impl Sync for Error {}
|
||||
unsafe impl Send for Error {}
|
||||
|
||||
static BORING_INTERNAL: &CStr = c"boring-rust";
|
||||
|
||||
impl Error {
|
||||
/// Returns the first error on the OpenSSL error stack.
|
||||
/// Pops the first error off the OpenSSL error stack.
|
||||
#[must_use = "Use ErrorStack::clear() to drop the error stack"]
|
||||
#[corresponds(ERR_get_error_line_data)]
|
||||
pub fn get() -> Option<Error> {
|
||||
unsafe {
|
||||
ffi::init();
|
||||
@ -117,12 +166,9 @@ impl Error {
|
||||
// The memory referenced by data is only valid until that slot is overwritten
|
||||
// in the error stack, so we'll need to copy it off if it's dynamic
|
||||
let data = if flags & ffi::ERR_FLAG_STRING != 0 {
|
||||
let bytes = CStr::from_ptr(data as *const _).to_bytes();
|
||||
let data = str::from_utf8(bytes).unwrap();
|
||||
let data = Cow::Owned(data.to_string());
|
||||
Some(data)
|
||||
Data::CString(CStr::from_ptr(data.cast()).to_owned())
|
||||
} else {
|
||||
None
|
||||
Data::None
|
||||
};
|
||||
Some(Error {
|
||||
code,
|
||||
@ -136,6 +182,7 @@ impl Error {
|
||||
}
|
||||
|
||||
/// Pushes the error back onto the OpenSSL error stack.
|
||||
#[corresponds(ERR_put_error)]
|
||||
pub fn put(&self) {
|
||||
unsafe {
|
||||
ffi::ERR_put_error(
|
||||
@ -145,117 +192,165 @@ impl Error {
|
||||
self.file,
|
||||
self.line,
|
||||
);
|
||||
let ptr = match self.data {
|
||||
Some(Cow::Borrowed(data)) => Some(data.as_ptr() as *mut c_char),
|
||||
Some(Cow::Owned(ref data)) => {
|
||||
let ptr = ffi::OPENSSL_malloc((data.len() + 1) as _) as *mut c_char;
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
ptr::copy_nonoverlapping(data.as_ptr(), ptr as *mut u8, data.len());
|
||||
*ptr.add(data.len()) = 0;
|
||||
Some(ptr)
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
if let Some(ptr) = ptr {
|
||||
ffi::ERR_add_error_data(1, ptr);
|
||||
if let Some(cstr) = self.data_cstr() {
|
||||
ffi::ERR_add_error_data(1, cstr.as_ptr().cast_mut());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the raw OpenSSL error code for this error.
|
||||
/// Get `{lib}_R_{reason}` reason code for the given library, or `None` if the error is from a different library.
|
||||
///
|
||||
/// Libraries are identified by [`ERR_LIB_{name}`(ffi::ERR_LIB_SSL) constants.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
pub fn library_reason(&self, library_code: ErrLib) -> Option<c_int> {
|
||||
debug_assert!(library_code.0 < ffi::ERR_NUM_LIBS.0);
|
||||
(self.library_code() == library_code.0 as c_int).then_some(self.reason_code())
|
||||
}
|
||||
|
||||
/// Returns a raw OpenSSL **packed** error code for this error, which **can't be reliably compared to any error constant**.
|
||||
///
|
||||
/// Use [`Error::library_code()`] and [`Error::library_reason()`] instead.
|
||||
/// Packed error codes are different than [SSL error codes](crate::ssl::ErrorCode).
|
||||
#[must_use]
|
||||
#[deprecated(note = "use library_reason() to compare error codes")]
|
||||
pub fn code(&self) -> c_uint {
|
||||
self.code
|
||||
}
|
||||
|
||||
/// Returns the name of the library reporting the error, if available.
|
||||
#[must_use]
|
||||
pub fn library(&self) -> Option<&'static str> {
|
||||
if self.is_internal() {
|
||||
return None;
|
||||
}
|
||||
unsafe {
|
||||
let cstr = ffi::ERR_lib_error_string(self.code);
|
||||
if cstr.is_null() {
|
||||
return None;
|
||||
}
|
||||
let bytes = CStr::from_ptr(cstr as *const _).to_bytes();
|
||||
Some(str::from_utf8(bytes).unwrap())
|
||||
CStr::from_ptr(cstr.cast())
|
||||
.to_str()
|
||||
.ok()
|
||||
.filter(|&msg| msg != "unknown library")
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the raw OpenSSL error constant for the library reporting the
|
||||
/// error.
|
||||
pub fn library_code(&self) -> libc::c_int {
|
||||
/// Returns the raw OpenSSL error constant for the library reporting the error (`ERR_LIB_{name}`).
|
||||
///
|
||||
/// Error [reason codes](Error::library_reason) are not globally unique, but scoped to each library.
|
||||
#[must_use]
|
||||
pub fn library_code(&self) -> c_int {
|
||||
ffi::ERR_GET_LIB(self.code)
|
||||
}
|
||||
|
||||
/// Returns the name of the function reporting the error.
|
||||
/// Returns `None`. Boring doesn't use function codes.
|
||||
pub fn function(&self) -> Option<&'static str> {
|
||||
unsafe {
|
||||
let cstr = ffi::ERR_func_error_string(self.code);
|
||||
if cstr.is_null() {
|
||||
return None;
|
||||
}
|
||||
let bytes = CStr::from_ptr(cstr as *const _).to_bytes();
|
||||
Some(str::from_utf8(bytes).unwrap())
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns the reason for the error.
|
||||
pub fn reason(&self) -> Option<&'static str> {
|
||||
#[must_use]
|
||||
pub fn reason(&self) -> Option<&str> {
|
||||
if self.is_internal() {
|
||||
return self.data();
|
||||
}
|
||||
unsafe {
|
||||
let cstr = ffi::ERR_reason_error_string(self.code);
|
||||
if cstr.is_null() {
|
||||
return None;
|
||||
}
|
||||
let bytes = CStr::from_ptr(cstr as *const _).to_bytes();
|
||||
Some(str::from_utf8(bytes).unwrap())
|
||||
CStr::from_ptr(cstr.cast()).to_str().ok()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the raw OpenSSL error constant for the reason for the error.
|
||||
pub fn reason_code(&self) -> libc::c_int {
|
||||
/// Returns [library-specific](Error::library_code) reason code corresponding to some of the `{lib}_R_{reason}` constants.
|
||||
///
|
||||
/// Reason codes are ambiguous, and different libraries reuse the same numeric values for different errors.
|
||||
/// Use [`Error::library_reason`] to compare error codes.
|
||||
///
|
||||
/// For `ERR_LIB_SYS` the reason code is `errno`. `ERR_LIB_USER` can use any values.
|
||||
/// Other libraries may use [`ERR_R_*`](ffi::ERR_R_FATAL) or their own codes.
|
||||
#[must_use]
|
||||
pub fn reason_code(&self) -> c_int {
|
||||
ffi::ERR_GET_REASON(self.code)
|
||||
}
|
||||
|
||||
/// Returns the name of the source file which encountered the error.
|
||||
#[must_use]
|
||||
pub fn file(&self) -> &'static str {
|
||||
unsafe {
|
||||
assert!(!self.file.is_null());
|
||||
let bytes = CStr::from_ptr(self.file as *const _).to_bytes();
|
||||
str::from_utf8(bytes).unwrap()
|
||||
if self.file.is_null() {
|
||||
return "";
|
||||
}
|
||||
CStr::from_ptr(self.file.cast())
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the line in the source file which encountered the error.
|
||||
///
|
||||
/// 0 if unknown
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
#[must_use]
|
||||
pub fn line(&self) -> u32 {
|
||||
self.line as u32
|
||||
}
|
||||
|
||||
/// Returns additional data describing the error.
|
||||
#[allow(clippy::option_as_ref_deref)]
|
||||
#[must_use]
|
||||
pub fn data(&self) -> Option<&str> {
|
||||
self.data.as_ref().map(|s| &**s)
|
||||
match &self.data {
|
||||
Data::None => None,
|
||||
Data::CString(cstring) => cstring.to_str().ok(),
|
||||
Data::String(s) => Some(s),
|
||||
Data::Static(s) => Some(s),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn data_cstr(&self) -> Option<Cow<'_, CStr>> {
|
||||
let s = match &self.data {
|
||||
Data::None => return None,
|
||||
Data::CString(cstr) => return Some(Cow::Borrowed(cstr)),
|
||||
Data::String(s) => s.as_str(),
|
||||
Data::Static(s) => s,
|
||||
};
|
||||
CString::new(s).ok().map(Cow::Owned)
|
||||
}
|
||||
|
||||
fn new_internal(msg: Data) -> Self {
|
||||
Self {
|
||||
code: ffi::ERR_PACK(ffi::ERR_LIB_NONE.0 as _, 0, 0) as _,
|
||||
file: BORING_INTERNAL.as_ptr(),
|
||||
line: 0,
|
||||
data: msg,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_internal(&self) -> bool {
|
||||
std::ptr::eq(self.file, BORING_INTERNAL.as_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Error {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut builder = fmt.debug_struct("Error");
|
||||
builder.field("code", &self.code());
|
||||
if let Some(library) = self.library() {
|
||||
builder.field("library", &library);
|
||||
builder.field("code", &self.code);
|
||||
if !self.is_internal() {
|
||||
if let Some(library) = self.library() {
|
||||
builder.field("library", &library);
|
||||
}
|
||||
builder.field("library_code", &self.library_code());
|
||||
if let Some(reason) = self.reason() {
|
||||
builder.field("reason", &reason);
|
||||
}
|
||||
builder.field("reason_code", &self.reason_code());
|
||||
builder.field("file", &self.file());
|
||||
builder.field("line", &self.line());
|
||||
}
|
||||
builder.field("library_code", &self.library_code());
|
||||
if let Some(function) = self.function() {
|
||||
builder.field("function", &function);
|
||||
}
|
||||
if let Some(reason) = self.reason() {
|
||||
builder.field("reason", &reason);
|
||||
}
|
||||
builder.field("reason_code", &self.reason_code());
|
||||
builder.field("file", &self.file());
|
||||
builder.field("line", &self.line());
|
||||
if let Some(data) = self.data() {
|
||||
builder.field("data", &data);
|
||||
}
|
||||
@ -269,7 +364,7 @@ impl fmt::Display for Error {
|
||||
fmt,
|
||||
"{}\n\nCode: {:08X}\nLoc: {}:{}",
|
||||
self.reason().unwrap_or("unknown TLS error"),
|
||||
self.code(),
|
||||
&self.code,
|
||||
self.file(),
|
||||
self.line()
|
||||
)
|
||||
@ -277,3 +372,14 @@ impl fmt::Display for Error {
|
||||
}
|
||||
|
||||
impl error::Error for Error {}
|
||||
|
||||
#[test]
|
||||
fn internal_err() {
|
||||
let e = ErrorStack::internal_error(io::Error::other("hello, boring"));
|
||||
assert_eq!(1, e.errors().len());
|
||||
assert!(e.to_string().contains("hello, boring"), "{e} {e:?}");
|
||||
|
||||
e.put();
|
||||
let e = ErrorStack::get();
|
||||
assert!(e.to_string().contains("hello, boring"), "{e} {e:?}");
|
||||
}
|
||||
|
||||
@ -21,11 +21,13 @@ impl<T, U> Index<T, U> {
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that the index correctly maps to a `U` value stored in a `T`.
|
||||
#[must_use]
|
||||
pub unsafe fn from_raw(idx: c_int) -> Index<T, U> {
|
||||
Index(idx, PhantomData)
|
||||
}
|
||||
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
#[must_use]
|
||||
pub fn as_raw(&self) -> c_int {
|
||||
self.0
|
||||
}
|
||||
|
||||
@ -8,14 +8,15 @@ use openssl_macros::corresponds;
|
||||
|
||||
/// Determines if the library is running in the FIPS 140-2 mode of operation.
|
||||
#[corresponds(FIPS_mode)]
|
||||
#[must_use]
|
||||
pub fn enabled() -> bool {
|
||||
unsafe { ffi::FIPS_mode() != 0 }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_enabled() {
|
||||
#[cfg(any(feature = "fips", feature = "fips-link-precompiled"))]
|
||||
#[cfg(feature = "fips")]
|
||||
assert!(enabled());
|
||||
#[cfg(not(any(feature = "fips", feature = "fips-link-precompiled")))]
|
||||
#[cfg(not(feature = "fips"))]
|
||||
assert!(!enabled());
|
||||
}
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
use crate::ffi;
|
||||
use openssl_macros::corresponds;
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::{c_uint, c_void};
|
||||
use std::ffi::c_uint;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
@ -9,8 +7,10 @@ use std::ops::{Deref, DerefMut};
|
||||
use std::ptr;
|
||||
|
||||
use crate::error::ErrorStack;
|
||||
use crate::ffi;
|
||||
use crate::ffi::{EVP_MD_CTX_free, EVP_MD_CTX_new};
|
||||
use crate::nid::Nid;
|
||||
use crate::try_int;
|
||||
use crate::{cvt, cvt_p};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
@ -22,12 +22,14 @@ impl MessageDigest {
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure the pointer is valid.
|
||||
#[must_use]
|
||||
pub unsafe fn from_ptr(x: *const ffi::EVP_MD) -> Self {
|
||||
MessageDigest(x)
|
||||
}
|
||||
|
||||
/// Returns the `MessageDigest` corresponding to an `Nid`.
|
||||
#[corresponds(EVP_get_digestbynid)]
|
||||
#[must_use]
|
||||
pub fn from_nid(type_: Nid) -> Option<MessageDigest> {
|
||||
unsafe {
|
||||
let ptr = ffi::EVP_get_digestbynid(type_.as_raw());
|
||||
@ -39,47 +41,57 @@ impl MessageDigest {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn md5() -> MessageDigest {
|
||||
unsafe { MessageDigest(ffi::EVP_md5()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn sha1() -> MessageDigest {
|
||||
unsafe { MessageDigest(ffi::EVP_sha1()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn sha224() -> MessageDigest {
|
||||
unsafe { MessageDigest(ffi::EVP_sha224()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn sha256() -> MessageDigest {
|
||||
unsafe { MessageDigest(ffi::EVP_sha256()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn sha384() -> MessageDigest {
|
||||
unsafe { MessageDigest(ffi::EVP_sha384()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn sha512() -> MessageDigest {
|
||||
unsafe { MessageDigest(ffi::EVP_sha512()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn sha512_256() -> MessageDigest {
|
||||
unsafe { MessageDigest(ffi::EVP_sha512_256()) }
|
||||
}
|
||||
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
#[must_use]
|
||||
pub fn as_ptr(&self) -> *const ffi::EVP_MD {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// The size of the digest in bytes.
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
#[must_use]
|
||||
pub fn size(&self) -> usize {
|
||||
unsafe { ffi::EVP_MD_size(self.0) }
|
||||
}
|
||||
|
||||
/// The name of the digest.
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
#[must_use]
|
||||
pub fn type_(&self) -> Nid {
|
||||
Nid::from_raw(unsafe { ffi::EVP_MD_type(self.0) })
|
||||
}
|
||||
@ -184,7 +196,7 @@ impl Hasher {
|
||||
unsafe {
|
||||
cvt(ffi::EVP_DigestUpdate(
|
||||
self.ctx,
|
||||
data.as_ptr() as *mut _,
|
||||
data.as_ptr().cast_mut().cast(),
|
||||
data.len(),
|
||||
))?;
|
||||
}
|
||||
@ -198,7 +210,7 @@ impl Hasher {
|
||||
self.init()?;
|
||||
}
|
||||
unsafe {
|
||||
let mut len = ffi::EVP_MAX_MD_SIZE.try_into().unwrap();
|
||||
let mut len = try_int(ffi::EVP_MAX_MD_SIZE)?;
|
||||
let mut buf = [0; ffi::EVP_MAX_MD_SIZE as usize];
|
||||
cvt(ffi::EVP_DigestFinal_ex(
|
||||
self.ctx,
|
||||
@ -208,7 +220,7 @@ impl Hasher {
|
||||
self.state = Finalized;
|
||||
Ok(DigestBytes {
|
||||
buf,
|
||||
len: len as usize,
|
||||
len: try_int(len)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -308,7 +320,7 @@ impl DerefMut for DigestBytes {
|
||||
impl AsRef<[u8]> for DigestBytes {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.deref()
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@ -347,7 +359,7 @@ pub fn hmac_sha1(key: &[u8], data: &[u8]) -> Result<[u8; 20], ErrorStack> {
|
||||
hmac(MessageDigest::sha1(), key, data)
|
||||
}
|
||||
|
||||
fn hmac<const N: usize>(
|
||||
pub(crate) fn hmac<const N: usize>(
|
||||
digest: MessageDigest,
|
||||
key: &[u8],
|
||||
data: &[u8],
|
||||
@ -358,7 +370,7 @@ fn hmac<const N: usize>(
|
||||
cvt_p(unsafe {
|
||||
ffi::HMAC(
|
||||
digest.as_ptr(),
|
||||
key.as_ptr() as *const c_void,
|
||||
key.as_ptr().cast(),
|
||||
key.len(),
|
||||
data.as_ptr(),
|
||||
data.len(),
|
||||
@ -455,7 +467,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_md5() {
|
||||
for test in MD5_TESTS.iter() {
|
||||
for test in &MD5_TESTS {
|
||||
hash_test(MessageDigest::md5(), test);
|
||||
}
|
||||
}
|
||||
@ -463,7 +475,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_md5_recycle() {
|
||||
let mut h = Hasher::new(MessageDigest::md5()).unwrap();
|
||||
for test in MD5_TESTS.iter() {
|
||||
for test in &MD5_TESTS {
|
||||
hash_recycle_test(&mut h, test);
|
||||
}
|
||||
}
|
||||
@ -514,7 +526,7 @@ mod tests {
|
||||
fn test_sha1() {
|
||||
let tests = [("616263", "a9993e364706816aba3e25717850c26c9cd0d89d")];
|
||||
|
||||
for test in tests.iter() {
|
||||
for test in &tests {
|
||||
hash_test(MessageDigest::sha1(), test);
|
||||
}
|
||||
}
|
||||
@ -526,7 +538,7 @@ mod tests {
|
||||
"23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7",
|
||||
)];
|
||||
|
||||
for test in tests.iter() {
|
||||
for test in &tests {
|
||||
hash_test(MessageDigest::sha224(), test);
|
||||
}
|
||||
}
|
||||
@ -538,7 +550,7 @@ mod tests {
|
||||
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
|
||||
)];
|
||||
|
||||
for test in tests.iter() {
|
||||
for test in &tests {
|
||||
hash_test(MessageDigest::sha256(), test);
|
||||
}
|
||||
}
|
||||
@ -551,7 +563,7 @@ mod tests {
|
||||
192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
|
||||
)];
|
||||
|
||||
for test in tests.iter() {
|
||||
for test in &tests {
|
||||
hash_test(MessageDigest::sha512(), test);
|
||||
}
|
||||
}
|
||||
@ -563,7 +575,7 @@ mod tests {
|
||||
"53048e2681941ef99b2e29b76b4c7dabe4c2d0c634fc6d46e0e2f13107e7af23",
|
||||
)];
|
||||
|
||||
for test in tests.iter() {
|
||||
for test in &tests {
|
||||
hash_test(MessageDigest::sha512_256(), test);
|
||||
}
|
||||
}
|
||||
|
||||
130
boring/src/hmac.rs
Normal file
130
boring/src/hmac.rs
Normal file
@ -0,0 +1,130 @@
|
||||
use crate::cvt;
|
||||
use crate::error::ErrorStack;
|
||||
use crate::foreign_types::ForeignTypeRef;
|
||||
use crate::hash::MessageDigest;
|
||||
use openssl_macros::corresponds;
|
||||
|
||||
foreign_type_and_impl_send_sync! {
|
||||
type CType = ffi::HMAC_CTX;
|
||||
fn drop = ffi::HMAC_CTX_free;
|
||||
|
||||
pub struct HmacCtx;
|
||||
}
|
||||
|
||||
impl HmacCtxRef {
|
||||
/// Configures HmacCtx to use `md` as the hash function and `key` as the key.
|
||||
///
|
||||
#[corresponds(HMAC_Init_ex)]
|
||||
pub fn init(&mut self, key: &[u8], md: &MessageDigest) -> Result<(), ErrorStack> {
|
||||
ffi::init();
|
||||
|
||||
unsafe {
|
||||
cvt(ffi::HMAC_Init_ex(
|
||||
self.as_ptr(),
|
||||
key.as_ptr().cast(),
|
||||
key.len(),
|
||||
md.as_ptr(),
|
||||
// ENGINE api is deprecated
|
||||
core::ptr::null_mut(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides an init-update-finalize API for HMAC.
|
||||
pub struct Hmac(*mut ffi::HMAC_CTX);
|
||||
|
||||
impl Hmac {
|
||||
/// Creates a new HMAC object with the given key and hash algorithm.
|
||||
pub fn init(key: &[u8], md: &MessageDigest) -> Result<Hmac, ErrorStack> {
|
||||
ffi::init();
|
||||
|
||||
let ctx = unsafe {
|
||||
let ctx = ffi::HMAC_CTX_new();
|
||||
cvt(ffi::HMAC_Init_ex(
|
||||
ctx,
|
||||
key.as_ptr().cast(),
|
||||
key.len(),
|
||||
md.as_ptr(),
|
||||
// ENGINE api is deprecated
|
||||
core::ptr::null_mut(),
|
||||
))?;
|
||||
ctx
|
||||
};
|
||||
|
||||
Ok(Hmac(ctx))
|
||||
}
|
||||
|
||||
/// Updates the HMAC input.
|
||||
pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::HMAC_Update(self.0, data.as_ptr().cast(), data.len())) }
|
||||
}
|
||||
|
||||
/// Finalizes the HMAC and returns the output.
|
||||
pub fn finalize(self) -> Result<Vec<u8>, ErrorStack> {
|
||||
let out_len = unsafe { ffi::HMAC_size(self.0) };
|
||||
let mut out = vec![0; out_len];
|
||||
unsafe {
|
||||
cvt(ffi::HMAC_Final(
|
||||
self.0,
|
||||
out.as_mut_ptr().cast(),
|
||||
// ENGINE api is deprecated
|
||||
core::ptr::null_mut(),
|
||||
))?;
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Hmac {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::HMAC_CTX_free(self.0) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::hash;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn test<const N: usize>(md: MessageDigest) {
|
||||
assert_eq!(N, md.size());
|
||||
let key = vec![0; N];
|
||||
let message_parts = [
|
||||
b"hello".to_vec(),
|
||||
b"world!".to_vec(),
|
||||
b"".to_vec(),
|
||||
vec![0; 23],
|
||||
b"fella guy".to_vec(),
|
||||
];
|
||||
let message = message_parts.concat();
|
||||
|
||||
let mut hmac = Hmac::init(&key, &md).unwrap();
|
||||
for part in &message_parts {
|
||||
hmac.update(part).unwrap();
|
||||
}
|
||||
let res = hmac.finalize().unwrap();
|
||||
assert_eq!(res, hash::hmac::<N>(md, &key, &message).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sha1() {
|
||||
test::<20>(MessageDigest::sha1());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sha256() {
|
||||
test::<32>(MessageDigest::sha256());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sha384() {
|
||||
test::<48>(MessageDigest::sha384());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sha512() {
|
||||
test::<64>(MessageDigest::sha512());
|
||||
}
|
||||
}
|
||||
@ -61,6 +61,13 @@
|
||||
//! Note that `BORING_BSSL_PRECOMPILED_BCM_O` is never used, as linking BoringSSL with precompiled non-FIPS
|
||||
//! module is not supported.
|
||||
//!
|
||||
//! ## Linking with a C++ standard library
|
||||
//!
|
||||
//! Recent versions of boringssl require some C++ standard library features, so boring needs to link
|
||||
//! with a STL implementation. This can be controlled using the BORING_BSSL_RUST_CPPLIB variable. If
|
||||
//! no library is specified, libc++ is used on macOS and iOS whereas libstdc++ is used on other Unix
|
||||
//! systems.
|
||||
//!
|
||||
//! # Optional patches
|
||||
//!
|
||||
//! ## Raw Public Key
|
||||
@ -81,16 +88,14 @@
|
||||
//! before the end of 2024.
|
||||
//! - `X25519Kyber768Draft00Old` is the same as `X25519Kyber768Draft00`, but under its old codepoint.
|
||||
//! - `X25519Kyber512Draft00`. Similar to `X25519Kyber768Draft00`, but uses level 1 parameter set for
|
||||
//! Kyber. Not recommended. It's useful to test whether the shorter ClientHello upsets fewer middle
|
||||
//! boxes.
|
||||
//! Kyber. Not recommended. It's useful to test whether the shorter ClientHello upsets fewer middle
|
||||
//! boxes.
|
||||
//! - `P256Kyber768Draft00`. Similar again to `X25519Kyber768Draft00`, but uses P256 as classical
|
||||
//! part. It uses a non-standard codepoint. Not recommended.
|
||||
//! part. It uses a non-standard codepoint. Not recommended.
|
||||
//!
|
||||
//! Presently all these key agreements are deployed by Cloudflare, but we do not guarantee continued
|
||||
//! support for them.
|
||||
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
#[macro_use]
|
||||
@ -101,11 +106,12 @@ extern crate libc;
|
||||
#[cfg(test)]
|
||||
extern crate hex;
|
||||
|
||||
use std::ffi::{c_int, c_long, c_void};
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use crate::ffi::init;
|
||||
|
||||
use libc::{c_int, size_t};
|
||||
|
||||
use crate::error::ErrorStack;
|
||||
|
||||
#[macro_use]
|
||||
@ -128,9 +134,11 @@ pub mod error;
|
||||
pub mod ex_data;
|
||||
pub mod fips;
|
||||
pub mod hash;
|
||||
#[cfg(not(feature = "fips"))]
|
||||
pub mod hmac;
|
||||
pub mod hpke;
|
||||
pub mod memcmp;
|
||||
#[cfg(feature = "mlkem")]
|
||||
pub mod mlkem;
|
||||
pub mod nid;
|
||||
pub mod pkcs12;
|
||||
pub mod pkcs5;
|
||||
@ -155,11 +163,11 @@ fn cvt_p<T>(r: *mut T) -> Result<*mut T, ErrorStack> {
|
||||
}
|
||||
}
|
||||
|
||||
fn cvt_0(r: size_t) -> Result<size_t, ErrorStack> {
|
||||
fn cvt_0(r: usize) -> Result<(), ErrorStack> {
|
||||
if r == 0 {
|
||||
Err(ErrorStack::get())
|
||||
} else {
|
||||
Ok(r)
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,14 +179,21 @@ fn cvt_0i(r: c_int) -> Result<c_int, ErrorStack> {
|
||||
}
|
||||
}
|
||||
|
||||
fn cvt(r: c_int) -> Result<c_int, ErrorStack> {
|
||||
fn cvt(r: c_int) -> Result<(), ErrorStack> {
|
||||
if r <= 0 {
|
||||
Err(ErrorStack::get())
|
||||
} else {
|
||||
Ok(r)
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn cvt_nz(r: c_int) -> Result<NonZeroUsize, ErrorStack> {
|
||||
usize::try_from(r)
|
||||
.ok()
|
||||
.and_then(NonZeroUsize::new)
|
||||
.ok_or_else(ErrorStack::get)
|
||||
}
|
||||
|
||||
fn cvt_n(r: c_int) -> Result<c_int, ErrorStack> {
|
||||
if r < 0 {
|
||||
Err(ErrorStack::get())
|
||||
@ -186,3 +201,25 @@ fn cvt_n(r: c_int) -> Result<c_int, ErrorStack> {
|
||||
Ok(r)
|
||||
}
|
||||
}
|
||||
|
||||
fn try_int<F, T>(from: F) -> Result<T, ErrorStack>
|
||||
where
|
||||
F: TryInto<T> + Send + Sync + Copy + 'static,
|
||||
T: Send + Sync + Copy + 'static,
|
||||
{
|
||||
from.try_into()
|
||||
.map_err(|_| ErrorStack::internal_error_str("int overflow"))
|
||||
}
|
||||
|
||||
unsafe extern "C" fn free_data_box<T>(
|
||||
_parent: *mut c_void,
|
||||
ptr: *mut c_void,
|
||||
_ad: *mut ffi::CRYPTO_EX_DATA,
|
||||
_idx: c_int,
|
||||
_argl: c_long,
|
||||
_argp: *mut c_void,
|
||||
) {
|
||||
if !ptr.is_null() {
|
||||
drop(Box::<T>::from_raw(ptr.cast::<T>()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,8 @@ macro_rules! private_key_from_pem {
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let bio = crate::bio::MemBioSlice::new(pem)?;
|
||||
let passphrase = ::std::ffi::CString::new(passphrase).unwrap();
|
||||
let passphrase = ::std::ffi::CString::new(passphrase)
|
||||
.map_err(crate::error::ErrorStack::internal_error)?;
|
||||
cvt_p($f(bio.as_ptr(),
|
||||
ptr::null_mut(),
|
||||
None,
|
||||
@ -27,7 +28,7 @@ macro_rules! private_key_from_pem {
|
||||
cvt_p($f(bio.as_ptr(),
|
||||
ptr::null_mut(),
|
||||
Some(crate::util::invoke_passwd_cb::<F>),
|
||||
&mut cb as *mut _ as *mut _))
|
||||
ptr::from_mut(&mut cb).cast()))
|
||||
.map(|p| ::foreign_types::ForeignType::from_ptr(p))
|
||||
}
|
||||
}
|
||||
@ -59,12 +60,11 @@ macro_rules! private_key_to_pem {
|
||||
) -> Result<Vec<u8>, crate::error::ErrorStack> {
|
||||
unsafe {
|
||||
let bio = crate::bio::MemBio::new()?;
|
||||
assert!(passphrase.len() <= ::libc::c_int::MAX as usize);
|
||||
cvt($f(bio.as_ptr(),
|
||||
self.as_ptr(),
|
||||
cipher.as_ptr(),
|
||||
passphrase.as_ptr() as *const _ as *mut _,
|
||||
passphrase.len() as ::libc::c_int,
|
||||
try_int(passphrase.len())?,
|
||||
None,
|
||||
ptr::null_mut()))?;
|
||||
Ok(bio.get_buf().to_owned())
|
||||
@ -91,9 +91,9 @@ macro_rules! to_der {
|
||||
$(#[$m])*
|
||||
pub fn $n(&self) -> Result<Vec<u8>, crate::error::ErrorStack> {
|
||||
unsafe {
|
||||
let len = crate::cvt($f(::foreign_types::ForeignTypeRef::as_ptr(self),
|
||||
let len = crate::cvt_nz($f(::foreign_types::ForeignTypeRef::as_ptr(self),
|
||||
ptr::null_mut()))?;
|
||||
let mut buf = vec![0; len as usize];
|
||||
let mut buf = vec![0; len.get()];
|
||||
crate::cvt($f(::foreign_types::ForeignTypeRef::as_ptr(self),
|
||||
&mut buf.as_mut_ptr()))?;
|
||||
Ok(buf)
|
||||
|
||||
@ -30,7 +30,6 @@
|
||||
//! assert!(!eq(&a, &c));
|
||||
//! ```
|
||||
use crate::ffi;
|
||||
use libc::size_t;
|
||||
|
||||
/// Returns `true` iff `a` and `b` contain the same bytes.
|
||||
///
|
||||
@ -61,15 +60,10 @@ use libc::size_t;
|
||||
/// assert!(!eq(&a, &b));
|
||||
/// assert!(!eq(&a, &c));
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn eq(a: &[u8], b: &[u8]) -> bool {
|
||||
assert!(a.len() == b.len());
|
||||
let ret = unsafe {
|
||||
ffi::CRYPTO_memcmp(
|
||||
a.as_ptr() as *const _,
|
||||
b.as_ptr() as *const _,
|
||||
a.len() as size_t,
|
||||
)
|
||||
};
|
||||
let ret = unsafe { ffi::CRYPTO_memcmp(a.as_ptr().cast(), b.as_ptr().cast(), a.len()) };
|
||||
ret == 0
|
||||
}
|
||||
|
||||
@ -87,6 +81,6 @@ mod tests {
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_diff_lens() {
|
||||
eq(&[], &[1]);
|
||||
let _ = eq(&[], &[1]);
|
||||
}
|
||||
}
|
||||
|
||||
743
boring/src/mlkem.rs
Normal file
743
boring/src/mlkem.rs
Normal file
@ -0,0 +1,743 @@
|
||||
//! ML-KEM (FIPS 203) post-quantum key encapsulation.
|
||||
//!
|
||||
//! ML-KEM is a low-level cryptographic primitive. For most applications,
|
||||
//! using higher-level constructions like HPKE is preferred.
|
||||
//! Note that it's also enabled in TLS by default, in the X25519MLKEM768 exchange.
|
||||
//!
|
||||
//! Provides ML-KEM-768 (recommended) and ML-KEM-1024 variants via [`Algorithm`].
|
||||
//!
|
||||
//! ```
|
||||
//! use boring::mlkem::{Algorithm, MlKemPrivateKey};
|
||||
//!
|
||||
//! let (public_key, private_key) = MlKemPrivateKey::generate(Algorithm::MlKem768).unwrap();
|
||||
//! let (ciphertext, shared_secret) = public_key.encapsulate().unwrap();
|
||||
//! let decrypted = private_key.decapsulate(&ciphertext).unwrap();
|
||||
//! assert_eq!(shared_secret, decrypted);
|
||||
//! ```
|
||||
|
||||
use std::fmt;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
use crate::cvt;
|
||||
use crate::error::ErrorStack;
|
||||
use crate::ffi;
|
||||
|
||||
// CBS_init is inline in BoringSSL, so bindgen can't generate bindings for it.
|
||||
#[inline]
|
||||
fn cbs_init(data: &[u8]) -> ffi::CBS {
|
||||
ffi::CBS {
|
||||
data: data.as_ptr(),
|
||||
len: data.len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Private key seed size (64 bytes).
|
||||
pub const PRIVATE_KEY_SEED_BYTES: usize = ffi::MLKEM_SEED_BYTES as usize;
|
||||
|
||||
/// Shared secret size (32 bytes).
|
||||
pub const SHARED_SECRET_BYTES: usize = ffi::MLKEM_SHARED_SECRET_BYTES as usize;
|
||||
|
||||
/// Raw bytes of the private key seed ([`PRIVATE_KEY_SEED_BYTES`] long)
|
||||
pub type MlKemPrivateKeySeed = [u8; PRIVATE_KEY_SEED_BYTES];
|
||||
|
||||
/// Raw bytes of the shared secret ([`SHARED_SECRET_BYTES`] long)
|
||||
pub type MlKemSharedSecret = [u8; SHARED_SECRET_BYTES];
|
||||
|
||||
/// ML-KEM runtime algorithm selection.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Algorithm {
|
||||
/// Recommended. AES-192 equivalent security.
|
||||
MlKem768,
|
||||
/// AES-256 equivalent security.
|
||||
MlKem1024,
|
||||
}
|
||||
|
||||
impl Algorithm {
|
||||
/// Returns 1184 for ML-KEM-768, 1568 for ML-KEM-1024.
|
||||
#[must_use]
|
||||
pub const fn public_key_bytes(&self) -> usize {
|
||||
match self {
|
||||
Self::MlKem768 => MlKem768PublicKey::PUBLIC_KEY_BYTES,
|
||||
Self::MlKem1024 => MlKem1024PublicKey::PUBLIC_KEY_BYTES,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns 1088 for ML-KEM-768, 1568 for ML-KEM-1024.
|
||||
#[must_use]
|
||||
pub const fn ciphertext_bytes(&self) -> usize {
|
||||
match self {
|
||||
Self::MlKem768 => MlKem768PrivateKey::CIPHERTEXT_BYTES,
|
||||
Self::MlKem1024 => MlKem1024PrivateKey::CIPHERTEXT_BYTES,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MlKemPublicKey(Either<Box<MlKem768PublicKey>, Box<MlKem1024PublicKey>>);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MlKemPrivateKey(Either<Box<MlKem768PrivateKey>, Box<MlKem1024PrivateKey>>);
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Either<T768, T1024> {
|
||||
MlKem768(T768),
|
||||
MlKem1024(T1024),
|
||||
}
|
||||
|
||||
impl MlKemPrivateKey {
|
||||
/// Generates a new key pair, returning `(public_key, private_key)`.
|
||||
///
|
||||
/// The private key is a 64-byte seed. Keep it secret.
|
||||
pub fn generate(algorithm: Algorithm) -> Result<(MlKemPublicKey, MlKemPrivateKey), ErrorStack> {
|
||||
match algorithm {
|
||||
Algorithm::MlKem768 => {
|
||||
let (pk, sk) = MlKem768PrivateKey::generate()?;
|
||||
Ok((
|
||||
MlKemPublicKey(Either::MlKem768(pk)),
|
||||
MlKemPrivateKey(Either::MlKem768(sk)),
|
||||
))
|
||||
}
|
||||
Algorithm::MlKem1024 => {
|
||||
let (pk, sk) = MlKem1024PrivateKey::generate()?;
|
||||
Ok((
|
||||
MlKemPublicKey(Either::MlKem1024(pk)),
|
||||
MlKemPrivateKey(Either::MlKem1024(sk)),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MlKemPublicKey {
|
||||
pub fn from_slice(algorithm: Algorithm, public_key: &[u8]) -> Result<Self, ErrorStack> {
|
||||
match algorithm {
|
||||
Algorithm::MlKem768 => Ok(Self(Either::MlKem768(Box::new(
|
||||
MlKem768PublicKey::from_slice(public_key)?,
|
||||
)))),
|
||||
Algorithm::MlKem1024 => Ok(Self(Either::MlKem1024(Box::new(
|
||||
MlKem1024PublicKey::from_slice(public_key)?,
|
||||
)))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialized bytes of the public key
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
match &self.0 {
|
||||
Either::MlKem768(pk) => &pk.bytes,
|
||||
Either::MlKem1024(pk) => &pk.bytes,
|
||||
}
|
||||
}
|
||||
|
||||
/// Encapsulates a shared secret to the given public key, returning
|
||||
/// `(ciphertext, shared_secret)`.
|
||||
pub fn encapsulate(&self) -> Result<(Vec<u8>, MlKemSharedSecret), ErrorStack> {
|
||||
match &self.0 {
|
||||
Either::MlKem768(pk) => {
|
||||
let (ct, ss) = pk.encapsulate();
|
||||
Ok((ct.to_vec(), ss))
|
||||
}
|
||||
Either::MlKem1024(pk) => {
|
||||
let (ct, ss) = pk.encapsulate();
|
||||
Ok((ct.to_vec(), ss))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Query public key and ciphertext length
|
||||
pub fn algorithm(&self) -> Algorithm {
|
||||
match self.0 {
|
||||
Either::MlKem768(_) => Algorithm::MlKem768,
|
||||
Either::MlKem1024(_) => Algorithm::MlKem1024,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MlKemPrivateKey {
|
||||
/// Expand private key from the seed bytes
|
||||
pub fn from_seed(
|
||||
algorithm: Algorithm,
|
||||
private_seed: &MlKemPrivateKeySeed,
|
||||
) -> Result<Self, ErrorStack> {
|
||||
match algorithm {
|
||||
Algorithm::MlKem768 => Ok(Self(Either::MlKem768(Box::new(
|
||||
MlKem768PrivateKey::from_seed(private_seed)?,
|
||||
)))),
|
||||
Algorithm::MlKem1024 => Ok(Self(Either::MlKem1024(Box::new(
|
||||
MlKem1024PrivateKey::from_seed(private_seed)?,
|
||||
)))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Secret seed bytes of this private key
|
||||
pub fn seed_bytes(&self) -> &MlKemPrivateKeySeed {
|
||||
match &self.0 {
|
||||
Either::MlKem768(sk) => &sk.seed,
|
||||
Either::MlKem1024(sk) => &sk.seed,
|
||||
}
|
||||
}
|
||||
|
||||
/// Decapsulates a shared secret from a ciphertext using the private key.
|
||||
pub fn decapsulate(&self, ciphertext: &[u8]) -> Result<MlKemSharedSecret, ErrorStack> {
|
||||
match &self.0 {
|
||||
Either::MlKem768(sk) => {
|
||||
let ct: &[u8; MlKem768PrivateKey::CIPHERTEXT_BYTES] = ciphertext
|
||||
.try_into()
|
||||
.map_err(|_| ErrorStack::internal_error_str("invalid ciphertext length"))?;
|
||||
Ok(sk.decapsulate(ct))
|
||||
}
|
||||
Either::MlKem1024(sk) => {
|
||||
let ct: &[u8; MlKem1024PrivateKey::CIPHERTEXT_BYTES] = ciphertext
|
||||
.try_into()
|
||||
.map_err(|_| ErrorStack::internal_error_str("invalid ciphertext length"))?;
|
||||
Ok(sk.decapsulate(ct))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Query public key and ciphertext length
|
||||
pub fn algorithm(&self) -> Algorithm {
|
||||
match self.0 {
|
||||
Either::MlKem768(_) => Algorithm::MlKem768,
|
||||
Either::MlKem1024(_) => Algorithm::MlKem1024,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ML-KEM-768 private key.
|
||||
///
|
||||
/// Caches the expanded key for fast decapsulation.
|
||||
struct MlKem768PrivateKey {
|
||||
seed: MlKemPrivateKeySeed,
|
||||
expanded: ffi::MLKEM768_private_key,
|
||||
}
|
||||
|
||||
impl Clone for MlKem768PrivateKey {
|
||||
fn clone(&self) -> Self {
|
||||
// unwrap is safe: cloning a valid key with a valid seed always succeeds
|
||||
Self::from_seed(&self.seed).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl MlKem768PrivateKey {
|
||||
pub const CIPHERTEXT_BYTES: usize = ffi::MLKEM768_CIPHERTEXT_BYTES as usize;
|
||||
|
||||
/// Generate a new key pair.
|
||||
fn generate() -> Result<(Box<MlKem768PublicKey>, Box<MlKem768PrivateKey>), ErrorStack> {
|
||||
// SAFETY: all buffers are out parameters, correctly sized
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let mut bytes = [0; MlKem768PublicKey::PUBLIC_KEY_BYTES];
|
||||
let mut seed = [0; PRIVATE_KEY_SEED_BYTES];
|
||||
let mut expanded: MaybeUninit<ffi::MLKEM768_private_key> = MaybeUninit::uninit();
|
||||
|
||||
ffi::MLKEM768_generate_key(
|
||||
bytes.as_mut_ptr().cast(),
|
||||
seed.as_mut_ptr(),
|
||||
expanded.as_mut_ptr(),
|
||||
);
|
||||
|
||||
Ok((
|
||||
Box::new(MlKem768PublicKey::from_slice(&bytes)?),
|
||||
Box::new(MlKem768PrivateKey {
|
||||
seed,
|
||||
expanded: expanded.assume_init(),
|
||||
}),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Restore private key from seed.
|
||||
fn from_seed(seed: &MlKemPrivateKeySeed) -> Result<Self, ErrorStack> {
|
||||
// SAFETY: seed is 64 bytes, out parameter correctly sized
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let mut expanded: MaybeUninit<ffi::MLKEM768_private_key> = MaybeUninit::uninit();
|
||||
cvt(ffi::MLKEM768_private_key_from_seed(
|
||||
expanded.as_mut_ptr(),
|
||||
seed.as_ptr(),
|
||||
seed.len(),
|
||||
))?;
|
||||
Ok(Self {
|
||||
seed: *seed,
|
||||
expanded: expanded.assume_init(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Derive the public key.
|
||||
#[cfg(test)]
|
||||
fn public_key(&self) -> Result<MlKem768PublicKey, ErrorStack> {
|
||||
// SAFETY: expanded key is valid, buffers correctly sized
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let mut parsed: MaybeUninit<ffi::MLKEM768_public_key> = MaybeUninit::uninit();
|
||||
ffi::MLKEM768_public_from_private(parsed.as_mut_ptr(), &self.expanded);
|
||||
|
||||
let mut bytes = [0u8; MlKem768PublicKey::PUBLIC_KEY_BYTES];
|
||||
let mut cbb: MaybeUninit<ffi::CBB> = MaybeUninit::uninit();
|
||||
cvt(ffi::CBB_init_fixed(
|
||||
cbb.as_mut_ptr(),
|
||||
bytes.as_mut_ptr(),
|
||||
bytes.len(),
|
||||
))?;
|
||||
cvt(ffi::MLKEM768_marshal_public_key(
|
||||
cbb.as_mut_ptr(),
|
||||
parsed.as_ptr(),
|
||||
))?;
|
||||
|
||||
Ok(MlKem768PublicKey {
|
||||
bytes,
|
||||
parsed: parsed.assume_init(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Decapsulate to get the shared secret.
|
||||
fn decapsulate(&self, ciphertext: &[u8; Self::CIPHERTEXT_BYTES]) -> MlKemSharedSecret {
|
||||
// SAFETY: expanded key is valid, ciphertext is correctly sized
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let mut shared_secret = [0u8; SHARED_SECRET_BYTES];
|
||||
|
||||
ffi::MLKEM768_decap(
|
||||
shared_secret.as_mut_ptr(),
|
||||
ciphertext.as_ptr(),
|
||||
ciphertext.len(),
|
||||
&self.expanded,
|
||||
);
|
||||
|
||||
shared_secret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MlKem768PrivateKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("MlKem768PrivateKey")
|
||||
.field("key", &"[redacted]")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MlKem768PrivateKey {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: pointers and lengths are valid
|
||||
unsafe {
|
||||
ffi::OPENSSL_cleanse(self.seed.as_mut_ptr().cast(), self.seed.len());
|
||||
ffi::OPENSSL_cleanse(
|
||||
self.expanded.opaque.bytes.as_mut_ptr().cast(),
|
||||
self.expanded.opaque.bytes.len(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ML-KEM-768 public key.
|
||||
#[derive(Clone)]
|
||||
struct MlKem768PublicKey {
|
||||
bytes: [u8; Self::PUBLIC_KEY_BYTES],
|
||||
parsed: ffi::MLKEM768_public_key,
|
||||
}
|
||||
|
||||
impl MlKem768PublicKey {
|
||||
pub const PUBLIC_KEY_BYTES: usize = ffi::MLKEM768_PUBLIC_KEY_BYTES as usize;
|
||||
|
||||
/// Parse and validate a public key.
|
||||
///
|
||||
/// The slice must be [`Self::PUBLIC_KEY_BYTES`] long.
|
||||
fn from_slice(slice: &[u8]) -> Result<Self, ErrorStack> {
|
||||
if slice.len() != Self::PUBLIC_KEY_BYTES {
|
||||
return Err(ErrorStack::internal_error_str("invalid public key length"));
|
||||
}
|
||||
|
||||
// SAFETY: CBS correctly initialized, length already checked
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let mut cbs = cbs_init(slice);
|
||||
let mut parsed: MaybeUninit<ffi::MLKEM768_public_key> = MaybeUninit::uninit();
|
||||
|
||||
cvt(ffi::MLKEM768_parse_public_key(
|
||||
parsed.as_mut_ptr(),
|
||||
&mut cbs,
|
||||
))?;
|
||||
if cbs.len != 0 {
|
||||
return Err(ErrorStack::internal_error_str(
|
||||
"trailing bytes after public key",
|
||||
));
|
||||
}
|
||||
|
||||
let mut bytes = [0u8; Self::PUBLIC_KEY_BYTES];
|
||||
bytes.copy_from_slice(slice);
|
||||
Ok(Self {
|
||||
bytes,
|
||||
parsed: parsed.assume_init(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Encapsulate: returns (ciphertext, shared_secret).
|
||||
fn encapsulate(
|
||||
&self,
|
||||
) -> (
|
||||
[u8; MlKem768PrivateKey::CIPHERTEXT_BYTES],
|
||||
MlKemSharedSecret,
|
||||
) {
|
||||
// SAFETY: buffers correctly sized, parsed key is valid
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let mut ciphertext = [0u8; MlKem768PrivateKey::CIPHERTEXT_BYTES];
|
||||
let mut shared_secret = [0u8; SHARED_SECRET_BYTES];
|
||||
|
||||
ffi::MLKEM768_encap(
|
||||
ciphertext.as_mut_ptr(),
|
||||
shared_secret.as_mut_ptr(),
|
||||
&self.parsed,
|
||||
);
|
||||
|
||||
(ciphertext, shared_secret)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MlKem768PublicKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("MlKem768PublicKey")
|
||||
.field("bytes", &format_args!("[{}]", self.bytes.len()))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// ML-KEM-1024 private key.
|
||||
///
|
||||
/// Prefer ML-KEM-768 unless you need AES-256 equivalent security.
|
||||
/// Caches the expanded key for fast decapsulation.
|
||||
struct MlKem1024PrivateKey {
|
||||
seed: MlKemPrivateKeySeed,
|
||||
expanded: ffi::MLKEM1024_private_key,
|
||||
}
|
||||
|
||||
impl Clone for MlKem1024PrivateKey {
|
||||
fn clone(&self) -> Self {
|
||||
// unwrap is safe: cloning a valid key with a valid seed always succeeds
|
||||
Self::from_seed(&self.seed).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl MlKem1024PrivateKey {
|
||||
pub const CIPHERTEXT_BYTES: usize = ffi::MLKEM1024_CIPHERTEXT_BYTES as usize;
|
||||
|
||||
/// Generate a new key pair.
|
||||
fn generate() -> Result<(Box<MlKem1024PublicKey>, Box<MlKem1024PrivateKey>), ErrorStack> {
|
||||
// SAFETY: all buffers are out parameters, correctly sized
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let mut bytes = [0; MlKem1024PublicKey::PUBLIC_KEY_BYTES];
|
||||
let mut seed = [0; PRIVATE_KEY_SEED_BYTES];
|
||||
let mut expanded: MaybeUninit<ffi::MLKEM1024_private_key> = MaybeUninit::uninit();
|
||||
|
||||
ffi::MLKEM1024_generate_key(
|
||||
bytes.as_mut_ptr().cast(),
|
||||
seed.as_mut_ptr(),
|
||||
expanded.as_mut_ptr(),
|
||||
);
|
||||
|
||||
Ok((
|
||||
Box::new(MlKem1024PublicKey::from_slice(&bytes)?),
|
||||
Box::new(MlKem1024PrivateKey {
|
||||
seed,
|
||||
expanded: expanded.assume_init(),
|
||||
}),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Restore private key from seed.
|
||||
fn from_seed(seed: &MlKemPrivateKeySeed) -> Result<Self, ErrorStack> {
|
||||
// SAFETY: seed is 64 bytes, out parameter correctly sized
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let mut expanded: MaybeUninit<ffi::MLKEM1024_private_key> = MaybeUninit::uninit();
|
||||
cvt(ffi::MLKEM1024_private_key_from_seed(
|
||||
expanded.as_mut_ptr(),
|
||||
seed.as_ptr(),
|
||||
seed.len(),
|
||||
))?;
|
||||
Ok(Self {
|
||||
seed: *seed,
|
||||
expanded: expanded.assume_init(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Derive the public key.
|
||||
#[cfg(test)]
|
||||
fn public_key(&self) -> Result<MlKem1024PublicKey, ErrorStack> {
|
||||
// SAFETY: expanded key is valid, buffers correctly sized
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let mut parsed: MaybeUninit<ffi::MLKEM1024_public_key> = MaybeUninit::uninit();
|
||||
ffi::MLKEM1024_public_from_private(parsed.as_mut_ptr(), &self.expanded);
|
||||
|
||||
let mut bytes = [0u8; MlKem1024PublicKey::PUBLIC_KEY_BYTES];
|
||||
let mut cbb: MaybeUninit<ffi::CBB> = MaybeUninit::uninit();
|
||||
cvt(ffi::CBB_init_fixed(
|
||||
cbb.as_mut_ptr(),
|
||||
bytes.as_mut_ptr(),
|
||||
bytes.len(),
|
||||
))?;
|
||||
cvt(ffi::MLKEM1024_marshal_public_key(
|
||||
cbb.as_mut_ptr(),
|
||||
parsed.as_ptr(),
|
||||
))?;
|
||||
|
||||
Ok(MlKem1024PublicKey {
|
||||
bytes,
|
||||
parsed: parsed.assume_init(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Decapsulate to get the shared secret.
|
||||
fn decapsulate(&self, ciphertext: &[u8; Self::CIPHERTEXT_BYTES]) -> MlKemSharedSecret {
|
||||
// SAFETY: expanded key is valid, ciphertext is correctly sized
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let mut shared_secret = [0u8; SHARED_SECRET_BYTES];
|
||||
|
||||
ffi::MLKEM1024_decap(
|
||||
shared_secret.as_mut_ptr(),
|
||||
ciphertext.as_ptr(),
|
||||
ciphertext.len(),
|
||||
&self.expanded,
|
||||
);
|
||||
|
||||
shared_secret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MlKem1024PrivateKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("MlKem1024PrivateKey")
|
||||
.field("key", &"[redacted]")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MlKem1024PrivateKey {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: pointers and lengths are valid
|
||||
unsafe {
|
||||
ffi::OPENSSL_cleanse(self.seed.as_mut_ptr().cast(), self.seed.len());
|
||||
ffi::OPENSSL_cleanse(
|
||||
self.expanded.opaque.bytes.as_mut_ptr().cast(),
|
||||
self.expanded.opaque.bytes.len(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ML-KEM-1024 public key.
|
||||
///
|
||||
/// Prefer ML-KEM-768 unless you need AES-256 equivalent security.
|
||||
#[derive(Clone)]
|
||||
struct MlKem1024PublicKey {
|
||||
bytes: [u8; Self::PUBLIC_KEY_BYTES],
|
||||
parsed: ffi::MLKEM1024_public_key,
|
||||
}
|
||||
|
||||
impl MlKem1024PublicKey {
|
||||
pub const PUBLIC_KEY_BYTES: usize = ffi::MLKEM1024_PUBLIC_KEY_BYTES as usize;
|
||||
|
||||
/// Parse and validate a serialized public key.
|
||||
///
|
||||
/// The slice must be [`Self::PUBLIC_KEY_BYTES`] long.
|
||||
fn from_slice(slice: &[u8]) -> Result<Self, ErrorStack> {
|
||||
if slice.len() != Self::PUBLIC_KEY_BYTES {
|
||||
return Err(ErrorStack::internal_error_str("invalid public key length"));
|
||||
}
|
||||
|
||||
// SAFETY: CBS correctly initialized, length already checked
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let mut cbs = cbs_init(slice);
|
||||
let mut parsed: MaybeUninit<ffi::MLKEM1024_public_key> = MaybeUninit::uninit();
|
||||
|
||||
cvt(ffi::MLKEM1024_parse_public_key(
|
||||
parsed.as_mut_ptr(),
|
||||
&mut cbs,
|
||||
))?;
|
||||
if cbs.len != 0 {
|
||||
return Err(ErrorStack::internal_error_str(
|
||||
"trailing bytes after public key",
|
||||
));
|
||||
}
|
||||
|
||||
let mut bytes = [0u8; Self::PUBLIC_KEY_BYTES];
|
||||
bytes.copy_from_slice(slice);
|
||||
Ok(Self {
|
||||
bytes,
|
||||
parsed: parsed.assume_init(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Encapsulate: returns (ciphertext, shared_secret).
|
||||
fn encapsulate(
|
||||
&self,
|
||||
) -> (
|
||||
[u8; MlKem1024PrivateKey::CIPHERTEXT_BYTES],
|
||||
[u8; SHARED_SECRET_BYTES],
|
||||
) {
|
||||
// SAFETY: buffers correctly sized, parsed key is valid
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let mut ciphertext = [0u8; MlKem1024PrivateKey::CIPHERTEXT_BYTES];
|
||||
let mut shared_secret = [0u8; SHARED_SECRET_BYTES];
|
||||
|
||||
ffi::MLKEM1024_encap(
|
||||
ciphertext.as_mut_ptr(),
|
||||
shared_secret.as_mut_ptr(),
|
||||
&self.parsed,
|
||||
);
|
||||
|
||||
(ciphertext, shared_secret)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MlKem1024PublicKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("MlKem1024PublicKey")
|
||||
.field("bytes", &format_args!("[{}]", self.bytes.len()))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! mlkem_tests {
|
||||
($name:ident, $priv:ty, $pub:ty, $ct_len:expr) => {
|
||||
mod $name {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn roundtrip() {
|
||||
let (pk, sk) = <$priv>::generate().unwrap();
|
||||
let (ct, ss1) = pk.encapsulate();
|
||||
let ss2 = sk.decapsulate(&ct);
|
||||
assert_eq!(ss1, ss2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seed_roundtrip() {
|
||||
let (pk, sk) = <$priv>::generate().unwrap();
|
||||
let sk2 = <$priv>::from_seed(&sk.seed).unwrap();
|
||||
let (ct, ss1) = pk.encapsulate();
|
||||
let ss2 = sk2.decapsulate(&ct);
|
||||
assert_eq!(ss1, ss2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_pubkey() {
|
||||
let (pk, sk) = <$priv>::generate().unwrap();
|
||||
assert_eq!(pk.bytes, sk.public_key().unwrap().bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_slice_rejects_bad_len() {
|
||||
assert!(<$pub>::from_slice(&[0u8; 100]).is_err());
|
||||
assert!(<$pub>::from_slice(&[]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_slice_roundtrip() {
|
||||
let (pk, _) = <$priv>::generate().unwrap();
|
||||
let pk2 = <$pub>::from_slice(&pk.bytes).unwrap();
|
||||
assert_eq!(pk.bytes, pk2.bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn implicit_rejection() {
|
||||
let (_, sk) = <$priv>::generate().unwrap();
|
||||
let bad_ct = [0x42u8; $ct_len];
|
||||
// bad ciphertext still "works", just returns deterministic garbage
|
||||
let ss1 = sk.decapsulate(&bad_ct);
|
||||
let ss2 = sk.decapsulate(&bad_ct);
|
||||
assert_eq!(ss1, ss2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_redacts_seed() {
|
||||
let (_, sk) = <$priv>::generate().unwrap();
|
||||
let dbg = format!("{:?}", sk);
|
||||
assert!(dbg.contains("redacted"));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mlkem_tests!(mlkem768, MlKem768PrivateKey, MlKem768PublicKey, 1088);
|
||||
mlkem_tests!(mlkem1024, MlKem1024PrivateKey, MlKem1024PublicKey, 1568);
|
||||
|
||||
// Tests for unified API (MlKem struct)
|
||||
mod unified_api {
|
||||
use super::*;
|
||||
|
||||
macro_rules! unified_tests {
|
||||
($name:ident, $algorithm:expr, $pk_len:expr, $ct_len:expr) => {
|
||||
mod $name {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn roundtrip() {
|
||||
let (pk, sk) = MlKemPrivateKey::generate($algorithm).unwrap();
|
||||
let (ct, ss1) = pk.encapsulate().unwrap();
|
||||
let ss2 = sk.decapsulate(&ct).unwrap();
|
||||
assert_eq!(ss1, ss2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_sizes() {
|
||||
assert_eq!($algorithm.public_key_bytes(), $pk_len);
|
||||
assert_eq!($algorithm.ciphertext_bytes(), $ct_len);
|
||||
|
||||
let (pk, private_key) = MlKemPrivateKey::generate($algorithm).unwrap();
|
||||
assert_eq!(pk.as_bytes().len(), $pk_len);
|
||||
assert_eq!(private_key.seed_bytes().len(), PRIVATE_KEY_SEED_BYTES);
|
||||
|
||||
let (ct, ss) = pk.encapsulate().unwrap();
|
||||
assert_eq!(ct.len(), $ct_len);
|
||||
assert_eq!(ss.len(), SHARED_SECRET_BYTES);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_public_key_length() {
|
||||
let result = MlKemPublicKey::from_slice($algorithm, &[0u8; 100]);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_ciphertext_length() {
|
||||
let (_, sk) = MlKemPrivateKey::generate($algorithm).unwrap();
|
||||
let result = sk.decapsulate(&[0u8; 100]);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
unified_tests!(mlkem768, Algorithm::MlKem768, 1184, 1088);
|
||||
unified_tests!(mlkem1024, Algorithm::MlKem1024, 1568, 1568);
|
||||
|
||||
#[test]
|
||||
fn params_constants() {
|
||||
assert_eq!(Algorithm::MlKem768.public_key_bytes(), 1184);
|
||||
assert_eq!(Algorithm::MlKem768.ciphertext_bytes(), 1088);
|
||||
assert_eq!(Algorithm::MlKem1024.public_key_bytes(), 1568);
|
||||
assert_eq!(Algorithm::MlKem1024.ciphertext_bytes(), 1568);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
//! A collection of numerical identifiers for OpenSSL objects.
|
||||
use crate::ffi;
|
||||
use libc::{c_char, c_int};
|
||||
use libc::c_int;
|
||||
use openssl_macros::corresponds;
|
||||
|
||||
use std::ffi::CStr;
|
||||
@ -51,12 +51,14 @@ pub struct Nid(c_int);
|
||||
#[allow(non_snake_case)]
|
||||
impl Nid {
|
||||
/// Create a `Nid` from an integer representation.
|
||||
#[must_use]
|
||||
pub fn from_raw(raw: c_int) -> Nid {
|
||||
Nid(raw)
|
||||
}
|
||||
|
||||
/// Return the integer representation of a `Nid`.
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
#[must_use]
|
||||
pub fn as_raw(&self) -> c_int {
|
||||
self.0
|
||||
}
|
||||
@ -64,6 +66,7 @@ impl Nid {
|
||||
/// Returns the `Nid`s of the digest and public key algorithms associated with a signature ID.
|
||||
#[corresponds(OBJ_find_sigid_algs)]
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
#[must_use]
|
||||
pub fn signature_algorithms(&self) -> Option<SignatureAlgorithms> {
|
||||
unsafe {
|
||||
let mut digest = 0;
|
||||
@ -84,8 +87,10 @@ impl Nid {
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
pub fn long_name(&self) -> Result<&'static str, ErrorStack> {
|
||||
unsafe {
|
||||
cvt_p(ffi::OBJ_nid2ln(self.0) as *mut c_char)
|
||||
.map(|nameptr| str::from_utf8(CStr::from_ptr(nameptr).to_bytes()).unwrap())
|
||||
let nameptr = cvt_p(ffi::OBJ_nid2ln(self.0).cast_mut())?;
|
||||
CStr::from_ptr(nameptr)
|
||||
.to_str()
|
||||
.map_err(ErrorStack::internal_error)
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,8 +99,10 @@ impl Nid {
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
pub fn short_name(&self) -> Result<&'static str, ErrorStack> {
|
||||
unsafe {
|
||||
cvt_p(ffi::OBJ_nid2sn(self.0) as *mut c_char)
|
||||
.map(|nameptr| str::from_utf8(CStr::from_ptr(nameptr).to_bytes()).unwrap())
|
||||
let nameptr = cvt_p(ffi::OBJ_nid2sn(self.0).cast_mut())?;
|
||||
CStr::from_ptr(nameptr)
|
||||
.to_str()
|
||||
.map_err(ErrorStack::internal_error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ use std::ptr;
|
||||
use crate::error::ErrorStack;
|
||||
use crate::nid::Nid;
|
||||
use crate::pkey::{HasPrivate, PKey, PKeyRef, Private};
|
||||
use crate::stack::Stack;
|
||||
use crate::stack::{Stack, StackRef};
|
||||
use crate::x509::{X509Ref, X509};
|
||||
use crate::{cvt_0i, cvt_p};
|
||||
|
||||
@ -31,33 +31,43 @@ impl Pkcs12Ref {
|
||||
ffi::i2d_PKCS12
|
||||
}
|
||||
|
||||
/// Extracts the contents of the `Pkcs12`.
|
||||
/// Extracts the contents of the `Pkcs12` with `pkey` and `cert` required.
|
||||
pub fn parse(&self, pass: &str) -> Result<ParsedPkcs12, ErrorStack> {
|
||||
let p2 = self.parse2(pass)?;
|
||||
Ok(ParsedPkcs12 {
|
||||
pkey: p2
|
||||
.pkey
|
||||
.ok_or_else(|| ErrorStack::internal_error_str("missing pkey"))?,
|
||||
cert: p2
|
||||
.cert
|
||||
.ok_or_else(|| ErrorStack::internal_error_str("missing cert"))?,
|
||||
chain: p2.ca,
|
||||
})
|
||||
}
|
||||
|
||||
/// Extracts the contents of the `Pkcs12` with `pkey` and `cert` optional.
|
||||
#[corresponds(PKCS12_parse)]
|
||||
pub fn parse2(&self, pass: &str) -> Result<ParsedPkcs12_2, ErrorStack> {
|
||||
unsafe {
|
||||
let pass = CString::new(pass.as_bytes()).unwrap();
|
||||
let pass = CString::new(pass.as_bytes()).map_err(ErrorStack::internal_error)?;
|
||||
|
||||
let mut pkey = ptr::null_mut();
|
||||
let mut cert = ptr::null_mut();
|
||||
let mut chain = ptr::null_mut();
|
||||
let mut ca = ptr::null_mut();
|
||||
|
||||
cvt_0i(ffi::PKCS12_parse(
|
||||
self.as_ptr(),
|
||||
pass.as_ptr(),
|
||||
&mut pkey,
|
||||
&mut cert,
|
||||
&mut chain,
|
||||
&mut ca,
|
||||
))?;
|
||||
|
||||
let pkey = PKey::from_ptr(pkey);
|
||||
let cert = X509::from_ptr(cert);
|
||||
let pkey = (!pkey.is_null()).then(|| PKey::from_ptr(pkey));
|
||||
let cert = (!cert.is_null()).then(|| X509::from_ptr(cert));
|
||||
let ca = (!ca.is_null()).then(|| Stack::from_ptr(ca));
|
||||
|
||||
let chain = if chain.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(Stack::from_ptr(chain))
|
||||
};
|
||||
|
||||
Ok(ParsedPkcs12 { pkey, cert, chain })
|
||||
Ok(ParsedPkcs12_2 { pkey, cert, ca })
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -80,6 +90,7 @@ impl Pkcs12 {
|
||||
/// * `nid_cert` - `nid::PBE_WITHSHA1AND40BITRC2_CBC`
|
||||
/// * `iter` - `2048`
|
||||
/// * `mac_iter` - `2048`
|
||||
#[must_use]
|
||||
pub fn builder() -> Pkcs12Builder {
|
||||
ffi::init();
|
||||
|
||||
@ -99,6 +110,19 @@ pub struct ParsedPkcs12 {
|
||||
pub chain: Option<Stack<X509>>,
|
||||
}
|
||||
|
||||
/// [`ParsedPkcs12`] with optional fields
|
||||
pub struct ParsedPkcs12_2 {
|
||||
pub pkey: Option<PKey<Private>>,
|
||||
pub cert: Option<X509>,
|
||||
pub ca: Option<Stack<X509>>,
|
||||
}
|
||||
|
||||
impl ParsedPkcs12_2 {
|
||||
pub fn chain(&self) -> Option<&StackRef<X509>> {
|
||||
self.ca.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Pkcs12Builder {
|
||||
nid_key: Nid,
|
||||
nid_cert: Nid,
|
||||
@ -161,8 +185,8 @@ impl Pkcs12Builder {
|
||||
T: HasPrivate,
|
||||
{
|
||||
unsafe {
|
||||
let pass = CString::new(password).unwrap();
|
||||
let friendly_name = CString::new(friendly_name).unwrap();
|
||||
let pass = CString::new(password).map_err(ErrorStack::internal_error)?;
|
||||
let friendly_name = CString::new(friendly_name).map_err(ErrorStack::internal_error)?;
|
||||
let pkey = pkey.as_ptr();
|
||||
let cert = cert.as_ptr();
|
||||
let ca = self
|
||||
@ -179,8 +203,8 @@ impl Pkcs12Builder {
|
||||
let keytype = 0;
|
||||
|
||||
cvt_p(ffi::PKCS12_create(
|
||||
pass.as_ptr() as *const _ as *mut _,
|
||||
friendly_name.as_ptr() as *const _ as *mut _,
|
||||
pass.as_ptr(),
|
||||
friendly_name.as_ptr(),
|
||||
pkey,
|
||||
cert,
|
||||
ca,
|
||||
@ -259,7 +283,7 @@ mod test {
|
||||
.unwrap();
|
||||
builder.set_subject_name(&name).unwrap();
|
||||
builder.set_issuer_name(&name).unwrap();
|
||||
builder.append_extension(key_usage).unwrap();
|
||||
builder.append_extension(&key_usage).unwrap();
|
||||
builder.set_pubkey(&pkey).unwrap();
|
||||
builder.sign(&pkey, MessageDigest::sha256()).unwrap();
|
||||
let cert = builder.build();
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
use crate::ffi;
|
||||
use libc::{c_int, c_uint};
|
||||
use std::ffi::c_int;
|
||||
use std::ptr;
|
||||
|
||||
use crate::cvt;
|
||||
use crate::error::ErrorStack;
|
||||
use crate::hash::MessageDigest;
|
||||
use crate::symm::Cipher;
|
||||
use crate::{cvt, cvt_nz, try_int};
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
|
||||
pub struct KeyIvPair {
|
||||
@ -49,7 +49,7 @@ pub fn bytes_to_key(
|
||||
let cipher = cipher.as_ptr();
|
||||
let digest = digest.as_ptr();
|
||||
|
||||
let len = cvt(ffi::EVP_BytesToKey(
|
||||
let len = cvt_nz(ffi::EVP_BytesToKey(
|
||||
cipher,
|
||||
digest,
|
||||
salt_ptr,
|
||||
@ -60,7 +60,7 @@ pub fn bytes_to_key(
|
||||
ptr::null_mut(),
|
||||
))?;
|
||||
|
||||
let mut key = vec![0; len as usize];
|
||||
let mut key = vec![0; len.get()];
|
||||
let iv_ptr = iv
|
||||
.as_mut()
|
||||
.map(|v| v.as_mut_ptr())
|
||||
@ -90,22 +90,18 @@ pub fn pbkdf2_hmac(
|
||||
key: &mut [u8],
|
||||
) -> Result<(), ErrorStack> {
|
||||
unsafe {
|
||||
assert!(pass.len() <= c_int::MAX as usize);
|
||||
assert!(salt.len() <= c_int::MAX as usize);
|
||||
assert!(key.len() <= c_int::MAX as usize);
|
||||
|
||||
ffi::init();
|
||||
|
||||
cvt(ffi::PKCS5_PBKDF2_HMAC(
|
||||
pass.as_ptr() as *const _,
|
||||
pass.as_ptr().cast(),
|
||||
pass.len(),
|
||||
salt.as_ptr(),
|
||||
salt.len(),
|
||||
iter as c_uint,
|
||||
try_int(iter)?,
|
||||
hash.as_ptr(),
|
||||
key.len(),
|
||||
key.as_mut_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,18 +118,17 @@ pub fn scrypt(
|
||||
unsafe {
|
||||
ffi::init();
|
||||
cvt(ffi::EVP_PBE_scrypt(
|
||||
pass.as_ptr() as *const _,
|
||||
pass.as_ptr().cast(),
|
||||
pass.len(),
|
||||
salt.as_ptr() as *const _,
|
||||
salt.as_ptr().cast(),
|
||||
salt.len(),
|
||||
n,
|
||||
r,
|
||||
p,
|
||||
maxmem,
|
||||
key.as_mut_ptr() as *mut _,
|
||||
key.as_mut_ptr(),
|
||||
key.len(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -40,7 +40,6 @@
|
||||
//! println!("{:?}", str::from_utf8(pub_key.as_slice()).unwrap());
|
||||
//! ```
|
||||
|
||||
use crate::ffi;
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use libc::{c_int, c_long};
|
||||
use openssl_macros::corresponds;
|
||||
@ -54,9 +53,11 @@ use crate::dh::Dh;
|
||||
use crate::dsa::Dsa;
|
||||
use crate::ec::EcKey;
|
||||
use crate::error::ErrorStack;
|
||||
use crate::ffi;
|
||||
use crate::rsa::Rsa;
|
||||
use crate::try_int;
|
||||
use crate::util::{invoke_passwd_cb, CallbackState};
|
||||
use crate::{cvt, cvt_p};
|
||||
use crate::{cvt, cvt_0i, cvt_p};
|
||||
|
||||
/// A tag type indicating that a key only has parameters.
|
||||
pub enum Params {}
|
||||
@ -83,12 +84,14 @@ impl Id {
|
||||
pub const X448: Id = Id(ffi::EVP_PKEY_X448);
|
||||
|
||||
/// Creates a `Id` from an integer representation.
|
||||
#[must_use]
|
||||
pub fn from_raw(value: c_int) -> Id {
|
||||
Id(value)
|
||||
}
|
||||
|
||||
/// Returns the integer representation of the `Id`.
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
#[must_use]
|
||||
pub fn as_raw(&self) -> c_int {
|
||||
self.0
|
||||
}
|
||||
@ -176,12 +179,14 @@ impl<T> PKeyRef<T> {
|
||||
|
||||
/// Returns the `Id` that represents the type of this key.
|
||||
#[corresponds(EVP_PKEY_id)]
|
||||
#[must_use]
|
||||
pub fn id(&self) -> Id {
|
||||
unsafe { Id::from_raw(ffi::EVP_PKEY_id(self.as_ptr())) }
|
||||
}
|
||||
|
||||
/// Returns the maximum size of a signature in bytes.
|
||||
#[corresponds(EVP_PKEY_size)]
|
||||
#[must_use]
|
||||
pub fn size(&self) -> usize {
|
||||
unsafe { ffi::EVP_PKEY_size(self.as_ptr()) as usize }
|
||||
}
|
||||
@ -211,17 +216,49 @@ where
|
||||
///
|
||||
/// This corresponds to the bit length of the modulus of an RSA key, and the bit length of the
|
||||
/// group order for an elliptic curve key, for example.
|
||||
#[must_use]
|
||||
pub fn bits(&self) -> u32 {
|
||||
unsafe { ffi::EVP_PKEY_bits(self.as_ptr()) as u32 }
|
||||
}
|
||||
|
||||
/// Compares the public component of this key with another.
|
||||
#[must_use]
|
||||
pub fn public_eq<U>(&self, other: &PKeyRef<U>) -> bool
|
||||
where
|
||||
U: HasPublic,
|
||||
{
|
||||
unsafe { ffi::EVP_PKEY_cmp(self.as_ptr(), other.as_ptr()) == 1 }
|
||||
}
|
||||
|
||||
/// Returns the length of the "raw" form of the public key. Only supported for certain key types.
|
||||
#[corresponds(EVP_PKEY_get_raw_public_key)]
|
||||
pub fn raw_public_key_len(&self) -> Result<usize, ErrorStack> {
|
||||
unsafe {
|
||||
let mut size = 0;
|
||||
_ = cvt_0i(ffi::EVP_PKEY_get_raw_public_key(
|
||||
self.as_ptr(),
|
||||
std::ptr::null_mut(),
|
||||
&mut size,
|
||||
))?;
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
/// Outputs a copy of the "raw" form of the public key. Only supported for certain key types.
|
||||
///
|
||||
/// Returns the used portion of `out`.
|
||||
#[corresponds(EVP_PKEY_get_raw_public_key)]
|
||||
pub fn raw_public_key<'a>(&self, out: &'a mut [u8]) -> Result<&'a [u8], ErrorStack> {
|
||||
unsafe {
|
||||
let mut size = out.len();
|
||||
_ = cvt_0i(ffi::EVP_PKEY_get_raw_public_key(
|
||||
self.as_ptr(),
|
||||
out.as_mut_ptr(),
|
||||
&mut size,
|
||||
))?;
|
||||
Ok(&out[..size])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PKeyRef<T>
|
||||
@ -260,6 +297,36 @@ where
|
||||
private_key_to_der_pkcs8_passphrase,
|
||||
ffi::i2d_PKCS8PrivateKey_bio
|
||||
}
|
||||
|
||||
/// Returns the length of the "raw" form of the private key. Only supported for certain key types.
|
||||
#[corresponds(EVP_PKEY_get_raw_private_key)]
|
||||
pub fn raw_private_key_len(&self) -> Result<usize, ErrorStack> {
|
||||
unsafe {
|
||||
let mut size = 0;
|
||||
_ = cvt_0i(ffi::EVP_PKEY_get_raw_private_key(
|
||||
self.as_ptr(),
|
||||
std::ptr::null_mut(),
|
||||
&mut size,
|
||||
))?;
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
/// Outputs a copy of the "raw" form of the private key. Only supported for certain key types.
|
||||
///
|
||||
/// Returns the used portion of `out`.
|
||||
#[corresponds(EVP_PKEY_get_raw_private_key)]
|
||||
pub fn raw_private_key<'a>(&self, out: &'a mut [u8]) -> Result<&'a [u8], ErrorStack> {
|
||||
unsafe {
|
||||
let mut size = out.len();
|
||||
_ = cvt_0i(ffi::EVP_PKEY_get_raw_private_key(
|
||||
self.as_ptr(),
|
||||
out.as_mut_ptr(),
|
||||
&mut size,
|
||||
))?;
|
||||
Ok(&out[..size])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for PKey<T> {
|
||||
@ -295,7 +362,7 @@ impl<T> PKey<T> {
|
||||
cvt(ffi::EVP_PKEY_assign(
|
||||
pkey.0,
|
||||
ffi::EVP_PKEY_RSA,
|
||||
rsa.as_ptr() as *mut _,
|
||||
rsa.as_ptr().cast(),
|
||||
))?;
|
||||
mem::forget(rsa);
|
||||
Ok(pkey)
|
||||
@ -311,7 +378,7 @@ impl<T> PKey<T> {
|
||||
cvt(ffi::EVP_PKEY_assign(
|
||||
pkey.0,
|
||||
ffi::EVP_PKEY_EC,
|
||||
ec_key.as_ptr() as *mut _,
|
||||
ec_key.as_ptr().cast(),
|
||||
))?;
|
||||
mem::forget(ec_key);
|
||||
Ok(pkey)
|
||||
@ -389,7 +456,7 @@ impl PKey<Private> {
|
||||
bio.as_ptr(),
|
||||
ptr::null_mut(),
|
||||
Some(invoke_passwd_cb::<F>),
|
||||
&mut cb as *mut _ as *mut _,
|
||||
std::ptr::addr_of_mut!(cb).cast(),
|
||||
))
|
||||
.map(|p| PKey::from_ptr(p))
|
||||
}
|
||||
@ -408,12 +475,12 @@ impl PKey<Private> {
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let bio = MemBioSlice::new(der)?;
|
||||
let passphrase = CString::new(passphrase).unwrap();
|
||||
let passphrase = CString::new(passphrase).map_err(ErrorStack::internal_error)?;
|
||||
cvt_p(ffi::d2i_PKCS8PrivateKey_bio(
|
||||
bio.as_ptr(),
|
||||
ptr::null_mut(),
|
||||
None,
|
||||
passphrase.as_ptr() as *const _ as *mut _,
|
||||
passphrase.as_ptr().cast_mut().cast(),
|
||||
))
|
||||
.map(|p| PKey::from_ptr(p))
|
||||
}
|
||||
@ -445,6 +512,8 @@ use crate::ffi::EVP_PKEY_up_ref;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use hex::FromHex as _;
|
||||
|
||||
use crate::ec::EcKey;
|
||||
use crate::nid::Nid;
|
||||
use crate::rsa::Rsa;
|
||||
@ -555,4 +624,34 @@ mod tests {
|
||||
assert_eq!(pkey.id(), Id::EC);
|
||||
assert!(pkey.rsa().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_raw_accessors() {
|
||||
const ED25519_PRIVATE_KEY_DER: &str = concat!(
|
||||
"302e020100300506032b6570042204207c8c6497f9960d5595d7815f550569e5",
|
||||
"f77764ac97e63e339aaa68cc1512b683"
|
||||
);
|
||||
let pkey =
|
||||
PKey::private_key_from_der(&Vec::from_hex(ED25519_PRIVATE_KEY_DER).unwrap()).unwrap();
|
||||
assert_eq!(pkey.id(), Id::ED25519);
|
||||
|
||||
let priv_len = pkey.raw_private_key_len().unwrap();
|
||||
assert_eq!(priv_len, 32);
|
||||
let mut raw_private_key_buf = [0; 40];
|
||||
let raw_private_key = pkey.raw_private_key(&mut raw_private_key_buf).unwrap();
|
||||
assert_eq!(raw_private_key.len(), 32);
|
||||
assert_ne!(raw_private_key, [0; 32]);
|
||||
pkey.raw_private_key(&mut [0; 5])
|
||||
.expect_err("buffer too small");
|
||||
|
||||
let pub_len = pkey.raw_public_key_len().unwrap();
|
||||
assert_eq!(pub_len, 32);
|
||||
let mut raw_public_key_buf = [0; 40];
|
||||
let raw_public_key = pkey.raw_public_key(&mut raw_public_key_buf).unwrap();
|
||||
assert_eq!(raw_public_key.len(), 32);
|
||||
assert_ne!(raw_public_key, [0; 32]);
|
||||
assert_ne!(raw_public_key, raw_private_key);
|
||||
pkey.raw_public_key(&mut [0; 5])
|
||||
.expect_err("buffer too small");
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ pub fn rand_bytes(buf: &mut [u8]) -> Result<(), ErrorStack> {
|
||||
unsafe {
|
||||
ffi::init();
|
||||
assert!(buf.len() <= c_int::MAX as usize);
|
||||
cvt(ffi::RAND_bytes(buf.as_mut_ptr(), buf.len())).map(|_| ())
|
||||
cvt(ffi::RAND_bytes(buf.as_mut_ptr(), buf.len()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,7 +23,6 @@
|
||||
//! let mut buf = vec![0; rsa.size() as usize];
|
||||
//! let encrypted_len = rsa.public_encrypt(data, &mut buf, Padding::PKCS1).unwrap();
|
||||
//! ```
|
||||
use crate::ffi;
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use libc::c_int;
|
||||
use openssl_macros::corresponds;
|
||||
@ -33,7 +32,9 @@ use std::ptr;
|
||||
|
||||
use crate::bn::{BigNum, BigNumRef};
|
||||
use crate::error::ErrorStack;
|
||||
use crate::ffi;
|
||||
use crate::pkey::{HasPrivate, HasPublic, Private, Public};
|
||||
use crate::try_int;
|
||||
use crate::{cvt, cvt_n, cvt_p};
|
||||
|
||||
pub const EVP_PKEY_OP_SIGN: c_int = 1 << 3;
|
||||
@ -67,12 +68,14 @@ impl Padding {
|
||||
pub const PKCS1_PSS: Padding = Padding(ffi::RSA_PKCS1_PSS_PADDING);
|
||||
|
||||
/// Creates a `Padding` from an integer representation.
|
||||
#[must_use]
|
||||
pub fn from_raw(value: c_int) -> Padding {
|
||||
Padding(value)
|
||||
}
|
||||
|
||||
/// Returns the integer representation of `Padding`.
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
#[must_use]
|
||||
pub fn as_raw(&self) -> c_int {
|
||||
self.0
|
||||
}
|
||||
@ -143,7 +146,7 @@ where
|
||||
to: &mut [u8],
|
||||
padding: Padding,
|
||||
) -> Result<usize, ErrorStack> {
|
||||
assert!(from.len() <= i32::MAX as usize);
|
||||
assert!(i32::try_from(from.len()).is_ok());
|
||||
assert!(to.len() >= self.size() as usize);
|
||||
|
||||
unsafe {
|
||||
@ -170,7 +173,7 @@ where
|
||||
to: &mut [u8],
|
||||
padding: Padding,
|
||||
) -> Result<usize, ErrorStack> {
|
||||
assert!(from.len() <= i32::MAX as usize);
|
||||
assert!(i32::try_from(from.len()).is_ok());
|
||||
assert!(to.len() >= self.size() as usize);
|
||||
|
||||
unsafe {
|
||||
@ -187,16 +190,18 @@ where
|
||||
|
||||
/// Returns a reference to the private exponent of the key.
|
||||
#[corresponds(RSA_get0_key)]
|
||||
#[must_use]
|
||||
pub fn d(&self) -> &BigNumRef {
|
||||
unsafe {
|
||||
let mut d = ptr::null();
|
||||
RSA_get0_key(self.as_ptr(), ptr::null_mut(), ptr::null_mut(), &mut d);
|
||||
BigNumRef::from_ptr(d as *mut _)
|
||||
BigNumRef::from_ptr(d.cast_mut())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the first factor of the exponent of the key.
|
||||
#[corresponds(RSA_get0_factors)]
|
||||
#[must_use]
|
||||
pub fn p(&self) -> Option<&BigNumRef> {
|
||||
unsafe {
|
||||
let mut p = ptr::null();
|
||||
@ -204,13 +209,14 @@ where
|
||||
if p.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(BigNumRef::from_ptr(p as *mut _))
|
||||
Some(BigNumRef::from_ptr(p.cast_mut()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the second factor of the exponent of the key.
|
||||
#[corresponds(RSA_get0_factors)]
|
||||
#[must_use]
|
||||
pub fn q(&self) -> Option<&BigNumRef> {
|
||||
unsafe {
|
||||
let mut q = ptr::null();
|
||||
@ -218,13 +224,14 @@ where
|
||||
if q.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(BigNumRef::from_ptr(q as *mut _))
|
||||
Some(BigNumRef::from_ptr(q.cast_mut()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the first exponent used for CRT calculations.
|
||||
#[corresponds(RSA_get0_crt_params)]
|
||||
#[must_use]
|
||||
pub fn dmp1(&self) -> Option<&BigNumRef> {
|
||||
unsafe {
|
||||
let mut dp = ptr::null();
|
||||
@ -232,13 +239,14 @@ where
|
||||
if dp.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(BigNumRef::from_ptr(dp as *mut _))
|
||||
Some(BigNumRef::from_ptr(dp.cast_mut()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the second exponent used for CRT calculations.
|
||||
#[corresponds(RSA_get0_crt_params)]
|
||||
#[must_use]
|
||||
pub fn dmq1(&self) -> Option<&BigNumRef> {
|
||||
unsafe {
|
||||
let mut dq = ptr::null();
|
||||
@ -246,13 +254,14 @@ where
|
||||
if dq.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(BigNumRef::from_ptr(dq as *mut _))
|
||||
Some(BigNumRef::from_ptr(dq.cast_mut()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the coefficient used for CRT calculations.
|
||||
#[corresponds(RSA_get0_crt_params)]
|
||||
#[must_use]
|
||||
pub fn iqmp(&self) -> Option<&BigNumRef> {
|
||||
unsafe {
|
||||
let mut qi = ptr::null();
|
||||
@ -260,7 +269,7 @@ where
|
||||
if qi.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(BigNumRef::from_ptr(qi as *mut _))
|
||||
Some(BigNumRef::from_ptr(qi.cast_mut()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -319,6 +328,7 @@ where
|
||||
/// Returns the size of the modulus in bytes.
|
||||
#[corresponds(RSA_size)]
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
#[must_use]
|
||||
pub fn size(&self) -> u32 {
|
||||
unsafe { ffi::RSA_size(self.as_ptr()) as u32 }
|
||||
}
|
||||
@ -334,7 +344,7 @@ where
|
||||
to: &mut [u8],
|
||||
padding: Padding,
|
||||
) -> Result<usize, ErrorStack> {
|
||||
assert!(from.len() <= i32::MAX as usize);
|
||||
assert!(i32::try_from(from.len()).is_ok());
|
||||
assert!(to.len() >= self.size() as usize);
|
||||
|
||||
unsafe {
|
||||
@ -360,7 +370,7 @@ where
|
||||
to: &mut [u8],
|
||||
padding: Padding,
|
||||
) -> Result<usize, ErrorStack> {
|
||||
assert!(from.len() <= i32::MAX as usize);
|
||||
assert!(i32::try_from(from.len()).is_ok());
|
||||
assert!(to.len() >= self.size() as usize);
|
||||
|
||||
unsafe {
|
||||
@ -377,21 +387,23 @@ where
|
||||
|
||||
/// Returns a reference to the modulus of the key.
|
||||
#[corresponds(RSA_get0_key)]
|
||||
#[must_use]
|
||||
pub fn n(&self) -> &BigNumRef {
|
||||
unsafe {
|
||||
let mut n = ptr::null();
|
||||
RSA_get0_key(self.as_ptr(), &mut n, ptr::null_mut(), ptr::null_mut());
|
||||
BigNumRef::from_ptr(n as *mut _)
|
||||
BigNumRef::from_ptr(n.cast_mut())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the public exponent of the key.
|
||||
#[corresponds(RSA_get0_key)]
|
||||
#[must_use]
|
||||
pub fn e(&self) -> &BigNumRef {
|
||||
unsafe {
|
||||
let mut e = ptr::null();
|
||||
RSA_get0_key(self.as_ptr(), ptr::null_mut(), &mut e, ptr::null_mut());
|
||||
BigNumRef::from_ptr(e as *mut _)
|
||||
BigNumRef::from_ptr(e.cast_mut())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -402,11 +414,10 @@ impl Rsa<Public> {
|
||||
/// `n` is the modulus common to both public and private key.
|
||||
/// `e` is the public exponent.
|
||||
#[corresponds(RSA_new)]
|
||||
/// [`RSA_set0_key`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_set0_key.html
|
||||
pub fn from_public_components(n: BigNum, e: BigNum) -> Result<Rsa<Public>, ErrorStack> {
|
||||
unsafe {
|
||||
let rsa = cvt_p(ffi::RSA_new())?;
|
||||
RSA_set0_key(rsa, n.as_ptr(), e.as_ptr(), ptr::null_mut());
|
||||
cvt(RSA_set0_key(rsa, n.as_ptr(), e.as_ptr(), ptr::null_mut()))?;
|
||||
mem::forget((n, e));
|
||||
Ok(Rsa::from_ptr(rsa))
|
||||
}
|
||||
@ -461,11 +472,10 @@ impl RsaPrivateKeyBuilder {
|
||||
/// `n` is the modulus common to both public and private key.
|
||||
/// `e` is the public exponent and `d` is the private exponent.
|
||||
#[corresponds(RSA_new)]
|
||||
/// [`RSA_set0_key`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_set0_key.html
|
||||
pub fn new(n: BigNum, e: BigNum, d: BigNum) -> Result<RsaPrivateKeyBuilder, ErrorStack> {
|
||||
unsafe {
|
||||
let rsa = cvt_p(ffi::RSA_new())?;
|
||||
RSA_set0_key(rsa, n.as_ptr(), e.as_ptr(), d.as_ptr());
|
||||
cvt(RSA_set0_key(rsa, n.as_ptr(), e.as_ptr(), d.as_ptr()))?;
|
||||
mem::forget((n, e, d));
|
||||
Ok(RsaPrivateKeyBuilder {
|
||||
rsa: Rsa::from_ptr(rsa),
|
||||
@ -476,12 +486,10 @@ impl RsaPrivateKeyBuilder {
|
||||
/// Sets the factors of the Rsa key.
|
||||
///
|
||||
/// `p` and `q` are the first and second factors of `n`.
|
||||
///
|
||||
// FIXME should be infallible
|
||||
#[corresponds(RSA_set0_factors)]
|
||||
pub fn set_factors(self, p: BigNum, q: BigNum) -> Result<RsaPrivateKeyBuilder, ErrorStack> {
|
||||
unsafe {
|
||||
RSA_set0_factors(self.rsa.as_ptr(), p.as_ptr(), q.as_ptr());
|
||||
cvt(RSA_set0_factors(self.rsa.as_ptr(), p.as_ptr(), q.as_ptr()))?;
|
||||
mem::forget((p, q));
|
||||
}
|
||||
Ok(self)
|
||||
@ -491,8 +499,6 @@ impl RsaPrivateKeyBuilder {
|
||||
///
|
||||
/// `dmp1`, `dmq1`, and `iqmp` are the exponents and coefficient for
|
||||
/// CRT calculations which is used to speed up RSA operations.
|
||||
///
|
||||
// FIXME should be infallible
|
||||
#[corresponds(RSA_set0_crt_params)]
|
||||
pub fn set_crt_params(
|
||||
self,
|
||||
@ -501,18 +507,19 @@ impl RsaPrivateKeyBuilder {
|
||||
iqmp: BigNum,
|
||||
) -> Result<RsaPrivateKeyBuilder, ErrorStack> {
|
||||
unsafe {
|
||||
RSA_set0_crt_params(
|
||||
cvt(RSA_set0_crt_params(
|
||||
self.rsa.as_ptr(),
|
||||
dmp1.as_ptr(),
|
||||
dmq1.as_ptr(),
|
||||
iqmp.as_ptr(),
|
||||
);
|
||||
))?;
|
||||
mem::forget((dmp1, dmq1, iqmp));
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Returns the Rsa key.
|
||||
#[must_use]
|
||||
pub fn build(self) -> Rsa<Private> {
|
||||
self.rsa
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ use std::mem::MaybeUninit;
|
||||
/// SHA1 is known to be insecure - it should not be used unless required for
|
||||
/// compatibility with existing systems.
|
||||
#[inline]
|
||||
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
|
||||
#[must_use]
|
||||
pub fn sha1(data: &[u8]) -> [u8; 20] {
|
||||
unsafe {
|
||||
let mut hash: MaybeUninit<[u8; 20]> = MaybeUninit::uninit();
|
||||
@ -65,7 +65,7 @@ pub fn sha1(data: &[u8]) -> [u8; 20] {
|
||||
|
||||
/// Computes the SHA224 hash of some data.
|
||||
#[inline]
|
||||
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
|
||||
#[must_use]
|
||||
pub fn sha224(data: &[u8]) -> [u8; 28] {
|
||||
unsafe {
|
||||
let mut hash: MaybeUninit<[u8; 28]> = MaybeUninit::uninit();
|
||||
@ -76,7 +76,7 @@ pub fn sha224(data: &[u8]) -> [u8; 28] {
|
||||
|
||||
/// Computes the SHA256 hash of some data.
|
||||
#[inline]
|
||||
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
|
||||
#[must_use]
|
||||
pub fn sha256(data: &[u8]) -> [u8; 32] {
|
||||
unsafe {
|
||||
let mut hash: MaybeUninit<[u8; 32]> = MaybeUninit::uninit();
|
||||
@ -87,7 +87,7 @@ pub fn sha256(data: &[u8]) -> [u8; 32] {
|
||||
|
||||
/// Computes the SHA384 hash of some data.
|
||||
#[inline]
|
||||
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
|
||||
#[must_use]
|
||||
pub fn sha384(data: &[u8]) -> [u8; 48] {
|
||||
unsafe {
|
||||
let mut hash: MaybeUninit<[u8; 48]> = MaybeUninit::uninit();
|
||||
@ -98,7 +98,7 @@ pub fn sha384(data: &[u8]) -> [u8; 48] {
|
||||
|
||||
/// Computes the SHA512 hash of some data.
|
||||
#[inline]
|
||||
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
|
||||
#[must_use]
|
||||
pub fn sha512(data: &[u8]) -> [u8; 64] {
|
||||
unsafe {
|
||||
let mut hash: MaybeUninit<[u8; 64]> = MaybeUninit::uninit();
|
||||
@ -109,7 +109,7 @@ pub fn sha512(data: &[u8]) -> [u8; 64] {
|
||||
|
||||
/// Computes the SHA512-256 hash of some data.
|
||||
#[inline]
|
||||
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
|
||||
#[must_use]
|
||||
pub fn sha512_256(data: &[u8]) -> [u8; 32] {
|
||||
unsafe {
|
||||
let mut hash: MaybeUninit<[u8; 32]> = MaybeUninit::uninit();
|
||||
@ -137,7 +137,7 @@ impl Default for Sha1 {
|
||||
impl Sha1 {
|
||||
/// Creates a new hasher.
|
||||
#[inline]
|
||||
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
|
||||
#[must_use]
|
||||
pub fn new() -> Sha1 {
|
||||
unsafe {
|
||||
let mut ctx = MaybeUninit::uninit();
|
||||
@ -152,13 +152,13 @@ impl Sha1 {
|
||||
#[inline]
|
||||
pub fn update(&mut self, buf: &[u8]) {
|
||||
unsafe {
|
||||
ffi::SHA1_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len());
|
||||
ffi::SHA1_Update(&mut self.0, buf.as_ptr().cast::<c_void>(), buf.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the hash of the data.
|
||||
#[inline]
|
||||
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
|
||||
#[must_use]
|
||||
pub fn finish(mut self) -> [u8; 20] {
|
||||
unsafe {
|
||||
let mut hash: MaybeUninit<[u8; 20]> = MaybeUninit::uninit();
|
||||
@ -182,7 +182,7 @@ impl Default for Sha224 {
|
||||
impl Sha224 {
|
||||
/// Creates a new hasher.
|
||||
#[inline]
|
||||
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
|
||||
#[must_use]
|
||||
pub fn new() -> Sha224 {
|
||||
unsafe {
|
||||
let mut ctx = MaybeUninit::uninit();
|
||||
@ -197,13 +197,13 @@ impl Sha224 {
|
||||
#[inline]
|
||||
pub fn update(&mut self, buf: &[u8]) {
|
||||
unsafe {
|
||||
ffi::SHA224_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len());
|
||||
ffi::SHA224_Update(&mut self.0, buf.as_ptr().cast::<c_void>(), buf.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the hash of the data.
|
||||
#[inline]
|
||||
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
|
||||
#[must_use]
|
||||
pub fn finish(mut self) -> [u8; 28] {
|
||||
unsafe {
|
||||
let mut hash: MaybeUninit<[u8; 28]> = MaybeUninit::uninit();
|
||||
@ -227,7 +227,7 @@ impl Default for Sha256 {
|
||||
impl Sha256 {
|
||||
/// Creates a new hasher.
|
||||
#[inline]
|
||||
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
|
||||
#[must_use]
|
||||
pub fn new() -> Sha256 {
|
||||
unsafe {
|
||||
let mut ctx = MaybeUninit::uninit();
|
||||
@ -242,13 +242,13 @@ impl Sha256 {
|
||||
#[inline]
|
||||
pub fn update(&mut self, buf: &[u8]) {
|
||||
unsafe {
|
||||
ffi::SHA256_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len());
|
||||
ffi::SHA256_Update(&mut self.0, buf.as_ptr().cast::<c_void>(), buf.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the hash of the data.
|
||||
#[inline]
|
||||
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
|
||||
#[must_use]
|
||||
pub fn finish(mut self) -> [u8; 32] {
|
||||
unsafe {
|
||||
let mut hash: MaybeUninit<[u8; 32]> = MaybeUninit::uninit();
|
||||
@ -272,7 +272,7 @@ impl Default for Sha384 {
|
||||
impl Sha384 {
|
||||
/// Creates a new hasher.
|
||||
#[inline]
|
||||
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
|
||||
#[must_use]
|
||||
pub fn new() -> Sha384 {
|
||||
unsafe {
|
||||
let mut ctx = MaybeUninit::uninit();
|
||||
@ -287,13 +287,13 @@ impl Sha384 {
|
||||
#[inline]
|
||||
pub fn update(&mut self, buf: &[u8]) {
|
||||
unsafe {
|
||||
ffi::SHA384_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len());
|
||||
ffi::SHA384_Update(&mut self.0, buf.as_ptr().cast::<c_void>(), buf.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the hash of the data.
|
||||
#[inline]
|
||||
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
|
||||
#[must_use]
|
||||
pub fn finish(mut self) -> [u8; 48] {
|
||||
unsafe {
|
||||
let mut hash: MaybeUninit<[u8; 48]> = MaybeUninit::uninit();
|
||||
@ -317,7 +317,7 @@ impl Default for Sha512 {
|
||||
impl Sha512 {
|
||||
/// Creates a new hasher.
|
||||
#[inline]
|
||||
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
|
||||
#[must_use]
|
||||
pub fn new() -> Sha512 {
|
||||
unsafe {
|
||||
let mut ctx = MaybeUninit::uninit();
|
||||
@ -332,13 +332,13 @@ impl Sha512 {
|
||||
#[inline]
|
||||
pub fn update(&mut self, buf: &[u8]) {
|
||||
unsafe {
|
||||
ffi::SHA512_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len());
|
||||
ffi::SHA512_Update(&mut self.0, buf.as_ptr().cast::<c_void>(), buf.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the hash of the data.
|
||||
#[inline]
|
||||
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
|
||||
#[must_use]
|
||||
pub fn finish(mut self) -> [u8; 64] {
|
||||
unsafe {
|
||||
let mut hash: MaybeUninit<[u8; 64]> = MaybeUninit::uninit();
|
||||
@ -362,7 +362,7 @@ impl Default for Sha512_256 {
|
||||
impl Sha512_256 {
|
||||
/// Creates a new hasher.
|
||||
#[inline]
|
||||
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
|
||||
#[must_use]
|
||||
pub fn new() -> Sha512_256 {
|
||||
unsafe {
|
||||
let mut ctx = MaybeUninit::uninit();
|
||||
@ -377,13 +377,13 @@ impl Sha512_256 {
|
||||
#[inline]
|
||||
pub fn update(&mut self, buf: &[u8]) {
|
||||
unsafe {
|
||||
ffi::SHA512_256_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len());
|
||||
ffi::SHA512_256_Update(&mut self.0, buf.as_ptr().cast::<c_void>(), buf.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the hash of the data.
|
||||
#[inline]
|
||||
#[allow(deprecated)] // https://github.com/rust-lang/rust/issues/63566
|
||||
#[must_use]
|
||||
pub fn finish(mut self) -> [u8; 32] {
|
||||
unsafe {
|
||||
let mut hash: MaybeUninit<[u8; 32]> = MaybeUninit::uninit();
|
||||
|
||||
@ -60,6 +60,7 @@ impl RsaPssSaltlen {
|
||||
}
|
||||
|
||||
/// Sets the salt length to the given value.
|
||||
#[must_use]
|
||||
pub fn custom(val: c_int) -> RsaPssSaltlen {
|
||||
RsaPssSaltlen(val)
|
||||
}
|
||||
@ -173,7 +174,6 @@ impl<'a> Signer<'a> {
|
||||
self.pctx,
|
||||
padding.as_raw(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,7 +187,6 @@ impl<'a> Signer<'a> {
|
||||
self.pctx,
|
||||
len.as_raw(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,9 +198,8 @@ impl<'a> Signer<'a> {
|
||||
unsafe {
|
||||
cvt(ffi::EVP_PKEY_CTX_set_rsa_mgf1_md(
|
||||
self.pctx,
|
||||
md.as_ptr() as *mut _,
|
||||
md.as_ptr().cast_mut(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,10 +212,9 @@ impl<'a> Signer<'a> {
|
||||
unsafe {
|
||||
cvt(ffi::EVP_DigestUpdate(
|
||||
self.md_ctx,
|
||||
buf.as_ptr() as *const _,
|
||||
buf.as_ptr().cast(),
|
||||
buf.len(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,7 +251,7 @@ impl<'a> Signer<'a> {
|
||||
let mut len = buf.len();
|
||||
cvt(ffi::EVP_DigestSignFinal(
|
||||
self.md_ctx,
|
||||
buf.as_mut_ptr() as *mut _,
|
||||
buf.as_mut_ptr().cast(),
|
||||
&mut len,
|
||||
))?;
|
||||
Ok(len)
|
||||
@ -289,9 +286,9 @@ impl<'a> Signer<'a> {
|
||||
let mut sig_len = sig_buf.len();
|
||||
cvt(ffi::EVP_DigestSign(
|
||||
self.md_ctx,
|
||||
sig_buf.as_mut_ptr() as *mut _,
|
||||
sig_buf.as_mut_ptr(),
|
||||
&mut sig_len,
|
||||
data_buf.as_ptr() as *const _,
|
||||
data_buf.as_ptr(),
|
||||
data_buf.len(),
|
||||
))?;
|
||||
Ok(sig_len)
|
||||
@ -420,7 +417,6 @@ impl<'a> Verifier<'a> {
|
||||
self.pctx,
|
||||
padding.as_raw(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -434,7 +430,6 @@ impl<'a> Verifier<'a> {
|
||||
self.pctx,
|
||||
len.as_raw(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -446,9 +441,8 @@ impl<'a> Verifier<'a> {
|
||||
unsafe {
|
||||
cvt(ffi::EVP_PKEY_CTX_set_rsa_mgf1_md(
|
||||
self.pctx,
|
||||
md.as_ptr() as *mut _,
|
||||
md.as_ptr().cast_mut(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -461,10 +455,9 @@ impl<'a> Verifier<'a> {
|
||||
unsafe {
|
||||
cvt(ffi::EVP_DigestUpdate(
|
||||
self.md_ctx,
|
||||
buf.as_ptr() as *const _,
|
||||
buf.as_ptr().cast(),
|
||||
buf.len(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -473,11 +466,11 @@ impl<'a> Verifier<'a> {
|
||||
pub fn verify(&self, signature: &[u8]) -> Result<bool, ErrorStack> {
|
||||
unsafe {
|
||||
let r =
|
||||
EVP_DigestVerifyFinal(self.md_ctx, signature.as_ptr() as *mut _, signature.len());
|
||||
EVP_DigestVerifyFinal(self.md_ctx, signature.as_ptr().cast_mut(), signature.len());
|
||||
match r {
|
||||
1 => Ok(true),
|
||||
0 => {
|
||||
ErrorStack::get(); // discard error stack
|
||||
ErrorStack::clear(); // discard error stack
|
||||
Ok(false)
|
||||
}
|
||||
_ => Err(ErrorStack::get()),
|
||||
@ -491,15 +484,15 @@ impl<'a> Verifier<'a> {
|
||||
unsafe {
|
||||
let r = ffi::EVP_DigestVerify(
|
||||
self.md_ctx,
|
||||
signature.as_ptr() as *const _,
|
||||
signature.as_ptr().cast(),
|
||||
signature.len(),
|
||||
buf.as_ptr() as *const _,
|
||||
buf.as_ptr().cast(),
|
||||
buf.len(),
|
||||
);
|
||||
match r {
|
||||
1 => Ok(true),
|
||||
0 => {
|
||||
ErrorStack::get();
|
||||
ErrorStack::clear();
|
||||
Ok(false)
|
||||
}
|
||||
_ => Err(ErrorStack::get()),
|
||||
|
||||
@ -20,11 +20,14 @@ impl Stackable for SrtpProtectionProfile {
|
||||
}
|
||||
|
||||
impl SrtpProtectionProfileRef {
|
||||
#[must_use]
|
||||
pub fn id(&self) -> SrtpProfileId {
|
||||
SrtpProfileId::from_raw(unsafe { (*self.as_ptr()).id })
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn name(&self) -> &'static str {
|
||||
unsafe { CStr::from_ptr((*self.as_ptr()).name as *const _) }
|
||||
unsafe { CStr::from_ptr((*self.as_ptr()).name.cast()) }
|
||||
.to_str()
|
||||
.expect("should be UTF-8")
|
||||
}
|
||||
@ -47,12 +50,14 @@ impl SrtpProfileId {
|
||||
pub const SRTP_NULL_SHA1_32: SrtpProfileId = SrtpProfileId(ffi::SRTP_NULL_SHA1_32 as _);
|
||||
|
||||
/// Creates a `SrtpProfileId` from an integer representation.
|
||||
#[must_use]
|
||||
pub fn from_raw(value: c_ulong) -> SrtpProfileId {
|
||||
SrtpProfileId(value)
|
||||
}
|
||||
|
||||
/// Returns the integer representation of `SrtpProfileId`.
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
#[must_use]
|
||||
pub fn as_raw(&self) -> c_ulong {
|
||||
self.0
|
||||
}
|
||||
|
||||
@ -4,14 +4,18 @@ use super::{
|
||||
Ssl, SslAlert, SslContextBuilder, SslRef, SslSession, SslSignatureAlgorithm, SslVerifyError,
|
||||
SslVerifyMode,
|
||||
};
|
||||
#[cfg(feature = "credential")]
|
||||
use crate::error::ErrorStack;
|
||||
use crate::ex_data::Index;
|
||||
#[cfg(feature = "credential")]
|
||||
use crate::ssl::SslCredentialBuilder;
|
||||
use std::convert::identity;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::LazyLock;
|
||||
use std::task::{ready, Context, Poll, Waker};
|
||||
|
||||
/// The type of futures to pass to [`SslContextBuilderExt::set_async_select_certificate_callback`].
|
||||
/// The type of futures to pass to [`SslContextBuilder::set_async_select_certificate_callback`].
|
||||
pub type BoxSelectCertFuture = ExDataFuture<Result<BoxSelectCertFinish, AsyncSelectCertError>>;
|
||||
|
||||
/// The type of callbacks returned by [`BoxSelectCertFuture`] methods.
|
||||
@ -25,19 +29,19 @@ pub type BoxPrivateKeyMethodFuture =
|
||||
pub type BoxPrivateKeyMethodFinish =
|
||||
Box<dyn FnOnce(&mut SslRef, &mut [u8]) -> Result<usize, AsyncPrivateKeyMethodError>>;
|
||||
|
||||
/// The type of futures to pass to [`SslContextBuilderExt::set_async_get_session_callback`].
|
||||
/// The type of futures to pass to [`SslContextBuilder::set_async_get_session_callback`].
|
||||
pub type BoxGetSessionFuture = ExDataFuture<Option<BoxGetSessionFinish>>;
|
||||
|
||||
/// The type of callbacks returned by [`BoxSelectCertFuture`] methods.
|
||||
pub type BoxGetSessionFinish = Box<dyn FnOnce(&mut SslRef, &[u8]) -> Option<SslSession>>;
|
||||
|
||||
/// The type of futures to pass to [`SslContextBuilderExt::set_async_custom_verify_callback`].
|
||||
/// The type of futures to pass to [`SslContextBuilder::set_async_custom_verify_callback`].
|
||||
pub type BoxCustomVerifyFuture = ExDataFuture<Result<BoxCustomVerifyFinish, SslAlert>>;
|
||||
|
||||
/// The type of callbacks returned by [`BoxCustomVerifyFuture`] methods.
|
||||
pub type BoxCustomVerifyFinish = Box<dyn FnOnce(&mut SslRef) -> Result<(), SslAlert>>;
|
||||
|
||||
/// Convenience alias for futures stored in [`Ssl`] ex data by [`SslContextBuilderExt`] methods.
|
||||
/// Convenience alias for futures stored in [`Ssl`] ex data by [`SslContextBuilder`] methods.
|
||||
///
|
||||
/// Public for documentation purposes.
|
||||
pub type ExDataFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
|
||||
@ -95,7 +99,7 @@ impl SslContextBuilder {
|
||||
let finish = fut_result.or(Err(SelectCertError::ERROR))?;
|
||||
|
||||
finish(client_hello).or(Err(SelectCertError::ERROR))
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/// Configures a custom private key method on the context.
|
||||
@ -123,7 +127,7 @@ impl SslContextBuilder {
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The returned [`SslSession`] must not be associated with a different [`SslContext`].
|
||||
/// The returned [`SslSession`] must not be associated with a different [`SslContextBuilder`].
|
||||
pub unsafe fn set_async_get_session_callback<F>(&mut self, callback: F)
|
||||
where
|
||||
F: Fn(&mut SslRef, &[u8]) -> Option<BoxGetSessionFuture> + Send + Sync + 'static,
|
||||
@ -144,7 +148,7 @@ impl SslContextBuilder {
|
||||
}
|
||||
};
|
||||
|
||||
self.set_get_session_callback(async_callback)
|
||||
self.set_get_session_callback(async_callback);
|
||||
}
|
||||
|
||||
/// Configures certificate verification.
|
||||
@ -167,7 +171,23 @@ impl SslContextBuilder {
|
||||
where
|
||||
F: Fn(&mut SslRef) -> Result<BoxCustomVerifyFuture, SslAlert> + Send + Sync + 'static,
|
||||
{
|
||||
self.set_custom_verify_callback(mode, async_custom_verify_callback(callback))
|
||||
self.set_custom_verify_callback(mode, async_custom_verify_callback(callback));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "credential")]
|
||||
impl SslCredentialBuilder {
|
||||
/// Configures a custom private key method on the context.
|
||||
///
|
||||
/// A task waker must be set on `Ssl` values associated with the resulting
|
||||
/// `SslContext` with [`SslRef::set_task_waker`].
|
||||
///
|
||||
/// See [`AsyncPrivateKeyMethod`] for more details.
|
||||
pub fn set_async_private_key_method(
|
||||
&mut self,
|
||||
method: impl AsyncPrivateKeyMethod,
|
||||
) -> Result<(), ErrorStack> {
|
||||
self.set_private_key_method(AsyncPrivateKeyMethodBridge(Box::new(method)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,7 +196,7 @@ impl SslRef {
|
||||
where
|
||||
F: Fn(&mut SslRef) -> Result<BoxCustomVerifyFuture, SslAlert> + Send + Sync + 'static,
|
||||
{
|
||||
self.set_custom_verify_callback(mode, async_custom_verify_callback(callback))
|
||||
self.set_custom_verify_callback(mode, async_custom_verify_callback(callback));
|
||||
}
|
||||
|
||||
/// Sets the task waker to be used in async callbacks installed on this `Ssl`.
|
||||
|
||||
@ -44,7 +44,7 @@ pub fn new<S: Read + Write>(stream: S) -> Result<(*mut BIO, BioMethod), ErrorSta
|
||||
|
||||
unsafe {
|
||||
let bio = cvt_p(BIO_new(method.0.get()))?;
|
||||
BIO_set_data(bio, Box::into_raw(state) as *mut _);
|
||||
BIO_set_data(bio, Box::into_raw(state).cast());
|
||||
BIO_set_init(bio, 1);
|
||||
|
||||
Ok((bio, method))
|
||||
@ -76,7 +76,7 @@ pub unsafe extern "C" fn take_stream<S>(bio: *mut BIO) -> S {
|
||||
|
||||
assert!(!data.is_null());
|
||||
|
||||
let state = Box::<StreamState<S>>::from_raw(data as *mut _);
|
||||
let state = Box::<StreamState<S>>::from_raw(data.cast());
|
||||
|
||||
BIO_set_data(bio, ptr::null_mut());
|
||||
|
||||
@ -85,16 +85,13 @@ pub unsafe extern "C" fn take_stream<S>(bio: *mut BIO) -> S {
|
||||
|
||||
pub unsafe fn set_dtls_mtu_size<S>(bio: *mut BIO, mtu_size: usize) {
|
||||
if mtu_size as u64 > c_long::MAX as u64 {
|
||||
panic!(
|
||||
"Given MTU size {} can't be represented in a positive `c_long` range",
|
||||
mtu_size
|
||||
)
|
||||
panic!("Given MTU size {mtu_size} can't be represented in a positive `c_long` range")
|
||||
}
|
||||
state::<S>(bio).dtls_mtu_size = mtu_size as c_long;
|
||||
}
|
||||
|
||||
unsafe fn state<'a, S: 'a>(bio: *mut BIO) -> &'a mut StreamState<S> {
|
||||
let data = BIO_get_data(bio) as *mut StreamState<S>;
|
||||
let data = BIO_get_data(bio).cast::<StreamState<S>>();
|
||||
|
||||
assert!(!data.is_null());
|
||||
|
||||
@ -104,8 +101,12 @@ unsafe fn state<'a, S: 'a>(bio: *mut BIO) -> &'a mut StreamState<S> {
|
||||
unsafe extern "C" fn bwrite<S: Write>(bio: *mut BIO, buf: *const c_char, len: c_int) -> c_int {
|
||||
BIO_clear_retry_flags(bio);
|
||||
|
||||
let Ok(len) = usize::try_from(len) else {
|
||||
return -1;
|
||||
};
|
||||
|
||||
let state = state::<S>(bio);
|
||||
let buf = slice::from_raw_parts(buf as *const _, len as usize);
|
||||
let buf = slice::from_raw_parts(buf.cast(), len);
|
||||
|
||||
match catch_unwind(AssertUnwindSafe(|| state.stream.write(buf))) {
|
||||
Ok(Ok(len)) => len as c_int,
|
||||
@ -126,8 +127,12 @@ unsafe extern "C" fn bwrite<S: Write>(bio: *mut BIO, buf: *const c_char, len: c_
|
||||
unsafe extern "C" fn bread<S: Read>(bio: *mut BIO, buf: *mut c_char, len: c_int) -> c_int {
|
||||
BIO_clear_retry_flags(bio);
|
||||
|
||||
let Ok(len) = usize::try_from(len) else {
|
||||
return -1;
|
||||
};
|
||||
|
||||
let state = state::<S>(bio);
|
||||
let buf = slice::from_raw_parts_mut(buf as *mut _, len as usize);
|
||||
let buf = slice::from_raw_parts_mut(buf.cast(), len);
|
||||
|
||||
match catch_unwind(AssertUnwindSafe(|| state.stream.read(buf))) {
|
||||
Ok(Ok(len)) => len as c_int,
|
||||
@ -166,9 +171,13 @@ unsafe extern "C" fn ctrl<S: Write>(
|
||||
let state = state::<S>(bio);
|
||||
|
||||
if cmd == BIO_CTRL_FLUSH {
|
||||
BIO_clear_retry_flags(bio);
|
||||
match catch_unwind(AssertUnwindSafe(|| state.stream.flush())) {
|
||||
Ok(Ok(())) => 1,
|
||||
Ok(Err(err)) => {
|
||||
if retriable_error(&err) {
|
||||
BIO_set_retry_write(bio);
|
||||
}
|
||||
state.error = Some(err);
|
||||
0
|
||||
}
|
||||
@ -200,7 +209,7 @@ unsafe extern "C" fn destroy<S>(bio: *mut BIO) -> c_int {
|
||||
let data = BIO_get_data(bio);
|
||||
|
||||
if !data.is_null() {
|
||||
drop(Box::<StreamState<S>>::from_raw(data as *mut _));
|
||||
drop(Box::<StreamState<S>>::from_raw(data.cast()));
|
||||
BIO_set_data(bio, ptr::null_mut());
|
||||
}
|
||||
|
||||
|
||||
@ -8,12 +8,15 @@ use super::{
|
||||
};
|
||||
use crate::error::ErrorStack;
|
||||
use crate::ffi;
|
||||
use crate::hmac::HmacCtxRef;
|
||||
use crate::ssl::TicketKeyCallbackResult;
|
||||
use crate::symm::CipherCtxRef;
|
||||
use crate::x509::{X509StoreContext, X509StoreContextRef};
|
||||
use foreign_types::ForeignType;
|
||||
use foreign_types::ForeignTypeRef;
|
||||
use libc::c_char;
|
||||
use libc::{c_int, c_uchar, c_uint, c_void};
|
||||
use libc::{c_char, c_int, c_uchar, c_uint, c_void};
|
||||
use std::ffi::CStr;
|
||||
use std::mem::{self, MaybeUninit};
|
||||
use std::ptr;
|
||||
use std::slice;
|
||||
use std::str;
|
||||
@ -38,9 +41,9 @@ where
|
||||
|
||||
// SAFETY: The callback won't outlive the context it's associated with
|
||||
// because there is no `X509StoreContextRef::ssl_mut(&mut self)` method.
|
||||
let verify = unsafe { &*(verify as *const F) };
|
||||
let verify = unsafe { &*std::ptr::from_ref::<F>(verify) };
|
||||
|
||||
verify(preverify_ok != 0, ctx) as c_int
|
||||
c_int::from(verify(preverify_ok != 0, ctx))
|
||||
}
|
||||
|
||||
pub(super) unsafe extern "C" fn raw_custom_verify<F>(
|
||||
@ -87,9 +90,9 @@ where
|
||||
// SAFETY: The callback won't outlive the context it's associated with
|
||||
// because there is no way to get a mutable reference to the `SslContext`,
|
||||
// so the callback can't replace itself.
|
||||
let verify = unsafe { &*(verify as *const F) };
|
||||
let verify = unsafe { &*std::ptr::from_ref::<F>(verify) };
|
||||
|
||||
verify(ctx) as c_int
|
||||
c_int::from(verify(ctx))
|
||||
}
|
||||
|
||||
pub(super) unsafe extern "C" fn ssl_raw_custom_verify<F>(
|
||||
@ -157,7 +160,7 @@ where
|
||||
|
||||
// Give the callback mutable slices into which it can write the identity and psk.
|
||||
let identity_sl =
|
||||
unsafe { slice::from_raw_parts_mut(identity as *mut u8, max_identity_len as usize) };
|
||||
unsafe { slice::from_raw_parts_mut(identity.cast::<u8>(), max_identity_len as usize) };
|
||||
let psk_sl = unsafe { slice::from_raw_parts_mut(psk, max_psk_len as usize) };
|
||||
|
||||
let ssl_context = ssl.ssl_context().to_owned();
|
||||
@ -235,7 +238,7 @@ where
|
||||
.expect("BUG: ssl verify callback missing")
|
||||
.clone();
|
||||
|
||||
callback(preverify_ok != 0, ctx) as c_int
|
||||
c_int::from(callback(preverify_ok != 0, ctx))
|
||||
}
|
||||
|
||||
pub(super) unsafe extern "C" fn raw_sni<F>(
|
||||
@ -269,6 +272,68 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn to_uninit<'a, T: 'a>(ptr: *mut T) -> &'a mut MaybeUninit<T> {
|
||||
assert!(!ptr.is_null());
|
||||
unsafe { &mut *ptr.cast::<MaybeUninit<T>>() }
|
||||
}
|
||||
|
||||
pub(super) unsafe extern "C" fn raw_ticket_key<F>(
|
||||
ssl: *mut ffi::SSL,
|
||||
key_name: *mut u8,
|
||||
iv: *mut u8,
|
||||
evp_ctx: *mut ffi::EVP_CIPHER_CTX,
|
||||
hmac_ctx: *mut ffi::HMAC_CTX,
|
||||
encrypt: c_int,
|
||||
) -> c_int
|
||||
where
|
||||
F: Fn(
|
||||
&SslRef,
|
||||
&mut [u8; 16],
|
||||
&mut [u8; ffi::EVP_MAX_IV_LENGTH as usize],
|
||||
&mut CipherCtxRef,
|
||||
&mut HmacCtxRef,
|
||||
bool,
|
||||
) -> TicketKeyCallbackResult
|
||||
+ 'static
|
||||
+ Sync
|
||||
+ Send,
|
||||
{
|
||||
// SAFETY: boring provides valid inputs.
|
||||
let ssl = unsafe { SslRef::from_ptr_mut(ssl) };
|
||||
|
||||
let ssl_context = ssl.ssl_context().to_owned();
|
||||
let callback = ssl_context
|
||||
.ex_data::<F>(SslContext::cached_ex_index::<F>())
|
||||
.expect("expected session resumption callback");
|
||||
|
||||
// SAFETY: the callback guarantees that key_name is 16 bytes
|
||||
let key_name =
|
||||
unsafe { to_uninit(key_name.cast::<[u8; ffi::SSL_TICKET_KEY_NAME_LEN as usize]>()) };
|
||||
|
||||
// SAFETY: the callback provides 16 bytes iv
|
||||
//
|
||||
// https://github.com/google/boringssl/blob/main/ssl/ssl_session.cc#L331
|
||||
let iv = unsafe { to_uninit(iv.cast::<[u8; ffi::EVP_MAX_IV_LENGTH as usize]>()) };
|
||||
|
||||
// When encrypting a new ticket, encrypt will be one.
|
||||
let encrypt = encrypt == 1;
|
||||
|
||||
// Zero-initialize the key_name and iv, since the application is expected to populate these
|
||||
// fields in the encrypt mode.
|
||||
if encrypt {
|
||||
*key_name = MaybeUninit::zeroed();
|
||||
*iv = MaybeUninit::zeroed();
|
||||
}
|
||||
let key_name = unsafe { key_name.assume_init_mut() };
|
||||
let iv = unsafe { iv.assume_init_mut() };
|
||||
|
||||
// The EVP_CIPHER_CTX and HMAC_CTX are owned by boringSSL.
|
||||
let evp_ctx = unsafe { CipherCtxRef::from_ptr_mut(evp_ctx) };
|
||||
let hmac_ctx = unsafe { HmacCtxRef::from_ptr_mut(hmac_ctx) };
|
||||
|
||||
callback(ssl, key_name, iv, evp_ctx, hmac_ctx, encrypt).into()
|
||||
}
|
||||
|
||||
pub(super) unsafe extern "C" fn raw_alpn_select<F>(
|
||||
ssl: *mut ffi::SSL,
|
||||
out: *mut *const c_uchar,
|
||||
@ -293,7 +358,7 @@ where
|
||||
|
||||
match callback(ssl, protos) {
|
||||
Ok(proto) => {
|
||||
*out = proto.as_ptr() as *const c_uchar;
|
||||
*out = proto.as_ptr();
|
||||
*outlen = proto.len() as c_uchar;
|
||||
|
||||
ffi::SSL_TLSEXT_ERR_OK
|
||||
@ -377,7 +442,7 @@ where
|
||||
// SAFETY: We can make `callback` outlive `ssl` because it is a callback
|
||||
// stored in the session context set in `Ssl::new` so it is always
|
||||
// guaranteed to outlive the lifetime of this function's scope.
|
||||
let callback = unsafe { &*(callback as *const F) };
|
||||
let callback = unsafe { &*std::ptr::from_ref::<F>(callback) };
|
||||
|
||||
callback(ssl, session);
|
||||
|
||||
@ -399,7 +464,7 @@ pub(super) unsafe extern "C" fn raw_remove_session<F>(
|
||||
.ex_data(SslContext::cached_ex_index::<F>())
|
||||
.expect("BUG: remove session callback missing");
|
||||
|
||||
callback(ctx, session)
|
||||
callback(ctx, session);
|
||||
}
|
||||
|
||||
type DataPtr = *const c_uchar;
|
||||
@ -430,7 +495,7 @@ where
|
||||
// SAFETY: We can make `callback` outlive `ssl` because it is a callback
|
||||
// stored in the session context set in `Ssl::new` so it is always
|
||||
// guaranteed to outlive the lifetime of this function's scope.
|
||||
let callback = unsafe { &*(callback as *const F) };
|
||||
let callback = unsafe { &*std::ptr::from_ref::<F>(callback) };
|
||||
|
||||
match callback(ssl, data) {
|
||||
Ok(Some(session)) => {
|
||||
@ -450,15 +515,15 @@ where
|
||||
F: Fn(&SslRef, &str) + 'static + Sync + Send,
|
||||
{
|
||||
// SAFETY: boring provides valid inputs.
|
||||
let ssl = unsafe { SslRef::from_ptr(ssl as *mut _) };
|
||||
let line = unsafe { str::from_utf8_unchecked(CStr::from_ptr(line).to_bytes()) };
|
||||
let ssl = unsafe { SslRef::from_ptr(ssl.cast_mut()) };
|
||||
let line = unsafe { CStr::from_ptr(line).to_string_lossy() };
|
||||
|
||||
let callback = ssl
|
||||
.ssl_context()
|
||||
.ex_data(SslContext::cached_ex_index::<F>())
|
||||
.expect("BUG: get session callback missing");
|
||||
|
||||
callback(ssl, line);
|
||||
callback(ssl, &line);
|
||||
}
|
||||
|
||||
pub(super) unsafe extern "C" fn raw_sign<M>(
|
||||
@ -560,7 +625,7 @@ pub(super) unsafe extern "C" fn raw_info_callback<F>(
|
||||
{
|
||||
// Due to FFI signature requirements we have to pass a *const SSL into this function, but
|
||||
// foreign-types requires a *mut SSL to get the Rust SslRef
|
||||
let mut_ref = ssl as *mut ffi::SSL;
|
||||
let mut_ref = ssl.cast_mut();
|
||||
|
||||
// SAFETY: boring provides valid inputs.
|
||||
let ssl = unsafe { SslRef::from_ptr(mut_ref) };
|
||||
@ -702,14 +767,10 @@ impl<'a> CryptoBufferBuilder<'a> {
|
||||
let buffer_capacity = unsafe { ffi::CRYPTO_BUFFER_len(self.buffer) };
|
||||
if self.cursor.position() != buffer_capacity as u64 {
|
||||
// Make sure all bytes in buffer initialized as required by Boring SSL.
|
||||
return Err(ErrorStack::get());
|
||||
}
|
||||
unsafe {
|
||||
let mut result = ptr::null_mut();
|
||||
ptr::swap(&mut self.buffer, &mut result);
|
||||
std::mem::forget(self);
|
||||
Ok(result)
|
||||
return Err(ErrorStack::internal_error_str("invalid len"));
|
||||
}
|
||||
// Drop is no-op if the buffer is null
|
||||
Ok(mem::replace(&mut self.buffer, ptr::null_mut()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,19 +23,9 @@ ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
|
||||
-----END DH PARAMETERS-----
|
||||
";
|
||||
|
||||
enum ContextType {
|
||||
WithMethod(SslMethod),
|
||||
#[cfg(feature = "rpk")]
|
||||
Rpk,
|
||||
}
|
||||
|
||||
#[allow(clippy::inconsistent_digit_grouping)]
|
||||
fn ctx(ty: ContextType) -> Result<SslContextBuilder, ErrorStack> {
|
||||
let mut ctx = match ty {
|
||||
ContextType::WithMethod(method) => SslContextBuilder::new(method),
|
||||
#[cfg(feature = "rpk")]
|
||||
ContextType::Rpk => SslContextBuilder::new_rpk(),
|
||||
}?;
|
||||
fn ctx(method: SslMethod) -> Result<SslContextBuilder, ErrorStack> {
|
||||
let mut ctx = SslContextBuilder::new(method)?;
|
||||
|
||||
let mut opts = SslOptions::ALL
|
||||
| SslOptions::NO_COMPRESSION
|
||||
@ -77,7 +67,7 @@ impl SslConnector {
|
||||
///
|
||||
/// The default configuration is subject to change, and is currently derived from Python.
|
||||
pub fn builder(method: SslMethod) -> Result<SslConnectorBuilder, ErrorStack> {
|
||||
let mut ctx = ctx(ContextType::WithMethod(method))?;
|
||||
let mut ctx = ctx(method)?;
|
||||
ctx.set_default_verify_paths()?;
|
||||
ctx.set_cipher_list(
|
||||
"DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK",
|
||||
@ -87,17 +77,6 @@ impl SslConnector {
|
||||
Ok(SslConnectorBuilder(ctx))
|
||||
}
|
||||
|
||||
/// Creates a new builder for TLS connections with raw public key.
|
||||
#[cfg(feature = "rpk")]
|
||||
pub fn rpk_builder() -> Result<SslConnectorBuilder, ErrorStack> {
|
||||
let mut ctx = ctx(ContextType::Rpk)?;
|
||||
ctx.set_cipher_list(
|
||||
"DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK",
|
||||
)?;
|
||||
|
||||
Ok(SslConnectorBuilder(ctx))
|
||||
}
|
||||
|
||||
/// Initiates a client-side TLS session on a stream.
|
||||
///
|
||||
/// The domain is used for SNI and hostname verification.
|
||||
@ -137,11 +116,13 @@ impl SslConnector {
|
||||
}
|
||||
|
||||
/// Consumes the `SslConnector`, returning the inner raw `SslContext`.
|
||||
#[must_use]
|
||||
pub fn into_context(self) -> SslContext {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns a shared reference to the inner raw `SslContext`.
|
||||
#[must_use]
|
||||
pub fn context(&self) -> &SslContextRef {
|
||||
&self.0
|
||||
}
|
||||
@ -152,6 +133,7 @@ pub struct SslConnectorBuilder(SslContextBuilder);
|
||||
|
||||
impl SslConnectorBuilder {
|
||||
/// Consumes the builder, returning an `SslConnector`.
|
||||
#[must_use]
|
||||
pub fn build(self) -> SslConnector {
|
||||
SslConnector(self.0.build())
|
||||
}
|
||||
@ -180,6 +162,7 @@ pub struct ConnectConfiguration {
|
||||
|
||||
impl ConnectConfiguration {
|
||||
/// A builder-style version of `set_use_server_name_indication`.
|
||||
#[must_use]
|
||||
pub fn use_server_name_indication(mut self, use_sni: bool) -> ConnectConfiguration {
|
||||
self.set_use_server_name_indication(use_sni);
|
||||
self
|
||||
@ -193,6 +176,7 @@ impl ConnectConfiguration {
|
||||
}
|
||||
|
||||
/// A builder-style version of `set_verify_hostname`.
|
||||
#[must_use]
|
||||
pub fn verify_hostname(mut self, verify_hostname: bool) -> ConnectConfiguration {
|
||||
self.set_verify_hostname(verify_hostname);
|
||||
self
|
||||
@ -219,13 +203,7 @@ impl ConnectConfiguration {
|
||||
self.ssl.set_hostname(domain)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "rpk")]
|
||||
let verify_hostname = !self.ssl.ssl_context().is_rpk() && self.verify_hostname;
|
||||
|
||||
#[cfg(not(feature = "rpk"))]
|
||||
let verify_hostname = self.verify_hostname;
|
||||
|
||||
if verify_hostname {
|
||||
if self.verify_hostname {
|
||||
setup_verify_hostname(&mut self.ssl, domain)?;
|
||||
}
|
||||
|
||||
@ -287,21 +265,6 @@ impl DerefMut for ConnectConfiguration {
|
||||
pub struct SslAcceptor(SslContext);
|
||||
|
||||
impl SslAcceptor {
|
||||
/// Creates a new builder configured to connect to clients that support Raw Public Keys.
|
||||
#[cfg(feature = "rpk")]
|
||||
pub fn rpk() -> Result<SslAcceptorBuilder, ErrorStack> {
|
||||
let mut ctx = ctx(ContextType::Rpk)?;
|
||||
ctx.set_options(SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1);
|
||||
let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?;
|
||||
ctx.set_tmp_dh(&dh)?;
|
||||
ctx.set_cipher_list(
|
||||
"ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:\
|
||||
ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\
|
||||
DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"
|
||||
)?;
|
||||
Ok(SslAcceptorBuilder(ctx))
|
||||
}
|
||||
|
||||
/// Creates a new builder configured to connect to non-legacy clients. This should generally be
|
||||
/// considered a reasonable default choice.
|
||||
///
|
||||
@ -310,7 +273,7 @@ impl SslAcceptor {
|
||||
///
|
||||
/// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS
|
||||
pub fn mozilla_intermediate_v5(method: SslMethod) -> Result<SslAcceptorBuilder, ErrorStack> {
|
||||
let mut ctx = ctx(ContextType::WithMethod(method))?;
|
||||
let mut ctx = ctx(method)?;
|
||||
ctx.set_options(SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1);
|
||||
let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?;
|
||||
ctx.set_tmp_dh(&dh)?;
|
||||
@ -331,7 +294,7 @@ impl SslAcceptor {
|
||||
/// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS
|
||||
// FIXME remove in next major version
|
||||
pub fn mozilla_intermediate(method: SslMethod) -> Result<SslAcceptorBuilder, ErrorStack> {
|
||||
let mut ctx = ctx(ContextType::WithMethod(method))?;
|
||||
let mut ctx = ctx(method)?;
|
||||
ctx.set_options(SslOptions::CIPHER_SERVER_PREFERENCE);
|
||||
ctx.set_options(SslOptions::NO_TLSV1_3);
|
||||
let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?;
|
||||
@ -357,7 +320,7 @@ impl SslAcceptor {
|
||||
/// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS
|
||||
// FIXME remove in next major version
|
||||
pub fn mozilla_modern(method: SslMethod) -> Result<SslAcceptorBuilder, ErrorStack> {
|
||||
let mut ctx = ctx(ContextType::WithMethod(method))?;
|
||||
let mut ctx = ctx(method)?;
|
||||
ctx.set_options(
|
||||
SslOptions::CIPHER_SERVER_PREFERENCE | SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1,
|
||||
);
|
||||
@ -396,11 +359,13 @@ impl SslAcceptor {
|
||||
}
|
||||
|
||||
/// Consumes the `SslAcceptor`, returning the inner raw `SslContext`.
|
||||
#[must_use]
|
||||
pub fn into_context(self) -> SslContext {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns a shared reference to the inner raw `SslContext`.
|
||||
#[must_use]
|
||||
pub fn context(&self) -> &SslContextRef {
|
||||
&self.0
|
||||
}
|
||||
@ -411,6 +376,7 @@ pub struct SslAcceptorBuilder(SslContextBuilder);
|
||||
|
||||
impl SslAcceptorBuilder {
|
||||
/// Consumes the builder, returning a `SslAcceptor`.
|
||||
#[must_use]
|
||||
pub fn build(self) -> SslAcceptor {
|
||||
SslAcceptor(self.0.build())
|
||||
}
|
||||
|
||||
211
boring/src/ssl/credential.rs
Normal file
211
boring/src/ssl/credential.rs
Normal file
@ -0,0 +1,211 @@
|
||||
#[cfg(feature = "rpk")]
|
||||
use crate::cvt_p;
|
||||
use crate::error::ErrorStack;
|
||||
use crate::ex_data::Index;
|
||||
use crate::pkey::{PKeyRef, Private};
|
||||
use crate::ssl::callbacks;
|
||||
use crate::ssl::PrivateKeyMethod;
|
||||
use crate::{cvt_0i, cvt_n};
|
||||
use crate::{ffi, free_data_box};
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use openssl_macros::corresponds;
|
||||
use std::any::TypeId;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{c_int, c_void};
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
static SSL_CREDENTIAL_INDEXES: LazyLock<Mutex<HashMap<TypeId, c_int>>> =
|
||||
LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
foreign_type_and_impl_send_sync! {
|
||||
type CType = ffi::SSL_CREDENTIAL;
|
||||
fn drop = ffi::SSL_CREDENTIAL_free;
|
||||
|
||||
/// A credential.
|
||||
pub struct SslCredential;
|
||||
}
|
||||
|
||||
impl SslCredential {
|
||||
/// Create a credential suitable for a handshake using a raw public key.
|
||||
#[corresponds(SSL_CREDENTIAL_new_raw_public_key)]
|
||||
#[cfg(feature = "rpk")]
|
||||
pub fn new_raw_public_key() -> Result<SslCredentialBuilder, ErrorStack> {
|
||||
unsafe {
|
||||
Ok(SslCredentialBuilder(Self::from_ptr(cvt_p(
|
||||
ffi::SSL_CREDENTIAL_new_raw_public_key(),
|
||||
)?)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new extra data index.
|
||||
///
|
||||
/// Each invocation of this function is guaranteed to return a distinct index. These can be used
|
||||
/// to store data in the context that can be retrieved later by callbacks, for example.
|
||||
#[corresponds(SSL_C_get_ex_new_index)]
|
||||
pub fn new_ex_index<T>() -> Result<Index<Self, T>, ErrorStack>
|
||||
where
|
||||
T: 'static + Sync + Send,
|
||||
{
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let idx = cvt_n(get_new_ssl_credential_idx(Some(free_data_box::<T>)))?;
|
||||
Ok(Index::from_raw(idx))
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME should return a result?
|
||||
pub(crate) fn cached_ex_index<T>() -> Index<Self, T>
|
||||
where
|
||||
T: 'static + Sync + Send,
|
||||
{
|
||||
unsafe {
|
||||
let idx = *SSL_CREDENTIAL_INDEXES
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.entry(TypeId::of::<T>())
|
||||
.or_insert_with(|| Self::new_ex_index::<T>().unwrap().as_raw());
|
||||
Index::from_raw(idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SslCredentialRef {
|
||||
/// Returns a reference to the extra data at the specified index.
|
||||
#[corresponds(SSL_CREDENTIAL_get_ex_data)]
|
||||
#[must_use]
|
||||
pub fn ex_data<T>(&self, index: Index<SslCredential, T>) -> Option<&T> {
|
||||
unsafe {
|
||||
let data = ffi::SSL_CREDENTIAL_get_ex_data(self.as_ptr(), index.as_raw());
|
||||
if data.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(&*(data as *const T))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unsafe because SSL contexts are not guaranteed to be unique, we call
|
||||
// this only from SslCredentialBuilder.
|
||||
#[corresponds(SSL_CREDENTIAL_get_ex_data)]
|
||||
pub(crate) unsafe fn ex_data_mut<T>(
|
||||
&mut self,
|
||||
index: Index<SslCredential, T>,
|
||||
) -> Option<&mut T> {
|
||||
let data = ffi::SSL_CREDENTIAL_get_ex_data(self.as_ptr(), index.as_raw());
|
||||
if data.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(&mut *(data as *mut T))
|
||||
}
|
||||
}
|
||||
|
||||
// Unsafe because SSL contexts are not guaranteed to be unique, we call
|
||||
// this only from SslCredentialBuilder.
|
||||
#[corresponds(SSL_CREDENTIAL_set_ex_data)]
|
||||
pub(crate) unsafe fn replace_ex_data<T>(
|
||||
&mut self,
|
||||
index: Index<SslCredential, T>,
|
||||
data: T,
|
||||
) -> Option<T> {
|
||||
if let Some(old) = self.ex_data_mut(index) {
|
||||
return Some(mem::replace(old, data));
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let data = Box::into_raw(Box::new(data)) as *mut c_void;
|
||||
ffi::SSL_CREDENTIAL_set_ex_data(self.as_ptr(), index.as_raw(), data);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder for [`SslCredential`]
|
||||
pub struct SslCredentialBuilder(SslCredential);
|
||||
|
||||
impl SslCredentialBuilder {
|
||||
/// Sets or overwrites the extra data at the specified index.
|
||||
///
|
||||
/// This can be used to provide data to callbacks registered with the context. Use the
|
||||
/// `SslCredential::new_ex_index` method to create an `Index`.
|
||||
///
|
||||
/// Any previous value will be returned and replaced by the new one.
|
||||
#[corresponds(SSL_CREDENTIAL_set_ex_data)]
|
||||
pub fn replace_ex_data<T>(&mut self, index: Index<SslCredential, T>, data: T) -> Option<T> {
|
||||
unsafe { self.0.replace_ex_data(index, data) }
|
||||
}
|
||||
|
||||
// Sets the private key of the credential.
|
||||
#[corresponds(SSL_CREDENTIAL_set1_private_key)]
|
||||
pub fn set_private_key(&mut self, private_key: &PKeyRef<Private>) -> Result<(), ErrorStack> {
|
||||
unsafe {
|
||||
cvt_0i(ffi::SSL_CREDENTIAL_set1_private_key(
|
||||
self.0.as_ptr(),
|
||||
private_key.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Configures a custom private key method on the credential.
|
||||
///
|
||||
/// See [`PrivateKeyMethod`] for more details.
|
||||
#[corresponds(SSL_CREDENTIAL_set_private_key_method)]
|
||||
pub fn set_private_key_method<M>(&mut self, method: M) -> Result<(), ErrorStack>
|
||||
where
|
||||
M: PrivateKeyMethod,
|
||||
{
|
||||
unsafe {
|
||||
self.replace_ex_data(SslCredential::cached_ex_index::<M>(), method);
|
||||
|
||||
cvt_0i(ffi::SSL_CREDENTIAL_set_private_key_method(
|
||||
self.0.as_ptr(),
|
||||
&ffi::SSL_PRIVATE_KEY_METHOD {
|
||||
sign: Some(callbacks::raw_sign::<M>),
|
||||
decrypt: Some(callbacks::raw_decrypt::<M>),
|
||||
complete: Some(callbacks::raw_complete::<M>),
|
||||
},
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the SPKI of the raw public key credential.
|
||||
//
|
||||
// If `spki` is `None`, the SPKI is extracted from the credential's private key.
|
||||
#[corresponds(SSL_CREDENTIAL_set1_spki)]
|
||||
#[cfg(feature = "rpk")]
|
||||
pub fn set_spki_bytes(&mut self, spki: Option<&[u8]>) -> Result<(), ErrorStack> {
|
||||
unsafe {
|
||||
let spki = spki
|
||||
.map(|spki| {
|
||||
cvt_p(ffi::CRYPTO_BUFFER_new(
|
||||
spki.as_ptr(),
|
||||
spki.len(),
|
||||
ptr::null_mut(),
|
||||
))
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or(ptr::null_mut());
|
||||
|
||||
let ret = cvt_0i(ffi::SSL_CREDENTIAL_set1_spki(self.0.as_ptr(), spki)).map(|_| ());
|
||||
|
||||
if !spki.is_null() {
|
||||
ffi::CRYPTO_BUFFER_free(spki);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn build(self) -> SslCredential {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn get_new_ssl_credential_idx(f: ffi::CRYPTO_EX_free) -> c_int {
|
||||
ffi::SSL_CREDENTIAL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f)
|
||||
}
|
||||
@ -35,7 +35,7 @@ impl SslEchKeysBuilder {
|
||||
unsafe {
|
||||
cvt_0i(ffi::SSL_ECH_KEYS_add(
|
||||
self.keys.as_ptr(),
|
||||
is_retry_config as c_int,
|
||||
c_int::from(is_retry_config),
|
||||
ech_config.as_ptr(),
|
||||
ech_config.len(),
|
||||
key.as_ptr(),
|
||||
|
||||
@ -1,16 +1,20 @@
|
||||
use crate::ffi;
|
||||
use crate::x509::X509VerifyError;
|
||||
use libc::c_int;
|
||||
use openssl_macros::corresponds;
|
||||
use std::error;
|
||||
use std::error::Error as StdError;
|
||||
use std::ffi::CStr;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
|
||||
use crate::error::ErrorStack;
|
||||
use crate::ssl::MidHandshakeSslStream;
|
||||
|
||||
/// An error code returned from SSL functions.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
/// `SSL_ERROR_*` error code returned from SSL functions.
|
||||
///
|
||||
/// This is different than [packed error codes](crate::error::Error).
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct ErrorCode(c_int);
|
||||
|
||||
impl ErrorCode {
|
||||
@ -50,14 +54,53 @@ impl ErrorCode {
|
||||
/// An error occurred in the SSL library.
|
||||
pub const SSL: ErrorCode = ErrorCode(ffi::SSL_ERROR_SSL);
|
||||
|
||||
/// Wrap an `SSL_ERROR_*` error code.
|
||||
///
|
||||
/// This is different than [packed error codes](crate::error::Error).
|
||||
#[must_use]
|
||||
#[inline]
|
||||
#[cfg_attr(debug_assertions, track_caller)]
|
||||
pub fn from_raw(raw: c_int) -> ErrorCode {
|
||||
ErrorCode(raw)
|
||||
let code = ErrorCode(raw);
|
||||
debug_assert!(
|
||||
raw < 64 || code.description().is_some(),
|
||||
"{raw} is not an SSL_ERROR_* code"
|
||||
);
|
||||
code
|
||||
}
|
||||
|
||||
/// An `SSL_ERROR_*` error code.
|
||||
///
|
||||
/// This is different than [packed error codes](crate::error::Error).
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
#[must_use]
|
||||
pub fn as_raw(&self) -> c_int {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[corresponds(SSL_error_description)]
|
||||
#[must_use]
|
||||
pub fn description(self) -> Option<&'static str> {
|
||||
unsafe {
|
||||
let msg = ffi::SSL_error_description(self.0);
|
||||
if msg.is_null() {
|
||||
return None;
|
||||
}
|
||||
CStr::from_ptr(msg).to_str().ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ErrorCode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} ({})", self.description().unwrap_or("error"), self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ErrorCode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -66,7 +109,7 @@ pub(crate) enum InnerError {
|
||||
Ssl(ErrorStack),
|
||||
}
|
||||
|
||||
/// An SSL error.
|
||||
/// A general SSL error, based on [`SSL_ERROR_*` error codes](ErrorCode).
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
pub(crate) code: ErrorCode,
|
||||
@ -74,10 +117,13 @@ pub struct Error {
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// An `SSL_ERROR_*` error code.
|
||||
#[must_use]
|
||||
pub fn code(&self) -> ErrorCode {
|
||||
self.code
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn io_error(&self) -> Option<&io::Error> {
|
||||
match self.cause {
|
||||
Some(InnerError::Io(ref e)) => Some(e),
|
||||
@ -92,6 +138,8 @@ impl Error {
|
||||
}
|
||||
}
|
||||
|
||||
/// Stack of [library-specific errors](crate::error::Error), if available.
|
||||
#[must_use]
|
||||
pub fn ssl_error(&self) -> Option<&ErrorStack> {
|
||||
match self.cause {
|
||||
Some(InnerError::Ssl(ref e)) => Some(e),
|
||||
@ -99,6 +147,7 @@ impl Error {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn would_block(&self) -> bool {
|
||||
matches!(
|
||||
self.code,
|
||||
@ -125,26 +174,27 @@ impl From<ErrorStack> for Error {
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.code {
|
||||
ErrorCode::ZERO_RETURN => fmt.write_str("the SSL session has been shut down"),
|
||||
let msg = match self.code {
|
||||
ErrorCode::ZERO_RETURN => "the SSL session has been shut down",
|
||||
ErrorCode::WANT_READ => match self.io_error() {
|
||||
Some(_) => fmt.write_str("a nonblocking read call would have blocked"),
|
||||
None => fmt.write_str("the operation should be retried"),
|
||||
Some(_) => "a nonblocking read call would have blocked",
|
||||
None => "the operation should be retried",
|
||||
},
|
||||
ErrorCode::WANT_WRITE => match self.io_error() {
|
||||
Some(_) => fmt.write_str("a nonblocking write call would have blocked"),
|
||||
None => fmt.write_str("the operation should be retried"),
|
||||
Some(_) => "a nonblocking write call would have blocked",
|
||||
None => "the operation should be retried",
|
||||
},
|
||||
ErrorCode::SYSCALL => match self.io_error() {
|
||||
Some(err) => write!(fmt, "{}", err),
|
||||
None => fmt.write_str("unexpected EOF"),
|
||||
Some(err) => return err.fmt(fmt),
|
||||
None => "unexpected EOF",
|
||||
},
|
||||
ErrorCode::SSL => match self.ssl_error() {
|
||||
Some(e) => write!(fmt, "{}", e),
|
||||
None => fmt.write_str("unknown BoringSSL error"),
|
||||
Some(err) => return err.fmt(fmt),
|
||||
None => "unknown BoringSSL error",
|
||||
},
|
||||
ErrorCode(code) => write!(fmt, "unknown error code {}", code),
|
||||
}
|
||||
ErrorCode(code) => return code.fmt(fmt),
|
||||
};
|
||||
fmt.write_str(msg)
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,7 +235,7 @@ impl<S> fmt::Display for HandshakeError<S> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
HandshakeError::SetupFailure(ref e) => {
|
||||
write!(f, "TLS stream setup failed {}", e)
|
||||
write!(f, "TLS stream setup failed {e}")
|
||||
}
|
||||
HandshakeError::Failure(ref s) => fmt_mid_handshake_error(s, f, "TLS handshake failed"),
|
||||
HandshakeError::WouldBlock(ref s) => {
|
||||
@ -200,8 +250,7 @@ fn fmt_mid_handshake_error(
|
||||
f: &mut fmt::Formatter,
|
||||
prefix: &str,
|
||||
) -> fmt::Result {
|
||||
#[cfg(feature = "rpk")]
|
||||
if s.ssl().ssl_context().is_rpk() {
|
||||
if !s.ssl().ssl_context().has_x509_support() {
|
||||
write!(f, "{}", prefix)?;
|
||||
return write!(f, " {}", s.error());
|
||||
}
|
||||
@ -209,8 +258,8 @@ fn fmt_mid_handshake_error(
|
||||
match s.ssl().verify_result() {
|
||||
// INVALID_CALL is returned if no verification took place,
|
||||
// such as before a cert is sent.
|
||||
Ok(()) | Err(X509VerifyError::INVALID_CALL) => write!(f, "{}", prefix)?,
|
||||
Err(verify) => write!(f, "{}: cert verification failed - {}", prefix, verify)?,
|
||||
Ok(()) | Err(X509VerifyError::INVALID_CALL) => write!(f, "{prefix}")?,
|
||||
Err(verify) => write!(f, "{prefix}: cert verification failed - {verify}")?,
|
||||
}
|
||||
|
||||
write!(f, " {}", s.error())
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -54,7 +54,7 @@ fn server_only_cert_compression() {
|
||||
|
||||
let mut store = X509StoreBuilder::new().unwrap();
|
||||
let x509 = X509::from_pem(super::ROOT_CERT).unwrap();
|
||||
store.add_cert(x509).unwrap();
|
||||
store.add_cert(&x509).unwrap();
|
||||
|
||||
let client = server.client();
|
||||
|
||||
@ -67,7 +67,7 @@ fn client_only_cert_compression() {
|
||||
|
||||
let mut store = X509StoreBuilder::new().unwrap();
|
||||
let x509 = X509::from_pem(super::ROOT_CERT).unwrap();
|
||||
store.add_cert(x509).unwrap();
|
||||
store.add_cert(&x509).unwrap();
|
||||
|
||||
let mut client = server_builder.client();
|
||||
client
|
||||
@ -90,7 +90,7 @@ fn client_and_server_cert_compression() {
|
||||
|
||||
let mut store = X509StoreBuilder::new().unwrap();
|
||||
let x509 = X509::from_pem(super::ROOT_CERT).unwrap();
|
||||
store.add_cert(x509).unwrap();
|
||||
store.add_cert(&x509).unwrap();
|
||||
|
||||
let mut client = server.client();
|
||||
client
|
||||
|
||||
@ -56,17 +56,50 @@ fn no_error_when_trusted_and_callback_returns_true() {
|
||||
|
||||
#[test]
|
||||
fn callback_receives_correct_certificate() {
|
||||
let server = Server::builder().build();
|
||||
// Server sends the full chain (leaf + root)...
|
||||
let server = Server::builder_full_chain().build();
|
||||
// but client doesn't load the root as trusted.
|
||||
// So we expect an error.
|
||||
let mut client = server.client();
|
||||
let expected = "59172d9313e84459bcff27f967e79e6e9217e584";
|
||||
let leaf_sha1 = "59172d9313e84459bcff27f967e79e6e9217e584";
|
||||
let root_sha1 = "c0cbdf7cdd03c9773e5468e1f6d2da7d5cbb1875";
|
||||
client.ctx().set_verify(SslVerifyMode::PEER);
|
||||
client.ctx().set_cert_verify_callback(move |x509| {
|
||||
assert!(!x509.verify_cert().unwrap());
|
||||
// This is set to the root, since that's the problematic cert.
|
||||
assert!(x509.current_cert().is_some());
|
||||
// This is set to the leaf, since that's the cert we're verifying.
|
||||
assert!(x509.cert().is_some());
|
||||
assert!(x509.verify_result().is_err());
|
||||
let cert = x509.current_cert().unwrap();
|
||||
let digest = cert.digest(MessageDigest::sha1()).unwrap();
|
||||
assert_eq!(hex::encode(digest), expected);
|
||||
|
||||
let root = x509
|
||||
.current_cert()
|
||||
.unwrap()
|
||||
.digest(MessageDigest::sha1())
|
||||
.unwrap();
|
||||
assert_eq!(hex::encode(root), root_sha1);
|
||||
|
||||
let leaf = x509.cert().unwrap().digest(MessageDigest::sha1()).unwrap();
|
||||
assert_eq!(hex::encode(leaf), leaf_sha1);
|
||||
|
||||
// Test that `untrusted` is set to the original chain.
|
||||
assert_eq!(x509.untrusted().unwrap().len(), 2);
|
||||
let leaf = x509
|
||||
.untrusted()
|
||||
.unwrap()
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.digest(MessageDigest::sha1())
|
||||
.unwrap();
|
||||
assert_eq!(hex::encode(leaf), leaf_sha1);
|
||||
let root = x509
|
||||
.untrusted()
|
||||
.unwrap()
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.digest(MessageDigest::sha1())
|
||||
.unwrap();
|
||||
assert_eq!(hex::encode(root), root_sha1);
|
||||
true
|
||||
});
|
||||
|
||||
|
||||
@ -40,7 +40,7 @@ fn ech() {
|
||||
let (_server, client) = bootstrap_ech(ECH_CONFIG, ECH_KEY, ECH_CONFIG_LIST);
|
||||
|
||||
let ssl_stream = client.connect();
|
||||
assert!(ssl_stream.ssl().ech_accepted())
|
||||
assert!(ssl_stream.ssl().ech_accepted());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -57,7 +57,7 @@ fn ech_rejection() {
|
||||
Some(b"ech.com".to_vec().as_ref())
|
||||
);
|
||||
assert!(failed_ssl_stream.ssl().get_ech_retry_configs().is_some());
|
||||
assert!(!failed_ssl_stream.ssl().ech_accepted())
|
||||
assert!(!failed_ssl_stream.ssl().ech_accepted());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -69,5 +69,5 @@ fn ech_grease() {
|
||||
client.ssl().set_enable_ech_grease(true);
|
||||
|
||||
let ssl_stream = client.connect();
|
||||
assert!(!ssl_stream.ssl().ech_accepted())
|
||||
assert!(!ssl_stream.ssl().ech_accepted());
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use hex;
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
use std::mem;
|
||||
@ -12,26 +12,25 @@ use crate::hash::MessageDigest;
|
||||
use crate::pkey::PKey;
|
||||
use crate::srtp::SrtpProfileId;
|
||||
use crate::ssl::test::server::Server;
|
||||
use crate::ssl::SslVersion;
|
||||
use crate::ssl::{self, SslCurve};
|
||||
use crate::ssl::{
|
||||
ExtensionType, ShutdownResult, ShutdownState, Ssl, SslAcceptor, SslAcceptorBuilder,
|
||||
self, ExtensionType, ShutdownResult, ShutdownState, Ssl, SslAcceptor, SslAcceptorBuilder,
|
||||
SslConnector, SslContext, SslFiletype, SslMethod, SslOptions, SslStream, SslVerifyMode,
|
||||
};
|
||||
use crate::ssl::{HandshakeError, SslVersion};
|
||||
use crate::x509::store::X509StoreBuilder;
|
||||
use crate::x509::verify::X509CheckFlags;
|
||||
use crate::x509::{X509Name, X509};
|
||||
|
||||
#[cfg(not(feature = "fips"))]
|
||||
use super::CompliancePolicy;
|
||||
|
||||
mod cert_compressor;
|
||||
mod cert_verify;
|
||||
mod custom_verify;
|
||||
#[cfg(not(feature = "fips"))]
|
||||
mod ech;
|
||||
mod private_key_method;
|
||||
mod server;
|
||||
mod session;
|
||||
mod session_resumption;
|
||||
mod verify;
|
||||
|
||||
static ROOT_CERT: &[u8] = include_bytes!("../../../test/root-ca.pem");
|
||||
@ -41,7 +40,7 @@ static KEY: &[u8] = include_bytes!("../../../test/key.pem");
|
||||
#[test]
|
||||
fn get_ctx_options() {
|
||||
let ctx = SslContext::builder(SslMethod::tls()).unwrap();
|
||||
ctx.options();
|
||||
let _ = ctx.options();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -308,6 +307,52 @@ fn test_select_cert_ok() {
|
||||
client.connect();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mutable_store() {
|
||||
#![allow(deprecated)]
|
||||
|
||||
let cert = include_bytes!("../../../test/cert.pem");
|
||||
let cert = X509::from_pem(cert).unwrap();
|
||||
let cert2 = include_bytes!("../../../test/root-ca.pem");
|
||||
let cert2 = X509::from_pem(cert2).unwrap();
|
||||
|
||||
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
|
||||
ctx.cert_store_mut().add_cert(cert.clone()).unwrap();
|
||||
assert_eq!(1, ctx.cert_store().objects_len());
|
||||
|
||||
ctx.set_cert_store_builder(X509StoreBuilder::new().unwrap());
|
||||
assert_eq!(0, ctx.cert_store().objects_len());
|
||||
|
||||
ctx.cert_store_mut().add_cert(cert.clone()).unwrap();
|
||||
assert_eq!(1, ctx.cert_store().objects_len());
|
||||
|
||||
let mut new_store = X509StoreBuilder::new().unwrap();
|
||||
new_store.add_cert(&cert).unwrap();
|
||||
new_store.add_cert(&cert2).unwrap();
|
||||
let new_store = new_store.build();
|
||||
assert_eq!(2, new_store.objects_len());
|
||||
|
||||
ctx.set_cert_store_ref(&new_store);
|
||||
assert_eq!(2, ctx.cert_store().objects_len());
|
||||
assert!(std::ptr::eq(new_store.as_ptr(), ctx.cert_store().as_ptr()));
|
||||
|
||||
let ctx = ctx.build();
|
||||
assert!(std::ptr::eq(new_store.as_ptr(), ctx.cert_store().as_ptr()));
|
||||
|
||||
drop(new_store);
|
||||
assert_eq!(2, ctx.cert_store().objects_len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "mutated")]
|
||||
fn shared_store_must_not_be_mutated() {
|
||||
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
|
||||
|
||||
let shared = X509StoreBuilder::new().unwrap().build();
|
||||
ctx.set_cert_store_ref(&shared);
|
||||
ctx.cert_store_mut();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_cert_error() {
|
||||
let mut server = Server::builder();
|
||||
@ -372,6 +417,68 @@ fn test_select_cert_alpn_extension() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_io_retry() {
|
||||
#[derive(Debug)]
|
||||
struct RetryStream {
|
||||
inner: TcpStream,
|
||||
first_read: bool,
|
||||
first_write: bool,
|
||||
first_flush: bool,
|
||||
}
|
||||
|
||||
impl Read for RetryStream {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
if mem::replace(&mut self.first_read, false) {
|
||||
Err(io::Error::new(io::ErrorKind::WouldBlock, "first read"))
|
||||
} else {
|
||||
self.inner.read(buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for RetryStream {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
if mem::replace(&mut self.first_write, false) {
|
||||
Err(io::Error::new(io::ErrorKind::WouldBlock, "first write"))
|
||||
} else {
|
||||
self.inner.write(buf)
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
if mem::replace(&mut self.first_flush, false) {
|
||||
Err(io::Error::new(io::ErrorKind::WouldBlock, "first flush"))
|
||||
} else {
|
||||
self.inner.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let server = Server::builder().build();
|
||||
|
||||
let stream = RetryStream {
|
||||
inner: server.connect_tcp(),
|
||||
first_read: true,
|
||||
first_write: true,
|
||||
first_flush: true,
|
||||
};
|
||||
|
||||
let ctx = SslContext::builder(SslMethod::tls()).unwrap();
|
||||
let mut s = match Ssl::new(&ctx.build()).unwrap().connect(stream) {
|
||||
Ok(mut s) => return s.read_exact(&mut [0]).unwrap(),
|
||||
Err(HandshakeError::WouldBlock(s)) => s,
|
||||
Err(_) => panic!("should not fail on setup"),
|
||||
};
|
||||
loop {
|
||||
match s.handshake() {
|
||||
Ok(mut s) => return s.read_exact(&mut [0]).unwrap(),
|
||||
Err(HandshakeError::WouldBlock(mid_s)) => s = mid_s,
|
||||
Err(_) => panic!("should not fail on handshake"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "blammo")]
|
||||
fn write_panic() {
|
||||
@ -907,59 +1014,15 @@ fn sni_callback_swapped_ctx() {
|
||||
assert!(CALLED_BACK.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[cfg(feature = "kx-safe-default")]
|
||||
#[test]
|
||||
fn client_set_default_curves_list() {
|
||||
let ssl_ctx = crate::ssl::SslContextBuilder::new(SslMethod::tls())
|
||||
.unwrap()
|
||||
.build();
|
||||
let mut ssl = Ssl::new(&ssl_ctx).unwrap();
|
||||
|
||||
// Panics if Kyber768 missing in boringSSL.
|
||||
ssl.client_set_default_curves_list();
|
||||
}
|
||||
|
||||
#[cfg(feature = "kx-safe-default")]
|
||||
#[test]
|
||||
fn server_set_default_curves_list() {
|
||||
let ssl_ctx = crate::ssl::SslContextBuilder::new(SslMethod::tls())
|
||||
.unwrap()
|
||||
.build();
|
||||
let mut ssl = Ssl::new(&ssl_ctx).unwrap();
|
||||
|
||||
// Panics if Kyber768 missing in boringSSL.
|
||||
ssl.server_set_default_curves_list();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_curve() {
|
||||
let server = Server::builder().build();
|
||||
let client = server.client_with_root_ca();
|
||||
let client_stream = client.connect();
|
||||
let curve = client_stream.ssl().curve().expect("curve");
|
||||
assert!(curve.name().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_curve_name() {
|
||||
assert_eq!(SslCurve::SECP224R1.name(), Some("P-224"));
|
||||
assert_eq!(SslCurve::SECP256R1.name(), Some("P-256"));
|
||||
assert_eq!(SslCurve::SECP384R1.name(), Some("P-384"));
|
||||
assert_eq!(SslCurve::SECP521R1.name(), Some("P-521"));
|
||||
assert_eq!(SslCurve::X25519.name(), Some("X25519"));
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "kx-safe-default"))]
|
||||
#[test]
|
||||
fn set_curves() {
|
||||
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
|
||||
ctx.set_curves(&[
|
||||
SslCurve::SECP224R1,
|
||||
SslCurve::SECP256R1,
|
||||
SslCurve::SECP384R1,
|
||||
SslCurve::X25519,
|
||||
])
|
||||
.expect("Failed to set curves");
|
||||
let curve = client_stream.ssl().curve();
|
||||
assert!(curve.is_some());
|
||||
let curve_name = client_stream.ssl().curve_name();
|
||||
assert!(curve_name.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -990,7 +1053,6 @@ fn test_get_ciphers() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "fips"))]
|
||||
fn test_set_compliance() {
|
||||
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
|
||||
ctx.set_compliance_policy(CompliancePolicy::FIPS_202205)
|
||||
@ -1010,7 +1072,7 @@ fn test_set_compliance() {
|
||||
assert_eq!(ciphers.len(), FIPS_CIPHERS.len());
|
||||
|
||||
for cipher in ciphers.into_iter().zip(FIPS_CIPHERS) {
|
||||
assert_eq!(cipher.0.name(), cipher.1)
|
||||
assert_eq!(cipher.0.name(), cipher.1);
|
||||
}
|
||||
|
||||
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
|
||||
@ -1029,7 +1091,7 @@ fn test_set_compliance() {
|
||||
assert_eq!(ciphers.len(), WPA3_192_CIPHERS.len());
|
||||
|
||||
for cipher in ciphers.into_iter().zip(WPA3_192_CIPHERS) {
|
||||
assert_eq!(cipher.0.name(), cipher.1)
|
||||
assert_eq!(cipher.0.name(), cipher.1);
|
||||
}
|
||||
|
||||
ctx.set_compliance_policy(CompliancePolicy::NONE)
|
||||
@ -1070,3 +1132,94 @@ fn test_info_callback() {
|
||||
client.connect();
|
||||
assert!(CALLED_BACK.load(Ordering::Relaxed));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ssl_set_compliance() {
|
||||
let ctx = SslContext::builder(SslMethod::tls()).unwrap().build();
|
||||
let mut ssl = Ssl::new(&ctx).unwrap();
|
||||
ssl.set_compliance_policy(CompliancePolicy::FIPS_202205)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(ssl.max_proto_version().unwrap(), SslVersion::TLS1_3);
|
||||
assert_eq!(ssl.min_proto_version().unwrap(), SslVersion::TLS1_2);
|
||||
|
||||
const FIPS_CIPHERS: [&str; 4] = [
|
||||
"ECDHE-ECDSA-AES128-GCM-SHA256",
|
||||
"ECDHE-RSA-AES128-GCM-SHA256",
|
||||
"ECDHE-ECDSA-AES256-GCM-SHA384",
|
||||
"ECDHE-RSA-AES256-GCM-SHA384",
|
||||
];
|
||||
|
||||
let ciphers = ssl.ciphers();
|
||||
assert_eq!(ciphers.len(), FIPS_CIPHERS.len());
|
||||
|
||||
for cipher in ciphers.into_iter().zip(FIPS_CIPHERS) {
|
||||
assert_eq!(cipher.0.name(), cipher.1);
|
||||
}
|
||||
|
||||
let ctx = SslContext::builder(SslMethod::tls()).unwrap().build();
|
||||
let mut ssl = Ssl::new(&ctx).unwrap();
|
||||
ssl.set_compliance_policy(CompliancePolicy::WPA3_192_202304)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(ssl.max_proto_version().unwrap(), SslVersion::TLS1_3);
|
||||
assert_eq!(ssl.min_proto_version().unwrap(), SslVersion::TLS1_2);
|
||||
|
||||
const WPA3_192_CIPHERS: [&str; 2] = [
|
||||
"ECDHE-ECDSA-AES256-GCM-SHA384",
|
||||
"ECDHE-RSA-AES256-GCM-SHA384",
|
||||
];
|
||||
|
||||
let ciphers = ssl.ciphers();
|
||||
assert_eq!(ciphers.len(), WPA3_192_CIPHERS.len());
|
||||
|
||||
for cipher in ciphers.into_iter().zip(WPA3_192_CIPHERS) {
|
||||
assert_eq!(cipher.0.name(), cipher.1);
|
||||
}
|
||||
|
||||
ssl.set_compliance_policy(CompliancePolicy::NONE)
|
||||
.expect_err("Testing expect err if set compliance policy to NONE");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ex_data_drop() {
|
||||
use crate::ssl::SslContextBuilder;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
use std::sync::Arc;
|
||||
|
||||
struct TrackDrop(Arc<AtomicU32>);
|
||||
impl Drop for TrackDrop {
|
||||
fn drop(&mut self) {
|
||||
self.0.fetch_add(1, Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
let mut ctx = SslContextBuilder::new(SslMethod::tls()).unwrap();
|
||||
let index = SslContext::new_ex_index().unwrap();
|
||||
let d1 = Arc::new(AtomicU32::new(100));
|
||||
let d2 = Arc::new(AtomicU32::new(200));
|
||||
let d3 = Arc::new(AtomicU32::new(300));
|
||||
ctx.set_ex_data(index, TrackDrop(d1.clone()));
|
||||
assert_eq!(100, d1.load(Relaxed));
|
||||
assert_eq!(200, d2.load(Relaxed));
|
||||
ctx.replace_ex_data(index, TrackDrop(d2.clone()));
|
||||
assert_eq!(101, d1.load(Relaxed));
|
||||
assert_eq!(200, d2.load(Relaxed));
|
||||
ctx.replace_ex_data(index, TrackDrop(d3.clone()));
|
||||
assert_eq!(101, d1.load(Relaxed));
|
||||
assert_eq!(201, d2.load(Relaxed));
|
||||
assert_eq!(300, d3.load(Relaxed));
|
||||
drop(ctx);
|
||||
assert_eq!(101, d1.load(Relaxed));
|
||||
assert_eq!(201, d2.load(Relaxed));
|
||||
assert_eq!(301, d3.load(Relaxed));
|
||||
|
||||
let mut ctx2 = SslContextBuilder::new(SslMethod::tls()).unwrap();
|
||||
|
||||
ctx2.set_ex_data(index, TrackDrop(d1.clone()));
|
||||
ctx2.set_ex_data(index, TrackDrop(d2.clone()));
|
||||
drop(ctx2);
|
||||
assert_eq!(102, d1.load(Relaxed));
|
||||
assert_eq!(202, d2.load(Relaxed));
|
||||
}
|
||||
|
||||
@ -36,6 +36,24 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
/// Serves the leaf and the root together.
|
||||
pub fn builder_full_chain() -> Builder {
|
||||
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
|
||||
// Uses certs.pem instead of cert.pem.
|
||||
ctx.set_certificate_chain_file("test/certs.pem").unwrap();
|
||||
ctx.set_private_key_file("test/key.pem", SslFiletype::PEM)
|
||||
.unwrap();
|
||||
|
||||
Builder {
|
||||
ctx,
|
||||
ssl_cb: Box::new(|_| {}),
|
||||
io_cb: Box::new(|_| {}),
|
||||
err_cb: Box::new(|_| {}),
|
||||
should_error: false,
|
||||
expected_connections_count: 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn client(&self) -> ClientBuilder {
|
||||
ClientBuilder {
|
||||
ctx: SslContext::builder(SslMethod::tls()).unwrap(),
|
||||
|
||||
@ -49,7 +49,7 @@ fn new_get_session_callback() {
|
||||
.ctx()
|
||||
.set_session_cache_mode(SslSessionCacheMode::SERVER | SslSessionCacheMode::NO_INTERNAL);
|
||||
server.ctx().set_new_session_callback(|_, session| {
|
||||
SERVER_SESSION_DER.set(session.to_der().unwrap()).unwrap()
|
||||
SERVER_SESSION_DER.set(session.to_der().unwrap()).unwrap();
|
||||
});
|
||||
unsafe {
|
||||
server.ctx().set_get_session_callback(|_, id| {
|
||||
@ -76,7 +76,7 @@ fn new_get_session_callback() {
|
||||
.ctx()
|
||||
.set_session_cache_mode(SslSessionCacheMode::CLIENT);
|
||||
client.ctx().set_new_session_callback(|_, session| {
|
||||
CLIENT_SESSION_DER.set(session.to_der().unwrap()).unwrap()
|
||||
CLIENT_SESSION_DER.set(session.to_der().unwrap()).unwrap();
|
||||
});
|
||||
|
||||
let client = client.build();
|
||||
|
||||
242
boring/src/ssl/test/session_resumption.rs
Normal file
242
boring/src/ssl/test/session_resumption.rs
Normal file
@ -0,0 +1,242 @@
|
||||
use super::server::Server;
|
||||
use crate::ssl::test::MessageDigest;
|
||||
use crate::ssl::HmacCtxRef;
|
||||
use crate::ssl::SslRef;
|
||||
use crate::ssl::SslSession;
|
||||
use crate::ssl::SslSessionCacheMode;
|
||||
use crate::ssl::TicketKeyCallbackResult;
|
||||
use crate::symm::Cipher;
|
||||
use crate::symm::CipherCtxRef;
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
static SUCCESS_ENCRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0);
|
||||
static SUCCESS_DECRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0);
|
||||
static NOOP_ENCRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0);
|
||||
static NOOP_DECRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0);
|
||||
|
||||
#[test]
|
||||
fn resume_session() {
|
||||
static SESSION_TICKET: OnceLock<SslSession> = OnceLock::new();
|
||||
static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0);
|
||||
|
||||
let mut server = Server::builder();
|
||||
server.expected_connections_count(2);
|
||||
let server = server.build();
|
||||
|
||||
let mut client = server.client();
|
||||
client
|
||||
.ctx()
|
||||
.set_session_cache_mode(SslSessionCacheMode::CLIENT);
|
||||
client.ctx().set_new_session_callback(|_, session| {
|
||||
NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
// The server sends multiple session tickets but we only care to retrieve one.
|
||||
let _ = SESSION_TICKET.set(session);
|
||||
});
|
||||
let ssl_stream = client.connect();
|
||||
|
||||
assert!(!ssl_stream.ssl().session_reused());
|
||||
assert!(SESSION_TICKET.get().is_some());
|
||||
assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2);
|
||||
|
||||
// Retrieve the session ticket
|
||||
let session_ticket = SESSION_TICKET.get().unwrap();
|
||||
|
||||
// Attempt to resume the connection using the session ticket
|
||||
let client_2 = server.client();
|
||||
let mut ssl_builder = client_2.build().builder();
|
||||
unsafe { ssl_builder.ssl().set_session(session_ticket).unwrap() };
|
||||
let ssl_stream_2 = ssl_builder.connect();
|
||||
|
||||
assert!(ssl_stream_2.ssl().session_reused());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_callback_success() {
|
||||
static SESSION_TICKET: OnceLock<SslSession> = OnceLock::new();
|
||||
static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0);
|
||||
|
||||
let mut server = Server::builder();
|
||||
server.expected_connections_count(2);
|
||||
unsafe {
|
||||
server
|
||||
.ctx()
|
||||
.set_ticket_key_callback(test_success_tickey_key_callback);
|
||||
};
|
||||
let server = server.build();
|
||||
|
||||
let mut client = server.client();
|
||||
client
|
||||
.ctx()
|
||||
.set_session_cache_mode(SslSessionCacheMode::CLIENT);
|
||||
client.ctx().set_new_session_callback(|_, session| {
|
||||
NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
// The server sends multiple session tickets but we only care to retrieve one.
|
||||
let _ = SESSION_TICKET.set(session);
|
||||
});
|
||||
let ssl_stream = client.connect();
|
||||
|
||||
assert!(!ssl_stream.ssl().session_reused());
|
||||
assert!(SESSION_TICKET.get().is_some());
|
||||
assert_eq!(SUCCESS_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 2);
|
||||
assert_eq!(SUCCESS_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 0);
|
||||
assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2);
|
||||
|
||||
// Retrieve the session ticket
|
||||
let session_ticket = SESSION_TICKET.get().unwrap();
|
||||
|
||||
// Attempt to resume the connection using the session ticket
|
||||
let client_2 = server.client();
|
||||
let mut ssl_builder = client_2.build().builder();
|
||||
unsafe { ssl_builder.ssl().set_session(session_ticket).unwrap() };
|
||||
let ssl_stream_2 = ssl_builder.connect();
|
||||
|
||||
assert!(ssl_stream_2.ssl().session_reused());
|
||||
assert_eq!(SUCCESS_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 4);
|
||||
assert_eq!(SUCCESS_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_callback_unrecognized_decryption_ticket() {
|
||||
static SESSION_TICKET: OnceLock<SslSession> = OnceLock::new();
|
||||
static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0);
|
||||
|
||||
let mut server = Server::builder();
|
||||
server.expected_connections_count(2);
|
||||
unsafe {
|
||||
server
|
||||
.ctx()
|
||||
.set_ticket_key_callback(test_noop_tickey_key_callback);
|
||||
};
|
||||
let server = server.build();
|
||||
|
||||
let mut client = server.client();
|
||||
client
|
||||
.ctx()
|
||||
.set_session_cache_mode(SslSessionCacheMode::CLIENT);
|
||||
client.ctx().set_new_session_callback(|_, session| {
|
||||
NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
// The server sends multiple session tickets but we only care to retrieve one.
|
||||
let _ = SESSION_TICKET.set(session);
|
||||
});
|
||||
let ssl_stream = client.connect();
|
||||
|
||||
assert!(!ssl_stream.ssl().session_reused());
|
||||
assert!(SESSION_TICKET.get().is_some());
|
||||
assert_eq!(NOOP_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 2);
|
||||
assert_eq!(NOOP_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 0);
|
||||
assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2);
|
||||
|
||||
// Retrieve the session ticket
|
||||
let session_ticket = SESSION_TICKET.get().unwrap();
|
||||
|
||||
// Attempt to resume the connection using the session ticket
|
||||
let client_2 = server.client();
|
||||
let mut ssl_builder = client_2.build().builder();
|
||||
unsafe { ssl_builder.ssl().set_session(session_ticket).unwrap() };
|
||||
let ssl_stream_2 = ssl_builder.connect();
|
||||
|
||||
// Second connection was NOT resumed due to TicketKeyCallbackResult::Noop on decryption
|
||||
assert!(!ssl_stream_2.ssl().session_reused());
|
||||
assert_eq!(NOOP_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 4);
|
||||
assert_eq!(NOOP_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 1);
|
||||
}
|
||||
|
||||
// Successfully return a session ticket in encryption mode but return a
|
||||
// TicketKeyCallbackResult::Noop in decryption mode.
|
||||
fn test_noop_tickey_key_callback(
|
||||
_ssl: &SslRef,
|
||||
key_name: &mut [u8; 16],
|
||||
iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize],
|
||||
evp_ctx: &mut CipherCtxRef,
|
||||
hmac_ctx: &mut HmacCtxRef,
|
||||
encrypt: bool,
|
||||
) -> TicketKeyCallbackResult {
|
||||
// These should only be used for testing purposes.
|
||||
const TEST_KEY_NAME: [u8; 16] = [5; 16];
|
||||
const TEST_CBC_IV: [u8; ffi::EVP_MAX_IV_LENGTH as usize] = [1; ffi::EVP_MAX_IV_LENGTH as usize];
|
||||
const TEST_AES_128_CBC_KEY: [u8; 16] = [2; 16];
|
||||
const TEST_HMAC_KEY: [u8; 32] = [3; 32];
|
||||
|
||||
let digest = MessageDigest::sha256();
|
||||
let cipher = Cipher::aes_128_cbc();
|
||||
|
||||
if encrypt {
|
||||
NOOP_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
// Ensure key_name and iv are initialized and set test values.
|
||||
assert_eq!(key_name, &[0; 16]);
|
||||
assert_eq!(iv, &[0; 16]);
|
||||
key_name.copy_from_slice(&TEST_KEY_NAME);
|
||||
iv.copy_from_slice(&TEST_CBC_IV);
|
||||
|
||||
// Set the encryption context.
|
||||
evp_ctx
|
||||
.init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV)
|
||||
.unwrap();
|
||||
|
||||
// Set the hmac context.
|
||||
hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap();
|
||||
|
||||
TicketKeyCallbackResult::Success
|
||||
} else {
|
||||
NOOP_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
// Check key_name matches.
|
||||
assert_eq!(key_name, &TEST_KEY_NAME);
|
||||
|
||||
TicketKeyCallbackResult::Noop
|
||||
}
|
||||
}
|
||||
|
||||
// Custom callback to encrypt and decrypt session tickets
|
||||
fn test_success_tickey_key_callback(
|
||||
_ssl: &SslRef,
|
||||
key_name: &mut [u8; 16],
|
||||
iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize],
|
||||
evp_ctx: &mut CipherCtxRef,
|
||||
hmac_ctx: &mut HmacCtxRef,
|
||||
encrypt: bool,
|
||||
) -> TicketKeyCallbackResult {
|
||||
// These should only be used for testing purposes.
|
||||
const TEST_KEY_NAME: [u8; 16] = [5; 16];
|
||||
const TEST_CBC_IV: [u8; ffi::EVP_MAX_IV_LENGTH as usize] = [1; ffi::EVP_MAX_IV_LENGTH as usize];
|
||||
const TEST_AES_128_CBC_KEY: [u8; 16] = [2; 16];
|
||||
const TEST_HMAC_KEY: [u8; 32] = [3; 32];
|
||||
|
||||
let digest = MessageDigest::sha256();
|
||||
let cipher = Cipher::aes_128_cbc();
|
||||
|
||||
if encrypt {
|
||||
SUCCESS_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
// Ensure key_name and iv are initialized and set test values.
|
||||
assert_eq!(key_name, &[0; 16]);
|
||||
assert_eq!(iv, &[0; 16]);
|
||||
key_name.copy_from_slice(&TEST_KEY_NAME);
|
||||
iv.copy_from_slice(&TEST_CBC_IV);
|
||||
|
||||
// Set the encryption context.
|
||||
evp_ctx
|
||||
.init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV)
|
||||
.unwrap();
|
||||
|
||||
// Set the hmac context.
|
||||
hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap();
|
||||
} else {
|
||||
SUCCESS_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
// Check key_name matches.
|
||||
assert_eq!(key_name, &TEST_KEY_NAME);
|
||||
|
||||
// Set the decryption context.
|
||||
evp_ctx
|
||||
.init_decrypt(&cipher, &TEST_AES_128_CBC_KEY, iv)
|
||||
.unwrap();
|
||||
|
||||
// Set the hmac context.
|
||||
hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap();
|
||||
}
|
||||
|
||||
TicketKeyCallbackResult::Success
|
||||
}
|
||||
@ -32,7 +32,7 @@ fn trusted_with_set_cert() {
|
||||
|
||||
let mut store = X509StoreBuilder::new().unwrap();
|
||||
let x509 = X509::from_pem(super::ROOT_CERT).unwrap();
|
||||
store.add_cert(x509).unwrap();
|
||||
store.add_cert(&x509).unwrap();
|
||||
|
||||
let mut client = server.client();
|
||||
client.ctx().set_verify(SslVerifyMode::PEER);
|
||||
|
||||
@ -48,7 +48,7 @@ impl<T: Stackable> Drop for Stack<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
while self.pop().is_some() {}
|
||||
OPENSSL_sk_free(self.0 as *mut _);
|
||||
OPENSSL_sk_free(self.0.cast());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -58,7 +58,7 @@ impl<T: Stackable> Stack<T> {
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let ptr = cvt_p(OPENSSL_sk_new_null())?;
|
||||
Ok(Stack(ptr as *mut _))
|
||||
Ok(Stack(ptr.cast()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -132,7 +132,7 @@ impl<T: Stackable> Drop for IntoIter<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
for _ in &mut *self {}
|
||||
OPENSSL_sk_free(self.stack as *mut _);
|
||||
OPENSSL_sk_free(self.stack.cast());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -144,7 +144,7 @@ impl<T: Stackable> Iterator for IntoIter<T> {
|
||||
unsafe {
|
||||
self.idxs
|
||||
.next()
|
||||
.map(|i| T::from_ptr(OPENSSL_sk_value(self.stack as *mut _, i) as *mut _))
|
||||
.map(|i| T::from_ptr(OPENSSL_sk_value(self.stack.cast(), i).cast()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,7 +158,7 @@ impl<T: Stackable> DoubleEndedIterator for IntoIter<T> {
|
||||
unsafe {
|
||||
self.idxs
|
||||
.next_back()
|
||||
.map(|i| T::from_ptr(OPENSSL_sk_value(self.stack as *mut _, i) as *mut _))
|
||||
.map(|i| T::from_ptr(OPENSSL_sk_value(self.stack.cast(), i).cast()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -176,27 +176,30 @@ unsafe impl<T: Stackable> ForeignTypeRef for StackRef<T> {
|
||||
|
||||
impl<T: Stackable> StackRef<T> {
|
||||
fn as_stack(&self) -> *mut OPENSSL_STACK {
|
||||
self.as_ptr() as *mut _
|
||||
self.as_ptr().cast()
|
||||
}
|
||||
|
||||
/// Returns the number of items in the stack.
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
unsafe { OPENSSL_sk_num(self.as_stack()) }
|
||||
}
|
||||
|
||||
/// Determines if the stack is empty.
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> Iter<T> {
|
||||
#[must_use]
|
||||
pub fn iter(&self) -> Iter<'_, T> {
|
||||
Iter {
|
||||
stack: self,
|
||||
idxs: 0..self.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> IterMut<T> {
|
||||
pub fn iter_mut(&mut self) -> IterMut<'_, T> {
|
||||
IterMut {
|
||||
idxs: 0..self.len(),
|
||||
stack: self,
|
||||
@ -205,6 +208,7 @@ impl<T: Stackable> StackRef<T> {
|
||||
|
||||
/// Returns a reference to the element at the given index in the
|
||||
/// stack or `None` if the index is out of bounds
|
||||
#[must_use]
|
||||
pub fn get(&self, idx: usize) -> Option<&T::Ref> {
|
||||
unsafe {
|
||||
if idx >= self.len() {
|
||||
@ -230,7 +234,7 @@ impl<T: Stackable> StackRef<T> {
|
||||
/// Pushes a value onto the top of the stack.
|
||||
pub fn push(&mut self, data: T) -> Result<(), ErrorStack> {
|
||||
unsafe {
|
||||
cvt_0(OPENSSL_sk_push(self.as_stack(), data.as_ptr() as *mut _))?;
|
||||
cvt_0(OPENSSL_sk_push(self.as_stack(), data.as_ptr().cast()))?;
|
||||
mem::forget(data);
|
||||
Ok(())
|
||||
}
|
||||
@ -243,13 +247,23 @@ impl<T: Stackable> StackRef<T> {
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(T::from_ptr(ptr as *mut _))
|
||||
Some(T::from_ptr(ptr.cast()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn _get(&self, idx: usize) -> *mut T::CType {
|
||||
OPENSSL_sk_value(self.as_stack(), idx) as *mut _
|
||||
OPENSSL_sk_value(self.as_stack(), idx).cast()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for StackRef<T>
|
||||
where
|
||||
T: Stackable,
|
||||
T::Ref: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.debug_list().entries(self).finish()
|
||||
}
|
||||
}
|
||||
|
||||
@ -319,7 +333,7 @@ impl<'a, T: Stackable> Iterator for Iter<'a, T> {
|
||||
unsafe {
|
||||
self.idxs
|
||||
.next()
|
||||
.map(|i| T::Ref::from_ptr(OPENSSL_sk_value(self.stack.as_stack(), i) as *mut _))
|
||||
.map(|i| T::Ref::from_ptr(OPENSSL_sk_value(self.stack.as_stack(), i).cast()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -333,7 +347,7 @@ impl<'a, T: Stackable> DoubleEndedIterator for Iter<'a, T> {
|
||||
unsafe {
|
||||
self.idxs
|
||||
.next_back()
|
||||
.map(|i| T::Ref::from_ptr(OPENSSL_sk_value(self.stack.as_stack(), i) as *mut _))
|
||||
.map(|i| T::Ref::from_ptr(OPENSSL_sk_value(self.stack.as_stack(), i).cast()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -353,7 +367,7 @@ impl<'a, T: Stackable> Iterator for IterMut<'a, T> {
|
||||
unsafe {
|
||||
self.idxs
|
||||
.next()
|
||||
.map(|i| T::Ref::from_ptr_mut(OPENSSL_sk_value(self.stack.as_stack(), i) as *mut _))
|
||||
.map(|i| T::Ref::from_ptr_mut(OPENSSL_sk_value(self.stack.as_stack(), i).cast()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -367,7 +381,7 @@ impl<'a, T: Stackable> DoubleEndedIterator for IterMut<'a, T> {
|
||||
unsafe {
|
||||
self.idxs
|
||||
.next_back()
|
||||
.map(|i| T::Ref::from_ptr_mut(OPENSSL_sk_value(self.stack.as_stack(), i) as *mut _))
|
||||
.map(|i| T::Ref::from_ptr_mut(OPENSSL_sk_value(self.stack.as_stack(), i).cast()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use crate::ffi;
|
||||
use foreign_types::ForeignTypeRef;
|
||||
use libc::{c_char, c_void};
|
||||
use libc::c_char;
|
||||
use std::convert::AsRef;
|
||||
use std::ffi::CStr;
|
||||
use std::fmt;
|
||||
@ -13,6 +13,9 @@ foreign_type_and_impl_send_sync! {
|
||||
type CType = c_char;
|
||||
fn drop = free;
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// MUST be UTF-8.
|
||||
pub struct OpensslString;
|
||||
}
|
||||
|
||||
@ -80,5 +83,5 @@ impl fmt::Debug for OpensslStringRef {
|
||||
}
|
||||
|
||||
unsafe fn free(buf: *mut c_char) {
|
||||
crate::ffi::OPENSSL_free(buf as *mut c_void);
|
||||
crate::ffi::OPENSSL_free(buf.cast());
|
||||
}
|
||||
|
||||
@ -53,14 +53,15 @@
|
||||
//! ```
|
||||
|
||||
use crate::ffi;
|
||||
use libc::{c_int, c_uint};
|
||||
use foreign_types::ForeignTypeRef;
|
||||
use openssl_macros::corresponds;
|
||||
use std::cmp;
|
||||
use std::ffi::c_int;
|
||||
use std::ptr;
|
||||
|
||||
use crate::error::ErrorStack;
|
||||
use crate::nid::Nid;
|
||||
use crate::{cvt, cvt_p};
|
||||
use crate::{cvt, cvt_p, try_int};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Mode {
|
||||
@ -68,19 +69,83 @@ pub enum Mode {
|
||||
Decrypt,
|
||||
}
|
||||
|
||||
foreign_type_and_impl_send_sync! {
|
||||
type CType = ffi::EVP_CIPHER_CTX;
|
||||
fn drop = ffi::EVP_CIPHER_CTX_free;
|
||||
|
||||
pub struct CipherCtx;
|
||||
}
|
||||
|
||||
impl CipherCtxRef {
|
||||
/// Configures CipherCtx for a fresh encryption operation using `cipher`.
|
||||
///
|
||||
#[corresponds(EVP_EncryptInit_ex)]
|
||||
pub fn init_encrypt(
|
||||
&mut self,
|
||||
cipher: &Cipher,
|
||||
key: &[u8],
|
||||
iv: &[u8; ffi::EVP_MAX_IV_LENGTH as usize],
|
||||
) -> Result<(), ErrorStack> {
|
||||
ffi::init();
|
||||
|
||||
if key.len() != cipher.key_len() {
|
||||
return Err(ErrorStack::internal_error_str("invalid key size"));
|
||||
}
|
||||
|
||||
unsafe {
|
||||
cvt(ffi::EVP_EncryptInit_ex(
|
||||
self.as_ptr(),
|
||||
cipher.as_ptr(),
|
||||
// ENGINE api is deprecated
|
||||
ptr::null_mut(),
|
||||
key.as_ptr(),
|
||||
iv.as_ptr(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Configures CipherCtx for a fresh decryption operation using `cipher`.
|
||||
///
|
||||
#[corresponds(EVP_DecryptInit_ex)]
|
||||
pub fn init_decrypt(
|
||||
&mut self,
|
||||
cipher: &Cipher,
|
||||
key: &[u8],
|
||||
iv: &[u8; ffi::EVP_MAX_IV_LENGTH as usize],
|
||||
) -> Result<(), ErrorStack> {
|
||||
ffi::init();
|
||||
|
||||
if key.len() != cipher.key_len() {
|
||||
return Err(ErrorStack::internal_error_str("invalid key size"));
|
||||
}
|
||||
|
||||
unsafe {
|
||||
cvt(ffi::EVP_DecryptInit_ex(
|
||||
self.as_ptr(),
|
||||
cipher.as_ptr(),
|
||||
// ENGINE api is deprecated
|
||||
ptr::null_mut(),
|
||||
key.as_ptr(),
|
||||
iv.as_ptr(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a particular cipher algorithm.
|
||||
///
|
||||
/// See OpenSSL doc at [`EVP_EncryptInit`] for more information on each algorithms.
|
||||
///
|
||||
/// [`EVP_EncryptInit`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_EncryptInit.html
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Cipher(*const ffi::EVP_CIPHER);
|
||||
|
||||
impl Cipher {
|
||||
/// Looks up the cipher for a certain nid.
|
||||
#[corresponds(EVP_get_cipherbynid)]
|
||||
#[must_use]
|
||||
pub fn from_nid(nid: Nid) -> Option<Cipher> {
|
||||
let ptr = unsafe { ffi::EVP_get_cipherbyname(ffi::OBJ_nid2sn(nid.as_raw())) };
|
||||
let ptr = unsafe { ffi::EVP_get_cipherbynid(nid.as_raw()) };
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
@ -88,82 +153,102 @@ impl Cipher {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn aes_128_ecb() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_aes_128_ecb()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn aes_128_cbc() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_aes_128_cbc()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn aes_128_ctr() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_aes_128_ctr()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn aes_128_gcm() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_aes_128_gcm()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn aes_128_ofb() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_aes_128_ofb()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn aes_192_ecb() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_aes_192_ecb()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn aes_192_cbc() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_aes_192_cbc()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn aes_192_ctr() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_aes_192_ctr()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn aes_192_gcm() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_aes_192_gcm()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn aes_192_ofb() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_aes_192_ofb()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn aes_256_ecb() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_aes_256_ecb()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn aes_256_cbc() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_aes_256_cbc()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn aes_256_ctr() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_aes_256_ctr()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn aes_256_gcm() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_aes_256_gcm()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn aes_256_ofb() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_aes_256_ofb()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn des_cbc() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_des_cbc()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn des_ecb() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_des_ecb()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn des_ede3() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_des_ede3()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn des_ede3_cbc() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_des_ede3_cbc()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn rc4() -> Cipher {
|
||||
unsafe { Cipher(ffi::EVP_rc4()) }
|
||||
}
|
||||
@ -173,17 +258,20 @@ impl Cipher {
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure the pointer is valid for the `'static` lifetime.
|
||||
#[must_use]
|
||||
pub unsafe fn from_ptr(ptr: *const ffi::EVP_CIPHER) -> Cipher {
|
||||
Cipher(ptr)
|
||||
}
|
||||
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
#[must_use]
|
||||
pub fn as_ptr(&self) -> *const ffi::EVP_CIPHER {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the length of keys used with this cipher.
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
#[must_use]
|
||||
pub fn key_len(&self) -> usize {
|
||||
unsafe { EVP_CIPHER_key_length(self.0) as usize }
|
||||
}
|
||||
@ -191,6 +279,7 @@ impl Cipher {
|
||||
/// Returns the length of the IV used with this cipher, or `None` if the
|
||||
/// cipher does not use an IV.
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
#[must_use]
|
||||
pub fn iv_len(&self) -> Option<usize> {
|
||||
unsafe {
|
||||
let len = EVP_CIPHER_iv_length(self.0) as usize;
|
||||
@ -208,9 +297,18 @@ impl Cipher {
|
||||
///
|
||||
/// Stream ciphers such as RC4 have a block size of 1.
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
#[must_use]
|
||||
pub fn block_size(&self) -> usize {
|
||||
unsafe { EVP_CIPHER_block_size(self.0) as usize }
|
||||
}
|
||||
|
||||
/// Returns the cipher's NID.
|
||||
#[corresponds(EVP_CIPHER_nid)]
|
||||
pub fn nid(&self) -> Nid {
|
||||
ffi::init();
|
||||
let nid = unsafe { ffi::EVP_CIPHER_nid(self.as_ptr()) };
|
||||
Nid::from_raw(nid)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Sync for Cipher {}
|
||||
@ -321,34 +419,31 @@ impl Crypter {
|
||||
mode,
|
||||
))?;
|
||||
|
||||
assert!(key.len() <= c_int::MAX as usize);
|
||||
cvt(ffi::EVP_CIPHER_CTX_set_key_length(
|
||||
crypter.ctx,
|
||||
key.len() as c_uint,
|
||||
try_int(key.len())?,
|
||||
))?;
|
||||
|
||||
let key = key.as_ptr() as *mut _;
|
||||
let iv = match (iv, t.iv_len()) {
|
||||
(Some(iv), Some(len)) => {
|
||||
if iv.len() != len {
|
||||
assert!(iv.len() <= c_int::MAX as usize);
|
||||
cvt(ffi::EVP_CIPHER_CTX_ctrl(
|
||||
crypter.ctx,
|
||||
ffi::EVP_CTRL_GCM_SET_IVLEN,
|
||||
iv.len() as c_int,
|
||||
try_int(iv.len())?,
|
||||
ptr::null_mut(),
|
||||
))?;
|
||||
}
|
||||
iv.as_ptr() as *mut _
|
||||
iv.as_ptr().cast_mut()
|
||||
}
|
||||
(Some(_), None) | (None, None) => ptr::null_mut(),
|
||||
(Some(_) | None, None) => ptr::null_mut(),
|
||||
(None, Some(_)) => panic!("an IV is required for this cipher"),
|
||||
};
|
||||
cvt(ffi::EVP_CipherInit_ex(
|
||||
crypter.ctx,
|
||||
ptr::null(),
|
||||
ptr::null_mut(),
|
||||
key,
|
||||
key.as_ptr().cast_mut(),
|
||||
iv,
|
||||
mode,
|
||||
))?;
|
||||
@ -363,7 +458,7 @@ impl Crypter {
|
||||
/// be a multiple of the cipher's block size.
|
||||
pub fn pad(&mut self, padding: bool) {
|
||||
unsafe {
|
||||
ffi::EVP_CIPHER_CTX_set_padding(self.ctx, padding as c_int);
|
||||
ffi::EVP_CIPHER_CTX_set_padding(self.ctx, c_int::from(padding));
|
||||
}
|
||||
}
|
||||
|
||||
@ -372,15 +467,13 @@ impl Crypter {
|
||||
/// When decrypting cipher text using an AEAD cipher, this must be called before `finalize`.
|
||||
pub fn set_tag(&mut self, tag: &[u8]) -> Result<(), ErrorStack> {
|
||||
unsafe {
|
||||
assert!(tag.len() <= c_int::MAX as usize);
|
||||
// NB: this constant is actually more general than just GCM.
|
||||
cvt(ffi::EVP_CIPHER_CTX_ctrl(
|
||||
self.ctx,
|
||||
ffi::EVP_CTRL_GCM_SET_TAG,
|
||||
tag.len() as c_int,
|
||||
tag.as_ptr() as *mut _,
|
||||
try_int(tag.len())?,
|
||||
tag.as_ptr().cast_mut().cast(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -390,15 +483,13 @@ impl Crypter {
|
||||
/// to use a value different than the default 12 bytes.
|
||||
pub fn set_tag_len(&mut self, tag_len: usize) -> Result<(), ErrorStack> {
|
||||
unsafe {
|
||||
assert!(tag_len <= c_int::MAX as usize);
|
||||
// NB: this constant is actually more general than just GCM.
|
||||
cvt(ffi::EVP_CIPHER_CTX_ctrl(
|
||||
self.ctx,
|
||||
ffi::EVP_CTRL_GCM_SET_TAG,
|
||||
tag_len as c_int,
|
||||
try_int(tag_len)?,
|
||||
ptr::null_mut(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -408,16 +499,14 @@ impl Crypter {
|
||||
/// CCM mode.
|
||||
pub fn set_data_len(&mut self, data_len: usize) -> Result<(), ErrorStack> {
|
||||
unsafe {
|
||||
assert!(data_len <= c_int::MAX as usize);
|
||||
let mut len = 0;
|
||||
cvt(ffi::EVP_CipherUpdate(
|
||||
self.ctx,
|
||||
ptr::null_mut(),
|
||||
&mut len,
|
||||
ptr::null_mut(),
|
||||
data_len as c_int,
|
||||
try_int(data_len)?,
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -428,16 +517,14 @@ impl Crypter {
|
||||
/// `update`.
|
||||
pub fn aad_update(&mut self, input: &[u8]) -> Result<(), ErrorStack> {
|
||||
unsafe {
|
||||
assert!(input.len() <= c_int::MAX as usize);
|
||||
let mut len = 0;
|
||||
cvt(ffi::EVP_CipherUpdate(
|
||||
self.ctx,
|
||||
ptr::null_mut(),
|
||||
&mut len,
|
||||
input.as_ptr(),
|
||||
input.len() as c_int,
|
||||
try_int(input.len())?,
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -453,8 +540,6 @@ impl Crypter {
|
||||
///
|
||||
/// Panics for block ciphers if `output.len() < input.len() + block_size`,
|
||||
/// where `block_size` is the block size of the cipher (see `Cipher::block_size`).
|
||||
///
|
||||
/// Panics if `output.len() > c_int::MAX`.
|
||||
pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, ErrorStack> {
|
||||
unsafe {
|
||||
let block_size = if self.block_size > 1 {
|
||||
@ -463,16 +548,14 @@ impl Crypter {
|
||||
0
|
||||
};
|
||||
assert!(output.len() >= input.len() + block_size);
|
||||
assert!(output.len() <= c_int::MAX as usize);
|
||||
let mut outl = output.len() as c_int;
|
||||
let inl = input.len() as c_int;
|
||||
let mut outl = try_int(output.len())?;
|
||||
|
||||
cvt(ffi::EVP_CipherUpdate(
|
||||
self.ctx,
|
||||
output.as_mut_ptr(),
|
||||
&mut outl,
|
||||
input.as_ptr(),
|
||||
inl,
|
||||
try_int(input.len())?,
|
||||
))?;
|
||||
|
||||
Ok(outl as usize)
|
||||
@ -517,14 +600,12 @@ impl Crypter {
|
||||
/// bytes, for example.
|
||||
pub fn get_tag(&self, tag: &mut [u8]) -> Result<(), ErrorStack> {
|
||||
unsafe {
|
||||
assert!(tag.len() <= c_int::MAX as usize);
|
||||
cvt(ffi::EVP_CIPHER_CTX_ctrl(
|
||||
self.ctx,
|
||||
ffi::EVP_CTRL_GCM_GET_TAG,
|
||||
tag.len() as c_int,
|
||||
tag.as_mut_ptr() as *mut _,
|
||||
try_int(tag.len())?,
|
||||
tag.as_mut_ptr().cast(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -959,4 +1040,129 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(pt, hex::encode(out));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nid_roundtrip() {
|
||||
for cipher in [
|
||||
Cipher::aes_128_gcm(),
|
||||
Cipher::aes_192_gcm(),
|
||||
Cipher::aes_256_gcm(),
|
||||
Cipher::aes_128_ecb(),
|
||||
Cipher::aes_128_cbc(),
|
||||
Cipher::aes_128_ctr(),
|
||||
Cipher::aes_128_ofb(),
|
||||
Cipher::aes_192_ecb(),
|
||||
Cipher::aes_192_cbc(),
|
||||
Cipher::aes_192_ctr(),
|
||||
Cipher::aes_192_ofb(),
|
||||
Cipher::aes_256_ecb(),
|
||||
Cipher::aes_256_cbc(),
|
||||
Cipher::aes_256_ctr(),
|
||||
Cipher::aes_256_ofb(),
|
||||
Cipher::des_ecb(),
|
||||
Cipher::des_ede3_cbc(),
|
||||
Cipher::des_cbc(),
|
||||
Cipher::rc4(),
|
||||
] {
|
||||
let name = cipher.nid().short_name().unwrap_or("unknown");
|
||||
assert_eq!(Cipher::from_nid(cipher.nid()), Some(cipher), "{}", name);
|
||||
}
|
||||
|
||||
assert_eq!(Cipher::from_nid(Cipher::des_ede3().nid()), None);
|
||||
}
|
||||
|
||||
// Make sure the NIDs don't actually change upstream.
|
||||
#[test]
|
||||
fn test_nid_regression() {
|
||||
struct TestCase {
|
||||
cipher: Cipher,
|
||||
nid: c_int,
|
||||
}
|
||||
|
||||
for t in [
|
||||
TestCase {
|
||||
cipher: Cipher::aes_128_ecb(),
|
||||
nid: 418,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::aes_128_cbc(),
|
||||
nid: 419,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::aes_128_ctr(),
|
||||
nid: 904,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::aes_128_gcm(),
|
||||
nid: 895,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::aes_128_ofb(),
|
||||
nid: 420,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::aes_192_ecb(),
|
||||
nid: 422,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::aes_192_cbc(),
|
||||
nid: 423,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::aes_192_ctr(),
|
||||
nid: 905,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::aes_192_gcm(),
|
||||
nid: 898,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::aes_192_ofb(),
|
||||
nid: 424,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::aes_256_ecb(),
|
||||
nid: 426,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::aes_256_cbc(),
|
||||
nid: 427,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::aes_256_ctr(),
|
||||
nid: 906,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::aes_256_gcm(),
|
||||
nid: 901,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::aes_256_ofb(),
|
||||
nid: 428,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::des_ecb(),
|
||||
nid: 29,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::des_ede3_cbc(),
|
||||
nid: 44,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::des_cbc(),
|
||||
nid: 31,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::rc4(),
|
||||
nid: 5,
|
||||
},
|
||||
TestCase {
|
||||
cipher: Cipher::des_ede3(),
|
||||
nid: 33,
|
||||
},
|
||||
] {
|
||||
let name = t.cipher.nid().short_name().unwrap_or("unknown");
|
||||
assert_eq!(t.cipher.nid().as_raw(), t.nid, "{}", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,17 +46,17 @@ pub unsafe extern "C" fn invoke_passwd_cb<F>(
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> Result<usize, ErrorStack>,
|
||||
{
|
||||
let callback = &mut *(cb_state as *mut CallbackState<F>);
|
||||
let callback = &mut *cb_state.cast::<CallbackState<F>>();
|
||||
|
||||
let result = panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
let pass_slice = slice::from_raw_parts_mut(buf as *mut u8, size as usize);
|
||||
let pass_slice = slice::from_raw_parts_mut(buf.cast::<u8>(), size as usize);
|
||||
callback.cb.take().unwrap()(pass_slice)
|
||||
}));
|
||||
|
||||
match result {
|
||||
Ok(Ok(len)) => len as c_int,
|
||||
Ok(Err(_)) => {
|
||||
// FIXME restore error stack
|
||||
Ok(Err(err)) => {
|
||||
err.put();
|
||||
0
|
||||
}
|
||||
Err(err) => {
|
||||
@ -80,14 +80,14 @@ impl<FT: ForeignType> ForeignTypeExt for FT {}
|
||||
|
||||
pub trait ForeignTypeRefExt: ForeignTypeRef {
|
||||
unsafe fn from_const_ptr<'a>(ptr: *const Self::CType) -> &'a Self {
|
||||
Self::from_ptr(ptr as *mut Self::CType)
|
||||
Self::from_ptr(ptr.cast_mut())
|
||||
}
|
||||
|
||||
unsafe fn from_const_ptr_opt<'a>(ptr: *const Self::CType) -> Option<&'a Self> {
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(Self::from_const_ptr(ptr as *mut Self::CType))
|
||||
Some(Self::from_const_ptr(ptr.cast_mut()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,11 +42,13 @@ use crate::ffi::{
|
||||
/// Version 0.9.5a had an interim interpretation that is like the current one, except the patch level got the highest bit set, to keep continuity. The number was therefore 0x0090581f
|
||||
///
|
||||
/// The return value of this function can be compared to the macro to make sure that the correct version of the library has been loaded, especially when using DLLs on Windows systems.
|
||||
#[must_use]
|
||||
pub fn number() -> i64 {
|
||||
unsafe { OpenSSL_version_num() as i64 }
|
||||
}
|
||||
|
||||
/// The text variant of the version number and the release date. For example, "OpenSSL 0.9.5a 1 Apr 2000".
|
||||
#[must_use]
|
||||
pub fn version() -> &'static str {
|
||||
unsafe {
|
||||
CStr::from_ptr(OpenSSL_version(OPENSSL_VERSION))
|
||||
@ -57,6 +59,7 @@ pub fn version() -> &'static str {
|
||||
|
||||
/// The compiler flags set for the compilation process in the form "compiler: ..." if available or
|
||||
/// "compiler: information not available" otherwise.
|
||||
#[must_use]
|
||||
pub fn c_flags() -> &'static str {
|
||||
unsafe {
|
||||
CStr::from_ptr(OpenSSL_version(OPENSSL_CFLAGS))
|
||||
@ -66,6 +69,7 @@ pub fn c_flags() -> &'static str {
|
||||
}
|
||||
|
||||
/// The date of the build process in the form "built on: ..." if available or "built on: date not available" otherwise.
|
||||
#[must_use]
|
||||
pub fn built_on() -> &'static str {
|
||||
unsafe {
|
||||
CStr::from_ptr(OpenSSL_version(OPENSSL_BUILT_ON))
|
||||
@ -75,6 +79,7 @@ pub fn built_on() -> &'static str {
|
||||
}
|
||||
|
||||
/// The "Configure" target of the library build in the form "platform: ..." if available or "platform: information not available" otherwise.
|
||||
#[must_use]
|
||||
pub fn platform() -> &'static str {
|
||||
unsafe {
|
||||
CStr::from_ptr(OpenSSL_version(OPENSSL_PLATFORM))
|
||||
@ -84,6 +89,7 @@ pub fn platform() -> &'static str {
|
||||
}
|
||||
|
||||
/// The "OPENSSLDIR" setting of the library build in the form "OPENSSLDIR: "..."" if available or "OPENSSLDIR: N/A" otherwise.
|
||||
#[must_use]
|
||||
pub fn dir() -> &'static str {
|
||||
unsafe {
|
||||
CStr::from_ptr(OpenSSL_version(OPENSSL_DIR))
|
||||
|
||||
509
boring/src/x509/crl.rs
Normal file
509
boring/src/x509/crl.rs
Normal file
@ -0,0 +1,509 @@
|
||||
//! Certificate revocation lists describe certificates that have been revoked
|
||||
//! by their issuer and should no longer be trusted.
|
||||
//!
|
||||
//! An `X509CRL` can be provided along with an issuing `X509` to verify that
|
||||
//! issued certificates have not been revoked.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use boring::hash::MessageDigest;
|
||||
//! use boring::pkey::{PKey, Private};
|
||||
//! use boring::x509::crl::{X509CRLBuilder, X509Revoked};
|
||||
//! use boring::x509::extension::BasicConstraints;
|
||||
//! use boring::x509::verify::X509VerifyFlags;
|
||||
//! use boring::x509::X509Extension;
|
||||
//! use boring::x509::X509;
|
||||
//! use boring::x509::store::{X509Store, X509StoreBuilder};
|
||||
//! use boring::asn1::Asn1Time;
|
||||
//! use boring::bn::BigNum;
|
||||
//! use boring::error::ErrorStack;
|
||||
//!
|
||||
//! fn crl_checking_store(issuer: X509, pkey: PKey<Private>) -> Result<X509Store, ErrorStack> {
|
||||
//! let mut builder = X509CRLBuilder::new()?;
|
||||
//! builder.set_issuer_name(issuer.subject_name())?;
|
||||
//! builder.add_revoked(X509Revoked::from_parts(
|
||||
//! &*BigNum::from_u32(1)?.to_asn1_integer()?,
|
||||
//! &*Asn1Time::days_from_now(0)?
|
||||
//! )?)?;
|
||||
//! builder.set_last_update(&*Asn1Time::days_from_now(0)?)?;
|
||||
//! builder.set_next_update(&*Asn1Time::days_from_now(30)?)?;
|
||||
//! builder.sign(&pkey, MessageDigest::sha256())?;
|
||||
//!
|
||||
//! let mut store_builder = X509StoreBuilder::new()?;
|
||||
//! store_builder.add_cert(issuer)?;
|
||||
//! store_builder.add_crl(builder.build())?;
|
||||
//! store_builder
|
||||
//! .param_mut()
|
||||
//! .set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL);
|
||||
//! Ok(store_builder.build())
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use crate::asn1::{Asn1BitStringRef, Asn1IntegerRef, Asn1TimeRef};
|
||||
use crate::foreign_types::ForeignType;
|
||||
use crate::foreign_types::ForeignTypeRef;
|
||||
use crate::hash::{DigestBytes, MessageDigest};
|
||||
use crate::pkey::{HasPrivate, HasPublic, PKeyRef};
|
||||
use crate::stack::{StackRef, Stackable};
|
||||
use crate::x509::X509ExtensionRef;
|
||||
use crate::x509::{X509AlgorithmRef, X509Extension, X509NameRef};
|
||||
use crate::{cvt, cvt_n, cvt_p, ErrorStack};
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::Formatter;
|
||||
use std::{fmt, mem, ptr};
|
||||
|
||||
foreign_type_and_impl_send_sync! {
|
||||
type CType = ffi::X509_REVOKED;
|
||||
fn drop = ffi::X509_REVOKED_free;
|
||||
|
||||
/// An `X509_REVOKED` containing information about a revoked certificate
|
||||
pub struct X509Revoked;
|
||||
}
|
||||
|
||||
impl Stackable for X509Revoked {
|
||||
type StackType = ffi::stack_st_X509_REVOKED;
|
||||
}
|
||||
|
||||
impl X509Revoked {
|
||||
/// Create an `X509Revoked`
|
||||
///
|
||||
/// This corresponds to [`X509_REVOKED_new`] followed by calls to
|
||||
/// [`X509_REVOKED_set_serialNumber`] and [`X509_REVOKED_set_revocationDate`]
|
||||
/// with the provided parameters
|
||||
///
|
||||
/// [`X509_REVOKED_new`]: https://www.openssl.org/docs/man1.1.1/man3/X509_REVOKED_new.html
|
||||
/// [`X509_REVOKED_set_serialNumber`]: https://www.openssl.org/docs/man1.1.1/man3/X509_REVOKED_set_serialNumber.html
|
||||
/// [`X509_REVOKED_set_revocationDate`]: https://www.openssl.org/docs/man1.1.1/man3/X509_REVOKED_set_revocationDate.html
|
||||
pub fn from_parts(
|
||||
serial_number: &Asn1IntegerRef,
|
||||
revocation_date: &Asn1TimeRef,
|
||||
) -> Result<X509Revoked, ErrorStack> {
|
||||
unsafe {
|
||||
ffi::init();
|
||||
let revoked = cvt_p(ffi::X509_REVOKED_new())?;
|
||||
cvt(ffi::X509_REVOKED_set_serialNumber(
|
||||
revoked,
|
||||
serial_number.as_ptr(),
|
||||
))?;
|
||||
cvt(ffi::X509_REVOKED_set_revocationDate(
|
||||
revoked,
|
||||
revocation_date.as_ptr(),
|
||||
))?;
|
||||
Ok(X509Revoked::from_ptr(revoked))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl X509RevokedRef {
|
||||
/// Returns the serial number of the revoked certificate
|
||||
///
|
||||
/// This corresponds to [`X509_REVOKED_get0_serialNumber`].
|
||||
///
|
||||
/// [`X509_REVOKED_get0_serialNumber`]: https://www.openssl.org/docs/man1.1.1/man3/X509_REVOKED_get0_serialNumber.html
|
||||
pub fn serial_number(&self) -> &Asn1IntegerRef {
|
||||
unsafe {
|
||||
let r = ffi::X509_REVOKED_get0_serialNumber(self.as_ptr());
|
||||
assert!(!r.is_null());
|
||||
Asn1IntegerRef::from_ptr(r as *mut _)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns certificate's revocation date
|
||||
///
|
||||
/// This corresponds to [`X509_REVOKED_get0_revocationDate`].
|
||||
///
|
||||
/// [`X509_REVOKED_get0_revocationDate`]: https://www.openssl.org/docs/man1.1.1/man3/X509_REVOKED_get0_revocationDate
|
||||
pub fn revocation_date(&self) -> &Asn1TimeRef {
|
||||
unsafe {
|
||||
let date = ffi::X509_REVOKED_get0_revocationDate(self.as_ptr());
|
||||
assert!(!date.is_null());
|
||||
Asn1TimeRef::from_ptr(date as *mut _)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for X509RevokedRef {
|
||||
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||
let sn = self.serial_number().to_bn().and_then(|bn| bn.to_hex_str());
|
||||
let sn = sn.as_ref().map(|x| &***x).unwrap_or("");
|
||||
|
||||
fmt.debug_struct("X509Revoked")
|
||||
.field("serial_number", &sn)
|
||||
.field("revocation_date", self.revocation_date())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
foreign_type_and_impl_send_sync! {
|
||||
type CType = ffi::X509_CRL;
|
||||
fn drop = ffi::X509_CRL_free;
|
||||
|
||||
/// An `X509CRL` certificate revocation list
|
||||
pub struct X509CRL;
|
||||
}
|
||||
|
||||
impl Stackable for X509CRL {
|
||||
type StackType = ffi::stack_st_X509_CRL;
|
||||
}
|
||||
|
||||
impl X509CRL {
|
||||
from_pem! {
|
||||
/// Deserializes a PEM-encoded X509CRL structure.
|
||||
///
|
||||
/// The input should have a header of `-----BEGIN X509 CRL-----`
|
||||
///
|
||||
/// This corresponds to [`PEM_read_bio_X509_CRL`].
|
||||
///
|
||||
/// [`PEM_read_bio_X509_CRL`]: https://www.openssl.org/docs/man1.1.1/man3/PEM_read_bio_X509_CRL
|
||||
from_pem,
|
||||
X509CRL,
|
||||
ffi::PEM_read_bio_X509_CRL
|
||||
}
|
||||
|
||||
from_der! {
|
||||
/// Deserializes a DER-encoded X509 structure.
|
||||
///
|
||||
/// This corresponds to [`d2i_X509_CRL`].
|
||||
///
|
||||
/// [`d2i_X509_CRL`]: https://www.openssl.org/docs/man1.1.1/man3/d2i_X509_CRL.html
|
||||
from_der,
|
||||
X509CRL,
|
||||
ffi::d2i_X509_CRL,
|
||||
::libc::c_long
|
||||
}
|
||||
}
|
||||
|
||||
impl X509CRLRef {
|
||||
/// Returns the CRL's last update time
|
||||
///
|
||||
/// This corresponds to [`X509_CRL_get0_lastUpdate`]
|
||||
///
|
||||
/// [`X509_CRL_get0_lastUpdate`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_get0_lastUpdate
|
||||
pub fn last_update(&self) -> Option<&Asn1TimeRef> {
|
||||
unsafe {
|
||||
let date = ffi::X509_CRL_get0_lastUpdate(self.as_ptr());
|
||||
if date.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(Asn1TimeRef::from_ptr(date as *mut _))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the CRL's next update time
|
||||
///
|
||||
/// This corresponds to [`X509_CRL_get0_nextUpdate`]
|
||||
///
|
||||
/// [`X509_CRL_get0_nextUpdate`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_get0_nextUpdate
|
||||
pub fn next_update(&self) -> Option<&Asn1TimeRef> {
|
||||
unsafe {
|
||||
let date = ffi::X509_CRL_get0_nextUpdate(self.as_ptr());
|
||||
if date.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(Asn1TimeRef::from_ptr(date as *mut _))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the CRL's issuer name
|
||||
///
|
||||
/// This corresponds to [`X509_CRL_get_issuer`]
|
||||
///
|
||||
/// [`X509_CRL_get_issuer`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_get_issuer
|
||||
pub fn issuer(&self) -> &X509NameRef {
|
||||
unsafe {
|
||||
let name = ffi::X509_CRL_get_issuer(self.as_ptr());
|
||||
|
||||
assert!(!name.is_null());
|
||||
X509NameRef::from_ptr(name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the CRL's extensions
|
||||
///
|
||||
/// This corresponds to [`X509_CRL_get0_extensions`]
|
||||
///
|
||||
/// [`X509_CRL_get0_extensions`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_get0_extensions
|
||||
pub fn extensions(&self) -> Option<&StackRef<X509Extension>> {
|
||||
unsafe {
|
||||
let extensions = ffi::X509_CRL_get0_extensions(self.as_ptr());
|
||||
if extensions.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(StackRef::from_ptr(extensions as *mut _))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the revoked certificates in this CRL
|
||||
///
|
||||
/// This corresponds to [`X509_CRL_get_REVOKED`]
|
||||
///
|
||||
/// [`X509_CRL_get_REVOKED`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_get_REVOKED
|
||||
pub fn revoked(&self) -> Option<&StackRef<X509Revoked>> {
|
||||
unsafe {
|
||||
let revoked = ffi::X509_CRL_get_REVOKED(self.as_ptr());
|
||||
if revoked.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(StackRef::from_ptr(revoked))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the CRL's signature and signature algorithm
|
||||
///
|
||||
/// This corresponds to [`X509_CRL_get0_signature`]
|
||||
///
|
||||
/// [`X509_CRL_get0_signature`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_get0_signature
|
||||
pub fn signature(&self) -> (&Asn1BitStringRef, &X509AlgorithmRef) {
|
||||
unsafe {
|
||||
let mut signature = ptr::null();
|
||||
let mut algor = ptr::null();
|
||||
ffi::X509_CRL_get0_signature(self.as_ptr(), &mut signature, &mut algor);
|
||||
assert!(!algor.is_null());
|
||||
assert!(!signature.is_null());
|
||||
(
|
||||
Asn1BitStringRef::from_ptr(signature as *mut _),
|
||||
X509AlgorithmRef::from_ptr(algor as *mut _),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a digest of the DER representation of the CRL
|
||||
///
|
||||
/// This corresponds to [`X509_CRL_digest`]
|
||||
///
|
||||
/// [`X509_CRL_digest`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_digest
|
||||
pub fn digest(&self, hash_type: MessageDigest) -> Result<DigestBytes, ErrorStack> {
|
||||
unsafe {
|
||||
let mut digest = DigestBytes {
|
||||
buf: [0; ffi::EVP_MAX_MD_SIZE as usize],
|
||||
len: ffi::EVP_MAX_MD_SIZE as usize,
|
||||
};
|
||||
let mut len = ffi::EVP_MAX_MD_SIZE.try_into().unwrap();
|
||||
cvt(ffi::X509_CRL_digest(
|
||||
self.as_ptr(),
|
||||
hash_type.as_ptr(),
|
||||
digest.buf.as_mut_ptr() as *mut _,
|
||||
&mut len,
|
||||
))?;
|
||||
digest.len = len as usize;
|
||||
|
||||
Ok(digest)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the CRL is signed using the given public key.
|
||||
///
|
||||
/// Only the signature is checked: no other checks (such as certificate chain validity)
|
||||
/// are performed.
|
||||
///
|
||||
/// Returns `true` if verification succeeds.
|
||||
///
|
||||
/// This corresponds to [`X509_CRL_verify"].
|
||||
///
|
||||
/// [`X509_CRL_verify`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_verify
|
||||
pub fn verify<T>(&self, key: &PKeyRef<T>) -> Result<bool, ErrorStack>
|
||||
where
|
||||
T: HasPublic,
|
||||
{
|
||||
unsafe { cvt_n(ffi::X509_CRL_verify(self.as_ptr(), key.as_ptr())).map(|n| n != 0) }
|
||||
}
|
||||
|
||||
to_pem! {
|
||||
/// Serializes the CRL into a PEM-encoded X509 CRL structure.
|
||||
///
|
||||
/// The output will have a header of `-----BEGIN X509 CRL-----`
|
||||
///
|
||||
/// This corresponds to [`PEM_write_bio_X509_CRL`]
|
||||
///
|
||||
/// [`PEM_write_bio_X509_CRL`]: https://www.openssl.org/docs/man1.1.1/man3/PEM_write_bio_X509_CRL
|
||||
to_pem,
|
||||
ffi::PEM_write_bio_X509_CRL
|
||||
}
|
||||
|
||||
to_der! {
|
||||
/// Serializes the CRL into a DER-encoded X509 CRL structure
|
||||
///
|
||||
/// This corresponds to `i2d_X509_CRL`
|
||||
///
|
||||
/// [`i2d_X509_CRL`]: https://www.openssl.org/docs/man1.1.1/man3/i2d_X509_CRL
|
||||
to_der,
|
||||
ffi::i2d_X509_CRL
|
||||
}
|
||||
}
|
||||
impl ToOwned for X509CRLRef {
|
||||
type Owned = X509CRL;
|
||||
|
||||
fn to_owned(&self) -> X509CRL {
|
||||
unsafe {
|
||||
ffi::X509_CRL_up_ref(self.as_ptr());
|
||||
X509CRL::from_ptr(self.as_ptr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for X509CRL {
|
||||
fn clone(&self) -> X509CRL {
|
||||
X509CRLRef::to_owned(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for X509CRLRef {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut debug_struct = formatter.debug_struct("X509CRL");
|
||||
debug_struct.field("issuer", self.issuer());
|
||||
debug_struct.field("signature_algorithm", &self.signature().1.object());
|
||||
|
||||
if let Some(next_update) = self.next_update() {
|
||||
debug_struct.field("next_update", next_update);
|
||||
}
|
||||
if let Some(last_update) = self.last_update() {
|
||||
debug_struct.field("last_update", last_update);
|
||||
}
|
||||
if let Some(revoked) = self.revoked() {
|
||||
debug_struct.field("revoked", &revoked);
|
||||
}
|
||||
if let Some(extensions) = self.extensions() {
|
||||
debug_struct.field("extensions", &extensions);
|
||||
}
|
||||
debug_struct.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for X509CRL {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
let x: &X509CRLRef = self;
|
||||
x.fmt(formatter)
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder used to construct an `X509CRL`
|
||||
pub struct X509CRLBuilder(X509CRL);
|
||||
|
||||
impl X509CRLBuilder {
|
||||
/// Creates a new builder.
|
||||
pub fn new() -> Result<X509CRLBuilder, ErrorStack> {
|
||||
unsafe {
|
||||
ffi::init();
|
||||
cvt_p(ffi::X509_CRL_new()).map(|p| X509CRLBuilder(X509CRL::from_ptr(p)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Append an `X509Extension` to the certificate revocation list
|
||||
///
|
||||
/// This corresponds to [`X509_CRL_add_ext`]
|
||||
///
|
||||
/// [`X509_CRL_add_ext`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_add_ext
|
||||
pub fn append_extension(&mut self, extension: &X509ExtensionRef) -> Result<(), ErrorStack> {
|
||||
unsafe {
|
||||
// -1 indicates append to end
|
||||
cvt(ffi::X509_CRL_add_ext(
|
||||
self.0.as_ptr(),
|
||||
extension.as_ptr(),
|
||||
-1,
|
||||
))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Signs the certificate revocation list with a private key.
|
||||
///
|
||||
/// This corresponds to [`X509_CRL_sign`]
|
||||
///
|
||||
/// [`X509_CRL_sign`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_sign
|
||||
pub fn sign<T>(&mut self, key: &PKeyRef<T>, hash: MessageDigest) -> Result<(), ErrorStack>
|
||||
where
|
||||
T: HasPrivate,
|
||||
{
|
||||
unsafe {
|
||||
cvt(ffi::X509_CRL_sign(
|
||||
self.0.as_ptr(),
|
||||
key.as_ptr(),
|
||||
hash.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a revoked certificate to the certificate revocation list
|
||||
///
|
||||
/// This corresponds to [`X509_CRL_add0_revoked`]
|
||||
///
|
||||
/// [`X509_CRL_add0_revoked`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_add0_revoked
|
||||
pub fn add_revoked(&mut self, revoked: X509Revoked) -> Result<(), ErrorStack> {
|
||||
unsafe {
|
||||
cvt(ffi::X509_CRL_add0_revoked(
|
||||
self.0.as_ptr(),
|
||||
revoked.as_ptr(),
|
||||
))?;
|
||||
mem::forget(revoked);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the issuer name of the certificate revocation list.
|
||||
///
|
||||
/// This corresponds to [`X509_CRL_set_issuer_name`]
|
||||
///
|
||||
/// [`X509_CRL_set_issuer_name`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_set_issuer_name
|
||||
pub fn set_issuer_name(&mut self, issuer_name: &X509NameRef) -> Result<(), ErrorStack> {
|
||||
unsafe {
|
||||
cvt(ffi::X509_CRL_set_issuer_name(
|
||||
self.0.as_ptr(),
|
||||
issuer_name.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the version of the certificate revocation list.
|
||||
///
|
||||
/// Note that the version is zero-indexed; that is, a certificate corresponding to version 3 of
|
||||
/// the X.509 standard should pass `2` to this method.
|
||||
///
|
||||
/// This corresponds to [`X509_CRL_set_version`]
|
||||
///
|
||||
/// [`X509_CRL_set_version`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_set_version
|
||||
pub fn set_version(&mut self, version: i32) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::X509_CRL_set_version(self.0.as_ptr(), version.into())).map(|_| ()) }
|
||||
}
|
||||
|
||||
/// Sets the last update time on the certificate revocation list.
|
||||
///
|
||||
/// This corresponds to [`X509_CRL_set1_lastUpdate`]
|
||||
///
|
||||
/// [`X509_CRL_set1_lastUpdate`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_set1_lastUpdate
|
||||
pub fn set_last_update(&mut self, last_update: &Asn1TimeRef) -> Result<(), ErrorStack> {
|
||||
unsafe {
|
||||
cvt(ffi::X509_CRL_set1_lastUpdate(
|
||||
self.0.as_ptr(),
|
||||
last_update.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the next update time on the certificate revocation list.
|
||||
///
|
||||
/// This corresponds to [`X509_CRL_set1_nextUpdate`]
|
||||
///
|
||||
/// [`X509_CRL_set1_nextUpdate`]: https://www.openssl.org/docs/man1.1.1/man3/X509_CRL_set1_nextUpdate
|
||||
pub fn set_next_update(&mut self, next_update: &Asn1TimeRef) -> Result<(), ErrorStack> {
|
||||
unsafe {
|
||||
cvt(ffi::X509_CRL_set1_nextUpdate(
|
||||
self.0.as_ptr(),
|
||||
next_update.as_ptr(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes the builder, returning the certificate revocation list
|
||||
pub fn build(self) -> X509CRL {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
@ -38,6 +38,7 @@ impl Default for BasicConstraints {
|
||||
|
||||
impl BasicConstraints {
|
||||
/// Construct a new `BasicConstraints` extension.
|
||||
#[must_use]
|
||||
pub fn new() -> BasicConstraints {
|
||||
BasicConstraints {
|
||||
critical: false,
|
||||
@ -78,7 +79,7 @@ impl BasicConstraints {
|
||||
value.push_str("FALSE");
|
||||
}
|
||||
if let Some(pathlen) = self.pathlen {
|
||||
write!(value, ",pathlen:{}", pathlen).unwrap();
|
||||
write!(value, ",pathlen:{pathlen}").unwrap();
|
||||
}
|
||||
X509Extension::new_nid(None, None, Nid::BASIC_CONSTRAINTS, &value)
|
||||
}
|
||||
@ -106,6 +107,7 @@ impl Default for KeyUsage {
|
||||
|
||||
impl KeyUsage {
|
||||
/// Construct a new `KeyUsage` extension.
|
||||
#[must_use]
|
||||
pub fn new() -> KeyUsage {
|
||||
KeyUsage {
|
||||
critical: false,
|
||||
@ -234,6 +236,7 @@ impl Default for ExtendedKeyUsage {
|
||||
|
||||
impl ExtendedKeyUsage {
|
||||
/// Construct a new `ExtendedKeyUsage` extension.
|
||||
#[must_use]
|
||||
pub fn new() -> ExtendedKeyUsage {
|
||||
ExtendedKeyUsage {
|
||||
critical: false,
|
||||
@ -329,6 +332,7 @@ impl Default for SubjectKeyIdentifier {
|
||||
|
||||
impl SubjectKeyIdentifier {
|
||||
/// Construct a new `SubjectKeyIdentifier` extension.
|
||||
#[must_use]
|
||||
pub fn new() -> SubjectKeyIdentifier {
|
||||
SubjectKeyIdentifier { critical: false }
|
||||
}
|
||||
@ -365,6 +369,7 @@ impl Default for AuthorityKeyIdentifier {
|
||||
|
||||
impl AuthorityKeyIdentifier {
|
||||
/// Construct a new `AuthorityKeyIdentifier` extension.
|
||||
#[must_use]
|
||||
pub fn new() -> AuthorityKeyIdentifier {
|
||||
AuthorityKeyIdentifier {
|
||||
critical: false,
|
||||
@ -433,6 +438,7 @@ impl Default for SubjectAlternativeName {
|
||||
|
||||
impl SubjectAlternativeName {
|
||||
/// Construct a new `SubjectAlternativeName` extension.
|
||||
#[must_use]
|
||||
pub fn new() -> SubjectAlternativeName {
|
||||
SubjectAlternativeName {
|
||||
critical: false,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -36,19 +36,20 @@
|
||||
//!
|
||||
//! let certificate: X509 = builder.build();
|
||||
//! let mut builder = X509StoreBuilder::new().unwrap();
|
||||
//! let _ = builder.add_cert(certificate);
|
||||
//! let _ = builder.add_cert(&certificate);
|
||||
//! let store: X509Store = builder.build();
|
||||
//! ```
|
||||
|
||||
use crate::error::ErrorStack;
|
||||
use crate::ffi;
|
||||
use crate::stack::StackRef;
|
||||
use crate::x509::crl::X509CRL;
|
||||
use crate::x509::verify::{X509VerifyFlags, X509VerifyParamRef};
|
||||
use crate::x509::{X509Object, X509};
|
||||
use crate::x509::{X509Object, X509Ref};
|
||||
use crate::{cvt, cvt_p};
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use openssl_macros::corresponds;
|
||||
use std::mem;
|
||||
use std::mem::ManuallyDrop;
|
||||
|
||||
foreign_type_and_impl_send_sync! {
|
||||
type CType = ffi::X509_STORE;
|
||||
@ -71,19 +72,36 @@ impl X509StoreBuilder {
|
||||
}
|
||||
|
||||
/// Constructs the `X509Store`.
|
||||
#[must_use]
|
||||
pub fn build(self) -> X509Store {
|
||||
let store = X509Store(self.0);
|
||||
mem::forget(self);
|
||||
store
|
||||
X509Store(ManuallyDrop::new(self).0)
|
||||
}
|
||||
}
|
||||
|
||||
impl X509StoreBuilderRef {
|
||||
/// Adds a certificate to the certificate store.
|
||||
// FIXME should take an &X509Ref
|
||||
#[corresponds(X509_STORE_add_cert)]
|
||||
pub fn add_cert(&mut self, cert: X509) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::X509_STORE_add_cert(self.as_ptr(), cert.as_ptr())).map(|_| ()) }
|
||||
pub fn add_cert(&mut self, cert: impl AsRef<X509Ref>) -> Result<(), ErrorStack> {
|
||||
let cert = cert.as_ref();
|
||||
unsafe { cvt(ffi::X509_STORE_add_cert(self.as_ptr(), cert.as_ptr())) }
|
||||
}
|
||||
|
||||
/// Adds a CRL to the certificate store.
|
||||
///
|
||||
/// This corresponds to [`X509_STORE_add_crl`].
|
||||
///
|
||||
/// [`X509_STORE_add_crl`]: https://www.openssl.org/docs/man1.1.1/man3/X509_STORE_add_crl
|
||||
pub fn add_crl(&mut self, crl: X509CRL) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::X509_STORE_add_crl(self.as_ptr(), crl.as_ptr())).map(|_| ()) }
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the X509 verification configuration.
|
||||
///
|
||||
/// This corresponds to [`X509_STORE_get0_param`].
|
||||
///
|
||||
/// [`X509_STORE_add_crl`]: https://www.openssl.org/docs/man1.1.1/man3/X509_STORE_get0_param
|
||||
pub fn param_mut(&mut self) -> &mut X509VerifyParamRef {
|
||||
unsafe { X509VerifyParamRef::from_ptr_mut(ffi::X509_STORE_get0_param(self.as_ptr())) }
|
||||
}
|
||||
|
||||
/// Load certificates from their default locations.
|
||||
@ -93,7 +111,7 @@ impl X509StoreBuilderRef {
|
||||
/// build time otherwise.
|
||||
#[corresponds(X509_STORE_set_default_paths)]
|
||||
pub fn set_default_paths(&mut self) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::X509_STORE_set_default_paths(self.as_ptr())).map(|_| ()) }
|
||||
unsafe { cvt(ffi::X509_STORE_set_default_paths(self.as_ptr())) }
|
||||
}
|
||||
|
||||
/// Sets certificate chain validation related flags.
|
||||
@ -113,7 +131,15 @@ impl X509StoreBuilderRef {
|
||||
/// Sets certificate chain validation related parameters.
|
||||
#[corresponds(X509_STORE_set1_param)]
|
||||
pub fn set_param(&mut self, param: &X509VerifyParamRef) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::X509_STORE_set1_param(self.as_ptr(), param.as_ptr())).map(|_| ()) }
|
||||
unsafe { cvt(ffi::X509_STORE_set1_param(self.as_ptr(), param.as_ptr())) }
|
||||
}
|
||||
|
||||
/// For testing only
|
||||
#[cfg(test)]
|
||||
pub fn objects_len(&self) -> usize {
|
||||
unsafe {
|
||||
StackRef::<X509Object>::from_ptr(ffi::X509_STORE_get0_objects(self.as_ptr())).len()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,6 +151,23 @@ foreign_type_and_impl_send_sync! {
|
||||
pub struct X509Store;
|
||||
}
|
||||
|
||||
impl ToOwned for X509StoreRef {
|
||||
type Owned = X509Store;
|
||||
|
||||
fn to_owned(&self) -> X509Store {
|
||||
unsafe {
|
||||
ffi::X509_STORE_up_ref(self.as_ptr());
|
||||
X509Store::from_ptr(self.as_ptr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for X509Store {
|
||||
fn clone(&self) -> X509Store {
|
||||
(**self).to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl X509StoreRef {
|
||||
/// **Warning: this method is unsound**
|
||||
///
|
||||
@ -136,6 +179,7 @@ impl X509StoreRef {
|
||||
note = "This method is unsound https://github.com/sfackler/rust-openssl/issues/2096"
|
||||
)]
|
||||
#[corresponds(X509_STORE_get0_objects)]
|
||||
#[must_use]
|
||||
pub fn objects(&self) -> &StackRef<X509Object> {
|
||||
unsafe { StackRef::from_ptr(ffi::X509_STORE_get0_objects(self.as_ptr())) }
|
||||
}
|
||||
@ -143,7 +187,23 @@ impl X509StoreRef {
|
||||
/// For testing only, where it doesn't have to expose an unsafe pointer
|
||||
#[cfg(test)]
|
||||
#[allow(deprecated)]
|
||||
#[must_use]
|
||||
pub fn objects_len(&self) -> usize {
|
||||
self.objects().len()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::redundant_clone)]
|
||||
#[should_panic = "Shared X509Store can't be mutated"]
|
||||
fn set_cert_store_pevents_mutability() {
|
||||
use crate::ssl::*;
|
||||
|
||||
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
|
||||
let store = X509StoreBuilder::new().unwrap().build();
|
||||
|
||||
ctx.set_cert_store(store.clone());
|
||||
|
||||
// This is bad.
|
||||
let _aliased_store = ctx.cert_store_mut();
|
||||
}
|
||||
|
||||
@ -6,13 +6,15 @@ use crate::hash::MessageDigest;
|
||||
use crate::nid::Nid;
|
||||
use crate::pkey::{PKey, Private};
|
||||
use crate::rsa::Rsa;
|
||||
use crate::stack::Stack;
|
||||
use crate::stack::{Stack, Stackable};
|
||||
use crate::x509::crl::{X509CRLBuilder, X509Revoked, X509CRL};
|
||||
use crate::x509::extension::{
|
||||
AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage, SubjectAlternativeName,
|
||||
SubjectKeyIdentifier,
|
||||
};
|
||||
use crate::x509::store::X509StoreBuilder;
|
||||
use crate::x509::{X509Extension, X509Name, X509Req, X509StoreContext, X509};
|
||||
use crate::x509::verify::X509VerifyFlags;
|
||||
use crate::x509::{X509Extension, X509Name, X509Req, X509StoreContext, X509VerifyError, X509};
|
||||
|
||||
mod trusted_first;
|
||||
|
||||
@ -21,6 +23,15 @@ fn pkey() -> PKey<Private> {
|
||||
PKey::from_rsa(rsa).unwrap()
|
||||
}
|
||||
|
||||
fn stack_of<T>(item: T) -> Stack<T>
|
||||
where
|
||||
T: Stackable,
|
||||
{
|
||||
let mut stack = Stack::new().expect("unable to initialize stack");
|
||||
stack.push(item).expect("failed to add to stack");
|
||||
stack
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cert_loading() {
|
||||
let cert = include_bytes!("../../../test/cert.pem");
|
||||
@ -37,7 +48,7 @@ fn test_cert_loading() {
|
||||
fn test_debug() {
|
||||
let cert = include_bytes!("../../../test/cert.pem");
|
||||
let cert = X509::from_pem(cert).unwrap();
|
||||
let debugged = format!("{:#?}", cert);
|
||||
let debugged = format!("{cert:#?}");
|
||||
|
||||
assert!(debugged.contains(r#"serial_number: "8771f7bdee982fa5""#));
|
||||
assert!(debugged.contains(r#"signature_algorithm: sha256WithRSAEncryption"#));
|
||||
@ -73,7 +84,7 @@ fn test_subject_read_cn() {
|
||||
let cert = X509::from_pem(cert).unwrap();
|
||||
let subject = cert.subject_name();
|
||||
let cn = subject.entries_by_nid(Nid::COMMONNAME).next().unwrap();
|
||||
assert_eq!(cn.data().as_slice(), b"foobar.com")
|
||||
assert_eq!(cn.data().as_slice(), b"foobar.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -250,34 +261,36 @@ fn x509_builder() {
|
||||
.unwrap();
|
||||
|
||||
let basic_constraints = BasicConstraints::new().critical().ca().build().unwrap();
|
||||
builder.append_extension(basic_constraints).unwrap();
|
||||
builder
|
||||
.append_extension(basic_constraints.as_ref())
|
||||
.unwrap();
|
||||
let key_usage = KeyUsage::new()
|
||||
.digital_signature()
|
||||
.key_encipherment()
|
||||
.build()
|
||||
.unwrap();
|
||||
builder.append_extension(key_usage).unwrap();
|
||||
builder.append_extension(&key_usage).unwrap();
|
||||
let ext_key_usage = ExtendedKeyUsage::new()
|
||||
.client_auth()
|
||||
.server_auth()
|
||||
.other("2.999.1")
|
||||
.build()
|
||||
.unwrap();
|
||||
builder.append_extension(ext_key_usage).unwrap();
|
||||
builder.append_extension(&ext_key_usage).unwrap();
|
||||
let subject_key_identifier = SubjectKeyIdentifier::new()
|
||||
.build(&builder.x509v3_context(None, None))
|
||||
.unwrap();
|
||||
builder.append_extension(subject_key_identifier).unwrap();
|
||||
builder.append_extension(&subject_key_identifier).unwrap();
|
||||
let authority_key_identifier = AuthorityKeyIdentifier::new()
|
||||
.keyid(true)
|
||||
.build(&builder.x509v3_context(None, None))
|
||||
.unwrap();
|
||||
builder.append_extension(authority_key_identifier).unwrap();
|
||||
builder.append_extension(&authority_key_identifier).unwrap();
|
||||
let subject_alternative_name = SubjectAlternativeName::new()
|
||||
.dns("example.com")
|
||||
.build(&builder.x509v3_context(None, None))
|
||||
.unwrap();
|
||||
builder.append_extension(subject_alternative_name).unwrap();
|
||||
builder.append_extension(&subject_alternative_name).unwrap();
|
||||
|
||||
builder.sign(&pkey, MessageDigest::sha256()).unwrap();
|
||||
|
||||
@ -285,6 +298,7 @@ fn x509_builder() {
|
||||
|
||||
assert!(pkey.public_eq(&x509.public_key().unwrap()));
|
||||
assert!(x509.verify(&pkey).unwrap());
|
||||
assert_eq!(x509.extensions().unwrap().len(), 6);
|
||||
|
||||
let cn = x509
|
||||
.subject_name()
|
||||
@ -295,6 +309,48 @@ fn x509_builder() {
|
||||
assert_eq!(serial, x509.serial_number().to_bn().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn x509_crl_builder() {
|
||||
let mut builder = X509CRLBuilder::new().unwrap();
|
||||
|
||||
let mut name = X509Name::builder().unwrap();
|
||||
name.append_entry_by_nid(Nid::COMMONNAME, "foobar.com")
|
||||
.unwrap();
|
||||
let name = name.build();
|
||||
builder.set_issuer_name(&name).unwrap();
|
||||
|
||||
let mut serial = BigNum::new().unwrap();
|
||||
serial.rand(128, MsbOption::MAYBE_ZERO, false).unwrap();
|
||||
let serial_asn = serial.to_asn1_integer().unwrap();
|
||||
let revoked =
|
||||
X509Revoked::from_parts(&serial_asn, &Asn1Time::days_from_now(0).unwrap()).unwrap();
|
||||
builder.add_revoked(revoked).unwrap();
|
||||
|
||||
builder
|
||||
.set_last_update(&Asn1Time::days_from_now(0).unwrap())
|
||||
.unwrap();
|
||||
builder
|
||||
.set_next_update(&Asn1Time::days_from_now(30).unwrap())
|
||||
.unwrap();
|
||||
|
||||
let pkey = pkey();
|
||||
builder.sign(&pkey, MessageDigest::sha256()).unwrap();
|
||||
|
||||
let crl = builder.build();
|
||||
|
||||
let cn = crl.issuer().entries_by_nid(Nid::COMMONNAME).next().unwrap();
|
||||
assert_eq!(cn.data().as_slice(), b"foobar.com");
|
||||
let revoked_sn = crl
|
||||
.revoked()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
.serial_number();
|
||||
assert_eq!(serial, revoked_sn.to_bn().unwrap());
|
||||
assert!(crl.verify(&pkey).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn x509_extension_new() {
|
||||
assert!(X509Extension::new(None, None, "crlDistributionPoints", "section").is_err());
|
||||
@ -449,16 +505,28 @@ fn test_verify_cert() {
|
||||
let chain = Stack::new().unwrap();
|
||||
|
||||
let mut store_bldr = X509StoreBuilder::new().unwrap();
|
||||
store_bldr.add_cert(ca).unwrap();
|
||||
store_bldr.add_cert(&ca).unwrap();
|
||||
let store = store_bldr.build();
|
||||
let empty_store = X509StoreBuilder::new().unwrap().build();
|
||||
|
||||
let mut context = X509StoreContext::new().unwrap();
|
||||
assert!(context
|
||||
.init(&store, &cert, &chain, |c| c.verify_cert())
|
||||
.unwrap());
|
||||
assert!(!context
|
||||
.init(&empty_store, &cert, &chain, |c| c.verify_cert())
|
||||
.unwrap());
|
||||
assert!(context
|
||||
.init(&store, &cert, &chain, |c| c.verify_cert())
|
||||
.unwrap());
|
||||
|
||||
context
|
||||
.reset_with_context_data(empty_store, cert.clone(), Stack::new().unwrap())
|
||||
.unwrap();
|
||||
assert!(!context.verify_cert().unwrap());
|
||||
|
||||
context.reset_with_context_data(store, cert, chain).unwrap();
|
||||
assert!(context.verify_cert().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -470,7 +538,7 @@ fn test_verify_fails() {
|
||||
let chain = Stack::new().unwrap();
|
||||
|
||||
let mut store_bldr = X509StoreBuilder::new().unwrap();
|
||||
store_bldr.add_cert(ca).unwrap();
|
||||
store_bldr.add_cert(&ca).unwrap();
|
||||
let store = store_bldr.build();
|
||||
|
||||
let mut context = X509StoreContext::new().unwrap();
|
||||
@ -479,13 +547,285 @@ fn test_verify_fails() {
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_revoked() {
|
||||
let cert = include_bytes!("../../../test/cert.pem");
|
||||
let cert = X509::from_pem(cert).unwrap();
|
||||
let ca = include_bytes!("../../../test/root-ca.pem");
|
||||
let ca = X509::from_pem(ca).unwrap();
|
||||
let crl = include_bytes!("../../../test/crl.pem");
|
||||
let crl = X509CRL::from_pem(crl).unwrap();
|
||||
let chain = Stack::new().unwrap();
|
||||
|
||||
let mut store_bldr = X509StoreBuilder::new().unwrap();
|
||||
store_bldr.add_cert(&ca).unwrap();
|
||||
store_bldr.add_crl(crl).unwrap();
|
||||
store_bldr
|
||||
.param_mut()
|
||||
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL);
|
||||
let store = store_bldr.build();
|
||||
|
||||
let mut context = X509StoreContext::new().unwrap();
|
||||
assert!(!context
|
||||
.init(&store, &cert, &chain, |c| c.verify_cert())
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_crl_signature() {
|
||||
let ca = include_bytes!("../../../test/root-ca.pem");
|
||||
let ca = X509::from_pem(ca).unwrap();
|
||||
|
||||
let crl = include_bytes!("../../../test/bad_sig.pem");
|
||||
let crl = X509CRL::from_pem(crl).unwrap();
|
||||
assert!(!crl.verify(&ca.public_key().unwrap()).unwrap());
|
||||
|
||||
let crl = include_bytes!("../../../test/crl.pem");
|
||||
let crl = X509CRL::from_pem(crl).unwrap();
|
||||
assert!(crl.verify(&ca.public_key().unwrap()).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_untrusted_valid_crl() {
|
||||
let cert = include_bytes!("../../../test/cert.pem");
|
||||
let cert = X509::from_pem(cert).unwrap();
|
||||
let ca = include_bytes!("../../../test/root-ca.pem");
|
||||
let ca = X509::from_pem(ca).unwrap();
|
||||
let chain = Stack::new().unwrap();
|
||||
|
||||
let mut store_bldr = X509StoreBuilder::new().unwrap();
|
||||
store_bldr.add_cert(&ca).unwrap();
|
||||
store_bldr
|
||||
.param_mut()
|
||||
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL);
|
||||
store_bldr.param_mut().set_time(1656633600); // 2022-07-01, everything is valid
|
||||
let store = store_bldr.build();
|
||||
|
||||
// cert is not revoked
|
||||
let crl = include_bytes!("../../../test/empty_crl.pem");
|
||||
let crl = X509CRL::from_pem(crl).unwrap();
|
||||
let mut context = X509StoreContext::new().unwrap();
|
||||
assert!(context
|
||||
.init(&store, &cert, &chain, |c| c
|
||||
.verify_cert_with_crls(stack_of(crl)))
|
||||
.unwrap());
|
||||
|
||||
// cert is revoked
|
||||
let crl = include_bytes!("../../../test/crl.pem");
|
||||
let crl = X509CRL::from_pem(crl).unwrap();
|
||||
let mut context = X509StoreContext::new().unwrap();
|
||||
context
|
||||
.init(&store, &cert, &chain, |c| {
|
||||
assert!(!c.verify_cert_with_crls(stack_of(crl)).unwrap());
|
||||
assert_eq!(c.verify_result(), Err(X509VerifyError::CERT_REVOKED));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_untrusted_invalid_crl() {
|
||||
let cert = include_bytes!("../../../test/cert.pem");
|
||||
let cert = X509::from_pem(cert).unwrap();
|
||||
let ca = include_bytes!("../../../test/root-ca.pem");
|
||||
let ca = X509::from_pem(ca).unwrap();
|
||||
let chain = Stack::new().unwrap();
|
||||
|
||||
let mut store_bldr = X509StoreBuilder::new().unwrap();
|
||||
store_bldr.add_cert(&ca).unwrap();
|
||||
store_bldr
|
||||
.param_mut()
|
||||
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL);
|
||||
store_bldr.param_mut().set_time(1656633600); // 2022-07-01, everything is valid
|
||||
let store = store_bldr.build();
|
||||
|
||||
// this CRL was issued by a different CA (not in the trusted store)
|
||||
let crl = include_bytes!("../../../test/invalid_crl.pem");
|
||||
let crl = X509CRL::from_pem(crl).unwrap();
|
||||
let mut context = X509StoreContext::new().unwrap();
|
||||
context
|
||||
.init(&store, &cert, &chain, |c| {
|
||||
assert!(!c.verify_cert_with_crls(stack_of(crl)).unwrap());
|
||||
assert_eq!(c.verify_result(), Err(X509VerifyError::UNABLE_TO_GET_CRL));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// this CRL has an invalid signature
|
||||
let crl = include_bytes!("../../../test/bad_sig.pem");
|
||||
let crl = X509CRL::from_pem(crl).unwrap();
|
||||
let mut context = X509StoreContext::new().unwrap();
|
||||
context
|
||||
.init(&store, &cert, &chain, |c| {
|
||||
assert!(!c.verify_cert_with_crls(stack_of(crl)).unwrap());
|
||||
assert_eq!(
|
||||
c.verify_result(),
|
||||
Err(X509VerifyError::CRL_SIGNATURE_FAILURE)
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_revoked_serial_numbers() {
|
||||
let cert = include_bytes!("../../../test/cert.pem");
|
||||
let cert = X509::from_pem(cert).unwrap();
|
||||
let cert_sn = cert.serial_number().to_bn().unwrap();
|
||||
|
||||
let crl = include_bytes!("../../../test/crl.pem");
|
||||
let crl = X509CRL::from_pem(crl).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
crl.revoked()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|revoked| revoked.serial_number().to_bn().unwrap() == cert_sn)
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_crl() {
|
||||
let crl = include_bytes!("../../../test/crl.pem");
|
||||
let crl = X509CRL::from_pem(crl).unwrap();
|
||||
let digest = hex::encode(crl.digest(MessageDigest::sha1()).unwrap());
|
||||
|
||||
let serialized = crl.to_pem().unwrap();
|
||||
let crl_deserialized = X509CRL::from_pem(&serialized).unwrap();
|
||||
let new_digest = crl_deserialized.digest(MessageDigest::sha1()).unwrap();
|
||||
assert_eq!(digest, hex::encode(new_digest));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_crl_extensions() {
|
||||
let crl = include_bytes!("../../../test/crl.pem");
|
||||
let crl = X509CRL::from_pem(crl).unwrap();
|
||||
let extensions = crl.extensions();
|
||||
assert_eq!(
|
||||
extensions
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|ext| ext.object().nid() == Nid::AUTHORITY_KEY_IDENTIFIER)
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
extensions
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|ext| ext.object().nid() == Nid::CRL_NUMBER)
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug_crl() {
|
||||
let crl = include_bytes!("../../../test/crl.pem");
|
||||
let crl = X509CRL::from_pem(crl).unwrap();
|
||||
let debugged = format!("{:#?}", crl);
|
||||
assert!(debugged.contains(r#"countryName = "AU""#));
|
||||
assert!(debugged.contains(r#"stateOrProvinceName = "Some-State""#));
|
||||
assert!(debugged.contains(r#"organizationName = "Internet Widgits Pty Ltd""#));
|
||||
assert!(debugged.contains(r#"last_update: Jun 21 20:22:02 2022 GMT"#));
|
||||
assert!(debugged.contains(r#"next_update: Jun 18 20:22:02 2032 GMT"#));
|
||||
assert!(debugged.contains(r#"revocation_date: Jun 21 20:21:55 2022 GMT"#));
|
||||
assert!(debugged.contains(r#"serial_number: "8771f7bdee982fa5""#));
|
||||
assert!(debugged.contains(r#"object_nid: X509v3 Authority Key Identifier"#));
|
||||
assert!(debugged.contains(r#"object_nid: X509v3 CRL Number"#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_time_valid() {
|
||||
let cert = include_bytes!("../../../test/cert.pem");
|
||||
let cert = X509::from_pem(cert).unwrap();
|
||||
let ca = include_bytes!("../../../test/root-ca.pem");
|
||||
let ca = X509::from_pem(ca).unwrap();
|
||||
let chain = Stack::new().unwrap();
|
||||
|
||||
let mut store_bldr = X509StoreBuilder::new().unwrap();
|
||||
store_bldr.add_cert(&ca).unwrap();
|
||||
store_bldr.param_mut().set_time(1656633600); // 2022-07-01, everything is valid
|
||||
let store = store_bldr.build();
|
||||
|
||||
let mut context = X509StoreContext::new().unwrap();
|
||||
assert!(context
|
||||
.init(&store, &cert, &chain, |c| c.verify_cert())
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_time_expired() {
|
||||
let cert = include_bytes!("../../../test/cert.pem");
|
||||
let cert = X509::from_pem(cert).unwrap();
|
||||
let ca = include_bytes!("../../../test/root-ca.pem");
|
||||
let ca = X509::from_pem(ca).unwrap();
|
||||
let chain = Stack::new().unwrap();
|
||||
|
||||
let mut store_bldr = X509StoreBuilder::new().unwrap();
|
||||
store_bldr.add_cert(&ca).unwrap();
|
||||
store_bldr.param_mut().set_time(1786838400); // 2026-08-16, after the root and leaf expiration
|
||||
let store = store_bldr.build();
|
||||
|
||||
let mut context = X509StoreContext::new().unwrap();
|
||||
assert!(!context
|
||||
.init(&store, &cert, &chain, |c| c.verify_cert())
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_time_too_soon() {
|
||||
let cert = include_bytes!("../../../test/cert.pem");
|
||||
let cert = X509::from_pem(cert).unwrap();
|
||||
let ca = include_bytes!("../../../test/root-ca.pem");
|
||||
let ca = X509::from_pem(ca).unwrap();
|
||||
let chain = Stack::new().unwrap();
|
||||
|
||||
let mut store_bldr = X509StoreBuilder::new().unwrap();
|
||||
store_bldr.add_cert(&ca).unwrap();
|
||||
store_bldr.param_mut().set_time(1262304000); // 2010-01-01, before the root and leaf issuance
|
||||
let store = store_bldr.build();
|
||||
|
||||
let mut context = X509StoreContext::new().unwrap();
|
||||
assert!(!context
|
||||
.init(&store, &cert, &chain, |c| c.verify_cert())
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_time_crl() {
|
||||
let cert = include_bytes!("../../../test/cert.pem");
|
||||
let cert = X509::from_pem(cert).unwrap();
|
||||
let ca = include_bytes!("../../../test/root-ca.pem");
|
||||
let ca = X509::from_pem(ca).unwrap();
|
||||
let crl = include_bytes!("../../../test/crl.pem");
|
||||
let crl = X509CRL::from_pem(crl).unwrap();
|
||||
let chain = Stack::new().unwrap();
|
||||
|
||||
let mut store_bldr = X509StoreBuilder::new().unwrap();
|
||||
store_bldr.add_cert(&ca).unwrap();
|
||||
store_bldr
|
||||
.param_mut()
|
||||
.set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL);
|
||||
store_bldr.param_mut().set_time(1640995200); // 2022-01-01, before the CRL's issue date
|
||||
let store = store_bldr.build();
|
||||
|
||||
let mut context = X509StoreContext::new().unwrap();
|
||||
assert!(!context
|
||||
.init(&store, &cert, &chain, |c| c
|
||||
.verify_cert_with_crls(stack_of(crl)))
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_save_subject_der() {
|
||||
let cert = include_bytes!("../../../test/cert.pem");
|
||||
let cert = X509::from_pem(cert).unwrap();
|
||||
|
||||
let der = cert.subject_name().to_der().unwrap();
|
||||
println!("der: {:?}", der);
|
||||
println!("der: {der:?}");
|
||||
assert!(!der.is_empty());
|
||||
}
|
||||
|
||||
@ -501,3 +841,16 @@ fn test_load_subject_der() {
|
||||
];
|
||||
X509Name::from_der(SUBJECT_DER).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_ip_asc() {
|
||||
// Covers 127.0.0.1 and 0:0:0:0:0:0:0:1
|
||||
let cert = include_bytes!("../../../test/alt_name_cert.pem");
|
||||
let cert = X509::from_pem(cert).unwrap();
|
||||
|
||||
assert!(cert.check_ip_asc("127.0.0.1").unwrap());
|
||||
assert!(!cert.check_ip_asc("127.0.0.2").unwrap());
|
||||
|
||||
assert!(cert.check_ip_asc("0:0:0:0:0:0:0:1").unwrap());
|
||||
assert!(!cert.check_ip_asc("0:0:0:0:0:0:0:2").unwrap());
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
use crate::stack::Stack;
|
||||
use crate::x509::store::X509StoreBuilder;
|
||||
use crate::x509::verify::{X509Flags, X509VerifyParamRef};
|
||||
use crate::x509::verify::{X509VerifyFlags, X509VerifyParamRef};
|
||||
use crate::x509::{X509Ref, X509StoreContext, X509VerifyError, X509VerifyResult, X509};
|
||||
|
||||
#[test]
|
||||
@ -15,7 +15,7 @@ fn test_verify_cert() {
|
||||
|
||||
assert_eq!(Ok(()), verify(&leaf, &[&root1], &[&intermediate], |_| {}));
|
||||
|
||||
#[cfg(not(feature = "fips-compat"))]
|
||||
#[cfg(not(feature = "legacy-compat-deprecated"))]
|
||||
assert_eq!(
|
||||
Ok(()),
|
||||
verify(
|
||||
@ -26,7 +26,7 @@ fn test_verify_cert() {
|
||||
)
|
||||
);
|
||||
|
||||
#[cfg(feature = "fips-compat")]
|
||||
#[cfg(feature = "legacy-compat-deprecated")]
|
||||
assert_eq!(
|
||||
Err(X509VerifyError::CERT_HAS_EXPIRED),
|
||||
verify(
|
||||
@ -43,7 +43,7 @@ fn test_verify_cert() {
|
||||
&leaf,
|
||||
&[&root1, &root2],
|
||||
&[&intermediate, &root1_cross],
|
||||
|param| param.set_flags(X509Flags::TRUSTED_FIRST),
|
||||
|param| param.set_flags(X509VerifyFlags::TRUSTED_FIRST),
|
||||
)
|
||||
);
|
||||
|
||||
@ -53,15 +53,15 @@ fn test_verify_cert() {
|
||||
&leaf,
|
||||
&[&root1, &root2],
|
||||
&[&intermediate, &root1_cross],
|
||||
|param| param.clear_flags(X509Flags::TRUSTED_FIRST),
|
||||
|param| param.clear_flags(X509VerifyFlags::TRUSTED_FIRST),
|
||||
)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Ok(()),
|
||||
verify(&leaf, &[&root1], &[&intermediate, &root1_cross], |param| {
|
||||
param.clear_flags(X509Flags::TRUSTED_FIRST)
|
||||
},)
|
||||
param.clear_flags(X509VerifyFlags::TRUSTED_FIRST);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ fn verify(
|
||||
let mut builder = X509StoreBuilder::new().unwrap();
|
||||
|
||||
for cert in trusted {
|
||||
builder.add_cert((**cert).to_owned()).unwrap();
|
||||
builder.add_cert(cert).unwrap();
|
||||
}
|
||||
|
||||
builder.build()
|
||||
|
||||
@ -20,20 +20,14 @@ bitflags! {
|
||||
const NEVER_CHECK_SUBJECT = ffi::X509_CHECK_FLAG_NEVER_CHECK_SUBJECT as _;
|
||||
#[cfg(feature = "underscore-wildcards")]
|
||||
const UNDERSCORE_WILDCARDS = ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS as _;
|
||||
|
||||
#[deprecated(since = "0.10.6", note = "renamed to NO_WILDCARDS")]
|
||||
const FLAG_NO_WILDCARDS = ffi::X509_CHECK_FLAG_NO_WILDCARDS as _;
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(note = "X509Flags renamed to X509VerifyFlags")]
|
||||
pub use X509VerifyFlags as X509Flags;
|
||||
|
||||
bitflags! {
|
||||
/// Flags used to check an `X509` certificate.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
#[doc(alias = "X509Flags")]
|
||||
pub struct X509VerifyFlags: c_ulong {
|
||||
const CB_ISSUER_CHECK = ffi::X509_V_FLAG_CB_ISSUER_CHECK as _;
|
||||
const USE_CHECK_TIME = ffi::X509_V_FLAG_USE_CHECK_TIME as _;
|
||||
@ -112,6 +106,7 @@ impl X509VerifyParamRef {
|
||||
|
||||
/// Gets verification flags.
|
||||
#[corresponds(X509_VERIFY_PARAM_get_flags)]
|
||||
#[must_use]
|
||||
pub fn flags(&self) -> X509VerifyFlags {
|
||||
let bits = unsafe { ffi::X509_VERIFY_PARAM_get_flags(self.as_ptr()) };
|
||||
X509VerifyFlags::from_bits_retain(bits)
|
||||
@ -125,10 +120,9 @@ impl X509VerifyParamRef {
|
||||
let raw_host = if host.is_empty() { "\0" } else { host };
|
||||
cvt(ffi::X509_VERIFY_PARAM_set1_host(
|
||||
self.as_ptr(),
|
||||
raw_host.as_ptr() as *const _,
|
||||
raw_host.as_ptr().cast(),
|
||||
host.len(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,10 +134,9 @@ impl X509VerifyParamRef {
|
||||
let raw_email = if email.is_empty() { "\0" } else { email };
|
||||
cvt(ffi::X509_VERIFY_PARAM_set1_email(
|
||||
self.as_ptr(),
|
||||
raw_email.as_ptr() as *const _,
|
||||
raw_email.as_ptr().cast(),
|
||||
email.len(),
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,10 +157,9 @@ impl X509VerifyParamRef {
|
||||
};
|
||||
cvt(ffi::X509_VERIFY_PARAM_set1_ip(
|
||||
self.as_ptr(),
|
||||
buf.as_ptr() as *const _,
|
||||
buf.as_ptr().cast(),
|
||||
len,
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,4 +174,12 @@ impl X509VerifyParamRef {
|
||||
pub fn set_depth(&mut self, depth: c_int) {
|
||||
unsafe { ffi::X509_VERIFY_PARAM_set_depth(self.as_ptr(), depth) }
|
||||
}
|
||||
|
||||
/// Copies parameters from `src`.
|
||||
///
|
||||
/// If a parameter is unset in `src`, the existing value in `self`` is preserved.
|
||||
#[corresponds(X509_VERIFY_PARAM_set1)]
|
||||
pub fn copy_from(&mut self, src: &Self) -> Result<(), ErrorStack> {
|
||||
unsafe { cvt(ffi::X509_VERIFY_PARAM_set1(self.as_ptr(), src.as_ptr())) }
|
||||
}
|
||||
}
|
||||
|
||||
11
boring/test/bad_sig.pem
Normal file
11
boring/test/bad_sig.pem
Normal file
@ -0,0 +1,11 @@
|
||||
-----BEGIN X509 CRL-----
|
||||
MIIBqTCBkjANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwK
|
||||
U29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkFw0y
|
||||
MjA2MTYyMjQ0NDFaFw0yMzA2MTYyMjQ0NDFaMBwwGgIJAIdx973umC+lFw0yMjA2
|
||||
MTYyMjIxMDVaMA0GCSqGSIb3DQEBBQUAA4IBAQApqFdwm46jxkJK8J5kprGm6cp8
|
||||
b7XMKB1epvhJGIkXHjp7O+2rxYGIExcNlM7jPcwqnUE0E50qGrqSMEupmtaBH03a
|
||||
fmmDKyhLema7KD64UaERLqWjaW2DPeX9VX6vL4ECc6zTVLxfmYzxVt6A9hhqCm3b
|
||||
fu8klWczGTa79r/WhTbA7uVf5+OI98da5tlxw+DlAQfqd34L2qq5aFg2dcTGqIdz
|
||||
3pxP6UlTyj0ZPK3tUtTpIURVO2/MX3j5V+QjWz81UeCv0gQcmOiIVSRUGwi9c6JY
|
||||
jDqBIvDY6df0riz5is1SS+D94sp1iovBlluwpq4kB8xyDuwt7vblkzleS2YU
|
||||
-----END X509 CRL-----
|
||||
13
boring/test/crl.pem
Normal file
13
boring/test/crl.pem
Normal file
@ -0,0 +1,13 @@
|
||||
-----BEGIN X509 CRL-----
|
||||
MIIB3jCBxwIBATANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJBVTETMBEGA1UE
|
||||
CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
|
||||
Fw0yMjA2MjEyMDIyMDJaFw0zMjA2MTgyMDIyMDJaMBwwGgIJAIdx973umC+lFw0y
|
||||
MjA2MjEyMDIxNTVaoDAwLjAfBgNVHSMEGDAWgBRs06UDqw1fLMmNipyIp4h3uDf9
|
||||
mjALBgNVHRQEBAICEAEwDQYJKoZIhvcNAQELBQADggEBABgsWr/sVZqXm1AzgGCJ
|
||||
JBMJW1oUY18aqroxo4kAIoI4QveLmHxi1Wm2I4dqdc6pM09SJhU5v5CfqpJ2BDc0
|
||||
JBfEk8KKi5O/OYyLcUKa4dEpAlYPgeDyLc6zF8rGLtJoDIYuk4JUeuuByoXt0Sh+
|
||||
7vx6UzuI7EH+mr4ZjnyAkD3f9jZy+mDcTm/+0REuh4iZ1AotE2YuQWQgxc1Y8TlD
|
||||
eK+ks1zBKI23s0hPBxJQunmz2k3Uu9Yf+Sg0KxCiDgJZWFiGSw/6DtnT0oYAFGaD
|
||||
mCyQWtwmS6zGBg+p76wNXkwyJMVvSDgrXSZ55bmImNmA38yKqOLOpB5i+FAS3r4V
|
||||
ApQ=
|
||||
-----END X509 CRL-----
|
||||
11
boring/test/empty_crl.pem
Normal file
11
boring/test/empty_crl.pem
Normal file
@ -0,0 +1,11 @@
|
||||
-----BEGIN X509 CRL-----
|
||||
MIIBijB0MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApT
|
||||
b21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQXDTIy
|
||||
MDYxNjIyNDU1NVoXDTIzMDYxNjIyNDU1NVowDQYJKoZIhvcNAQEFBQADggEBAEtx
|
||||
nBr3aI0qlegMVJsJn3GfkMzaPVTSTHuw76Dzdl9eGDj0hXzAzZW5k4WBHvaInzNT
|
||||
NKkeISoQJHLH981R9sQU2zA8sTESLTJGyCFu05Y6XhdmqX4ywmzVRjL6p/aoHNdZ
|
||||
H1mxgK16wG+Sv0pd+9qNJgC/cNFmNbWzbiEAi5kID4IUxSmId/FZsXsms1EjqDH4
|
||||
DFIwLIQO/kR5zwE5fZ5EjqUBdAxoSVHfD+OPKl4x2t8CHMmao+ih2FOfd70+NLBD
|
||||
2oxaJMjZL/SIf8vYxjpjimMR+7yJ5J5P1j/RBfG3LwwUDP0RtWLIvRQo/dZUyXTg
|
||||
LuC1vNuUoObe12z/NQ4=
|
||||
-----END X509 CRL-----
|
||||
11
boring/test/invalid_crl.pem
Normal file
11
boring/test/invalid_crl.pem
Normal file
@ -0,0 +1,11 @@
|
||||
-----BEGIN X509 CRL-----
|
||||
MIIBmTCBggIBATANBgkqhkiG9w0BAQsFADAhMQ0wCwYDVQQKDARGQUtFMRAwDgYD
|
||||
VQQDDAdGQUtFIENBFw0yMjA2MjcyMTQ3NTBaFw0zMjA2MjQyMTQ3NTBaMBwwGgIJ
|
||||
AIdx973umC+lFw0yMjA2MjcyMTQ2MjhaoA8wDTALBgNVHRQEBAICEAMwDQYJKoZI
|
||||
hvcNAQELBQADggEBAMXZRqTG28rnJSUPnVaqkmePSH15iz5Q/e4MdrM0cipXGuzX
|
||||
z5C8Oh0D2uT3ddawBxTosnbjuzlT7Tanbp3xCRBm9spRPxFbGaFWysBlG1aLTDka
|
||||
e9t9YeErg7wpwU6Qar0dzkLL5IkW3NArgbe8gP9PkYQxz/B0ESdHIPYJP1YBMNG6
|
||||
tLgEhg74Xs9UhOBInNsQB8qMGsEeOnzfiuvfspU/yvKHHzvjAcjeIrONLJaZcu2Y
|
||||
Dsfm5gOXGkHEm5/qJZ/IILoY0GSsSBekCAZda5+v3nvyjfRaBPyhx3Zv+rXh7u5z
|
||||
77bIzPZP60rslomacgEr4p/Y52E4GmKGV+X2+Hc=
|
||||
-----END X509 CRL-----
|
||||
@ -9,49 +9,35 @@ repository = { workspace = true }
|
||||
documentation = "https://docs.rs/hyper-boring"
|
||||
readme = "README.md"
|
||||
exclude = ["test/*"]
|
||||
rust-version = "1.80"
|
||||
rust-version = { workspace = true }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["pq-experimental"]
|
||||
features = []
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
default = ["runtime"]
|
||||
|
||||
runtime = ["hyper_old/runtime"]
|
||||
default = ["boring/default", "tokio-boring/default"]
|
||||
|
||||
# Use a FIPS-validated version of boringssl.
|
||||
fips = ["tokio-boring/fips"]
|
||||
|
||||
# Link with precompiled FIPS-validated `bcm.o` module.
|
||||
fips-link-precompiled = ["tokio-boring/fips-link-precompiled"]
|
||||
|
||||
# Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/)
|
||||
pq-experimental = ["tokio-boring/pq-experimental"]
|
||||
|
||||
# Enable Hyper 1 support
|
||||
hyper1 = ["dep:http", "dep:hyper", "dep:hyper-util", "dep:tower-service"]
|
||||
fips = ["boring/fips", "tokio-boring/fips"]
|
||||
|
||||
[dependencies]
|
||||
antidote = { workspace = true }
|
||||
http = { workspace = true, optional = true }
|
||||
http_old = { workspace = true }
|
||||
hyper = { workspace = true, optional = true }
|
||||
hyper-util = { workspace = true, optional = true, features = ["client", "client-legacy"] }
|
||||
hyper_old = { workspace = true, features = ["client"] }
|
||||
http = { workspace = true }
|
||||
hyper = { workspace = true }
|
||||
hyper-util = { workspace = true, features = ["client", "client-legacy"] }
|
||||
linked_hash_set = { workspace = true }
|
||||
boring = { workspace = true }
|
||||
boring = { workspace = true, default-features = false }
|
||||
tokio = { workspace = true }
|
||||
tokio-boring = { workspace = true }
|
||||
tokio-boring = { workspace = true, default-features = false }
|
||||
tower-layer = { workspace = true }
|
||||
tower-service = { workspace = true, optional = true }
|
||||
tower-service = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bytes = { workspace = true }
|
||||
http-body-util = { workspace = true }
|
||||
hyper-util = { workspace = true, features = ["http1", "http2", "service", "tokio"] }
|
||||
hyper = { workspace = true, features = ["server"] }
|
||||
hyper_old = { workspace = true, features = [ "full" ] }
|
||||
tokio = { workspace = true, features = [ "full" ] }
|
||||
tower = { workspace = true, features = ["util"] }
|
||||
futures = { workspace = true }
|
||||
|
||||
@ -86,9 +86,8 @@ impl SessionCache {
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, session: &SslSessionRef) {
|
||||
let key = match self.reverse.remove(session.id()) {
|
||||
Some(key) => key,
|
||||
None => return,
|
||||
let Some(key) = self.reverse.remove(session.id()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Entry::Occupied(mut sessions) = self.sessions.entry(key) {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
//! Hyper SSL support via OpenSSL.
|
||||
//! Hyper SSL support via BoringSSL.
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
||||
use crate::cache::SessionKey;
|
||||
use boring::error::ErrorStack;
|
||||
@ -11,12 +10,9 @@ use std::sync::LazyLock;
|
||||
use tokio_boring::SslStream;
|
||||
|
||||
mod cache;
|
||||
mod v0;
|
||||
/// Hyper 1 support.
|
||||
#[cfg(feature = "hyper1")]
|
||||
pub mod v1;
|
||||
mod v1;
|
||||
|
||||
pub use self::v0::*;
|
||||
pub use self::v1::*;
|
||||
|
||||
fn key_index() -> Result<Index<Ssl, SessionKey>, ErrorStack> {
|
||||
static IDX: LazyLock<Index<Ssl, SessionKey>> = LazyLock::new(|| Ssl::new_ex_index().unwrap());
|
||||
@ -30,6 +26,7 @@ pub struct HttpsLayerSettings {
|
||||
|
||||
impl HttpsLayerSettings {
|
||||
/// Constructs an [`HttpsLayerSettingsBuilder`] for configuring settings
|
||||
#[must_use]
|
||||
pub fn builder() -> HttpsLayerSettingsBuilder {
|
||||
HttpsLayerSettingsBuilder(HttpsLayerSettings::default())
|
||||
}
|
||||
@ -54,6 +51,7 @@ impl HttpsLayerSettingsBuilder {
|
||||
}
|
||||
|
||||
/// Consumes the builder, returning a new [`HttpsLayerSettings`]
|
||||
#[must_use]
|
||||
pub fn build(self) -> HttpsLayerSettings {
|
||||
self.0
|
||||
}
|
||||
|
||||
@ -1,345 +0,0 @@
|
||||
use crate::cache::{SessionCache, SessionKey};
|
||||
use crate::{key_index, HttpsLayerSettings, MaybeHttpsStream};
|
||||
use antidote::Mutex;
|
||||
use boring::error::ErrorStack;
|
||||
use boring::ssl::{
|
||||
ConnectConfiguration, Ssl, SslConnector, SslConnectorBuilder, SslMethod, SslRef,
|
||||
SslSessionCacheMode,
|
||||
};
|
||||
use http_old::uri::Scheme;
|
||||
use hyper_old::client::connect::{Connected, Connection};
|
||||
use hyper_old::client::HttpConnector;
|
||||
use hyper_old::service::Service;
|
||||
use hyper_old::Uri;
|
||||
use std::error::Error;
|
||||
use std::future::Future;
|
||||
use std::net;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{fmt, io};
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use tower_layer::Layer;
|
||||
|
||||
/// A Connector using OpenSSL to support `http` and `https` schemes.
|
||||
#[derive(Clone)]
|
||||
pub struct HttpsConnector<T> {
|
||||
http: T,
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime")]
|
||||
impl HttpsConnector<HttpConnector> {
|
||||
/// Creates a a new `HttpsConnector` using default settings.
|
||||
///
|
||||
/// The Hyper `HttpConnector` is used to perform the TCP socket connection. ALPN is configured to support both
|
||||
/// HTTP/2 and HTTP/1.1.
|
||||
///
|
||||
/// Requires the `runtime` Cargo feature.
|
||||
pub fn new() -> Result<HttpsConnector<HttpConnector>, ErrorStack> {
|
||||
let mut http = HttpConnector::new();
|
||||
http.enforce_http(false);
|
||||
|
||||
HttpsLayer::new().map(|l| l.layer(http))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> HttpsConnector<S>
|
||||
where
|
||||
S: Service<Uri, Response = T> + Send,
|
||||
S::Error: Into<Box<dyn Error + Send + Sync>>,
|
||||
S::Future: Unpin + Send + 'static,
|
||||
T: AsyncRead + AsyncWrite + Connection + Unpin + fmt::Debug + Sync + Send + 'static,
|
||||
{
|
||||
/// Creates a new `HttpsConnector`.
|
||||
///
|
||||
/// The session cache configuration of `ssl` will be overwritten.
|
||||
pub fn with_connector(
|
||||
http: S,
|
||||
ssl: SslConnectorBuilder,
|
||||
) -> Result<HttpsConnector<S>, ErrorStack> {
|
||||
HttpsLayer::with_connector(ssl).map(|l| l.layer(http))
|
||||
}
|
||||
|
||||
/// Registers a callback which can customize the configuration of each connection.
|
||||
///
|
||||
/// Unsuitable to change verify hostflags (with `config.param_mut().set_hostflags(…)`),
|
||||
/// as they are reset after the callback is executed. Use [`Self::set_ssl_callback`]
|
||||
/// instead.
|
||||
pub fn set_callback<F>(&mut self, callback: F)
|
||||
where
|
||||
F: Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
|
||||
{
|
||||
self.inner.callback = Some(Arc::new(callback));
|
||||
}
|
||||
|
||||
/// Registers a callback which can customize the `Ssl` of each connection.
|
||||
pub fn set_ssl_callback<F>(&mut self, callback: F)
|
||||
where
|
||||
F: Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
|
||||
{
|
||||
self.inner.ssl_callback = Some(Arc::new(callback));
|
||||
}
|
||||
}
|
||||
|
||||
/// A layer which wraps services in an `HttpsConnector`.
|
||||
pub struct HttpsLayer {
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Inner {
|
||||
ssl: SslConnector,
|
||||
cache: Arc<Mutex<SessionCache>>,
|
||||
callback: Option<Callback>,
|
||||
ssl_callback: Option<SslCallback>,
|
||||
}
|
||||
|
||||
type Callback =
|
||||
Arc<dyn Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + Sync + Send>;
|
||||
type SslCallback = Arc<dyn Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + Sync + Send>;
|
||||
|
||||
impl HttpsLayer {
|
||||
/// Creates a new `HttpsLayer` with default settings.
|
||||
///
|
||||
/// ALPN is configured to support both HTTP/1 and HTTP/1.1.
|
||||
pub fn new() -> Result<HttpsLayer, ErrorStack> {
|
||||
let mut ssl = SslConnector::builder(SslMethod::tls())?;
|
||||
|
||||
ssl.set_alpn_protos(b"\x02h2\x08http/1.1")?;
|
||||
|
||||
Self::with_connector(ssl)
|
||||
}
|
||||
|
||||
/// Creates a new `HttpsLayer`.
|
||||
///
|
||||
/// The session cache configuration of `ssl` will be overwritten.
|
||||
pub fn with_connector(ssl: SslConnectorBuilder) -> Result<HttpsLayer, ErrorStack> {
|
||||
Self::with_connector_and_settings(ssl, Default::default())
|
||||
}
|
||||
|
||||
/// Creates a new `HttpsLayer` with settings
|
||||
pub fn with_connector_and_settings(
|
||||
mut ssl: SslConnectorBuilder,
|
||||
settings: HttpsLayerSettings,
|
||||
) -> Result<HttpsLayer, ErrorStack> {
|
||||
let cache = Arc::new(Mutex::new(SessionCache::with_capacity(
|
||||
settings.session_cache_capacity,
|
||||
)));
|
||||
|
||||
ssl.set_session_cache_mode(SslSessionCacheMode::CLIENT);
|
||||
|
||||
ssl.set_new_session_callback({
|
||||
let cache = cache.clone();
|
||||
move |ssl, session| {
|
||||
if let Some(key) = key_index().ok().and_then(|idx| ssl.ex_data(idx)) {
|
||||
cache.lock().insert(key.clone(), session);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(HttpsLayer {
|
||||
inner: Inner {
|
||||
ssl: ssl.build(),
|
||||
cache,
|
||||
callback: None,
|
||||
ssl_callback: None,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Registers a callback which can customize the configuration of each connection.
|
||||
///
|
||||
/// Unsuitable to change verify hostflags (with `config.param_mut().set_hostflags(…)`),
|
||||
/// as they are reset after the callback is executed. Use [`Self::set_ssl_callback`]
|
||||
/// instead.
|
||||
pub fn set_callback<F>(&mut self, callback: F)
|
||||
where
|
||||
F: Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
|
||||
{
|
||||
self.inner.callback = Some(Arc::new(callback));
|
||||
}
|
||||
|
||||
/// Registers a callback which can customize the `Ssl` of each connection.
|
||||
pub fn set_ssl_callback<F>(&mut self, callback: F)
|
||||
where
|
||||
F: Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
|
||||
{
|
||||
self.inner.ssl_callback = Some(Arc::new(callback));
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Layer<S> for HttpsLayer {
|
||||
type Service = HttpsConnector<S>;
|
||||
|
||||
fn layer(&self, inner: S) -> HttpsConnector<S> {
|
||||
HttpsConnector {
|
||||
http: inner,
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
fn setup_ssl(&self, uri: &Uri, host: &str) -> Result<Ssl, ErrorStack> {
|
||||
let mut conf = self.ssl.configure()?;
|
||||
|
||||
if let Some(ref callback) = self.callback {
|
||||
callback(&mut conf, uri)?;
|
||||
}
|
||||
|
||||
let key = SessionKey {
|
||||
host: host.to_string(),
|
||||
port: uri.port_u16().unwrap_or(443),
|
||||
};
|
||||
|
||||
if let Some(session) = self.cache.lock().get(&key) {
|
||||
unsafe {
|
||||
conf.set_session(&session)?;
|
||||
}
|
||||
}
|
||||
|
||||
let idx = key_index()?;
|
||||
conf.set_ex_data(idx, key);
|
||||
|
||||
let mut ssl = conf.into_ssl(host)?;
|
||||
|
||||
if let Some(ref ssl_callback) = self.ssl_callback {
|
||||
ssl_callback(&mut ssl, uri)?;
|
||||
}
|
||||
|
||||
Ok(ssl)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Service<Uri> for HttpsConnector<S>
|
||||
where
|
||||
S: Service<Uri> + Send,
|
||||
S::Error: Into<Box<dyn Error + Send + Sync>>,
|
||||
S::Future: Unpin + Send + 'static,
|
||||
S::Response: AsyncRead + AsyncWrite + Connection + Unpin + fmt::Debug + Sync + Send + 'static,
|
||||
{
|
||||
type Response = MaybeHttpsStream<S::Response>;
|
||||
type Error = Box<dyn Error + Sync + Send>;
|
||||
#[allow(clippy::type_complexity)]
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.http.poll_ready(cx).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn call(&mut self, uri: Uri) -> Self::Future {
|
||||
let is_tls_scheme = uri
|
||||
.scheme()
|
||||
.map(|s| s == &Scheme::HTTPS || s.as_str() == "wss")
|
||||
.unwrap_or(false);
|
||||
|
||||
let tls_setup = if is_tls_scheme {
|
||||
Some((self.inner.clone(), uri.clone()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let connect = self.http.call(uri);
|
||||
|
||||
let f = async {
|
||||
let conn = connect.await.map_err(Into::into)?;
|
||||
|
||||
let (inner, uri) = match tls_setup {
|
||||
Some((inner, uri)) => (inner, uri),
|
||||
None => return Ok(MaybeHttpsStream::Http(conn)),
|
||||
};
|
||||
|
||||
let mut host = uri.host().ok_or("URI missing host")?;
|
||||
|
||||
// If `host` is an IPv6 address, we must strip away the square brackets that surround
|
||||
// it (otherwise, boring will fail to parse the host as an IP address, eventually
|
||||
// causing the handshake to fail due a hostname verification error).
|
||||
if !host.is_empty() {
|
||||
let last = host.len() - 1;
|
||||
let mut chars = host.chars();
|
||||
|
||||
if let (Some('['), Some(']')) = (chars.next(), chars.last()) {
|
||||
if host[1..last].parse::<net::Ipv6Addr>().is_ok() {
|
||||
host = &host[1..last];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ssl = inner.setup_ssl(&uri, host)?;
|
||||
let stream = tokio_boring::SslStreamBuilder::new(ssl, conn)
|
||||
.connect()
|
||||
.await?;
|
||||
|
||||
Ok(MaybeHttpsStream::Https(stream))
|
||||
};
|
||||
|
||||
Box::pin(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Connection for MaybeHttpsStream<T>
|
||||
where
|
||||
T: Connection,
|
||||
{
|
||||
fn connected(&self) -> Connected {
|
||||
match self {
|
||||
MaybeHttpsStream::Http(s) => s.connected(),
|
||||
MaybeHttpsStream::Https(s) => {
|
||||
let mut connected = s.get_ref().connected();
|
||||
|
||||
if s.ssl().selected_alpn_protocol() == Some(b"h2") {
|
||||
connected = connected.negotiated_h2();
|
||||
}
|
||||
|
||||
connected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsyncRead for MaybeHttpsStream<T>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
ctx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
match &mut *self {
|
||||
MaybeHttpsStream::Http(s) => Pin::new(s).poll_read(ctx, buf),
|
||||
MaybeHttpsStream::Https(s) => Pin::new(s).poll_read(ctx, buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsyncWrite for MaybeHttpsStream<T>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
ctx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
match &mut *self {
|
||||
MaybeHttpsStream::Http(s) => Pin::new(s).poll_write(ctx, buf),
|
||||
MaybeHttpsStream::Https(s) => Pin::new(s).poll_write(ctx, buf),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
match &mut *self {
|
||||
MaybeHttpsStream::Http(s) => Pin::new(s).poll_flush(ctx),
|
||||
MaybeHttpsStream::Https(s) => Pin::new(s).poll_flush(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_shutdown(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
match &mut *self {
|
||||
MaybeHttpsStream::Http(s) => Pin::new(s).poll_shutdown(ctx),
|
||||
MaybeHttpsStream::Https(s) => Pin::new(s).poll_shutdown(ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -29,14 +29,11 @@ pub struct HttpsConnector<T> {
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime")]
|
||||
impl HttpsConnector<HttpConnector> {
|
||||
/// Creates a a new `HttpsConnector` using default settings.
|
||||
///
|
||||
/// The Hyper `HttpConnector` is used to perform the TCP socket connection. ALPN is configured to support both
|
||||
/// HTTP/2 and HTTP/1.1.
|
||||
///
|
||||
/// Requires the `runtime` Cargo feature.
|
||||
pub fn new() -> Result<HttpsConnector<HttpConnector>, ErrorStack> {
|
||||
let mut http = HttpConnector::new();
|
||||
http.enforce_http(false);
|
||||
@ -116,7 +113,7 @@ impl HttpsLayer {
|
||||
///
|
||||
/// The session cache configuration of `ssl` will be overwritten.
|
||||
pub fn with_connector(ssl: SslConnectorBuilder) -> Result<HttpsLayer, ErrorStack> {
|
||||
Self::with_connector_and_settings(ssl, Default::default())
|
||||
Self::with_connector_and_settings(ssl, HttpsLayerSettings::default())
|
||||
}
|
||||
|
||||
/// Creates a new `HttpsLayer` with settings
|
||||
@ -246,9 +243,8 @@ where
|
||||
let f = async {
|
||||
let conn = connect.await.map_err(Into::into)?.into_inner();
|
||||
|
||||
let (inner, uri) = match tls_setup {
|
||||
Some((inner, uri)) => (inner, uri),
|
||||
None => return Ok(MaybeHttpsStream::Http(conn)),
|
||||
let Some((inner, uri)) = tls_setup else {
|
||||
return Ok(MaybeHttpsStream::Http(conn));
|
||||
};
|
||||
|
||||
let mut host = uri.host().ok_or("URI missing host")?;
|
||||
@ -256,15 +252,12 @@ where
|
||||
// If `host` is an IPv6 address, we must strip away the square brackets that surround
|
||||
// it (otherwise, boring will fail to parse the host as an IP address, eventually
|
||||
// causing the handshake to fail due a hostname verification error).
|
||||
if !host.is_empty() {
|
||||
let last = host.len() - 1;
|
||||
let mut chars = host.chars();
|
||||
|
||||
if let (Some('['), Some(']')) = (chars.next(), chars.last()) {
|
||||
if host[1..last].parse::<net::Ipv6Addr>().is_ok() {
|
||||
host = &host[1..last];
|
||||
}
|
||||
}
|
||||
if let Some(ipv6) = host
|
||||
.strip_prefix('[')
|
||||
.and_then(|h| h.strip_suffix(']'))
|
||||
.filter(|h| h.parse::<net::Ipv6Addr>().is_ok())
|
||||
{
|
||||
host = ipv6;
|
||||
}
|
||||
|
||||
let ssl = inner.setup_ssl(&uri, host)?;
|
||||
|
||||
@ -1,156 +0,0 @@
|
||||
use boring::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod};
|
||||
use futures::StreamExt;
|
||||
use hyper_boring::HttpsConnector;
|
||||
use hyper_old::client::HttpConnector;
|
||||
use hyper_old::server::conn::Http;
|
||||
use hyper_old::{service, Response};
|
||||
use hyper_old::{Body, Client};
|
||||
use std::convert::Infallible;
|
||||
use std::{io, iter};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(feature = "runtime")]
|
||||
async fn google() {
|
||||
let ssl = HttpsConnector::new().unwrap();
|
||||
let client = Client::builder()
|
||||
.pool_max_idle_per_host(0)
|
||||
.build::<_, Body>(ssl);
|
||||
|
||||
for _ in 0..3 {
|
||||
let resp = client
|
||||
.get("https://www.google.com".parse().unwrap())
|
||||
.await
|
||||
.expect("connection should succeed");
|
||||
let mut body = resp.into_body();
|
||||
while body.next().await.transpose().unwrap().is_some() {}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn localhost() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
let port = addr.port();
|
||||
|
||||
let server = async move {
|
||||
let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
||||
acceptor.set_session_id_context(b"test").unwrap();
|
||||
acceptor
|
||||
.set_private_key_file("tests/test/key.pem", SslFiletype::PEM)
|
||||
.unwrap();
|
||||
acceptor
|
||||
.set_certificate_chain_file("tests/test/cert.pem")
|
||||
.unwrap();
|
||||
let acceptor = acceptor.build();
|
||||
|
||||
for _ in 0..3 {
|
||||
let stream = listener.accept().await.unwrap().0;
|
||||
let stream = tokio_boring::accept(&acceptor, stream).await.unwrap();
|
||||
|
||||
let service =
|
||||
service::service_fn(|_| async { Ok::<_, io::Error>(Response::new(Body::empty())) });
|
||||
|
||||
Http::new()
|
||||
.http1_keep_alive(false)
|
||||
.serve_connection(stream, service)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
};
|
||||
tokio::spawn(server);
|
||||
|
||||
let resolver =
|
||||
tower::service_fn(move |_name| async move { Ok::<_, Infallible>(iter::once(addr)) });
|
||||
|
||||
let mut connector = HttpConnector::new_with_resolver(resolver);
|
||||
|
||||
connector.enforce_http(false);
|
||||
|
||||
let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||
|
||||
ssl.set_ca_file("tests/test/root-ca.pem").unwrap();
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
let file = File::create("../target/keyfile.log").unwrap();
|
||||
ssl.set_keylog_callback(move |_, line| {
|
||||
let _ = writeln!(&file, "{}", line);
|
||||
});
|
||||
|
||||
let ssl = HttpsConnector::with_connector(connector, ssl).unwrap();
|
||||
let client = Client::builder().build::<_, Body>(ssl);
|
||||
|
||||
for _ in 0..3 {
|
||||
let resp = client
|
||||
.get(format!("https://foobar.com:{}", port).parse().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(resp.status().is_success(), "{}", resp.status());
|
||||
let mut body = resp.into_body();
|
||||
while body.next().await.transpose().unwrap().is_some() {}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn alpn_h2() {
|
||||
use boring::ssl::{self, AlpnError};
|
||||
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
let port = addr.port();
|
||||
|
||||
let server = async move {
|
||||
let mut acceptor = SslAcceptor::mozilla_modern(SslMethod::tls()).unwrap();
|
||||
acceptor
|
||||
.set_certificate_chain_file("tests/test/cert.pem")
|
||||
.unwrap();
|
||||
acceptor
|
||||
.set_private_key_file("tests/test/key.pem", SslFiletype::PEM)
|
||||
.unwrap();
|
||||
acceptor.set_alpn_select_callback(|_, client| {
|
||||
ssl::select_next_proto(b"\x02h2", client).ok_or(AlpnError::NOACK)
|
||||
});
|
||||
let acceptor = acceptor.build();
|
||||
|
||||
let stream = listener.accept().await.unwrap().0;
|
||||
let stream = tokio_boring::accept(&acceptor, stream).await.unwrap();
|
||||
assert_eq!(stream.ssl().selected_alpn_protocol().unwrap(), b"h2");
|
||||
|
||||
let service =
|
||||
service::service_fn(|_| async { Ok::<_, io::Error>(Response::new(Body::empty())) });
|
||||
|
||||
Http::new()
|
||||
.http2_only(true)
|
||||
.serve_connection(stream, service)
|
||||
.await
|
||||
.unwrap();
|
||||
};
|
||||
tokio::spawn(server);
|
||||
|
||||
let resolver =
|
||||
tower::service_fn(move |_name| async move { Ok::<_, Infallible>(iter::once(addr)) });
|
||||
|
||||
let mut connector = HttpConnector::new_with_resolver(resolver);
|
||||
|
||||
connector.enforce_http(false);
|
||||
|
||||
let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||
|
||||
ssl.set_ca_file("tests/test/root-ca.pem").unwrap();
|
||||
|
||||
let mut ssl = HttpsConnector::with_connector(connector, ssl).unwrap();
|
||||
|
||||
ssl.set_ssl_callback(|ssl, _| ssl.set_alpn_protos(b"\x02h2\x08http/1.1"));
|
||||
|
||||
let client = Client::builder().build::<_, Body>(ssl);
|
||||
|
||||
let resp = client
|
||||
.get(format!("https://foobar.com:{}", port).parse().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(resp.status().is_success(), "{}", resp.status());
|
||||
let mut body = resp.into_body();
|
||||
while body.next().await.transpose().unwrap().is_some() {}
|
||||
}
|
||||
@ -1,11 +1,9 @@
|
||||
#![cfg(feature = "hyper1")]
|
||||
|
||||
use boring::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod};
|
||||
use bytes::Bytes;
|
||||
use futures::StreamExt;
|
||||
use http_body_util::{BodyStream, Empty};
|
||||
use hyper::{service, Response};
|
||||
use hyper_boring::v1::HttpsConnector;
|
||||
use hyper_boring::HttpsConnector;
|
||||
use hyper_util::client::legacy::connect::HttpConnector;
|
||||
use hyper_util::client::legacy::Client;
|
||||
use hyper_util::rt::{TokioExecutor, TokioIo};
|
||||
@ -80,7 +78,7 @@ async fn localhost() {
|
||||
|
||||
let file = File::create("../target/keyfile.log").unwrap();
|
||||
ssl.set_keylog_callback(move |_, line| {
|
||||
let _ = writeln!(&file, "{}", line);
|
||||
let _ = writeln!(&file, "{line}");
|
||||
});
|
||||
|
||||
let ssl = HttpsConnector::with_connector(connector, ssl).unwrap();
|
||||
@ -88,7 +86,7 @@ async fn localhost() {
|
||||
|
||||
for _ in 0..3 {
|
||||
let resp = client
|
||||
.get(format!("https://foobar.com:{}", port).parse().unwrap())
|
||||
.get(format!("https://foobar.com:{port}").parse().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(resp.status().is_success(), "{}", resp.status());
|
||||
@ -151,7 +149,7 @@ async fn alpn_h2() {
|
||||
let client = Client::builder(TokioExecutor::new()).build::<_, Empty<Bytes>>(ssl);
|
||||
|
||||
let resp = client
|
||||
.get(format!("https://foobar.com:{}", port).parse().unwrap())
|
||||
.get(format!("https://foobar.com:{port}").parse().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(resp.status().is_success(), "{}", resp.status());
|
||||
|
||||
@ -4,6 +4,7 @@ version = { workspace = true }
|
||||
authors = ["Alex Crichton <alex@alexcrichton.com>", "Ivan Nikulin <ifaaan@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
homepage = "https://github.com/cloudflare/boring"
|
||||
documentation = "https://docs.rs/tokio-boring"
|
||||
@ -12,25 +13,21 @@ An implementation of SSL streams for Tokio backed by BoringSSL
|
||||
"""
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["rpk", "pq-experimental"]
|
||||
features = ["rpk"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
default = ["boring/default"]
|
||||
|
||||
# Use a FIPS-validated version of boringssl.
|
||||
fips = ["boring/fips", "boring-sys/fips"]
|
||||
|
||||
# Link with precompiled FIPS-validated `bcm.o` module.
|
||||
fips-link-precompiled = ["boring/fips-link-precompiled", "boring-sys/fips-link-precompiled"]
|
||||
|
||||
# Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/)
|
||||
pq-experimental = ["boring/pq-experimental"]
|
||||
|
||||
# Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250)
|
||||
rpk = ["boring/rpk"]
|
||||
|
||||
[dependencies]
|
||||
boring = { workspace = true }
|
||||
boring-sys = { workspace = true }
|
||||
boring = { workspace = true, default-features = false }
|
||||
boring-sys = { workspace = true, default-features = false }
|
||||
tokio = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@ -6,7 +6,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
let listener = TcpListener::bind("127.0.0.1:8080").await?;
|
||||
let (tcp_stream, _addr) = listener.accept().await?;
|
||||
|
||||
let server = ssl::SslMethod::tls_server();
|
||||
let server = ssl::SslMethod::tls();
|
||||
let mut ssl_builder = boring::ssl::SslAcceptor::mozilla_modern(server)?;
|
||||
ssl_builder.set_default_verify_paths()?;
|
||||
ssl_builder.set_verify(ssl::SslVerifyMode::PEER);
|
||||
|
||||
@ -41,7 +41,7 @@ pub trait SslContextBuilderExt: private::Sealed {
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The returned [`SslSession`] must not be associated with a different [`SslContext`].
|
||||
/// The returned [`boring::ssl::SslSession`] must not be associated with a different [`boring::ssl::SslContext`].
|
||||
unsafe fn set_async_get_session_callback<F>(&mut self, callback: F)
|
||||
where
|
||||
F: Fn(&mut SslRef, &[u8]) -> Option<BoxGetSessionFuture> + Send + Sync + 'static;
|
||||
|
||||
@ -22,7 +22,7 @@ impl<S> AsyncStreamBridge<S> {
|
||||
}
|
||||
|
||||
pub(crate) fn set_waker(&mut self, ctx: Option<&mut Context<'_>>) {
|
||||
self.waker = ctx.map(|ctx| ctx.waker().clone())
|
||||
self.waker = ctx.map(|ctx| ctx.waker().clone());
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
//! [`boring`] crate, on which this crate is built. Configuration of TLS parameters is still
|
||||
//! primarily done through the [`boring`] crate.
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
||||
use boring::ssl::{
|
||||
self, ConnectConfiguration, ErrorCode, MidHandshakeSslStream, ShutdownResult, SslAcceptor,
|
||||
@ -113,6 +112,7 @@ where
|
||||
|
||||
impl<S> SslStreamBuilder<S> {
|
||||
/// Returns a shared reference to the `Ssl` object associated with this builder.
|
||||
#[must_use]
|
||||
pub fn ssl(&self) -> &SslRef {
|
||||
self.inner.ssl()
|
||||
}
|
||||
@ -135,6 +135,7 @@ pub struct SslStream<S>(ssl::SslStream<AsyncStreamBridge<S>>);
|
||||
|
||||
impl<S> SslStream<S> {
|
||||
/// Returns a shared reference to the `Ssl` object associated with this stream.
|
||||
#[must_use]
|
||||
pub fn ssl(&self) -> &SslRef {
|
||||
self.0.ssl()
|
||||
}
|
||||
@ -145,6 +146,7 @@ impl<S> SslStream<S> {
|
||||
}
|
||||
|
||||
/// Returns a shared reference to the underlying stream.
|
||||
#[must_use]
|
||||
pub fn get_ref(&self) -> &S {
|
||||
&self.0.get_ref().stream
|
||||
}
|
||||
@ -234,15 +236,13 @@ where
|
||||
|
||||
fn poll_shutdown(mut self: Pin<&mut Self>, ctx: &mut Context) -> Poll<io::Result<()>> {
|
||||
match self.run_in_context(ctx, |s| s.shutdown()) {
|
||||
Ok(ShutdownResult::Sent) | Ok(ShutdownResult::Received) => {}
|
||||
Ok(ShutdownResult::Sent | ShutdownResult::Received) => {}
|
||||
Err(ref e) if e.code() == ErrorCode::ZERO_RETURN => {}
|
||||
Err(ref e) if e.code() == ErrorCode::WANT_READ || e.code() == ErrorCode::WANT_WRITE => {
|
||||
return Poll::Pending;
|
||||
}
|
||||
Err(e) => {
|
||||
return Poll::Ready(Err(e
|
||||
.into_io_error()
|
||||
.unwrap_or_else(|e| io::Error::new(io::ErrorKind::Other, e))));
|
||||
return Poll::Ready(Err(e.into_io_error().unwrap_or_else(io::Error::other)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -255,6 +255,7 @@ pub struct HandshakeError<S>(ssl::HandshakeError<AsyncStreamBridge<S>>);
|
||||
|
||||
impl<S> HandshakeError<S> {
|
||||
/// Returns a shared reference to the `Ssl` object associated with this error.
|
||||
#[must_use]
|
||||
pub fn ssl(&self) -> Option<&SslRef> {
|
||||
match &self.0 {
|
||||
ssl::HandshakeError::Failure(s) => Some(s.ssl()),
|
||||
@ -263,6 +264,7 @@ impl<S> HandshakeError<S> {
|
||||
}
|
||||
|
||||
/// Converts error to the source data stream that was used for the handshake.
|
||||
#[must_use]
|
||||
pub fn into_source_stream(self) -> Option<S> {
|
||||
match self.0 {
|
||||
ssl::HandshakeError::Failure(s) => Some(s.into_source_stream().stream),
|
||||
@ -271,6 +273,7 @@ impl<S> HandshakeError<S> {
|
||||
}
|
||||
|
||||
/// Returns a reference to the source data stream.
|
||||
#[must_use]
|
||||
pub fn as_source_stream(&self) -> Option<&S> {
|
||||
match &self.0 {
|
||||
ssl::HandshakeError::Failure(s) => Some(&s.get_ref().stream),
|
||||
@ -279,6 +282,7 @@ impl<S> HandshakeError<S> {
|
||||
}
|
||||
|
||||
/// Returns the error code, if any.
|
||||
#[must_use]
|
||||
pub fn code(&self) -> Option<ErrorCode> {
|
||||
match &self.0 {
|
||||
ssl::HandshakeError::Failure(s) => Some(s.error().code()),
|
||||
@ -287,6 +291,7 @@ impl<S> HandshakeError<S> {
|
||||
}
|
||||
|
||||
/// Returns a reference to the inner I/O error, if any.
|
||||
#[must_use]
|
||||
pub fn as_io_error(&self) -> Option<&io::Error> {
|
||||
match &self.0 {
|
||||
ssl::HandshakeError::Failure(s) => s.error().io_error(),
|
||||
|
||||
@ -26,7 +26,7 @@ async fn test() {
|
||||
builder
|
||||
.set_session_cache_mode(SslSessionCacheMode::SERVER | SslSessionCacheMode::NO_INTERNAL);
|
||||
builder.set_new_session_callback(|_, session| {
|
||||
SERVER_SESSION_DER.set(session.to_der().unwrap()).unwrap()
|
||||
SERVER_SESSION_DER.set(session.to_der().unwrap()).unwrap();
|
||||
});
|
||||
|
||||
unsafe {
|
||||
@ -49,7 +49,7 @@ async fn test() {
|
||||
let connector = create_connector(|builder| {
|
||||
builder.set_session_cache_mode(SslSessionCacheMode::CLIENT);
|
||||
builder.set_new_session_callback(|_, session| {
|
||||
CLIENT_SESSION_DER.set(session.to_der().unwrap()).unwrap()
|
||||
CLIENT_SESSION_DER.set(session.to_der().unwrap()).unwrap();
|
||||
});
|
||||
|
||||
builder.set_ca_file("tests/cert.pem")
|
||||
|
||||
@ -1,109 +1,151 @@
|
||||
#[cfg(feature = "rpk")]
|
||||
mod test_rpk {
|
||||
use boring::pkey::PKey;
|
||||
use boring::ssl::{SslAcceptor, SslConnector};
|
||||
use futures::future;
|
||||
use std::future::Future;
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio_boring::{HandshakeError, SslStream};
|
||||
#![cfg(feature = "rpk")]
|
||||
|
||||
fn create_server() -> (
|
||||
impl Future<Output = Result<SslStream<TcpStream>, HandshakeError<TcpStream>>>,
|
||||
SocketAddr,
|
||||
) {
|
||||
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
use boring::pkey::PKey;
|
||||
use boring::ssl::{
|
||||
CertificateType, SslAcceptor, SslAlert, SslConnector, SslCredential, SslMethod, SslVerifyError,
|
||||
SslVerifyMode,
|
||||
};
|
||||
use futures::future;
|
||||
use std::future::Future;
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio_boring::{HandshakeError, SslStream};
|
||||
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
fn create_server() -> (
|
||||
impl Future<Output = Result<SslStream<TcpStream>, HandshakeError<TcpStream>>>,
|
||||
SocketAddr,
|
||||
) {
|
||||
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
|
||||
let listener = TcpListener::from_std(listener).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
|
||||
let server = async move {
|
||||
let mut acceptor = SslAcceptor::rpk().unwrap();
|
||||
let pkey = std::fs::read("tests/key.pem").unwrap();
|
||||
let pkey = PKey::private_key_from_pem(&pkey).unwrap();
|
||||
let cert = std::fs::read("tests/pubkey.der").unwrap();
|
||||
let listener = TcpListener::from_std(listener).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
acceptor.set_rpk_certificate(&cert).unwrap();
|
||||
acceptor.set_null_chain_private_key(&pkey).unwrap();
|
||||
let server = async move {
|
||||
let mut acceptor = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).unwrap();
|
||||
let private_key =
|
||||
PKey::private_key_from_pem(&std::fs::read("tests/key.pem").unwrap()).unwrap();
|
||||
let spki = std::fs::read("tests/pubkey.der").unwrap();
|
||||
|
||||
let acceptor = acceptor.build();
|
||||
acceptor
|
||||
.add_credential({
|
||||
let mut cred = SslCredential::new_raw_public_key().unwrap();
|
||||
|
||||
let stream = listener.accept().await.unwrap().0;
|
||||
cred.set_private_key(&private_key).unwrap();
|
||||
cred.set_spki_bytes(Some(&spki)).unwrap();
|
||||
|
||||
tokio_boring::accept(&acceptor, stream).await
|
||||
};
|
||||
&cred.build()
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
(server, addr)
|
||||
}
|
||||
let acceptor = acceptor.build();
|
||||
|
||||
#[tokio::test]
|
||||
async fn server_rpk() {
|
||||
let (stream, addr) = create_server();
|
||||
let stream = listener.accept().await.unwrap().0;
|
||||
|
||||
let server = async {
|
||||
let mut stream = stream.await.unwrap();
|
||||
let mut buf = [0; 4];
|
||||
stream.read_exact(&mut buf).await.unwrap();
|
||||
assert_eq!(&buf, b"asdf");
|
||||
tokio_boring::accept(&acceptor, stream).await
|
||||
};
|
||||
|
||||
stream.write_all(b"jkl;").await.unwrap();
|
||||
|
||||
future::poll_fn(|ctx| Pin::new(&mut stream).poll_shutdown(ctx))
|
||||
.await
|
||||
.unwrap();
|
||||
};
|
||||
|
||||
let client = async {
|
||||
let mut connector = SslConnector::rpk_builder().unwrap();
|
||||
let cert = std::fs::read("tests/pubkey.der").unwrap();
|
||||
|
||||
connector.set_rpk_certificate(&cert).unwrap();
|
||||
let config = connector.build().configure().unwrap();
|
||||
|
||||
let stream = TcpStream::connect(&addr).await.unwrap();
|
||||
let mut stream = tokio_boring::connect(config, "localhost", stream)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
stream.write_all(b"asdf").await.unwrap();
|
||||
|
||||
let mut buf = vec![];
|
||||
stream.read_to_end(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, b"jkl;");
|
||||
};
|
||||
|
||||
future::join(server, client).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn client_rpk_unknown_cert() {
|
||||
let (stream, addr) = create_server();
|
||||
|
||||
let server = async {
|
||||
assert!(stream.await.is_err());
|
||||
};
|
||||
|
||||
let client = async {
|
||||
let mut connector = SslConnector::rpk_builder().unwrap();
|
||||
let cert = std::fs::read("tests/pubkey2.der").unwrap();
|
||||
|
||||
connector.set_rpk_certificate(&cert).unwrap();
|
||||
let config = connector.build().configure().unwrap();
|
||||
|
||||
let stream = TcpStream::connect(&addr).await.unwrap();
|
||||
|
||||
let err = tokio_boring::connect(config, "localhost", stream)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
// NOTE: smoke test for https://github.com/cloudflare/boring/issues/140
|
||||
let _ = err.to_string();
|
||||
};
|
||||
|
||||
future::join(server, client).await;
|
||||
}
|
||||
(server, addr)
|
||||
}
|
||||
|
||||
async fn connect(
|
||||
addr: SocketAddr,
|
||||
spki_path: &str,
|
||||
is_ok_cell: &Arc<OnceLock<bool>>,
|
||||
) -> Result<SslStream<TcpStream>, HandshakeError<TcpStream>> {
|
||||
let mut connector = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||
let spki = PKey::public_key_from_der(&std::fs::read(spki_path).unwrap()).unwrap();
|
||||
let is_ok_cell = Arc::clone(is_ok_cell);
|
||||
|
||||
connector
|
||||
.set_server_certificate_types(&[CertificateType::RAW_PUBLIC_KEY])
|
||||
.unwrap();
|
||||
|
||||
connector.set_custom_verify_callback(SslVerifyMode::PEER, move |ssl| {
|
||||
let public_key = ssl
|
||||
.peer_pubkey()
|
||||
.ok_or(SslVerifyError::Invalid(SslAlert::CERTIFICATE_UNKNOWN))?;
|
||||
|
||||
let is_ok = public_key.public_eq(&spki);
|
||||
|
||||
is_ok_cell.set(is_ok).unwrap();
|
||||
|
||||
if !is_ok {
|
||||
return Err(SslVerifyError::Invalid(SslAlert::BAD_CERTIFICATE));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let config = connector.build().configure().unwrap();
|
||||
|
||||
tokio_boring::connect(
|
||||
config,
|
||||
"localhost",
|
||||
TcpStream::connect(&addr).await.unwrap(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn server_rpk() {
|
||||
let (stream, addr) = create_server();
|
||||
|
||||
let server = async {
|
||||
let mut stream = stream.await.unwrap();
|
||||
let mut buf = [0; 4];
|
||||
stream.read_exact(&mut buf).await.unwrap();
|
||||
assert_eq!(&buf, b"asdf");
|
||||
|
||||
stream.write_all(b"jkl;").await.unwrap();
|
||||
|
||||
future::poll_fn(|ctx| Pin::new(&mut stream).poll_shutdown(ctx))
|
||||
.await
|
||||
.unwrap();
|
||||
};
|
||||
|
||||
let client = async {
|
||||
let is_ok_cell = Arc::new(OnceLock::new());
|
||||
let mut stream = connect(addr, "tests/pubkey.der", &is_ok_cell)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(is_ok_cell.get().unwrap());
|
||||
|
||||
stream.write_all(b"asdf").await.unwrap();
|
||||
|
||||
let mut buf = vec![];
|
||||
stream.read_to_end(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, b"jkl;");
|
||||
};
|
||||
|
||||
future::join(server, client).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn client_rpk_unknown_cert() {
|
||||
let (stream, addr) = create_server();
|
||||
|
||||
let server = async {
|
||||
assert!(stream.await.is_err());
|
||||
};
|
||||
|
||||
let client = async {
|
||||
let is_ok_cell = Arc::new(OnceLock::new());
|
||||
let err = connect(addr, "tests/pubkey2.der", &is_ok_cell)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(!is_ok_cell.get().unwrap());
|
||||
|
||||
// NOTE: smoke test for https://github.com/cloudflare/boring/issues/140
|
||||
let _ = err.to_string();
|
||||
};
|
||||
|
||||
future::join(server, client).await;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user