Compare commits
No commits in common. "eec42222af734dfd223d5166bdcf9780591db90f" and "75ef5232300a7d005578003c2dfc4d2b38cb7b8a" have entirely different histories.
eec42222af
...
75ef523230
|
|
@ -1,15 +0,0 @@
|
||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
|
||||||
patreon: # Replace with a single Patreon username
|
|
||||||
open_collective: # Replace with a single Open Collective username
|
|
||||||
ko_fi:
|
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
||||||
liberapay: # Replace with a single Liberapay username
|
|
||||||
issuehunt: # Replace with a single IssueHunt username
|
|
||||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
|
||||||
polar: # Replace with a single Polar username
|
|
||||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
|
||||||
thanks_dev: # Replace with a single thanks.dev username
|
|
||||||
custom: ['https://github.com/0x676e67/0x676e67/blob/main/SPONSOR.md']
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
|
||||||
- OS: [e.g. iOS]
|
|
||||||
- Browser [e.g. chrome, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Smartphone (please complete the following information):**
|
|
||||||
- Device: [e.g. iPhone6]
|
|
||||||
- OS: [e.g. iOS8.1]
|
|
||||||
- Browser [e.g. stock browser, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
# To get started with Dependabot version updates, you'll need to specify which
|
|
||||||
# package ecosystems to update and where the package manifests are located.
|
|
||||||
# Please see the documentation for all configuration options:
|
|
||||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
||||||
|
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "cargo" # See documentation for possible values
|
|
||||||
directory: "/" # Location of package manifests
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
|
|
@ -1,24 +1,17 @@
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
tags: ["v*"]
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
branches:
|
||||||
- 'docs/**'
|
- master
|
||||||
- '*.md'
|
push:
|
||||||
- '.github/**'
|
branches:
|
||||||
- 'README.md'
|
- master
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
RUSTFLAGS: -Dwarnings
|
RUSTFLAGS: -Dwarnings
|
||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
packages: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
rustfmt:
|
rustfmt:
|
||||||
name: rustfmt
|
name: rustfmt
|
||||||
|
|
@ -67,7 +60,7 @@ jobs:
|
||||||
- name: Run clippy
|
- name: Run clippy
|
||||||
run: cargo clippy --all --all-targets
|
run: cargo clippy --all --all-targets
|
||||||
- name: Check docs
|
- name: Check docs
|
||||||
run: cargo doc --no-deps -p boring2 -p boring-sys2 --features underscore-wildcards
|
run: cargo doc --no-deps -p boring -p boring-sys --features rpk,pq-experimental,underscore-wildcards
|
||||||
env:
|
env:
|
||||||
DOCS_RS: 1
|
DOCS_RS: 1
|
||||||
test:
|
test:
|
||||||
|
|
@ -106,25 +99,21 @@ jobs:
|
||||||
rust: stable
|
rust: stable
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
check_only: true
|
check_only: true
|
||||||
extra_test_args: --workspace --exclude quinn-boring2
|
|
||||||
- thing: arm64-android
|
- thing: arm64-android
|
||||||
target: aarch64-linux-android
|
target: aarch64-linux-android
|
||||||
rust: stable
|
rust: stable
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
check_only: true
|
check_only: true
|
||||||
extra_test_args: --workspace --exclude quinn-boring2
|
|
||||||
- thing: i686-android
|
- thing: i686-android
|
||||||
target: i686-linux-android
|
target: i686-linux-android
|
||||||
rust: stable
|
rust: stable
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
check_only: true
|
check_only: true
|
||||||
extra_test_args: --workspace --exclude quinn-boring2
|
|
||||||
- thing: x86_64-android
|
- thing: x86_64-android
|
||||||
target: x86_64-linux-android
|
target: x86_64-linux-android
|
||||||
rust: stable
|
rust: stable
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
check_only: true
|
check_only: true
|
||||||
extra_test_args: --workspace --exclude quinn-boring2
|
|
||||||
- thing: aarch64-ios
|
- thing: aarch64-ios
|
||||||
target: aarch64-apple-ios
|
target: aarch64-apple-ios
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
|
|
@ -150,7 +139,6 @@ jobs:
|
||||||
rust: stable
|
rust: stable
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
apt_packages: gcc-multilib g++-multilib
|
apt_packages: gcc-multilib g++-multilib
|
||||||
extra_test_args: --workspace --exclude compio-boring2
|
|
||||||
- thing: arm-linux
|
- thing: arm-linux
|
||||||
target: arm-unknown-linux-gnueabi
|
target: arm-unknown-linux-gnueabi
|
||||||
rust: stable
|
rust: stable
|
||||||
|
|
@ -161,7 +149,6 @@ jobs:
|
||||||
CC_arm-unknown-linux-gnueabi: arm-linux-gnueabi-gcc
|
CC_arm-unknown-linux-gnueabi: arm-linux-gnueabi-gcc
|
||||||
CXX_arm-unknown-linux-gnueabi: arm-linux-gnueabi-g++
|
CXX_arm-unknown-linux-gnueabi: arm-linux-gnueabi-g++
|
||||||
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABI_LINKER: arm-linux-gnueabi-g++
|
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABI_LINKER: arm-linux-gnueabi-g++
|
||||||
extra_test_args: --workspace --exclude compio-boring2
|
|
||||||
- thing: aarch64-linux
|
- thing: aarch64-linux
|
||||||
target: aarch64-unknown-linux-gnu
|
target: aarch64-unknown-linux-gnu
|
||||||
rust: stable
|
rust: stable
|
||||||
|
|
@ -193,27 +180,27 @@ jobs:
|
||||||
CPLUS_INCLUDE_PATH: "C:\\msys64\\usr\\include"
|
CPLUS_INCLUDE_PATH: "C:\\msys64\\usr\\include"
|
||||||
LIBRARY_PATH: "C:\\msys64\\usr\\lib"
|
LIBRARY_PATH: "C:\\msys64\\usr\\lib"
|
||||||
# CI's Windows doesn't have required root certs
|
# CI's Windows doesn't have required root certs
|
||||||
extra_test_args: --workspace --exclude tokio-boring2 --exclude compio-boring2
|
extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring
|
||||||
- thing: i686-msvc
|
- thing: i686-msvc
|
||||||
target: i686-pc-windows-msvc
|
target: i686-pc-windows-msvc
|
||||||
rust: stable-x86_64-msvc
|
rust: stable-x86_64-msvc
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
# CI's Windows doesn't have required root certs
|
# CI's Windows doesn't have required root certs
|
||||||
extra_test_args: --workspace --exclude tokio-boring2 --exclude compio-boring2
|
extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring
|
||||||
- thing: x86_64-msvc
|
- thing: x86_64-msvc
|
||||||
target: x86_64-pc-windows-msvc
|
target: x86_64-pc-windows-msvc
|
||||||
rust: stable-x86_64-msvc
|
rust: stable-x86_64-msvc
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
# CI's Windows doesn't have required root certs
|
# CI's Windows doesn't have required root certs
|
||||||
extra_test_args: --workspace --exclude tokio-boring2 --exclude compio-boring2
|
extra_test_args: --workspace --exclude tokio-boring --exclude hyper-boring
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
# - name: Install Rust (rustup)
|
- 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 }}
|
||||||
# shell: bash
|
shell: bash
|
||||||
- run: rustup target add ${{ matrix.target }}
|
- run: rustup target add ${{ matrix.target }}
|
||||||
- name: Install golang
|
- name: Install golang
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
|
|
@ -249,19 +236,47 @@ jobs:
|
||||||
run: cargo test --target ${{ matrix.target }} ${{ matrix.extra_test_args }}
|
run: cargo test --target ${{ matrix.target }} ${{ matrix.extra_test_args }}
|
||||||
shell: bash
|
shell: bash
|
||||||
env: ${{ matrix.custom_env }}
|
env: ${{ matrix.custom_env }}
|
||||||
- name: Test boring-sys2 cargo publish
|
- name: Test boring-sys cargo publish
|
||||||
# Running `cargo publish --dry-run` tests two things:
|
# Running `cargo publish --dry-run` tests two things:
|
||||||
#
|
#
|
||||||
# 1. That `boring-sys2` can build BoringSSL with just the files included
|
# 1. That `boring-sys` can build BoringSSL with just the files included
|
||||||
# in the crates.io package (as determined by the `include` field in
|
# in the crates.io package (as determined by the `include` field in
|
||||||
# the `Cargo.toml`).
|
# the `Cargo.toml`).
|
||||||
# 2. That the final `boring-sys2` package size, including the BoringSSL
|
# 2. That the final `boring-sys` package size, including the BoringSSL
|
||||||
# submodules, is not too large to be published to `crates.io`.
|
# submodules, is not too large to be published to `crates.io`.
|
||||||
#
|
#
|
||||||
# Both of these may no longer be the case after updating the BoringSSL
|
# 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.
|
# submodules to a new revision, so it's important to test this on CI.
|
||||||
run: cargo publish --dry-run -p boring-sys2
|
run: cargo publish --dry-run -p boring-sys
|
||||||
|
|
||||||
|
test-fips:
|
||||||
|
name: Test FIPS integration
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: 'recursive'
|
||||||
|
- name: Install Rust (rustup)
|
||||||
|
run: rustup update stable --no-self-update && rustup default stable
|
||||||
|
shell: bash
|
||||||
|
- name: Install golang
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '>=1.22.0'
|
||||||
|
- name: Run tests
|
||||||
|
run: cargo test --features fips
|
||||||
|
- name: Test boring-sys cargo publish (FIPS)
|
||||||
|
# Running `cargo publish --dry-run` tests two things:
|
||||||
|
#
|
||||||
|
# 1. That `boring-sys` can build BoringSSL with just the files included
|
||||||
|
# in the crates.io package (as determined by the `include` field in
|
||||||
|
# the `Cargo.toml`).
|
||||||
|
# 2. That the final `boring-sys` package size, including the BoringSSL
|
||||||
|
# submodules, is not too large to be published to `crates.io`.
|
||||||
|
#
|
||||||
|
# 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 --features fips
|
||||||
|
|
||||||
cross-build:
|
cross-build:
|
||||||
name: Cross build from macOS to Linux
|
name: Cross build from macOS to Linux
|
||||||
|
|
@ -291,6 +306,45 @@ jobs:
|
||||||
- name: Build for ${{ matrix.target }}
|
- name: Build for ${{ matrix.target }}
|
||||||
run: cargo build --target ${{ matrix.target }} --all-targets
|
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:
|
test-features:
|
||||||
name: Test features
|
name: Test features
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
@ -300,43 +354,18 @@ jobs:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
- name: Install Rust (rustup)
|
- name: Install Rust (rustup)
|
||||||
run: rustup update stable --no-self-update && rustup default stable
|
run: rustup update stable --no-self-update && rustup default stable
|
||||||
- name: Run `underscore-wildcards` tests
|
shell: bash
|
||||||
run: cargo test --features underscore-wildcards
|
- run: cargo test --features rpk
|
||||||
|
name: Run `rpk` tests
|
||||||
crates:
|
- run: cargo test --features pq-experimental
|
||||||
name: crates
|
name: Run `pq-experimental` tests
|
||||||
needs: [rustfmt, clippy, test, cross-build, test-features]
|
- run: cargo test --features underscore-wildcards
|
||||||
runs-on: ubuntu-latest
|
name: Run `underscore-wildcards` tests
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
- run: cargo test --features pq-experimental,rpk
|
||||||
steps:
|
name: Run `pq-experimental,rpk` tests
|
||||||
- uses: actions/checkout@v4
|
- run: cargo test --features pq-experimental,underscore-wildcards
|
||||||
- uses: actions-rs/toolchain@v1
|
name: Run `pq-experimental,underscore-wildcards` tests
|
||||||
with:
|
- run: cargo test --features rpk,underscore-wildcards
|
||||||
toolchain: stable
|
name: Run `rpk,underscore-wildcards` tests
|
||||||
override: true
|
- run: cargo test --features pq-experimental,rpk,underscore-wildcards
|
||||||
- name: Prebuild boring-sys2
|
name: Run `pq-experimental,rpk,underscore-wildcards` tests
|
||||||
run: cargo build -p boring-sys2
|
|
||||||
- name: publish crates
|
|
||||||
run: |
|
|
||||||
cargo login ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
|
||||||
echo "=== Publishing boring-sys2... ==="
|
|
||||||
(cd boring-sys && cargo publish)
|
|
||||||
sleep 10
|
|
||||||
echo "=== Publishing boring2... ==="
|
|
||||||
(cd boring && cargo publish)
|
|
||||||
sleep 10
|
|
||||||
echo "=== Publishing tokio-boring2... ==="
|
|
||||||
(cd tokio-boring && cargo publish)
|
|
||||||
sleep 10
|
|
||||||
echo "=== Publishing compio-boring2... ==="
|
|
||||||
(cd compio-boring && cargo publish)
|
|
||||||
sleep 10
|
|
||||||
echo "=== Publishing quinn-boring2... ==="
|
|
||||||
(cd quinn-boring && cargo publish)
|
|
||||||
|
|
||||||
- name: Upload binaries to GitHub Release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
prerelease: ${{ contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
|
|
||||||
generate_release_notes: true
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
on:
|
||||||
|
pull_request: {}
|
||||||
|
workflow_dispatch: {}
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * *"
|
||||||
|
name: Semgrep config
|
||||||
|
jobs:
|
||||||
|
semgrep:
|
||||||
|
name: semgrep/ci
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
|
||||||
|
SEMGREP_URL: https://cloudflare.semgrep.dev
|
||||||
|
SEMGREP_APP_URL: https://cloudflare.semgrep.dev
|
||||||
|
SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version
|
||||||
|
container:
|
||||||
|
image: semgrep/semgrep
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: semgrep ci
|
||||||
24
Cargo.toml
24
Cargo.toml
|
|
@ -3,14 +3,13 @@ members = [
|
||||||
"boring",
|
"boring",
|
||||||
"boring-sys",
|
"boring-sys",
|
||||||
"tokio-boring",
|
"tokio-boring",
|
||||||
"compio-boring",
|
"hyper-boring"
|
||||||
"quinn-boring",
|
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "5.0.0-alpha.10"
|
version = "4.19.0"
|
||||||
repository = "https://github.com/0x676e67/boring2"
|
repository = "https://github.com/cloudflare/boring"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[workspace.metadata.release]
|
[workspace.metadata.release]
|
||||||
|
|
@ -20,10 +19,9 @@ tag-prefix = ""
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
boring-sys = { package = "boring-sys2", version = "5.0.0-alpha.10", path = "./boring-sys" }
|
boring-sys = { version = "4.19.0", path = "./boring-sys" }
|
||||||
boring = { package = "boring2", version = "5.0.0-alpha.10", path = "./boring" }
|
boring = { version = "4.19.0", path = "./boring" }
|
||||||
tokio-boring = { package = "tokio-boring2", version = "5.0.0-alpha.10", path = "./tokio-boring" }
|
tokio-boring = { version = "4.19.0", path = "./tokio-boring" }
|
||||||
compio-boring = { package = "compio-boring2", version = "5.0.0-alpha.10", path = "./compio-boring" }
|
|
||||||
|
|
||||||
bindgen = { version = "0.72.0", default-features = false, features = ["runtime"] }
|
bindgen = { version = "0.72.0", default-features = false, features = ["runtime"] }
|
||||||
bitflags = "2.9"
|
bitflags = "2.9"
|
||||||
|
|
@ -40,8 +38,12 @@ futures = "0.3"
|
||||||
tokio = "1"
|
tokio = "1"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
antidote = "1.0.0"
|
antidote = "1.0.0"
|
||||||
|
http = "1"
|
||||||
|
http-body-util = "0.1.2"
|
||||||
|
hyper = "1"
|
||||||
|
hyper-util = "0.1.6"
|
||||||
linked_hash_set = "0.1"
|
linked_hash_set = "0.1"
|
||||||
openssl-macros = "0.1.1"
|
openssl-macros = "0.1.1"
|
||||||
autocfg = "1.3.0"
|
tower = "0.4"
|
||||||
compio = { version = "0.16.0" }
|
tower-layer = "0.3"
|
||||||
compio-io = { version = "0.8.0" }
|
tower-service = "0.3"
|
||||||
|
|
|
||||||
26
README.md
26
README.md
|
|
@ -1,23 +1,15 @@
|
||||||
# boring2
|
# boring
|
||||||
|
|
||||||
[](https://crates.io/crates/boring2)
|
[](https://crates.io/crates/boring)
|
||||||
|
|
||||||
BoringSSL bindings for the Rust programming language and HTTP client for [wreq](https://github.com/0x676e67/wreq) built on top of it.
|
BoringSSL 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.
|
||||||
## Non-goals
|
|
||||||
|
|
||||||
This package implements only the TLS extensions specification and supports the original [boring](https://github.com/cloudflare/boring) library with the following features:
|
|
||||||
|
|
||||||
- RPK is not supported
|
|
||||||
- Required TLS extensions for Safari and Firefox
|
|
||||||
- kDHE, ffdhe2048, and ffdhe3072 implementations
|
|
||||||
- Support for LoongArch P64 and P32 architectures
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
- Boring API: <https://docs.rs/boring2>
|
- Boring API: <https://docs.rs/boring>
|
||||||
- tokio TLS adapters: <https://docs.rs/tokio-boring2>
|
- tokio TLS adapters: <https://docs.rs/tokio-boring>
|
||||||
- compio TLS adapters: <https://docs.rs/compio-boring2>
|
- hyper HTTPS connector: <https://docs.rs/hyper-boring>
|
||||||
- FFI bindings: <https://docs.rs/boring-sys2>
|
- FFI bindings: <https://docs.rs/boring-sys>
|
||||||
|
|
||||||
## Contribution
|
## Contribution
|
||||||
|
|
||||||
|
|
@ -28,4 +20,4 @@ Version 2.0 and the MIT license without any additional terms or conditions.
|
||||||
|
|
||||||
## Accolades
|
## Accolades
|
||||||
|
|
||||||
The project is based on a fork of [boring](https://github.com/cloudflare/boring).
|
The project is based on a fork of [rust-openssl](https://github.com/sfackler/rust-openssl).
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "boring-sys2"
|
name = "boring-sys"
|
||||||
version = { workspace = true }
|
version = { workspace = true }
|
||||||
authors = ["Alex Crichton <alex@alexcrichton.com>",
|
authors = ["Alex Crichton <alex@alexcrichton.com>",
|
||||||
"Steven Fackler <sfackler@gmail.com>",
|
"Steven Fackler <sfackler@gmail.com>",
|
||||||
|
|
@ -7,7 +7,7 @@ authors = ["Alex Crichton <alex@alexcrichton.com>",
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "FFI bindings to BoringSSL"
|
description = "FFI bindings to BoringSSL"
|
||||||
repository = { workspace = true }
|
repository = { workspace = true }
|
||||||
documentation = "https://docs.rs/boring-sys2"
|
documentation = "https://docs.rs/boring-sys"
|
||||||
links = "boringssl"
|
links = "boringssl"
|
||||||
build = "build/main.rs"
|
build = "build/main.rs"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
@ -21,10 +21,14 @@ include = [
|
||||||
"/cmake/*.cmake",
|
"/cmake/*.cmake",
|
||||||
"/deps/boringssl/**/*.[chS]",
|
"/deps/boringssl/**/*.[chS]",
|
||||||
"/deps/boringssl/**/*.asm",
|
"/deps/boringssl/**/*.asm",
|
||||||
"/deps/boringssl/sources.json",
|
"/deps/boringssl/**/*.pl",
|
||||||
"/deps/boringssl/src/crypto/obj/obj_mac.num",
|
"/deps/boringssl/**/*.go",
|
||||||
"/deps/boringssl/src/crypto/obj/objects.txt",
|
"/deps/boringssl/**/*.cmake",
|
||||||
"/deps/boringssl/src/util/32-bit-toolchain.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/**/*.bzl",
|
||||||
"/deps/boringssl/**/*.cc",
|
"/deps/boringssl/**/*.cc",
|
||||||
"/deps/boringssl/**/CMakeLists.txt",
|
"/deps/boringssl/**/CMakeLists.txt",
|
||||||
|
|
@ -37,7 +41,7 @@ include = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["underscore-wildcards"]
|
features = ["rpk", "pq-experimental", "underscore-wildcards"]
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
@ -49,17 +53,20 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||||
# for instructions and more details on the boringssl FIPS flag.
|
# for instructions and more details on the boringssl FIPS flag.
|
||||||
fips = []
|
fips = []
|
||||||
|
|
||||||
# Applies a patch (`patches/underscore-wildcards.patch`) to enable
|
# Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250)
|
||||||
# `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. This feature is necessary in
|
rpk = []
|
||||||
# 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 = []
|
|
||||||
|
|
||||||
# Add a prefix to all symbols in libcrypto and libssl to prevent conflicts
|
# Applies a patch (`patches/boring-pq.patch`) to the boringSSL source code that
|
||||||
# with other OpenSSL or BoringSSL versions that might be linked in the same process.
|
# enables support for PQ key exchange. This feature is necessary in order to
|
||||||
prefix-symbols = []
|
# compile the bindings for the default branch of boringSSL (`deps/boringssl`).
|
||||||
|
# Alternatively, a version of boringSSL that implements the same feature set
|
||||||
|
# can be provided by setting `BORING_BSSL{,_FIPS}_SOURCE_PATH`.
|
||||||
|
pq-experimental = []
|
||||||
|
|
||||||
|
# Applies a patch (`patches/underscore-wildcards.patch`) to enable
|
||||||
|
# `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. Same caveats as
|
||||||
|
# those for `pq-experimental` feature apply.
|
||||||
|
underscore-wildcards = []
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
bindgen = { workspace = true }
|
bindgen = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@ pub(crate) struct Config {
|
||||||
|
|
||||||
pub(crate) struct Features {
|
pub(crate) struct Features {
|
||||||
pub(crate) fips: bool,
|
pub(crate) fips: bool,
|
||||||
|
pub(crate) pq_experimental: bool,
|
||||||
pub(crate) rpk: bool,
|
pub(crate) rpk: bool,
|
||||||
pub(crate) underscore_wildcards: bool,
|
pub(crate) underscore_wildcards: bool,
|
||||||
pub(crate) prefix_symbols: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Env {
|
pub(crate) struct Env {
|
||||||
|
|
@ -89,7 +89,9 @@ impl Config {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let features_with_patches_enabled = self.features.rpk || self.features.underscore_wildcards;
|
let features_with_patches_enabled = self.features.rpk
|
||||||
|
|| self.features.pq_experimental
|
||||||
|
|| self.features.underscore_wildcards;
|
||||||
|
|
||||||
let patches_required = features_with_patches_enabled && !self.env.assume_patched;
|
let patches_required = features_with_patches_enabled && !self.env.assume_patched;
|
||||||
|
|
||||||
|
|
@ -104,15 +106,15 @@ impl Config {
|
||||||
impl Features {
|
impl Features {
|
||||||
fn from_env() -> Self {
|
fn from_env() -> Self {
|
||||||
let fips = env::var_os("CARGO_FEATURE_FIPS").is_some();
|
let fips = env::var_os("CARGO_FEATURE_FIPS").is_some();
|
||||||
|
let pq_experimental = env::var_os("CARGO_FEATURE_PQ_EXPERIMENTAL").is_some();
|
||||||
let rpk = env::var_os("CARGO_FEATURE_RPK").is_some();
|
let rpk = env::var_os("CARGO_FEATURE_RPK").is_some();
|
||||||
let underscore_wildcards = env::var_os("CARGO_FEATURE_UNDERSCORE_WILDCARDS").is_some();
|
let underscore_wildcards = env::var_os("CARGO_FEATURE_UNDERSCORE_WILDCARDS").is_some();
|
||||||
let prefix_symbols = env::var_os("CARGO_FEATURE_PREFIX_SYMBOLS").is_some();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
fips,
|
fips,
|
||||||
|
pq_experimental,
|
||||||
rpk,
|
rpk,
|
||||||
underscore_wildcards,
|
underscore_wildcards,
|
||||||
prefix_symbols,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,8 @@ use std::process::{Command, Output};
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::prefix::{prefix_symbols, PrefixCallback};
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod prefix;
|
|
||||||
|
|
||||||
fn should_use_cmake_cross_compilation(config: &Config) -> bool {
|
fn should_use_cmake_cross_compilation(config: &Config) -> bool {
|
||||||
if config.host == config.target {
|
if config.host == config.target {
|
||||||
|
|
@ -305,7 +303,7 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config {
|
||||||
config
|
config
|
||||||
.manifest_dir
|
.manifest_dir
|
||||||
.join(src_path)
|
.join(src_path)
|
||||||
.join("src/util/32-bit-toolchain.cmake")
|
.join("util/32-bit-toolchain.cmake")
|
||||||
.as_os_str(),
|
.as_os_str(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -436,12 +434,14 @@ fn ensure_patches_applied(config: &Config) -> io::Result<()> {
|
||||||
);
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else if config.env.source_path.is_some()
|
} else if config.env.source_path.is_some()
|
||||||
&& (config.features.rpk || config.features.underscore_wildcards)
|
&& (config.features.rpk
|
||||||
|
|| config.features.pq_experimental
|
||||||
|
|| config.features.underscore_wildcards)
|
||||||
{
|
{
|
||||||
panic!(
|
panic!(
|
||||||
"BORING_BSSL_ASSUME_PATCHED must be set when setting
|
"BORING_BSSL_ASSUME_PATCHED must be set when setting
|
||||||
BORING_BSSL_SOURCE_PATH and using any of the following
|
BORING_BSSL_SOURCE_PATH and using any of the following
|
||||||
features: rpk, underscore-wildcards"
|
features: rpk, pq-experimental, underscore-wildcards"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -456,14 +456,15 @@ fn ensure_patches_applied(config: &Config) -> io::Result<()> {
|
||||||
run_command(Command::new("git").arg("init").current_dir(src_path))?;
|
run_command(Command::new("git").arg("init").current_dir(src_path))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("cargo:warning=applying 44b3df6f03d85c901767250329c571db405122d5 patch to boringssl");
|
if config.features.pq_experimental {
|
||||||
apply_patch(
|
println!("cargo:warning=applying experimental post quantum crypto patch to boringssl");
|
||||||
config,
|
apply_patch(config, "boring-pq.patch")?;
|
||||||
"boringssl-44b3df6f03d85c901767250329c571db405122d5.patch",
|
}
|
||||||
)?;
|
|
||||||
|
|
||||||
println!("cargo:warning=applying loongarch patch to boringssl");
|
if config.features.rpk {
|
||||||
apply_patch(config, "boringssl-loongarch.patch")?;
|
println!("cargo:warning=applying RPK patch to boringssl");
|
||||||
|
apply_patch(config, "rpk.patch")?;
|
||||||
|
}
|
||||||
|
|
||||||
if config.features.underscore_wildcards {
|
if config.features.underscore_wildcards {
|
||||||
println!("cargo:warning=applying underscore wildcards patch to boringssl");
|
println!("cargo:warning=applying underscore wildcards patch to boringssl");
|
||||||
|
|
@ -546,10 +547,6 @@ fn built_boring_source_path(config: &Config) -> &PathBuf {
|
||||||
.define("FIPS", "1");
|
.define("FIPS", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.features.prefix_symbols {
|
|
||||||
cfg.define("CMAKE_POSITION_INDEPENDENT_CODE", "ON");
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.build_target("ssl").build();
|
cfg.build_target("ssl").build();
|
||||||
cfg.build_target("crypto").build()
|
cfg.build_target("crypto").build()
|
||||||
})
|
})
|
||||||
|
|
@ -563,7 +560,7 @@ fn get_cpp_runtime_lib(config: &Config) -> Option<String> {
|
||||||
// TODO(rmehra): figure out how to do this for windows
|
// TODO(rmehra): figure out how to do this for windows
|
||||||
if env::var_os("CARGO_CFG_UNIX").is_some() {
|
if env::var_os("CARGO_CFG_UNIX").is_some() {
|
||||||
match env::var("CARGO_CFG_TARGET_OS").unwrap().as_ref() {
|
match env::var("CARGO_CFG_TARGET_OS").unwrap().as_ref() {
|
||||||
"macos" | "ios" | "freebsd" => Some("c++".into()),
|
"macos" | "ios" => Some("c++".into()),
|
||||||
_ => Some("stdc++".into()),
|
_ => Some("stdc++".into()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -577,9 +574,6 @@ fn main() {
|
||||||
if !config.env.docs_rs {
|
if !config.env.docs_rs {
|
||||||
emit_link_directives(&config);
|
emit_link_directives(&config);
|
||||||
}
|
}
|
||||||
if config.features.prefix_symbols {
|
|
||||||
prefix_symbols(&config);
|
|
||||||
}
|
|
||||||
generate_bindings(&config);
|
generate_bindings(&config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -652,7 +646,7 @@ fn generate_bindings(config: &Config) {
|
||||||
.derive_copy(true)
|
.derive_copy(true)
|
||||||
.derive_debug(true)
|
.derive_debug(true)
|
||||||
.derive_default(true)
|
.derive_default(true)
|
||||||
.derive_eq(false)
|
.derive_eq(true)
|
||||||
.default_enum_style(bindgen::EnumVariation::NewType {
|
.default_enum_style(bindgen::EnumVariation::NewType {
|
||||||
is_bitfield: false,
|
is_bitfield: false,
|
||||||
is_global: false,
|
is_global: false,
|
||||||
|
|
@ -674,10 +668,6 @@ fn generate_bindings(config: &Config) {
|
||||||
.clang_arg(sysroot.display().to_string());
|
.clang_arg(sysroot.display().to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.features.prefix_symbols {
|
|
||||||
builder = builder.parse_callbacks(Box::new(PrefixCallback));
|
|
||||||
}
|
|
||||||
|
|
||||||
let headers = [
|
let headers = [
|
||||||
"aes.h",
|
"aes.h",
|
||||||
"asn1_mac.h",
|
"asn1_mac.h",
|
||||||
|
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
use crate::{config::Config, pick_best_android_ndk_toolchain, run_command};
|
|
||||||
use std::{fs, io::Write, path::PathBuf, process::Command};
|
|
||||||
// The prefix to add to all symbols
|
|
||||||
// RBSSL = Rust BoringSSL, chosen arbitrarily to avoid collisions with other projects
|
|
||||||
const PREFIX: &str = "RBSSL";
|
|
||||||
// Callback to add a `link_name` macro with the prefix to all generated bindings
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PrefixCallback;
|
|
||||||
impl bindgen::callbacks::ParseCallbacks for PrefixCallback {
|
|
||||||
fn generated_link_name_override(
|
|
||||||
&self,
|
|
||||||
item_info: bindgen::callbacks::ItemInfo<'_>,
|
|
||||||
) -> Option<String> {
|
|
||||||
Some(format!("{PREFIX}_{}", item_info.name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn android_toolchain(config: &Config) -> PathBuf {
|
|
||||||
let mut android_bin_path = config
|
|
||||||
.env
|
|
||||||
.android_ndk_home
|
|
||||||
.clone()
|
|
||||||
.expect("Please set ANDROID_NDK_HOME for Android build");
|
|
||||||
android_bin_path.extend(["toolchains", "llvm", "prebuilt"]);
|
|
||||||
android_bin_path.push(pick_best_android_ndk_toolchain(&android_bin_path).unwrap());
|
|
||||||
android_bin_path.push("bin");
|
|
||||||
android_bin_path
|
|
||||||
}
|
|
||||||
pub fn prefix_symbols(config: &Config) {
|
|
||||||
// List static libraries to prefix symbols in
|
|
||||||
let static_libs: Vec<PathBuf> = [
|
|
||||||
config.out_dir.join("build"),
|
|
||||||
config.out_dir.join("build").join("ssl"),
|
|
||||||
config.out_dir.join("build").join("crypto"),
|
|
||||||
]
|
|
||||||
.iter()
|
|
||||||
.flat_map(|dir| {
|
|
||||||
["libssl.a", "libcrypto.a"]
|
|
||||||
.into_iter()
|
|
||||||
.map(move |file| PathBuf::from(dir).join(file))
|
|
||||||
})
|
|
||||||
.filter(|p| p.exists())
|
|
||||||
.collect();
|
|
||||||
// Use `nm` to list symbols in these static libraries
|
|
||||||
let nm = match &*config.target_os {
|
|
||||||
"android" => android_toolchain(config).join("llvm-nm"),
|
|
||||||
_ => PathBuf::from("nm"),
|
|
||||||
};
|
|
||||||
let out = run_command(Command::new(nm).args(&static_libs)).unwrap();
|
|
||||||
let mut redefine_syms: Vec<String> = String::from_utf8_lossy(&out.stdout)
|
|
||||||
.lines()
|
|
||||||
.filter(|l| {
|
|
||||||
[" T ", " D ", " B ", " C ", " R ", " W "]
|
|
||||||
.iter()
|
|
||||||
.any(|s| l.contains(s))
|
|
||||||
})
|
|
||||||
.filter_map(|l| l.split_whitespace().nth(2).map(|s| s.to_string()))
|
|
||||||
.filter(|l| !l.starts_with("_"))
|
|
||||||
.map(|l| format!("{l} {PREFIX}_{l}"))
|
|
||||||
.collect();
|
|
||||||
redefine_syms.sort();
|
|
||||||
redefine_syms.dedup();
|
|
||||||
let redefine_syms_path = config.out_dir.join("redefine_syms.txt");
|
|
||||||
let mut f = fs::File::create(&redefine_syms_path).unwrap();
|
|
||||||
for sym in &redefine_syms {
|
|
||||||
writeln!(f, "{sym}").unwrap();
|
|
||||||
}
|
|
||||||
f.flush().unwrap();
|
|
||||||
// Use `objcopy` to prefix symbols in these static libraries
|
|
||||||
let objcopy = match &*config.target_os {
|
|
||||||
"android" => android_toolchain(config).join("llvm-objcopy"),
|
|
||||||
_ => PathBuf::from("objcopy"),
|
|
||||||
};
|
|
||||||
for static_lib in &static_libs {
|
|
||||||
run_command(
|
|
||||||
Command::new(&objcopy)
|
|
||||||
.arg(format!("--redefine-syms={}", redefine_syms_path.display()))
|
|
||||||
.arg(static_lib),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 44b3df6f03d85c901767250329c571db405122d5
|
Subproject commit 478b28ab12f2001a03261624261fd041f5439706
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,43 +0,0 @@
|
||||||
From d39506c373dc03b1bfab2c01abc2232e36d13ed6 Mon Sep 17 00:00:00 2001
|
|
||||||
From: WANG Rui <wangrui@loongson.cn>
|
|
||||||
Date: Sun, 27 Apr 2025 16:09:06 +0800
|
|
||||||
Subject: [PATCH] Add basic support for LoongArch
|
|
||||||
|
|
||||||
Signed-off-by: WANG Rui <wangrui@loongson.cn>
|
|
||||||
---
|
|
||||||
src/crypto/fipsmodule/rand/getrandom_fillin.h | 2 ++
|
|
||||||
src/include/openssl/base.h | 5 +++++
|
|
||||||
2 files changed, 7 insertions(+)
|
|
||||||
|
|
||||||
diff --git a/src/crypto/fipsmodule/rand/getrandom_fillin.h b/src/crypto/fipsmodule/rand/getrandom_fillin.h
|
|
||||||
index 0f290e963..669a9077c 100644
|
|
||||||
--- a/src/crypto/fipsmodule/rand/getrandom_fillin.h
|
|
||||||
+++ b/src/crypto/fipsmodule/rand/getrandom_fillin.h
|
|
||||||
@@ -30,6 +30,8 @@
|
|
||||||
#define EXPECTED_NR_getrandom 278
|
|
||||||
#elif defined(OPENSSL_ARM)
|
|
||||||
#define EXPECTED_NR_getrandom 384
|
|
||||||
+#elif defined(OPENSSL_LOONGARCH64)
|
|
||||||
+#define EXPECTED_NR_getrandom 278
|
|
||||||
#elif defined(OPENSSL_RISCV64)
|
|
||||||
#define EXPECTED_NR_getrandom 278
|
|
||||||
#endif
|
|
||||||
diff --git a/src/include/openssl/base.h b/src/include/openssl/base.h
|
|
||||||
index 67429c099..6ef32eeec 100644
|
|
||||||
--- a/src/include/openssl/base.h
|
|
||||||
+++ b/src/include/openssl/base.h
|
|
||||||
@@ -96,6 +96,11 @@ extern "C" {
|
|
||||||
#elif defined(__ARMEL__) || defined(_M_ARM)
|
|
||||||
#define OPENSSL_32_BIT
|
|
||||||
#define OPENSSL_ARM
|
|
||||||
+#elif defined(__loongarch__) && __SIZEOF_POINTER__ == 8
|
|
||||||
+#define OPENSSL_64_BIT
|
|
||||||
+#define OPENSSL_LOONGARCH64
|
|
||||||
+#elif defined(__loongarch__) && __SIZEOF_POINTER__ == 4
|
|
||||||
+#define OPENSSL_32_BIT
|
|
||||||
#elif defined(__MIPSEL__) && !defined(__LP64__)
|
|
||||||
#define OPENSSL_32_BIT
|
|
||||||
#define OPENSSL_MIPS
|
|
||||||
--
|
|
||||||
2.46.0
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,792 @@
|
||||||
|
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
|
||||||
|
index 003e0a5f7..b8f8d49c8 100644
|
||||||
|
--- a/include/openssl/ssl.h
|
||||||
|
+++ b/include/openssl/ssl.h
|
||||||
|
@@ -138,6 +138,25 @@
|
||||||
|
* OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR
|
||||||
|
* OTHERWISE.
|
||||||
|
*/
|
||||||
|
+/* ====================================================================
|
||||||
|
+ * Copyright 2020 Apple Inc.
|
||||||
|
+ *
|
||||||
|
+ * Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
+ * copy of this software and associated documentation files (the “Software”),
|
||||||
|
+ * to deal in the Software without restriction, including without limitation
|
||||||
|
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
+ * and/or sell copies of the Software, and to permit persons to whom
|
||||||
|
+ * the Software is furnished to do so, subject to the following conditions:
|
||||||
|
+ * The above copyright notice and this permission notice shall be included in
|
||||||
|
+ * all copies or substantial portions of the Software.
|
||||||
|
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
+ * IN THE SOFTWARE.
|
||||||
|
+ */
|
||||||
|
|
||||||
|
#ifndef OPENSSL_HEADER_SSL_H
|
||||||
|
#define OPENSSL_HEADER_SSL_H
|
||||||
|
@@ -1138,6 +1157,16 @@ OPENSSL_EXPORT int SSL_CTX_set_chain_and_key(
|
||||||
|
SSL_CTX *ctx, CRYPTO_BUFFER *const *certs, size_t num_certs,
|
||||||
|
EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method);
|
||||||
|
|
||||||
|
+// SSL_CTX_set_nullchain_and_key sets the private key for a
|
||||||
|
+// TLS client or server. Reference to the given |EVP_PKEY|
|
||||||
|
+// object is added as needed. Exactly one of |privkey| or |privkey_method|
|
||||||
|
+// may be non-NULL. Returns one on success and zero on error.
|
||||||
|
+// Note the lack of a corresponding public-key certificate.
|
||||||
|
+// See SSL_CTX_set_server_raw_public_key_certificate.
|
||||||
|
+OPENSSL_EXPORT int SSL_CTX_set_nullchain_and_key(
|
||||||
|
+ SSL_CTX *ctx,
|
||||||
|
+ EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method);
|
||||||
|
+
|
||||||
|
// SSL_set_chain_and_key sets the certificate chain and private key for a TLS
|
||||||
|
// client or server. References to the given |CRYPTO_BUFFER| and |EVP_PKEY|
|
||||||
|
// objects are added as needed. Exactly one of |privkey| or |privkey_method|
|
||||||
|
@@ -1146,6 +1175,16 @@ OPENSSL_EXPORT int SSL_set_chain_and_key(
|
||||||
|
SSL *ssl, CRYPTO_BUFFER *const *certs, size_t num_certs, EVP_PKEY *privkey,
|
||||||
|
const SSL_PRIVATE_KEY_METHOD *privkey_method);
|
||||||
|
|
||||||
|
+// SSL_set_nullchain_and_key sets the private key for a TLS
|
||||||
|
+// client or server. Reference to the given |EVP_PKEY|
|
||||||
|
+// object is added as needed. Exactly one of |privkey| or |privkey_method|
|
||||||
|
+// may be non-NULL. Returns one on success and zero on error.
|
||||||
|
+// Note the lack of a corresponding public-key certificate.
|
||||||
|
+// See SSL_set_server_raw_public_key_certificate.
|
||||||
|
+OPENSSL_EXPORT int SSL_set_nullchain_and_key(
|
||||||
|
+ SSL *ssl, EVP_PKEY *privkey,
|
||||||
|
+ const SSL_PRIVATE_KEY_METHOD *privkey_method);
|
||||||
|
+
|
||||||
|
// SSL_CTX_get0_chain returns the list of |CRYPTO_BUFFER|s that were set by
|
||||||
|
// |SSL_CTX_set_chain_and_key|. Reference counts are not incremented by this
|
||||||
|
// call. The return value may be |NULL| if no chain has been set.
|
||||||
|
@@ -3041,6 +3080,21 @@ OPENSSL_EXPORT int SSL_has_application_settings(const SSL *ssl);
|
||||||
|
OPENSSL_EXPORT void SSL_set_alps_use_new_codepoint(SSL *ssl, int use_new);
|
||||||
|
|
||||||
|
|
||||||
|
+// Server Certificate Type.
|
||||||
|
+
|
||||||
|
+#define TLSEXT_CERTIFICATETYPE_X509 0
|
||||||
|
+#define TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY 2
|
||||||
|
+
|
||||||
|
+OPENSSL_EXPORT int SSL_CTX_set_server_raw_public_key_certificate(
|
||||||
|
+ SSL_CTX *ctx, const uint8_t *raw_public_key, unsigned raw_public_key_len);
|
||||||
|
+
|
||||||
|
+OPENSSL_EXPORT int SSL_CTX_has_server_raw_public_key_certificate(SSL_CTX *ctx);
|
||||||
|
+
|
||||||
|
+OPENSSL_EXPORT int SSL_set_server_raw_public_key_certificate(
|
||||||
|
+ SSL *ssl, const uint8_t *raw_public_key, unsigned raw_public_key_len);
|
||||||
|
+
|
||||||
|
+OPENSSL_EXPORT int SSL_has_server_raw_public_key_certificate(SSL *ssl);
|
||||||
|
+
|
||||||
|
// Certificate compression.
|
||||||
|
//
|
||||||
|
// Certificates in TLS 1.3 can be compressed (RFC 8879). BoringSSL supports this
|
||||||
|
diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h
|
||||||
|
index c1207a3b7..ac6ed222a 100644
|
||||||
|
--- a/include/openssl/tls1.h
|
||||||
|
+++ b/include/openssl/tls1.h
|
||||||
|
@@ -146,6 +146,25 @@
|
||||||
|
* OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR
|
||||||
|
* OTHERWISE.
|
||||||
|
*/
|
||||||
|
+/* ====================================================================
|
||||||
|
+ * Copyright 2020 Apple Inc.
|
||||||
|
+ *
|
||||||
|
+ * Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
+ * copy of this software and associated documentation files (the “Software”),
|
||||||
|
+ * to deal in the Software without restriction, including without limitation
|
||||||
|
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
+ * and/or sell copies of the Software, and to permit persons to whom
|
||||||
|
+ * the Software is furnished to do so, subject to the following conditions:
|
||||||
|
+ * The above copyright notice and this permission notice shall be included in
|
||||||
|
+ * all copies or substantial portions of the Software.
|
||||||
|
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
+ * IN THE SOFTWARE.
|
||||||
|
+ */
|
||||||
|
|
||||||
|
#ifndef OPENSSL_HEADER_TLS1_H
|
||||||
|
#define OPENSSL_HEADER_TLS1_H
|
||||||
|
@@ -197,6 +216,9 @@ extern "C" {
|
||||||
|
// ExtensionType value from RFC 7301
|
||||||
|
#define TLSEXT_TYPE_application_layer_protocol_negotiation 16
|
||||||
|
|
||||||
|
+// ExtensionType value from RFC 7250
|
||||||
|
+#define TLSEXT_TYPE_server_certificate_type 20
|
||||||
|
+
|
||||||
|
// ExtensionType value from RFC 7685
|
||||||
|
#define TLSEXT_TYPE_padding 21
|
||||||
|
|
||||||
|
diff --git a/ssl/extensions.cc b/ssl/extensions.cc
|
||||||
|
index b13400097..8694712fd 100644
|
||||||
|
--- a/ssl/extensions.cc
|
||||||
|
+++ b/ssl/extensions.cc
|
||||||
|
@@ -105,6 +105,25 @@
|
||||||
|
* This product includes cryptographic software written by Eric Young
|
||||||
|
* (eay@cryptsoft.com). This product includes software written by Tim
|
||||||
|
* Hudson (tjh@cryptsoft.com). */
|
||||||
|
+/* ====================================================================
|
||||||
|
+ * Copyright 2020 Apple Inc.
|
||||||
|
+ *
|
||||||
|
+ * Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
+ * copy of this software and associated documentation files (the “Software”),
|
||||||
|
+ * to deal in the Software without restriction, including without limitation
|
||||||
|
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
+ * and/or sell copies of the Software, and to permit persons to whom
|
||||||
|
+ * the Software is furnished to do so, subject to the following conditions:
|
||||||
|
+ * The above copyright notice and this permission notice shall be included in
|
||||||
|
+ * all copies or substantial portions of the Software.
|
||||||
|
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
+ * IN THE SOFTWARE.
|
||||||
|
+ */
|
||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
|
@@ -3108,6 +3127,146 @@ bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert,
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
+// Server Certificate Type
|
||||||
|
+
|
||||||
|
+static bool ext_server_certificate_type_add_clienthello(const SSL_HANDSHAKE *hs,
|
||||||
|
+ CBB *out,
|
||||||
|
+ CBB *out_compressible,
|
||||||
|
+ ssl_client_hello_type_t type) {
|
||||||
|
+
|
||||||
|
+ if (hs->max_version <= TLS1_2_VERSION) {
|
||||||
|
+ return true;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (hs->config->server_certificate_type_list.empty()) {
|
||||||
|
+ return true;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ CBB contents, server_certificate_types;
|
||||||
|
+ if (!CBB_add_u16(out, TLSEXT_TYPE_server_certificate_type) ||
|
||||||
|
+ !CBB_add_u16_length_prefixed(out, &contents) ||
|
||||||
|
+ !CBB_add_u8_length_prefixed(&contents, &server_certificate_types) ||
|
||||||
|
+ !CBB_add_bytes(&server_certificate_types,
|
||||||
|
+ hs->config->server_certificate_type_list.data(),
|
||||||
|
+ hs->config->server_certificate_type_list.size()) ||
|
||||||
|
+ !CBB_flush(out)) {
|
||||||
|
+ return false;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return true;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static bool ssl_is_certificate_type_allowed(CBS *certificate_type_list,
|
||||||
|
+ uint8_t certificate_type)
|
||||||
|
+{
|
||||||
|
+ uint8_t supported_certificate_type;
|
||||||
|
+ while (CBS_len(certificate_type_list) > 0) {
|
||||||
|
+ if (!CBS_get_u8(certificate_type_list,
|
||||||
|
+ &supported_certificate_type)) {
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (supported_certificate_type != certificate_type) {
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return true;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return false;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static bool ext_server_certificate_type_parse_serverhello(SSL_HANDSHAKE *hs,
|
||||||
|
+ uint8_t *out_alert,
|
||||||
|
+ CBS *content)
|
||||||
|
+{
|
||||||
|
+ if (hs->max_version <= TLS1_2_VERSION ||
|
||||||
|
+ hs->config->server_certificate_type_list.empty()) {
|
||||||
|
+ return true;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Strict
|
||||||
|
+ if (!content) {
|
||||||
|
+ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
|
||||||
|
+ *out_alert = SSL_AD_ILLEGAL_PARAMETER;
|
||||||
|
+ return false;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ CBS certificate_type_list =
|
||||||
|
+ MakeConstSpan(hs->config->server_certificate_type_list);
|
||||||
|
+
|
||||||
|
+ uint8_t certificate_type;
|
||||||
|
+ if (CBS_get_u8(content, &certificate_type) &&
|
||||||
|
+ ssl_is_certificate_type_allowed(&certificate_type_list,
|
||||||
|
+ certificate_type)) {
|
||||||
|
+ hs->server_certificate_type = certificate_type;
|
||||||
|
+ hs->server_certificate_type_negotiated = 1;
|
||||||
|
+ return true;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
|
||||||
|
+ *out_alert = SSL_AD_ILLEGAL_PARAMETER;
|
||||||
|
+ return false;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static bool ext_server_certificate_type_parse_clienthello(SSL_HANDSHAKE *hs,
|
||||||
|
+ uint8_t *out_alert,
|
||||||
|
+ CBS *content)
|
||||||
|
+{
|
||||||
|
+ if (!content) {
|
||||||
|
+ return true;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (hs->max_version <= TLS1_2_VERSION ||
|
||||||
|
+ hs->config->server_certificate_type_list.empty()) {
|
||||||
|
+ return true;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ CBS certificate_type_list =
|
||||||
|
+ MakeConstSpan(hs->config->server_certificate_type_list);
|
||||||
|
+
|
||||||
|
+ CBS type_list;
|
||||||
|
+ if (!CBS_get_u8_length_prefixed(content, &type_list)) {
|
||||||
|
+ type_list.len = 0;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ uint8_t type;
|
||||||
|
+ while(CBS_len(&type_list) > 0) {
|
||||||
|
+ if (!CBS_get_u8(&type_list, &type)) {
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (!ssl_is_certificate_type_allowed(&certificate_type_list, type)) {
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ hs->server_certificate_type = type;
|
||||||
|
+ hs->server_certificate_type_negotiated = 1;
|
||||||
|
+ return true;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ *out_alert = SSL_AD_ILLEGAL_PARAMETER;
|
||||||
|
+ return false;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static bool ext_server_certificate_type_add_serverhello(SSL_HANDSHAKE *hs,
|
||||||
|
+ CBB *out)
|
||||||
|
+{
|
||||||
|
+ if (!hs->server_certificate_type_negotiated) {
|
||||||
|
+ return true;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ CBB contents;
|
||||||
|
+ if (!CBB_add_u16(out, TLSEXT_TYPE_server_certificate_type) ||
|
||||||
|
+ !CBB_add_u16_length_prefixed(out, &contents) ||
|
||||||
|
+ !CBB_add_u8(&contents, hs->server_certificate_type) ||
|
||||||
|
+ !CBB_flush(out)) {
|
||||||
|
+ return false;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return true;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
// kExtensions contains all the supported extensions.
|
||||||
|
static const struct tls_extension kExtensions[] = {
|
||||||
|
{
|
||||||
|
@@ -3289,6 +3448,13 @@ static const struct tls_extension kExtensions[] = {
|
||||||
|
ignore_parse_clienthello,
|
||||||
|
ext_alps_add_serverhello_old,
|
||||||
|
},
|
||||||
|
+ {
|
||||||
|
+ TLSEXT_TYPE_server_certificate_type,
|
||||||
|
+ ext_server_certificate_type_add_clienthello,
|
||||||
|
+ ext_server_certificate_type_parse_serverhello,
|
||||||
|
+ ext_server_certificate_type_parse_clienthello,
|
||||||
|
+ ext_server_certificate_type_add_serverhello,
|
||||||
|
+ },
|
||||||
|
};
|
||||||
|
|
||||||
|
#define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension))
|
||||||
|
diff --git a/ssl/handshake.cc b/ssl/handshake.cc
|
||||||
|
index 8d5a23872..c8ca629e8 100644
|
||||||
|
--- a/ssl/handshake.cc
|
||||||
|
+++ b/ssl/handshake.cc
|
||||||
|
@@ -109,6 +109,25 @@
|
||||||
|
* Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED.
|
||||||
|
* ECC cipher suite support in OpenSSL originally developed by
|
||||||
|
* SUN MICROSYSTEMS, INC., and contributed to the OpenSSL project. */
|
||||||
|
+/* ====================================================================
|
||||||
|
+ * Copyright 2020 Apple Inc.
|
||||||
|
+ *
|
||||||
|
+ * Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
+ * copy of this software and associated documentation files (the “Software”),
|
||||||
|
+ * to deal in the Software without restriction, including without limitation
|
||||||
|
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
+ * and/or sell copies of the Software, and to permit persons to whom
|
||||||
|
+ * the Software is furnished to do so, subject to the following conditions:
|
||||||
|
+ * The above copyright notice and this permission notice shall be included in
|
||||||
|
+ * all copies or substantial portions of the Software.
|
||||||
|
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
+ * IN THE SOFTWARE.
|
||||||
|
+ */
|
||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
|
@@ -148,6 +167,7 @@ SSL_HANDSHAKE::SSL_HANDSHAKE(SSL *ssl_arg)
|
||||||
|
handback(false),
|
||||||
|
hints_requested(false),
|
||||||
|
cert_compression_negotiated(false),
|
||||||
|
+ server_certificate_type_negotiated(false),
|
||||||
|
apply_jdk11_workaround(false),
|
||||||
|
can_release_private_key(false),
|
||||||
|
channel_id_negotiated(false) {
|
||||||
|
@@ -365,7 +385,21 @@ enum ssl_verify_result_t ssl_verify_peer_cert(SSL_HANDSHAKE *hs) {
|
||||||
|
|
||||||
|
uint8_t alert = SSL_AD_CERTIFICATE_UNKNOWN;
|
||||||
|
enum ssl_verify_result_t ret;
|
||||||
|
- if (hs->config->custom_verify_callback != nullptr) {
|
||||||
|
+ if (hs->server_certificate_type_negotiated &&
|
||||||
|
+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY) {
|
||||||
|
+ ret = ssl_verify_invalid;
|
||||||
|
+ EVP_PKEY *peer_pubkey = hs->peer_pubkey.get();
|
||||||
|
+ CBS spki = MakeConstSpan(ssl->config->server_raw_public_key_certificate);
|
||||||
|
+ EVP_PKEY *pubkey = EVP_parse_public_key(&spki);
|
||||||
|
+ if (!pubkey) {
|
||||||
|
+ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||||
|
+ alert = SSL_AD_INTERNAL_ERROR;
|
||||||
|
+ } else if (EVP_PKEY_cmp(peer_pubkey, pubkey) == 1 /* Equal */) {
|
||||||
|
+ ret = ssl_verify_ok;
|
||||||
|
+ } else {
|
||||||
|
+ alert = SSL_AD_BAD_CERTIFICATE;
|
||||||
|
+ }
|
||||||
|
+ } else if (hs->config->custom_verify_callback != nullptr) {
|
||||||
|
ret = hs->config->custom_verify_callback(ssl, &alert);
|
||||||
|
switch (ret) {
|
||||||
|
case ssl_verify_ok:
|
||||||
|
diff --git a/ssl/internal.h b/ssl/internal.h
|
||||||
|
index c9facb699..d7363e729 100644
|
||||||
|
--- a/ssl/internal.h
|
||||||
|
+++ b/ssl/internal.h
|
||||||
|
@@ -138,6 +138,25 @@
|
||||||
|
* OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR
|
||||||
|
* OTHERWISE.
|
||||||
|
*/
|
||||||
|
+/* ====================================================================
|
||||||
|
+ * Copyright 2020 Apple Inc.
|
||||||
|
+ *
|
||||||
|
+ * Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
+ * copy of this software and associated documentation files (the “Software”),
|
||||||
|
+ * to deal in the Software without restriction, including without limitation
|
||||||
|
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
+ * and/or sell copies of the Software, and to permit persons to whom
|
||||||
|
+ * the Software is furnished to do so, subject to the following conditions:
|
||||||
|
+ * The above copyright notice and this permission notice shall be included in
|
||||||
|
+ * all copies or substantial portions of the Software.
|
||||||
|
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
+ * IN THE SOFTWARE.
|
||||||
|
+ */
|
||||||
|
|
||||||
|
#ifndef OPENSSL_HEADER_SSL_INTERNAL_H
|
||||||
|
#define OPENSSL_HEADER_SSL_INTERNAL_H
|
||||||
|
@@ -1311,6 +1330,8 @@ int ssl_write_buffer_flush(SSL *ssl);
|
||||||
|
// configured.
|
||||||
|
bool ssl_has_certificate(const SSL_HANDSHAKE *hs);
|
||||||
|
|
||||||
|
+bool ssl_has_raw_public_key_certificate(const SSL_HANDSHAKE *hs);
|
||||||
|
+
|
||||||
|
// ssl_parse_cert_chain parses a certificate list from |cbs| in the format used
|
||||||
|
// by a TLS Certificate message. On success, it advances |cbs| and returns
|
||||||
|
// true. Otherwise, it returns false and sets |*out_alert| to an alert to send
|
||||||
|
@@ -1912,6 +1933,8 @@ struct SSL_HANDSHAKE {
|
||||||
|
// |cert_compression_negotiated| is true.
|
||||||
|
uint16_t cert_compression_alg_id;
|
||||||
|
|
||||||
|
+ uint8_t server_certificate_type;
|
||||||
|
+
|
||||||
|
// ech_hpke_ctx is the HPKE context used in ECH. On the server, it is
|
||||||
|
// initialized if |ech_status| is |ssl_ech_accepted|. On the client, it is
|
||||||
|
// initialized if |selected_ech_config| is not nullptr.
|
||||||
|
@@ -2062,6 +2085,8 @@ struct SSL_HANDSHAKE {
|
||||||
|
// cert_compression_negotiated is true iff |cert_compression_alg_id| is valid.
|
||||||
|
bool cert_compression_negotiated : 1;
|
||||||
|
|
||||||
|
+ bool server_certificate_type_negotiated : 1;
|
||||||
|
+
|
||||||
|
// apply_jdk11_workaround is true if the peer is probably a JDK 11 client
|
||||||
|
// which implemented TLS 1.3 incorrectly.
|
||||||
|
bool apply_jdk11_workaround : 1;
|
||||||
|
@@ -3074,6 +3099,9 @@ struct SSL_CONFIG {
|
||||||
|
// along with their corresponding ALPS values.
|
||||||
|
GrowableArray<ALPSConfig> alps_configs;
|
||||||
|
|
||||||
|
+ Array<uint8_t> server_certificate_type_list;
|
||||||
|
+ Array<uint8_t> server_raw_public_key_certificate;
|
||||||
|
+
|
||||||
|
// Contains the QUIC transport params that this endpoint will send.
|
||||||
|
Array<uint8_t> quic_transport_params;
|
||||||
|
|
||||||
|
@@ -3666,6 +3694,9 @@ struct ssl_ctx_st {
|
||||||
|
// format.
|
||||||
|
bssl::Array<uint8_t> alpn_client_proto_list;
|
||||||
|
|
||||||
|
+ bssl::Array<uint8_t> server_certificate_type_list;
|
||||||
|
+ bssl::Array<uint8_t> server_raw_public_key_certificate;
|
||||||
|
+
|
||||||
|
// SRTP profiles we are willing to do from RFC 5764
|
||||||
|
bssl::UniquePtr<STACK_OF(SRTP_PROTECTION_PROFILE)> srtp_profiles;
|
||||||
|
|
||||||
|
diff --git a/ssl/ssl_cert.cc b/ssl/ssl_cert.cc
|
||||||
|
index aa46a8bb6..d90840fce 100644
|
||||||
|
--- a/ssl/ssl_cert.cc
|
||||||
|
+++ b/ssl/ssl_cert.cc
|
||||||
|
@@ -111,6 +111,25 @@
|
||||||
|
* Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED.
|
||||||
|
* ECC cipher suite support in OpenSSL originally developed by
|
||||||
|
* SUN MICROSYSTEMS, INC., and contributed to the OpenSSL project. */
|
||||||
|
+/* ====================================================================
|
||||||
|
+ * Copyright 2020 Apple Inc.
|
||||||
|
+ *
|
||||||
|
+ * Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
+ * copy of this software and associated documentation files (the “Software”),
|
||||||
|
+ * to deal in the Software without restriction, including without limitation
|
||||||
|
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
+ * and/or sell copies of the Software, and to permit persons to whom
|
||||||
|
+ * the Software is furnished to do so, subject to the following conditions:
|
||||||
|
+ * The above copyright notice and this permission notice shall be included in
|
||||||
|
+ * all copies or substantial portions of the Software.
|
||||||
|
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
+ * IN THE SOFTWARE.
|
||||||
|
+ */
|
||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
|
@@ -302,6 +321,25 @@ static int cert_set_chain_and_key(
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
+static int cert_set_key(
|
||||||
|
+ CERT *cert,
|
||||||
|
+ EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method) {
|
||||||
|
+ if (privkey == NULL && privkey_method == NULL) {
|
||||||
|
+ OPENSSL_PUT_ERROR(SSL, ERR_R_PASSED_NULL_PARAMETER);
|
||||||
|
+ return 0;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (privkey != NULL && privkey_method != NULL) {
|
||||||
|
+ OPENSSL_PUT_ERROR(SSL, SSL_R_CANNOT_HAVE_BOTH_PRIVKEY_AND_METHOD);
|
||||||
|
+ return 0;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ cert->privatekey = UpRef(privkey);
|
||||||
|
+ cert->key_method = privkey_method;
|
||||||
|
+
|
||||||
|
+ return 1;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
bool ssl_set_cert(CERT *cert, UniquePtr<CRYPTO_BUFFER> buffer) {
|
||||||
|
switch (check_leaf_cert_and_privkey(buffer.get(), cert->privatekey.get())) {
|
||||||
|
case leaf_cert_and_privkey_error:
|
||||||
|
@@ -343,6 +381,12 @@ bool ssl_has_certificate(const SSL_HANDSHAKE *hs) {
|
||||||
|
ssl_has_private_key(hs);
|
||||||
|
}
|
||||||
|
|
||||||
|
+bool ssl_has_raw_public_key_certificate(const SSL_HANDSHAKE *hs) {
|
||||||
|
+ return hs->server_certificate_type_negotiated &&
|
||||||
|
+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY &&
|
||||||
|
+ ssl_has_private_key(hs);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
bool ssl_parse_cert_chain(uint8_t *out_alert,
|
||||||
|
UniquePtr<STACK_OF(CRYPTO_BUFFER)> *out_chain,
|
||||||
|
UniquePtr<EVP_PKEY> *out_pubkey,
|
||||||
|
@@ -721,11 +765,20 @@ bool ssl_check_leaf_certificate(SSL_HANDSHAKE *hs, EVP_PKEY *pkey,
|
||||||
|
|
||||||
|
bool ssl_on_certificate_selected(SSL_HANDSHAKE *hs) {
|
||||||
|
SSL *const ssl = hs->ssl;
|
||||||
|
- if (!ssl_has_certificate(hs)) {
|
||||||
|
+ if (!ssl_has_certificate(hs) &&
|
||||||
|
+ !ssl_has_raw_public_key_certificate(hs)) {
|
||||||
|
// Nothing to do.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ if (ssl_has_raw_public_key_certificate(hs)) {
|
||||||
|
+ CBS spki = MakeConstSpan(
|
||||||
|
+ ssl->config->server_raw_public_key_certificate.data(),
|
||||||
|
+ ssl->config->server_raw_public_key_certificate.size());
|
||||||
|
+ hs->local_pubkey = UniquePtr<EVP_PKEY>(EVP_parse_public_key(&spki));
|
||||||
|
+ return hs->local_pubkey != NULL;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
if (!ssl->ctx->x509_method->ssl_auto_chain_if_needed(hs)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@@ -880,6 +933,15 @@ int SSL_set_chain_and_key(SSL *ssl, CRYPTO_BUFFER *const *certs,
|
||||||
|
privkey, privkey_method);
|
||||||
|
}
|
||||||
|
|
||||||
|
+int SSL_set_nullchain_and_key(SSL *ssl,
|
||||||
|
+ EVP_PKEY *privkey,
|
||||||
|
+ const SSL_PRIVATE_KEY_METHOD *privkey_method) {
|
||||||
|
+ if (!ssl->config) {
|
||||||
|
+ return 0;
|
||||||
|
+ }
|
||||||
|
+ return cert_set_key(ssl->config->cert.get(), privkey, privkey_method);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
int SSL_CTX_set_chain_and_key(SSL_CTX *ctx, CRYPTO_BUFFER *const *certs,
|
||||||
|
size_t num_certs, EVP_PKEY *privkey,
|
||||||
|
const SSL_PRIVATE_KEY_METHOD *privkey_method) {
|
||||||
|
@@ -887,6 +949,12 @@ int SSL_CTX_set_chain_and_key(SSL_CTX *ctx, CRYPTO_BUFFER *const *certs,
|
||||||
|
privkey_method);
|
||||||
|
}
|
||||||
|
|
||||||
|
+int SSL_CTX_set_nullchain_and_key(SSL_CTX *ctx,
|
||||||
|
+ EVP_PKEY *privkey,
|
||||||
|
+ const SSL_PRIVATE_KEY_METHOD *privkey_method) {
|
||||||
|
+ return cert_set_key(ctx->cert.get(), privkey, privkey_method);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
const STACK_OF(CRYPTO_BUFFER)* SSL_CTX_get0_chain(const SSL_CTX *ctx) {
|
||||||
|
return ctx->cert->chain.get();
|
||||||
|
}
|
||||||
|
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
|
||||||
|
index 58b68e675..384debbd3 100644
|
||||||
|
--- a/ssl/ssl_lib.cc
|
||||||
|
+++ b/ssl/ssl_lib.cc
|
||||||
|
@@ -137,6 +137,25 @@
|
||||||
|
* SPECIFICALLY DISCLAIMS ANY LIABILITY FOR CLAIMS BROUGHT BY YOU OR ANY
|
||||||
|
* OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR
|
||||||
|
* OTHERWISE. */
|
||||||
|
+/* ====================================================================
|
||||||
|
+ * Copyright 2020 Apple Inc.
|
||||||
|
+ *
|
||||||
|
+ * Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
+ * copy of this software and associated documentation files (the “Software”),
|
||||||
|
+ * to deal in the Software without restriction, including without limitation
|
||||||
|
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
+ * and/or sell copies of the Software, and to permit persons to whom
|
||||||
|
+ * the Software is furnished to do so, subject to the following conditions:
|
||||||
|
+ * The above copyright notice and this permission notice shall be included in
|
||||||
|
+ * all copies or substantial portions of the Software.
|
||||||
|
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
+ * IN THE SOFTWARE.
|
||||||
|
+ */
|
||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
|
@@ -687,6 +706,11 @@ SSL *SSL_new(SSL_CTX *ctx) {
|
||||||
|
ssl->config->handoff = ctx->handoff;
|
||||||
|
ssl->quic_method = ctx->quic_method;
|
||||||
|
|
||||||
|
+ ssl->config->server_certificate_type_list.CopyFrom(
|
||||||
|
+ ctx->server_certificate_type_list);
|
||||||
|
+ ssl->config->server_raw_public_key_certificate.CopyFrom(
|
||||||
|
+ ctx->server_raw_public_key_certificate);
|
||||||
|
+
|
||||||
|
if (!ssl->method->ssl_new(ssl.get()) ||
|
||||||
|
!ssl->ctx->x509_method->ssl_new(ssl->s3->hs.get())) {
|
||||||
|
return nullptr;
|
||||||
|
@@ -3249,6 +3273,53 @@ int SSL_set1_curves_list(SSL *ssl, const char *curves) {
|
||||||
|
return SSL_set1_groups_list(ssl, curves);
|
||||||
|
}
|
||||||
|
|
||||||
|
+int SSL_CTX_set_server_raw_public_key_certificate(SSL_CTX *ctx,
|
||||||
|
+ const uint8_t *raw_public_key, unsigned raw_public_key_len) {
|
||||||
|
+ if (!ctx->server_raw_public_key_certificate.CopyFrom(
|
||||||
|
+ MakeConstSpan(raw_public_key, raw_public_key_len))) {
|
||||||
|
+ return 0; /* Failure */
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (!ctx->server_certificate_type_list.Init(1)) {
|
||||||
|
+ return 0;
|
||||||
|
+ }
|
||||||
|
+ ctx->server_certificate_type_list[0] = TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY;
|
||||||
|
+
|
||||||
|
+ return 1; /* Success */
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+int SSL_CTX_has_server_raw_public_key_certificate(SSL_CTX *ctx) {
|
||||||
|
+ return !ctx->server_raw_public_key_certificate.empty();
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+int SSL_set_server_raw_public_key_certificate(SSL *ssl,
|
||||||
|
+ const uint8_t *raw_public_key, unsigned raw_public_key_len) {
|
||||||
|
+ if (!ssl->config) {
|
||||||
|
+ return 0; /* Failure */
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (!ssl->config->server_raw_public_key_certificate.CopyFrom(
|
||||||
|
+ MakeConstSpan(raw_public_key, raw_public_key_len))) {
|
||||||
|
+ return 0;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (!ssl->config->server_certificate_type_list.Init(1)) {
|
||||||
|
+ return 0;
|
||||||
|
+ }
|
||||||
|
+ ssl->config->server_certificate_type_list[0] =
|
||||||
|
+ TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY;
|
||||||
|
+
|
||||||
|
+ return 1; /* Success */
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+int SSL_has_server_raw_public_key_certificate(SSL *ssl) {
|
||||||
|
+ if (!ssl->config) {
|
||||||
|
+ return 0; /* Failure */
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return !ssl->config->server_raw_public_key_certificate.empty();
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
namespace fips202205 {
|
||||||
|
|
||||||
|
// (References are to SP 800-52r2):
|
||||||
|
diff --git a/ssl/tls13_both.cc b/ssl/tls13_both.cc
|
||||||
|
index 5ab5a1c93..79135613e 100644
|
||||||
|
--- a/ssl/tls13_both.cc
|
||||||
|
+++ b/ssl/tls13_both.cc
|
||||||
|
@@ -11,6 +11,25 @@
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
|
||||||
|
+/* ====================================================================
|
||||||
|
+ * Copyright 2020 Apple Inc.
|
||||||
|
+ *
|
||||||
|
+ * Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
+ * copy of this software and associated documentation files (the “Software”),
|
||||||
|
+ * to deal in the Software without restriction, including without limitation
|
||||||
|
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
+ * and/or sell copies of the Software, and to permit persons to whom
|
||||||
|
+ * the Software is furnished to do so, subject to the following conditions:
|
||||||
|
+ * The above copyright notice and this permission notice shall be included in
|
||||||
|
+ * all copies or substantial portions of the Software.
|
||||||
|
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
+ * IN THE SOFTWARE.
|
||||||
|
+ */
|
||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
|
@@ -197,7 +216,16 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg,
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
- if (sk_CRYPTO_BUFFER_num(certs.get()) == 0) {
|
||||||
|
+ if (hs->server_certificate_type_negotiated &&
|
||||||
|
+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY) {
|
||||||
|
+ pkey = UniquePtr<EVP_PKEY>(EVP_parse_public_key(&certificate));
|
||||||
|
+ if (!pkey) {
|
||||||
|
+ ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
|
||||||
|
+ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||||
|
+ return false;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ else if (sk_CRYPTO_BUFFER_num(certs.get()) == 0) {
|
||||||
|
pkey = ssl_cert_parse_pubkey(&certificate);
|
||||||
|
if (!pkey) {
|
||||||
|
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
|
||||||
|
@@ -299,7 +327,10 @@ bool tls13_process_certificate(SSL_HANDSHAKE *hs, const SSLMessage &msg,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sk_CRYPTO_BUFFER_num(hs->new_session->certs.get()) == 0) {
|
||||||
|
- if (!allow_anonymous) {
|
||||||
|
+ if (!allow_anonymous &&
|
||||||
|
+ !(hs->server_certificate_type_negotiated &&
|
||||||
|
+ hs->server_certificate_type ==
|
||||||
|
+ TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY)) {
|
||||||
|
OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE);
|
||||||
|
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_CERTIFICATE_REQUIRED);
|
||||||
|
return false;
|
||||||
|
@@ -416,6 +447,20 @@ bool tls13_add_certificate(SSL_HANDSHAKE *hs) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ if (hs->server_certificate_type_negotiated &&
|
||||||
|
+ hs->server_certificate_type == TLSEXT_CERTIFICATETYPE_RAW_PUBLIC_KEY) {
|
||||||
|
+ CBB leaf, extensions;
|
||||||
|
+ if (!CBB_add_u24_length_prefixed(&certificate_list, &leaf) ||
|
||||||
|
+ !CBB_add_bytes(&leaf,
|
||||||
|
+ ssl->config->server_raw_public_key_certificate.data(),
|
||||||
|
+ ssl->config->server_raw_public_key_certificate.size()) ||
|
||||||
|
+ !CBB_add_u16_length_prefixed(&certificate_list, &extensions)) {
|
||||||
|
+ OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
||||||
|
+ return false;
|
||||||
|
+ }
|
||||||
|
+ return ssl_add_message_cbb(ssl, cbb.get());
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
if (!ssl_has_certificate(hs)) {
|
||||||
|
return ssl_add_message_cbb(ssl, cbb.get());
|
||||||
|
}
|
||||||
|
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc
|
||||||
|
index 707cf846b..6916606c2 100644
|
||||||
|
--- a/ssl/tls13_server.cc
|
||||||
|
+++ b/ssl/tls13_server.cc
|
||||||
|
@@ -11,6 +11,25 @@
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
|
||||||
|
+/* ====================================================================
|
||||||
|
+ * Copyright 2020 Apple Inc.
|
||||||
|
+ *
|
||||||
|
+ * Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
+ * copy of this software and associated documentation files (the “Software”),
|
||||||
|
+ * to deal in the Software without restriction, including without limitation
|
||||||
|
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
+ * and/or sell copies of the Software, and to permit persons to whom
|
||||||
|
+ * the Software is furnished to do so, subject to the following conditions:
|
||||||
|
+ * The above copyright notice and this permission notice shall be included in
|
||||||
|
+ * all copies or substantial portions of the Software.
|
||||||
|
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
+ * IN THE SOFTWARE.
|
||||||
|
+ */
|
||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
|
@@ -860,7 +879,8 @@ static enum ssl_hs_wait_t do_send_server_hello(SSL_HANDSHAKE *hs) {
|
||||||
|
|
||||||
|
// Send the server Certificate message, if necessary.
|
||||||
|
if (!ssl->s3->session_reused) {
|
||||||
|
- if (!ssl_has_certificate(hs)) {
|
||||||
|
+ if (!ssl_has_certificate(hs) &&
|
||||||
|
+ !ssl_has_raw_public_key_certificate(hs)) {
|
||||||
|
OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CERTIFICATE_SET);
|
||||||
|
return ssl_hs_error;
|
||||||
|
}
|
||||||
|
|
@ -1,21 +1,10 @@
|
||||||
https://github.com/google/boringssl/compare/master...cloudflare:boringssl:underscore-wildcards
|
https://github.com/google/boringssl/compare/master...cloudflare:boringssl:underscore-wildcards
|
||||||
|
|
||||||
--- a/src/crypto/x509v3/v3_utl.c
|
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
|
||||||
+++ b/src/crypto/x509v3/v3_utl.c
|
index 9699b5a75..b0e9b34a6 100644
|
||||||
@@ -790,7 +790,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len,
|
--- a/crypto/x509/x509_test.cc
|
||||||
// Check that the part matched by the wildcard contains only
|
+++ b/crypto/x509/x509_test.cc
|
||||||
// permitted characters and only matches a single label.
|
@@ -4420,6 +4420,31 @@ TEST(X509Test, Names) {
|
||||||
for (p = wildcard_start; p != wildcard_end; ++p) {
|
|
||||||
- if (!OPENSSL_isalnum(*p) && *p != '-') {
|
|
||||||
+ if (!OPENSSL_isalnum(*p) && *p != '-' &&
|
|
||||||
+ !(*p == '_' &&
|
|
||||||
+ (flags & X509_CHECK_FLAG_UNDERSCORE_WILDCARDS))) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
--- a/src/crypto/x509/x509_test.cc
|
|
||||||
+++ b/src/crypto/x509/x509_test.cc
|
|
||||||
@@ -4500,6 +4500,31 @@ TEST(X509Test, Names) {
|
|
||||||
/*invalid_emails=*/{},
|
/*invalid_emails=*/{},
|
||||||
/*flags=*/0,
|
/*flags=*/0,
|
||||||
},
|
},
|
||||||
|
|
@ -47,9 +36,26 @@ https://github.com/google/boringssl/compare/master...cloudflare:boringssl:unders
|
||||||
};
|
};
|
||||||
|
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
--- a/src/include/openssl/x509c3.h
|
diff --git a/crypto/x509v3/v3_utl.c b/crypto/x509v3/v3_utl.c
|
||||||
+++ b/src/include/openssl/x509v3.h
|
index bbc82e283..e61e1901d 100644
|
||||||
@@ -4497,6 +4497,8 @@ OPENSSL_EXPORT int X509_PURPOSE_get_id(const X509_PURPOSE *);
|
--- a/crypto/x509v3/v3_utl.c
|
||||||
|
+++ b/crypto/x509v3/v3_utl.c
|
||||||
|
@@ -790,7 +790,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len,
|
||||||
|
// Check that the part matched by the wildcard contains only
|
||||||
|
// permitted characters and only matches a single label.
|
||||||
|
for (p = wildcard_start; p != wildcard_end; ++p) {
|
||||||
|
- if (!OPENSSL_isalnum(*p) && *p != '-') {
|
||||||
|
+ if (!OPENSSL_isalnum(*p) && *p != '-' &&
|
||||||
|
+ !(*p == '_' &&
|
||||||
|
+ (flags & X509_CHECK_FLAG_UNDERSCORE_WILDCARDS))) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff --git a/include/openssl/x509v3.h b/include/openssl/x509v3.h
|
||||||
|
index 2a2e02c2e..24e0604b0 100644
|
||||||
|
--- a/include/openssl/x509v3.h
|
||||||
|
+++ b/include/openssl/x509v3.h
|
||||||
|
@@ -939,6 +939,8 @@ OPENSSL_EXPORT STACK_OF(OPENSSL_STRING) *X509_get1_ocsp(X509 *x);
|
||||||
#define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS 0
|
#define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS 0
|
||||||
// Skip the subject common name fallback if subjectAltNames is missing.
|
// Skip the subject common name fallback if subjectAltNames is missing.
|
||||||
#define X509_CHECK_FLAG_NEVER_CHECK_SUBJECT 0x20
|
#define X509_CHECK_FLAG_NEVER_CHECK_SUBJECT 0x20
|
||||||
|
|
@ -58,4 +64,3 @@ https://github.com/google/boringssl/compare/master...cloudflare:boringssl:unders
|
||||||
|
|
||||||
OPENSSL_EXPORT int X509_check_host(X509 *x, const char *chk, size_t chklen,
|
OPENSSL_EXPORT int X509_check_host(X509 *x, const char *chk, size_t chklen,
|
||||||
unsigned int flags, char **peername);
|
unsigned int flags, char **peername);
|
||||||
--
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
[package]
|
[package]
|
||||||
name = "boring2"
|
name = "boring"
|
||||||
version = { workspace = true }
|
version = { workspace = true }
|
||||||
authors = ["Steven Fackler <sfackler@gmail.com>", "Ivan Nikulin <ifaaan@gmail.com>"]
|
authors = ["Steven Fackler <sfackler@gmail.com>", "Ivan Nikulin <ifaaan@gmail.com>"]
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
description = "BoringSSL bindings"
|
description = "BoringSSL bindings"
|
||||||
repository = { workspace = true }
|
repository = { workspace = true }
|
||||||
documentation = "https://docs.rs/boring2"
|
documentation = "https://docs.rs/boring"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["crypto", "tls", "ssl", "dtls"]
|
keywords = ["crypto", "tls", "ssl", "dtls"]
|
||||||
categories = ["cryptography", "api-bindings"]
|
categories = ["cryptography", "api-bindings"]
|
||||||
|
|
@ -13,7 +13,7 @@ edition = { workspace = true }
|
||||||
rust-version = "1.80"
|
rust-version = "1.80"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["underscore-wildcards"]
|
features = ["rpk", "pq-experimental", "underscore-wildcards"]
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
@ -25,16 +25,24 @@ fips = ["boring-sys/fips"]
|
||||||
# **DO NOT USE** This will be removed without warning in future releases.
|
# **DO NOT USE** This will be removed without warning in future releases.
|
||||||
legacy-compat-deprecated = []
|
legacy-compat-deprecated = []
|
||||||
|
|
||||||
# Applies a patch to enable `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. This
|
# Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250)
|
||||||
# feature is necessary in order to compile the bindings for the default branch
|
# This feature is necessary in order to compile the bindings for the
|
||||||
# of boringSSL. Alternatively, a version of boringSSL that implements the same
|
# default branch of boringSSL. Alternatively, a version of boringSSL that
|
||||||
# feature set can be provided by setting `BORING_BSSL{,_FIPS}_SOURCE_PATH` and
|
# implements the same feature set can be provided by setting
|
||||||
# `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`.
|
# `BORING_BSSL{,_FIPS}_SOURCE_PATH` and `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`.
|
||||||
underscore-wildcards = ["boring-sys/underscore-wildcards"]
|
rpk = ["boring-sys/rpk"]
|
||||||
|
|
||||||
# Add a prefix to all symbols in libcrypto and libssl to prevent conflicts
|
# Applies a patch to the boringSSL source code that enables support for PQ key
|
||||||
# with other OpenSSL or BoringSSL versions that might be linked in the same process.
|
# exchange. This feature is necessary in order to compile the bindings for the
|
||||||
prefix-symbols = ["boring-sys/prefix-symbols"]
|
# 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.
|
||||||
|
underscore-wildcards = ["boring-sys/underscore-wildcards"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = { workspace = true }
|
bitflags = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use boring2 as boring;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("boring::fips::enabled(): {}", boring::fips::enabled());
|
println!("boring::fips::enabled(): {}", boring::fips::enabled());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//! A program that generates ca certs, certs verified by the ca, and public
|
//! A program that generates ca certs, certs verified by the ca, and public
|
||||||
//! and private keys.
|
//! and private keys.
|
||||||
|
|
||||||
extern crate boring2 as boring;
|
extern crate boring;
|
||||||
|
|
||||||
use boring::asn1::Asn1Time;
|
use boring::asn1::Asn1Time;
|
||||||
use boring::bn::{BigNum, MsbOption};
|
use boring::bn::{BigNum, MsbOption};
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
//!
|
//!
|
||||||
//! ## Key wrapping
|
//! ## Key wrapping
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use boring2::aes::{AesKey, unwrap_key, wrap_key};
|
//! use boring::aes::{AesKey, unwrap_key, wrap_key};
|
||||||
//!
|
//!
|
||||||
//! let kek = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F";
|
//! let kek = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F";
|
||||||
//! let key_to_wrap = b"\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF";
|
//! let key_to_wrap = b"\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF";
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use boring2::asn1::Asn1Time;
|
//! use boring::asn1::Asn1Time;
|
||||||
//! let tomorrow = Asn1Time::days_from_now(1);
|
//! let tomorrow = Asn1Time::days_from_now(1);
|
||||||
//! ```
|
//! ```
|
||||||
use crate::ffi;
|
use crate::ffi;
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@
|
||||||
//! # Examples
|
//! # Examples
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use boring2::bn::BigNum;
|
//! use boring::bn::BigNum;
|
||||||
//! use boring2::error::ErrorStack;
|
//! use boring::error::ErrorStack;
|
||||||
//!
|
//!
|
||||||
//! fn main() -> Result<(), ErrorStack> {
|
//! fn main() -> Result<(), ErrorStack> {
|
||||||
//! let a = BigNum::new()?; // a = 0
|
//! let a = BigNum::new()?; // a = 0
|
||||||
|
|
@ -97,8 +97,8 @@ foreign_type_and_impl_send_sync! {
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use boring2::bn::BigNum;
|
/// use boring::bn::BigNum;
|
||||||
/// # use boring2::error::ErrorStack;
|
/// # use boring::error::ErrorStack;
|
||||||
/// # fn bignums() -> Result< (), ErrorStack > {
|
/// # fn bignums() -> Result< (), ErrorStack > {
|
||||||
/// let little_big = BigNum::from_u32(std::u32::MAX)?;
|
/// let little_big = BigNum::from_u32(std::u32::MAX)?;
|
||||||
/// assert_eq!(*&little_big.num_bytes(), 4);
|
/// assert_eq!(*&little_big.num_bytes(), 4);
|
||||||
|
|
@ -272,7 +272,7 @@ impl BigNumRef {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use boring2::bn::BigNum;
|
/// # use boring::bn::BigNum;
|
||||||
/// # use std::cmp::Ordering;
|
/// # use std::cmp::Ordering;
|
||||||
/// let s = -BigNum::from_u32(8).unwrap();
|
/// let s = -BigNum::from_u32(8).unwrap();
|
||||||
/// let o = BigNum::from_u32(8).unwrap();
|
/// let o = BigNum::from_u32(8).unwrap();
|
||||||
|
|
@ -316,8 +316,8 @@ impl BigNumRef {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use boring2::bn::{BigNum, MsbOption};
|
/// use boring::bn::{BigNum, MsbOption};
|
||||||
/// use boring2::error::ErrorStack;
|
/// use boring::error::ErrorStack;
|
||||||
///
|
///
|
||||||
/// fn generate_random() -> Result< BigNum, ErrorStack > {
|
/// fn generate_random() -> Result< BigNum, ErrorStack > {
|
||||||
/// let mut big = BigNum::new()?;
|
/// let mut big = BigNum::new()?;
|
||||||
|
|
@ -370,8 +370,8 @@ impl BigNumRef {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use boring2::bn::BigNum;
|
/// use boring::bn::BigNum;
|
||||||
/// use boring2::error::ErrorStack;
|
/// use boring::error::ErrorStack;
|
||||||
///
|
///
|
||||||
/// fn generate_weak_prime() -> Result< BigNum, ErrorStack > {
|
/// fn generate_weak_prime() -> Result< BigNum, ErrorStack > {
|
||||||
/// let mut big = BigNum::new()?;
|
/// let mut big = BigNum::new()?;
|
||||||
|
|
@ -729,7 +729,7 @@ impl BigNumRef {
|
||||||
/// `self` can be recreated by using `from_slice`.
|
/// `self` can be recreated by using `from_slice`.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use boring2::bn::BigNum;
|
/// # use boring::bn::BigNum;
|
||||||
/// let s = -BigNum::from_u32(4543).unwrap();
|
/// let s = -BigNum::from_u32(4543).unwrap();
|
||||||
/// let r = BigNum::from_u32(4543).unwrap();
|
/// let r = BigNum::from_u32(4543).unwrap();
|
||||||
///
|
///
|
||||||
|
|
@ -756,7 +756,7 @@ impl BigNumRef {
|
||||||
/// `self` can be recreated by using `from_slice`.
|
/// `self` can be recreated by using `from_slice`.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use boring2::bn::BigNum;
|
/// # use boring::bn::BigNum;
|
||||||
/// let bn = BigNum::from_u32(0x4543).unwrap();
|
/// let bn = BigNum::from_u32(0x4543).unwrap();
|
||||||
///
|
///
|
||||||
/// let bn_vec = bn.to_vec_padded(4).unwrap();
|
/// let bn_vec = bn.to_vec_padded(4).unwrap();
|
||||||
|
|
@ -781,7 +781,7 @@ impl BigNumRef {
|
||||||
/// Returns a decimal string representation of `self`.
|
/// Returns a decimal string representation of `self`.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use boring2::bn::BigNum;
|
/// # use boring::bn::BigNum;
|
||||||
/// let s = -BigNum::from_u32(12345).unwrap();
|
/// let s = -BigNum::from_u32(12345).unwrap();
|
||||||
///
|
///
|
||||||
/// assert_eq!(&**s.to_dec_str().unwrap(), "-12345");
|
/// assert_eq!(&**s.to_dec_str().unwrap(), "-12345");
|
||||||
|
|
@ -797,7 +797,7 @@ impl BigNumRef {
|
||||||
/// Returns a hexadecimal string representation of `self`.
|
/// Returns a hexadecimal string representation of `self`.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use boring2::bn::BigNum;
|
/// # use boring::bn::BigNum;
|
||||||
/// let s = -BigNum::from_u32(0x99ff).unwrap();
|
/// let s = -BigNum::from_u32(0x99ff).unwrap();
|
||||||
///
|
///
|
||||||
/// assert_eq!(&**s.to_hex_str().unwrap(), "-99ff");
|
/// assert_eq!(&**s.to_hex_str().unwrap(), "-99ff");
|
||||||
|
|
@ -870,7 +870,7 @@ impl BigNum {
|
||||||
/// [`BN_bin2bn`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_bin2bn.html
|
/// [`BN_bin2bn`]: https://www.openssl.org/docs/man1.1.0/crypto/BN_bin2bn.html
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use boring2::bn::BigNum;
|
/// # use boring::bn::BigNum;
|
||||||
/// let bignum = BigNum::from_slice(&[0x12, 0x00, 0x34]).unwrap();
|
/// let bignum = BigNum::from_slice(&[0x12, 0x00, 0x34]).unwrap();
|
||||||
///
|
///
|
||||||
/// assert_eq!(bignum, BigNum::from_u32(0x120034).unwrap());
|
/// assert_eq!(bignum, BigNum::from_u32(0x120034).unwrap());
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,9 @@ generic_foreign_type_and_impl_send_sync! {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use boring2::dsa::Dsa;
|
/// use boring::dsa::Dsa;
|
||||||
/// use boring2::error::ErrorStack;
|
/// use boring::error::ErrorStack;
|
||||||
/// use boring2::pkey::Private;
|
/// use boring::pkey::Private;
|
||||||
///
|
///
|
||||||
/// fn create_dsa() -> Result<Dsa<Private>, ErrorStack> {
|
/// fn create_dsa() -> Result<Dsa<Private>, ErrorStack> {
|
||||||
/// let sign = Dsa::generate(2048)?;
|
/// let sign = Dsa::generate(2048)?;
|
||||||
|
|
|
||||||
|
|
@ -609,10 +609,10 @@ impl EcKey<Public> {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// use boring2::bn::BigNumContext;
|
/// use boring::bn::BigNumContext;
|
||||||
/// use boring2::ec::*;
|
/// use boring::ec::*;
|
||||||
/// use boring2::nid::Nid;
|
/// use boring::nid::Nid;
|
||||||
/// use boring2::pkey::PKey;
|
/// use boring::pkey::PKey;
|
||||||
///
|
///
|
||||||
/// // get bytes from somewhere, i.e. this will not produce a valid key
|
/// // get bytes from somewhere, i.e. this will not produce a valid key
|
||||||
/// let public_key: Vec<u8> = vec![];
|
/// let public_key: Vec<u8> = vec![];
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
//! # Examples
|
//! # Examples
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use boring2::error::ErrorStack;
|
//! use boring::error::ErrorStack;
|
||||||
//! use boring2::bn::BigNum;
|
//! use boring::bn::BigNum;
|
||||||
//!
|
//!
|
||||||
//! let an_error = BigNum::from_dec_str("Cannot parse letters");
|
//! let an_error = BigNum::from_dec_str("Cannot parse letters");
|
||||||
//! match an_error {
|
//! match an_error {
|
||||||
|
|
@ -103,7 +103,7 @@ impl fmt::Display for ErrorStack {
|
||||||
write!(
|
write!(
|
||||||
fmt,
|
fmt,
|
||||||
"[{}]",
|
"[{}]",
|
||||||
err.reason()
|
err.reason_internal()
|
||||||
.or_else(|| err.library())
|
.or_else(|| err.library())
|
||||||
.unwrap_or("unknown reason")
|
.unwrap_or("unknown reason")
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -252,10 +252,7 @@ impl Error {
|
||||||
|
|
||||||
/// Returns the reason for the error.
|
/// Returns the reason for the error.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn reason(&self) -> Option<&str> {
|
pub fn reason(&self) -> Option<&'static str> {
|
||||||
if self.is_internal() {
|
|
||||||
return self.data();
|
|
||||||
}
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let cstr = ffi::ERR_reason_error_string(self.code);
|
let cstr = ffi::ERR_reason_error_string(self.code);
|
||||||
if cstr.is_null() {
|
if cstr.is_null() {
|
||||||
|
|
@ -333,6 +330,15 @@ impl Error {
|
||||||
fn is_internal(&self) -> bool {
|
fn is_internal(&self) -> bool {
|
||||||
std::ptr::eq(self.file, BORING_INTERNAL.as_ptr())
|
std::ptr::eq(self.file, BORING_INTERNAL.as_ptr())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reason() needs 'static
|
||||||
|
fn reason_internal(&self) -> Option<&str> {
|
||||||
|
if self.is_internal() {
|
||||||
|
self.data()
|
||||||
|
} else {
|
||||||
|
self.reason()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Error {
|
impl fmt::Debug for Error {
|
||||||
|
|
@ -363,7 +369,7 @@ impl fmt::Display for Error {
|
||||||
write!(
|
write!(
|
||||||
fmt,
|
fmt,
|
||||||
"{}\n\nCode: {:08X}\nLoc: {}:{}",
|
"{}\n\nCode: {:08X}\nLoc: {}:{}",
|
||||||
self.reason().unwrap_or("unknown TLS error"),
|
self.reason_internal().unwrap_or("unknown TLS error"),
|
||||||
&self.code,
|
&self.code,
|
||||||
self.file(),
|
self.file(),
|
||||||
self.line()
|
self.line()
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ use self::State::*;
|
||||||
/// Calculate a hash in one go:
|
/// Calculate a hash in one go:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use boring2::hash::{hash, MessageDigest};
|
/// use boring::hash::{hash, MessageDigest};
|
||||||
///
|
///
|
||||||
/// let data = b"\x42\xF4\x97\xE0";
|
/// let data = b"\x42\xF4\x97\xE0";
|
||||||
/// let spec = b"\x7c\x43\x0f\x17\x8a\xef\xdf\x14\x87\xfe\xe7\x14\x4e\x96\x41\xe2";
|
/// let spec = b"\x7c\x43\x0f\x17\x8a\xef\xdf\x14\x87\xfe\xe7\x14\x4e\x96\x41\xe2";
|
||||||
|
|
@ -127,7 +127,7 @@ use self::State::*;
|
||||||
/// Supply the input in chunks:
|
/// Supply the input in chunks:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use boring2::hash::{Hasher, MessageDigest};
|
/// use boring::hash::{Hasher, MessageDigest};
|
||||||
///
|
///
|
||||||
/// let data = [b"\x42\xF4", b"\x97\xE0"];
|
/// let data = [b"\x42\xF4", b"\x97\xE0"];
|
||||||
/// let spec = b"\x7c\x43\x0f\x17\x8a\xef\xdf\x14\x87\xfe\xe7\x14\x4e\x96\x41\xe2";
|
/// let spec = b"\x7c\x43\x0f\x17\x8a\xef\xdf\x14\x87\xfe\xe7\x14\x4e\x96\x41\xe2";
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,11 @@
|
||||||
//!
|
//!
|
||||||
//! # Optional patches
|
//! # Optional patches
|
||||||
//!
|
//!
|
||||||
|
//! ## Raw Public Key
|
||||||
|
//!
|
||||||
|
//! The crate can be compiled with [RawPublicKey](https://datatracker.ietf.org/doc/html/rfc7250)
|
||||||
|
//! support by turning on `rpk` compilation feature.
|
||||||
|
//!
|
||||||
//! ## Experimental post-quantum cryptography
|
//! ## Experimental post-quantum cryptography
|
||||||
//!
|
//!
|
||||||
//! The crate can be compiled with [post-quantum cryptography](https://blog.cloudflare.com/post-quantum-for-all/)
|
//! The crate can be compiled with [post-quantum cryptography](https://blog.cloudflare.com/post-quantum-for-all/)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
//! values:
|
//! values:
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use boring2::memcmp::eq;
|
//! use boring::memcmp::eq;
|
||||||
//!
|
//!
|
||||||
//! // We want to compare `a` to `b` and `c`, without giving
|
//! // We want to compare `a` to `b` and `c`, without giving
|
||||||
//! // away through timing analysis that `c` is more similar to `a`
|
//! // away through timing analysis that `c` is more similar to `a`
|
||||||
|
|
@ -48,7 +48,7 @@ use libc::size_t;
|
||||||
/// values:
|
/// values:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use boring2::memcmp::eq;
|
/// use boring::memcmp::eq;
|
||||||
///
|
///
|
||||||
/// // We want to compare `a` to `b` and `c`, without giving
|
/// // We want to compare `a` to `b` and `c`, without giving
|
||||||
/// // away through timing analysis that `c` is more similar to `a`
|
/// // away through timing analysis that `c` is more similar to `a`
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ pub struct SignatureAlgorithms {
|
||||||
/// To view the integer representation of a `Nid`:
|
/// To view the integer representation of a `Nid`:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use boring2::nid::Nid;
|
/// use boring::nid::Nid;
|
||||||
///
|
///
|
||||||
/// assert!(Nid::AES_256_GCM.as_raw() == 901);
|
/// assert!(Nid::AES_256_GCM.as_raw() == 901);
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@
|
||||||
//! Generate a 2048-bit RSA public/private key pair and print the public key.
|
//! Generate a 2048-bit RSA public/private key pair and print the public key.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use boring2::rsa::Rsa;
|
//! use boring::rsa::Rsa;
|
||||||
//! use boring2::pkey::PKey;
|
//! use boring::pkey::PKey;
|
||||||
//! use std::str;
|
//! use std::str;
|
||||||
//!
|
//!
|
||||||
//! let rsa = Rsa::generate(2048).unwrap();
|
//! let rsa = Rsa::generate(2048).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
//! To generate a buffer with cryptographically strong bytes:
|
//! To generate a buffer with cryptographically strong bytes:
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use boring2::rand::rand_bytes;
|
//! use boring::rand::rand_bytes;
|
||||||
//!
|
//!
|
||||||
//! let mut buf = [0; 256];
|
//! let mut buf = [0; 256];
|
||||||
//! rand_bytes(&mut buf).unwrap();
|
//! rand_bytes(&mut buf).unwrap();
|
||||||
|
|
@ -25,7 +25,7 @@ use crate::error::ErrorStack;
|
||||||
/// To generate a buffer with cryptographically strong bytes:
|
/// To generate a buffer with cryptographically strong bytes:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use boring2::rand::rand_bytes;
|
/// use boring::rand::rand_bytes;
|
||||||
///
|
///
|
||||||
/// let mut buf = [0; 256];
|
/// let mut buf = [0; 256];
|
||||||
/// rand_bytes(&mut buf).unwrap();
|
/// rand_bytes(&mut buf).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
//! Generate a 2048-bit RSA key pair and use the public key to encrypt some data.
|
//! Generate a 2048-bit RSA key pair and use the public key to encrypt some data.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use boring2::rsa::{Rsa, Padding};
|
//! use boring::rsa::{Rsa, Padding};
|
||||||
//!
|
//!
|
||||||
//! let rsa = Rsa::generate(2048).unwrap();
|
//! let rsa = Rsa::generate(2048).unwrap();
|
||||||
//! let data = b"foobar";
|
//! let data = b"foobar";
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! extern crate hex;
|
//! extern crate hex;
|
||||||
//!
|
//!
|
||||||
//! use boring2::sha;
|
//! use boring::sha;
|
||||||
//!
|
//!
|
||||||
//! fn main() {
|
//! fn main() {
|
||||||
//! let mut hasher = sha::Sha256::new();
|
//! let mut hasher = sha::Sha256::new();
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! extern crate hex;
|
//! extern crate hex;
|
||||||
//!
|
//!
|
||||||
//! use boring2::sha::sha256;
|
//! use boring::sha::sha256;
|
||||||
//!
|
//!
|
||||||
//! fn main() {
|
//! fn main() {
|
||||||
//! let hash = sha256(b"your data or message");
|
//! let hash = sha256(b"your data or message");
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,10 @@
|
||||||
//! Sign and verify data given an RSA keypair:
|
//! Sign and verify data given an RSA keypair:
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use boring2::sign::{Signer, Verifier};
|
//! use boring::sign::{Signer, Verifier};
|
||||||
//! use boring2::rsa::Rsa;
|
//! use boring::rsa::Rsa;
|
||||||
//! use boring2::pkey::PKey;
|
//! use boring::pkey::PKey;
|
||||||
//! use boring2::hash::MessageDigest;
|
//! use boring::hash::MessageDigest;
|
||||||
//!
|
//!
|
||||||
//! // Generate a keypair
|
//! // Generate a keypair
|
||||||
//! let keypair = Rsa::generate(2048).unwrap();
|
//! let keypair = Rsa::generate(2048).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,13 @@
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use foreign_types::ForeignTypeRef;
|
|
||||||
use openssl_macros::corresponds;
|
|
||||||
|
|
||||||
use crate::dh::Dh;
|
use crate::dh::Dh;
|
||||||
use crate::error::ErrorStack;
|
use crate::error::ErrorStack;
|
||||||
use crate::ssl::{
|
use crate::ssl::{
|
||||||
HandshakeError, Ssl, SslContext, SslContextBuilder, SslContextRef, SslMethod, SslMode,
|
HandshakeError, Ssl, SslContext, SslContextBuilder, SslContextRef, SslMethod, SslMode,
|
||||||
SslOptions, SslRef, SslStream, SslVerifyMode,
|
SslOptions, SslRef, SslStream, SslVerifyMode,
|
||||||
};
|
};
|
||||||
use crate::{cvt, version};
|
use crate::version;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
use super::MidHandshakeSslStream;
|
use super::MidHandshakeSslStream;
|
||||||
|
|
@ -28,12 +25,16 @@ ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
|
||||||
|
|
||||||
enum ContextType {
|
enum ContextType {
|
||||||
WithMethod(SslMethod),
|
WithMethod(SslMethod),
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
Rpk,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::inconsistent_digit_grouping)]
|
#[allow(clippy::inconsistent_digit_grouping)]
|
||||||
fn ctx(ty: ContextType) -> Result<SslContextBuilder, ErrorStack> {
|
fn ctx(ty: ContextType) -> Result<SslContextBuilder, ErrorStack> {
|
||||||
let mut ctx = match ty {
|
let mut ctx = match ty {
|
||||||
ContextType::WithMethod(method) => SslContextBuilder::new(method),
|
ContextType::WithMethod(method) => SslContextBuilder::new(method),
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
ContextType::Rpk => SslContextBuilder::new_rpk(),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
let mut opts = SslOptions::ALL
|
let mut opts = SslOptions::ALL
|
||||||
|
|
@ -86,11 +87,10 @@ impl SslConnector {
|
||||||
Ok(SslConnectorBuilder(ctx))
|
Ok(SslConnectorBuilder(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new builder for TLS connections with no verification.
|
/// Creates a new builder for TLS connections with raw public key.
|
||||||
///
|
#[cfg(feature = "rpk")]
|
||||||
/// This is useful for testing and other purposes where you want to skip verification.
|
pub fn rpk_builder() -> Result<SslConnectorBuilder, ErrorStack> {
|
||||||
pub fn no_default_verify_builder(method: SslMethod) -> Result<SslConnectorBuilder, ErrorStack> {
|
let mut ctx = ctx(ContextType::Rpk)?;
|
||||||
let mut ctx = ctx(ContextType::WithMethod(method))?;
|
|
||||||
ctx.set_cipher_list(
|
ctx.set_cipher_list(
|
||||||
"DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK",
|
"DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK",
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -224,7 +224,13 @@ impl ConnectConfiguration {
|
||||||
self.ssl.set_hostname(domain)?;
|
self.ssl.set_hostname(domain)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.verify_hostname {
|
#[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 {
|
||||||
setup_verify_hostname(&mut self.ssl, domain)?;
|
setup_verify_hostname(&mut self.ssl, domain)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -264,49 +270,6 @@ impl ConnectConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConnectConfiguration {
|
|
||||||
/// Enables or disables ECH grease.
|
|
||||||
#[cfg(not(feature = "fips"))]
|
|
||||||
#[corresponds(SSL_set_enable_ech_grease)]
|
|
||||||
pub fn set_enable_ech_grease(&mut self, enable: bool) {
|
|
||||||
unsafe { ffi::SSL_set_enable_ech_grease(self.as_ptr(), enable as _) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets whether the aes hardware override should be enabled.
|
|
||||||
#[cfg(not(feature = "fips"))]
|
|
||||||
#[corresponds(SSL_set_aes_hw_override)]
|
|
||||||
pub fn set_aes_hw_override(&mut self, enable: bool) {
|
|
||||||
unsafe { ffi::SSL_set_aes_hw_override(self.as_ptr(), enable as _) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets application settings flag for ALPS (Application-Layer Protocol Negotiation).
|
|
||||||
#[corresponds(SSL_add_application_settings)]
|
|
||||||
pub fn add_application_settings(&mut self, alps: &[u8]) -> Result<(), ErrorStack> {
|
|
||||||
unsafe {
|
|
||||||
cvt(ffi::SSL_add_application_settings(
|
|
||||||
self.as_ptr(),
|
|
||||||
alps.as_ptr(),
|
|
||||||
alps.len(),
|
|
||||||
std::ptr::null(),
|
|
||||||
0,
|
|
||||||
))
|
|
||||||
.map(|_| ())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the ALPS use new codepoint flag.
|
|
||||||
#[corresponds(SSL_set_alps_use_new_codepoint)]
|
|
||||||
pub fn set_alps_use_new_codepoint(&mut self, use_new: bool) {
|
|
||||||
unsafe { ffi::SSL_set_alps_use_new_codepoint(self.as_ptr(), use_new as _) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the SSL options.
|
|
||||||
#[corresponds(SSL_set_options)]
|
|
||||||
pub fn set_options(&mut self, options: SslOptions) -> Result<(), ErrorStack> {
|
|
||||||
unsafe { cvt(ffi::SSL_set_options(self.as_ptr(), options.bits()) as _).map(|_| ()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for ConnectConfiguration {
|
impl Deref for ConnectConfiguration {
|
||||||
type Target = SslRef;
|
type Target = SslRef;
|
||||||
|
|
||||||
|
|
@ -329,6 +292,21 @@ impl DerefMut for ConnectConfiguration {
|
||||||
pub struct SslAcceptor(SslContext);
|
pub struct SslAcceptor(SslContext);
|
||||||
|
|
||||||
impl SslAcceptor {
|
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
|
/// Creates a new builder configured to connect to non-legacy clients. This should generally be
|
||||||
/// considered a reasonable default choice.
|
/// considered a reasonable default choice.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,12 @@ fn fmt_mid_handshake_error(
|
||||||
f: &mut fmt::Formatter,
|
f: &mut fmt::Formatter,
|
||||||
prefix: &str,
|
prefix: &str,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
if s.ssl().ssl_context().is_rpk() {
|
||||||
|
write!(f, "{}", prefix)?;
|
||||||
|
return write!(f, " {}", s.error());
|
||||||
|
}
|
||||||
|
|
||||||
match s.ssl().verify_result() {
|
match s.ssl().verify_result() {
|
||||||
// INVALID_CALL is returned if no verification took place,
|
// INVALID_CALL is returned if no verification took place,
|
||||||
// such as before a cert is sent.
|
// such as before a cert is sent.
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
//! To connect as a client to a remote server:
|
//! To connect as a client to a remote server:
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use boring2::ssl::{SslMethod, SslConnector};
|
//! use boring::ssl::{SslMethod, SslConnector};
|
||||||
//! use std::io::{Read, Write};
|
//! use std::io::{Read, Write};
|
||||||
//! use std::net::TcpStream;
|
//! use std::net::TcpStream;
|
||||||
//!
|
//!
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
//! To accept connections as a server from remote clients:
|
//! To accept connections as a server from remote clients:
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use boring2::ssl::{SslMethod, SslAcceptor, SslStream, SslFiletype};
|
//! use boring::ssl::{SslMethod, SslAcceptor, SslStream, SslFiletype};
|
||||||
//! use std::net::{TcpListener, TcpStream};
|
//! use std::net::{TcpListener, TcpStream};
|
||||||
//! use std::sync::Arc;
|
//! use std::sync::Arc;
|
||||||
//! use std::thread;
|
//! use std::thread;
|
||||||
|
|
@ -87,8 +87,6 @@ use crate::pkey::{HasPrivate, PKeyRef, Params, Private};
|
||||||
use crate::srtp::{SrtpProtectionProfile, SrtpProtectionProfileRef};
|
use crate::srtp::{SrtpProtectionProfile, SrtpProtectionProfileRef};
|
||||||
use crate::ssl::bio::BioMethod;
|
use crate::ssl::bio::BioMethod;
|
||||||
use crate::ssl::callbacks::*;
|
use crate::ssl::callbacks::*;
|
||||||
#[cfg(not(feature = "fips"))]
|
|
||||||
use crate::ssl::ech::SslEchKeys;
|
|
||||||
use crate::ssl::error::InnerError;
|
use crate::ssl::error::InnerError;
|
||||||
use crate::stack::{Stack, StackRef, Stackable};
|
use crate::stack::{Stack, StackRef, Stackable};
|
||||||
use crate::symm::CipherCtxRef;
|
use crate::symm::CipherCtxRef;
|
||||||
|
|
@ -108,8 +106,7 @@ pub use self::async_callbacks::{
|
||||||
pub use self::connector::{
|
pub use self::connector::{
|
||||||
ConnectConfiguration, SslAcceptor, SslAcceptorBuilder, SslConnector, SslConnectorBuilder,
|
ConnectConfiguration, SslAcceptor, SslAcceptorBuilder, SslConnector, SslConnectorBuilder,
|
||||||
};
|
};
|
||||||
#[cfg(not(feature = "fips"))]
|
pub use self::ech::{SslEchKeys, SslEchKeysRef};
|
||||||
pub use self::ech::SslEchKeysRef;
|
|
||||||
pub use self::error::{Error, ErrorCode, HandshakeError};
|
pub use self::error::{Error, ErrorCode, HandshakeError};
|
||||||
|
|
||||||
mod async_callbacks;
|
mod async_callbacks;
|
||||||
|
|
@ -192,9 +189,6 @@ bitflags! {
|
||||||
|
|
||||||
/// Disallow all renegotiation in TLSv1.2 and earlier.
|
/// Disallow all renegotiation in TLSv1.2 and earlier.
|
||||||
const NO_RENEGOTIATION = ffi::SSL_OP_NO_RENEGOTIATION as _;
|
const NO_RENEGOTIATION = ffi::SSL_OP_NO_RENEGOTIATION as _;
|
||||||
|
|
||||||
/// Disables PSK with DHE.
|
|
||||||
const NO_PSK_DHE_KE = ffi::SSL_OP_NO_PSK_DHE_KE as _;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -259,6 +253,12 @@ impl SslMethod {
|
||||||
unsafe { SslMethod(TLS_method()) }
|
unsafe { SslMethod(TLS_method()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Same as `tls`, but doesn't create X509 for certificates.
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
pub fn tls_with_buffer() -> SslMethod {
|
||||||
|
unsafe { SslMethod(ffi::TLS_with_buffers_method()) }
|
||||||
|
}
|
||||||
|
|
||||||
/// Support all versions of the DTLS protocol.
|
/// Support all versions of the DTLS protocol.
|
||||||
#[corresponds(DTLS_method)]
|
#[corresponds(DTLS_method)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
@ -447,6 +447,9 @@ static SSL_INDEXES: LazyLock<Mutex<HashMap<TypeId, c_int>>> =
|
||||||
LazyLock::new(|| Mutex::new(HashMap::new()));
|
LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||||
static SESSION_CTX_INDEX: LazyLock<Index<Ssl, SslContext>> =
|
static SESSION_CTX_INDEX: LazyLock<Index<Ssl, SslContext>> =
|
||||||
LazyLock::new(|| Ssl::new_ex_index().unwrap());
|
LazyLock::new(|| Ssl::new_ex_index().unwrap());
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
static RPK_FLAG_INDEX: LazyLock<Index<SslContext, bool>> =
|
||||||
|
LazyLock::new(|| SslContext::new_ex_index().unwrap());
|
||||||
|
|
||||||
/// An error returned from the SNI callback.
|
/// An error returned from the SNI callback.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
|
@ -533,8 +536,7 @@ impl SelectCertError {
|
||||||
/// **WARNING**: The current implementation of `From` is unsound, as it's possible to create an
|
/// **WARNING**: The current implementation of `From` is unsound, as it's possible to create an
|
||||||
/// ExtensionType that is not defined by the impl. `From` will be deprecated in favor of `TryFrom`
|
/// ExtensionType that is not defined by the impl. `From` will be deprecated in favor of `TryFrom`
|
||||||
/// in the next major bump of the library.
|
/// in the next major bump of the library.
|
||||||
#[repr(transparent)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
|
|
||||||
pub struct ExtensionType(u16);
|
pub struct ExtensionType(u16);
|
||||||
|
|
||||||
impl ExtensionType {
|
impl ExtensionType {
|
||||||
|
|
@ -566,13 +568,10 @@ impl ExtensionType {
|
||||||
pub const RENEGOTIATE: Self = Self(ffi::TLSEXT_TYPE_renegotiate as u16);
|
pub const RENEGOTIATE: Self = Self(ffi::TLSEXT_TYPE_renegotiate as u16);
|
||||||
pub const DELEGATED_CREDENTIAL: Self = Self(ffi::TLSEXT_TYPE_delegated_credential as u16);
|
pub const DELEGATED_CREDENTIAL: Self = Self(ffi::TLSEXT_TYPE_delegated_credential as u16);
|
||||||
pub const APPLICATION_SETTINGS: Self = Self(ffi::TLSEXT_TYPE_application_settings as u16);
|
pub const APPLICATION_SETTINGS: Self = Self(ffi::TLSEXT_TYPE_application_settings as u16);
|
||||||
pub const APPLICATION_SETTINGS_NEW: Self =
|
|
||||||
Self(ffi::TLSEXT_TYPE_application_settings_new as u16);
|
|
||||||
pub const ENCRYPTED_CLIENT_HELLO: Self = Self(ffi::TLSEXT_TYPE_encrypted_client_hello as u16);
|
pub const ENCRYPTED_CLIENT_HELLO: Self = Self(ffi::TLSEXT_TYPE_encrypted_client_hello as u16);
|
||||||
pub const CERTIFICATE_TIMESTAMP: Self = Self(ffi::TLSEXT_TYPE_certificate_timestamp as u16);
|
pub const CERTIFICATE_TIMESTAMP: Self = Self(ffi::TLSEXT_TYPE_certificate_timestamp as u16);
|
||||||
pub const NEXT_PROTO_NEG: Self = Self(ffi::TLSEXT_TYPE_next_proto_neg as u16);
|
pub const NEXT_PROTO_NEG: Self = Self(ffi::TLSEXT_TYPE_next_proto_neg as u16);
|
||||||
pub const CHANNEL_ID: Self = Self(ffi::TLSEXT_TYPE_channel_id as u16);
|
pub const CHANNEL_ID: Self = Self(ffi::TLSEXT_TYPE_channel_id as u16);
|
||||||
pub const RECORD_SIZE_LIMIT: Self = Self(ffi::TLSEXT_TYPE_record_size_limit as u16);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u16> for ExtensionType {
|
impl From<u16> for ExtensionType {
|
||||||
|
|
@ -582,7 +581,7 @@ impl From<u16> for ExtensionType {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An SSL/TLS protocol version.
|
/// An SSL/TLS protocol version.
|
||||||
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct SslVersion(u16);
|
pub struct SslVersion(u16);
|
||||||
|
|
||||||
impl SslVersion {
|
impl SslVersion {
|
||||||
|
|
@ -720,15 +719,13 @@ impl CompliancePolicy {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IANA assigned identifier of compression algorithm. See https://www.rfc-editor.org/rfc/rfc8879.html#name-compression-algorithms
|
// IANA assigned identifier of compression algorithm. See https://www.rfc-editor.org/rfc/rfc8879.html#name-compression-algorithms
|
||||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct CertificateCompressionAlgorithm(u16);
|
pub struct CertificateCompressionAlgorithm(u16);
|
||||||
|
|
||||||
impl CertificateCompressionAlgorithm {
|
impl CertificateCompressionAlgorithm {
|
||||||
pub const ZLIB: Self = Self(ffi::TLSEXT_cert_compression_zlib as u16);
|
pub const ZLIB: Self = Self(ffi::TLSEXT_cert_compression_zlib as u16);
|
||||||
|
|
||||||
pub const BROTLI: Self = Self(ffi::TLSEXT_cert_compression_brotli as u16);
|
pub const BROTLI: Self = Self(ffi::TLSEXT_cert_compression_brotli as u16);
|
||||||
|
|
||||||
pub const ZSTD: Self = Self(ffi::TLSEXT_cert_compression_zstd as u16);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A standard implementation of protocol selection for Application Layer Protocol Negotiation
|
/// A standard implementation of protocol selection for Application Layer Protocol Negotiation
|
||||||
|
|
@ -883,11 +880,67 @@ impl Ssl3AlertLevel {
|
||||||
pub const FATAL: Ssl3AlertLevel = Self(ffi::SSL3_AL_FATAL);
|
pub const FATAL: Ssl3AlertLevel = Self(ffi::SSL3_AL_FATAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
extern "C" fn rpk_verify_failure_callback(
|
||||||
|
_ssl: *mut ffi::SSL,
|
||||||
|
_out_alert: *mut u8,
|
||||||
|
) -> ffi::ssl_verify_result_t {
|
||||||
|
// Always verify the peer.
|
||||||
|
ffi::ssl_verify_result_t::ssl_verify_invalid
|
||||||
|
}
|
||||||
|
|
||||||
/// A builder for `SslContext`s.
|
/// A builder for `SslContext`s.
|
||||||
pub struct SslContextBuilder {
|
pub struct SslContextBuilder {
|
||||||
ctx: SslContext,
|
ctx: SslContext,
|
||||||
/// If it's not shared, it can be exposed as mutable
|
/// If it's not shared, it can be exposed as mutable
|
||||||
has_shared_cert_store: bool,
|
has_shared_cert_store: bool,
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
is_rpk: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
impl SslContextBuilder {
|
||||||
|
/// Creates a new `SslContextBuilder` to be used with Raw Public Key.
|
||||||
|
#[corresponds(SSL_CTX_new)]
|
||||||
|
pub fn new_rpk() -> Result<SslContextBuilder, ErrorStack> {
|
||||||
|
unsafe {
|
||||||
|
init();
|
||||||
|
let ctx = cvt_p(ffi::SSL_CTX_new(SslMethod::tls_with_buffer().as_ptr()))?;
|
||||||
|
|
||||||
|
let mut builder = SslContextBuilder::from_ptr(ctx);
|
||||||
|
builder.is_rpk = true;
|
||||||
|
builder.set_ex_data(*RPK_FLAG_INDEX, true);
|
||||||
|
|
||||||
|
Ok(builder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets raw public key certificate in DER format.
|
||||||
|
pub fn set_rpk_certificate(&mut self, cert: &[u8]) -> Result<(), ErrorStack> {
|
||||||
|
unsafe {
|
||||||
|
cvt(ffi::SSL_CTX_set_server_raw_public_key_certificate(
|
||||||
|
self.as_ptr(),
|
||||||
|
cert.as_ptr(),
|
||||||
|
cert.len() as u32,
|
||||||
|
))
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets RPK null chain private key.
|
||||||
|
pub fn set_null_chain_private_key<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack>
|
||||||
|
where
|
||||||
|
T: HasPrivate,
|
||||||
|
{
|
||||||
|
unsafe {
|
||||||
|
cvt(ffi::SSL_CTX_set_nullchain_and_key(
|
||||||
|
self.as_ptr(),
|
||||||
|
key.as_ptr(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
))
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SslContextBuilder {
|
impl SslContextBuilder {
|
||||||
|
|
@ -903,12 +956,20 @@ impl SslContextBuilder {
|
||||||
|
|
||||||
/// Creates an `SslContextBuilder` from a pointer to a raw OpenSSL value.
|
/// Creates an `SslContextBuilder` from a pointer to a raw OpenSSL value.
|
||||||
///
|
///
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "rpk",
|
||||||
|
doc = "Keeps previous RPK state. Use `new_rpk()` to enable RPK."
|
||||||
|
)]
|
||||||
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// The caller must ensure that the pointer is valid and uniquely owned by the builder.
|
/// The caller must ensure that the pointer is valid and uniquely owned by the builder.
|
||||||
|
/// The context must own its cert store exclusively.
|
||||||
pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX) -> SslContextBuilder {
|
pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX) -> SslContextBuilder {
|
||||||
let ctx = SslContext::from_ptr(ctx);
|
let ctx = SslContext::from_ptr(ctx);
|
||||||
SslContextBuilder {
|
SslContextBuilder {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
is_rpk: ctx.is_rpk(),
|
||||||
has_shared_cert_store: false,
|
has_shared_cert_store: false,
|
||||||
ctx,
|
ctx,
|
||||||
}
|
}
|
||||||
|
|
@ -944,6 +1005,9 @@ impl SslContextBuilder {
|
||||||
where
|
where
|
||||||
F: Fn(&mut X509StoreContextRef) -> bool + 'static + Sync + Send,
|
F: Fn(&mut X509StoreContextRef) -> bool + 'static + Sync + Send,
|
||||||
{
|
{
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
// NOTE(jlarisch): Q: Why don't we wrap the callback in an Arc, since
|
// NOTE(jlarisch): Q: Why don't we wrap the callback in an Arc, since
|
||||||
// `set_verify_callback` does?
|
// `set_verify_callback` does?
|
||||||
// A: I don't think that Arc is necessary, and I don't think one is necessary here.
|
// A: I don't think that Arc is necessary, and I don't think one is necessary here.
|
||||||
|
|
@ -963,6 +1027,9 @@ impl SslContextBuilder {
|
||||||
/// Configures the certificate verification method for new connections.
|
/// Configures the certificate verification method for new connections.
|
||||||
#[corresponds(SSL_CTX_set_verify)]
|
#[corresponds(SSL_CTX_set_verify)]
|
||||||
pub fn set_verify(&mut self, mode: SslVerifyMode) {
|
pub fn set_verify(&mut self, mode: SslVerifyMode) {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
ffi::SSL_CTX_set_verify(self.as_ptr(), mode.bits() as c_int, None);
|
ffi::SSL_CTX_set_verify(self.as_ptr(), mode.bits() as c_int, None);
|
||||||
}
|
}
|
||||||
|
|
@ -989,6 +1056,9 @@ impl SslContextBuilder {
|
||||||
where
|
where
|
||||||
F: Fn(bool, &mut X509StoreContextRef) -> bool + 'static + Sync + Send,
|
F: Fn(bool, &mut X509StoreContextRef) -> bool + 'static + Sync + Send,
|
||||||
{
|
{
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
self.replace_ex_data(SslContext::cached_ex_index::<F>(), callback);
|
self.replace_ex_data(SslContext::cached_ex_index::<F>(), callback);
|
||||||
ffi::SSL_CTX_set_verify(self.as_ptr(), mode.bits() as c_int, Some(raw_verify::<F>));
|
ffi::SSL_CTX_set_verify(self.as_ptr(), mode.bits() as c_int, Some(raw_verify::<F>));
|
||||||
|
|
@ -1014,6 +1084,9 @@ impl SslContextBuilder {
|
||||||
where
|
where
|
||||||
F: Fn(&mut SslRef) -> Result<(), SslVerifyError> + 'static + Sync + Send,
|
F: Fn(&mut SslRef) -> Result<(), SslVerifyError> + 'static + Sync + Send,
|
||||||
{
|
{
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
self.replace_ex_data(SslContext::cached_ex_index::<F>(), callback);
|
self.replace_ex_data(SslContext::cached_ex_index::<F>(), callback);
|
||||||
ffi::SSL_CTX_set_custom_verify(
|
ffi::SSL_CTX_set_custom_verify(
|
||||||
|
|
@ -1093,6 +1166,9 @@ impl SslContextBuilder {
|
||||||
+ Sync
|
+ Sync
|
||||||
+ Send,
|
+ Send,
|
||||||
{
|
{
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
self.replace_ex_data(SslContext::cached_ex_index::<F>(), callback);
|
self.replace_ex_data(SslContext::cached_ex_index::<F>(), callback);
|
||||||
ffi::SSL_CTX_set_tlsext_ticket_key_cb(self.as_ptr(), Some(raw_ticket_key::<F>))
|
ffi::SSL_CTX_set_tlsext_ticket_key_cb(self.as_ptr(), Some(raw_ticket_key::<F>))
|
||||||
|
|
@ -1104,6 +1180,9 @@ impl SslContextBuilder {
|
||||||
/// If the peer's certificate chain is longer than this value, verification will fail.
|
/// If the peer's certificate chain is longer than this value, verification will fail.
|
||||||
#[corresponds(SSL_CTX_set_verify_depth)]
|
#[corresponds(SSL_CTX_set_verify_depth)]
|
||||||
pub fn set_verify_depth(&mut self, depth: u32) {
|
pub fn set_verify_depth(&mut self, depth: u32) {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
ffi::SSL_CTX_set_verify_depth(self.as_ptr(), depth as c_int);
|
ffi::SSL_CTX_set_verify_depth(self.as_ptr(), depth as c_int);
|
||||||
}
|
}
|
||||||
|
|
@ -1112,6 +1191,9 @@ impl SslContextBuilder {
|
||||||
/// Sets a custom certificate store for verifying peer certificates.
|
/// Sets a custom certificate store for verifying peer certificates.
|
||||||
#[corresponds(SSL_CTX_set0_verify_cert_store)]
|
#[corresponds(SSL_CTX_set0_verify_cert_store)]
|
||||||
pub fn set_verify_cert_store(&mut self, cert_store: X509Store) -> Result<(), ErrorStack> {
|
pub fn set_verify_cert_store(&mut self, cert_store: X509Store) -> Result<(), ErrorStack> {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
cvt(
|
cvt(
|
||||||
ffi::SSL_CTX_set0_verify_cert_store(self.as_ptr(), cert_store.into_ptr()) as c_int,
|
ffi::SSL_CTX_set0_verify_cert_store(self.as_ptr(), cert_store.into_ptr()) as c_int,
|
||||||
|
|
@ -1127,6 +1209,9 @@ impl SslContextBuilder {
|
||||||
#[corresponds(SSL_CTX_set_cert_store)]
|
#[corresponds(SSL_CTX_set_cert_store)]
|
||||||
#[deprecated(note = "Use set_cert_store_builder or set_cert_store_ref instead")]
|
#[deprecated(note = "Use set_cert_store_builder or set_cert_store_ref instead")]
|
||||||
pub fn set_cert_store(&mut self, cert_store: X509Store) {
|
pub fn set_cert_store(&mut self, cert_store: X509Store) {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
self.has_shared_cert_store = false;
|
self.has_shared_cert_store = false;
|
||||||
unsafe {
|
unsafe {
|
||||||
ffi::SSL_CTX_set_cert_store(self.as_ptr(), cert_store.into_ptr());
|
ffi::SSL_CTX_set_cert_store(self.as_ptr(), cert_store.into_ptr());
|
||||||
|
|
@ -1136,6 +1221,9 @@ impl SslContextBuilder {
|
||||||
/// Replaces the context's certificate store, and allows mutating the store afterwards.
|
/// Replaces the context's certificate store, and allows mutating the store afterwards.
|
||||||
#[corresponds(SSL_CTX_set_cert_store)]
|
#[corresponds(SSL_CTX_set_cert_store)]
|
||||||
pub fn set_cert_store_builder(&mut self, cert_store: X509StoreBuilder) {
|
pub fn set_cert_store_builder(&mut self, cert_store: X509StoreBuilder) {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
self.has_shared_cert_store = false;
|
self.has_shared_cert_store = false;
|
||||||
unsafe {
|
unsafe {
|
||||||
ffi::SSL_CTX_set_cert_store(self.as_ptr(), cert_store.into_ptr());
|
ffi::SSL_CTX_set_cert_store(self.as_ptr(), cert_store.into_ptr());
|
||||||
|
|
@ -1147,6 +1235,9 @@ impl SslContextBuilder {
|
||||||
/// This method allows sharing the `X509Store`, but calls to `cert_store_mut` will panic.
|
/// This method allows sharing the `X509Store`, but calls to `cert_store_mut` will panic.
|
||||||
#[corresponds(SSL_CTX_set_cert_store)]
|
#[corresponds(SSL_CTX_set_cert_store)]
|
||||||
pub fn set_cert_store_ref(&mut self, cert_store: &X509Store) {
|
pub fn set_cert_store_ref(&mut self, cert_store: &X509Store) {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
self.has_shared_cert_store = true;
|
self.has_shared_cert_store = true;
|
||||||
unsafe {
|
unsafe {
|
||||||
ffi::X509_STORE_up_ref(cert_store.as_ptr());
|
ffi::X509_STORE_up_ref(cert_store.as_ptr());
|
||||||
|
|
@ -1192,6 +1283,9 @@ impl SslContextBuilder {
|
||||||
/// if present, or defaults specified at OpenSSL build time otherwise.
|
/// if present, or defaults specified at OpenSSL build time otherwise.
|
||||||
#[corresponds(SSL_CTX_set_default_verify_paths)]
|
#[corresponds(SSL_CTX_set_default_verify_paths)]
|
||||||
pub fn set_default_verify_paths(&mut self) -> Result<(), ErrorStack> {
|
pub fn set_default_verify_paths(&mut self) -> Result<(), ErrorStack> {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
unsafe { cvt(ffi::SSL_CTX_set_default_verify_paths(self.as_ptr())).map(|_| ()) }
|
unsafe { cvt(ffi::SSL_CTX_set_default_verify_paths(self.as_ptr())).map(|_| ()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1200,6 +1294,9 @@ impl SslContextBuilder {
|
||||||
/// The file should contain a sequence of PEM-formatted CA certificates.
|
/// The file should contain a sequence of PEM-formatted CA certificates.
|
||||||
#[corresponds(SSL_CTX_load_verify_locations)]
|
#[corresponds(SSL_CTX_load_verify_locations)]
|
||||||
pub fn set_ca_file<P: AsRef<Path>>(&mut self, file: P) -> Result<(), ErrorStack> {
|
pub fn set_ca_file<P: AsRef<Path>>(&mut self, file: P) -> Result<(), ErrorStack> {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
let file = CString::new(file.as_ref().as_os_str().as_encoded_bytes())
|
let file = CString::new(file.as_ref().as_os_str().as_encoded_bytes())
|
||||||
.map_err(ErrorStack::internal_error)?;
|
.map_err(ErrorStack::internal_error)?;
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
@ -1218,6 +1315,9 @@ impl SslContextBuilder {
|
||||||
/// as trusted by this method.
|
/// as trusted by this method.
|
||||||
#[corresponds(SSL_CTX_set_client_CA_list)]
|
#[corresponds(SSL_CTX_set_client_CA_list)]
|
||||||
pub fn set_client_ca_list(&mut self, list: Stack<X509Name>) {
|
pub fn set_client_ca_list(&mut self, list: Stack<X509Name>) {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
ffi::SSL_CTX_set_client_CA_list(self.as_ptr(), list.as_ptr());
|
ffi::SSL_CTX_set_client_CA_list(self.as_ptr(), list.as_ptr());
|
||||||
mem::forget(list);
|
mem::forget(list);
|
||||||
|
|
@ -1228,6 +1328,9 @@ impl SslContextBuilder {
|
||||||
/// requesting client-side TLS authentication.
|
/// requesting client-side TLS authentication.
|
||||||
#[corresponds(SSL_CTX_add_client_CA)]
|
#[corresponds(SSL_CTX_add_client_CA)]
|
||||||
pub fn add_client_ca(&mut self, cacert: &X509Ref) -> Result<(), ErrorStack> {
|
pub fn add_client_ca(&mut self, cacert: &X509Ref) -> Result<(), ErrorStack> {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
unsafe { cvt(ffi::SSL_CTX_add_client_CA(self.as_ptr(), cacert.as_ptr())).map(|_| ()) }
|
unsafe { cvt(ffi::SSL_CTX_add_client_CA(self.as_ptr(), cacert.as_ptr())).map(|_| ()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1263,6 +1366,9 @@ impl SslContextBuilder {
|
||||||
file: P,
|
file: P,
|
||||||
file_type: SslFiletype,
|
file_type: SslFiletype,
|
||||||
) -> Result<(), ErrorStack> {
|
) -> Result<(), ErrorStack> {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
let file = CString::new(file.as_ref().as_os_str().as_encoded_bytes())
|
let file = CString::new(file.as_ref().as_os_str().as_encoded_bytes())
|
||||||
.map_err(ErrorStack::internal_error)?;
|
.map_err(ErrorStack::internal_error)?;
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
@ -1310,6 +1416,9 @@ impl SslContextBuilder {
|
||||||
/// `set_certificate` to a trusted root.
|
/// `set_certificate` to a trusted root.
|
||||||
#[corresponds(SSL_CTX_add_extra_chain_cert)]
|
#[corresponds(SSL_CTX_add_extra_chain_cert)]
|
||||||
pub fn add_extra_chain_cert(&mut self, cert: X509) -> Result<(), ErrorStack> {
|
pub fn add_extra_chain_cert(&mut self, cert: X509) -> Result<(), ErrorStack> {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
cvt(ffi::SSL_CTX_add_extra_chain_cert(self.as_ptr(), cert.into_ptr()) as c_int)?;
|
cvt(ffi::SSL_CTX_add_extra_chain_cert(self.as_ptr(), cert.into_ptr()) as c_int)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -1619,6 +1728,9 @@ impl SslContextBuilder {
|
||||||
#[corresponds(SSL_CTX_get_cert_store)]
|
#[corresponds(SSL_CTX_get_cert_store)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn cert_store(&self) -> &X509StoreBuilderRef {
|
pub fn cert_store(&self) -> &X509StoreBuilderRef {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
unsafe { X509StoreBuilderRef::from_ptr(ffi::SSL_CTX_get_cert_store(self.as_ptr())) }
|
unsafe { X509StoreBuilderRef::from_ptr(ffi::SSL_CTX_get_cert_store(self.as_ptr())) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1633,6 +1745,9 @@ impl SslContextBuilder {
|
||||||
///
|
///
|
||||||
#[corresponds(SSL_CTX_get_cert_store)]
|
#[corresponds(SSL_CTX_get_cert_store)]
|
||||||
pub fn cert_store_mut(&mut self) -> &mut X509StoreBuilderRef {
|
pub fn cert_store_mut(&mut self) -> &mut X509StoreBuilderRef {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
!self.has_shared_cert_store,
|
!self.has_shared_cert_store,
|
||||||
"Shared X509Store can't be mutated. Make a new store"
|
"Shared X509Store can't be mutated. Make a new store"
|
||||||
|
|
@ -1858,77 +1973,6 @@ impl SslContextBuilder {
|
||||||
unsafe { ffi::SSL_CTX_set_grease_enabled(self.as_ptr(), enabled as _) }
|
unsafe { ffi::SSL_CTX_set_grease_enabled(self.as_ptr(), enabled as _) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets whether the context should enable record size limit.
|
|
||||||
#[corresponds(SSL_CTX_set_record_size_limit)]
|
|
||||||
pub fn set_record_size_limit(&mut self, limit: u16) {
|
|
||||||
unsafe { ffi::SSL_CTX_set_record_size_limit(self.as_ptr(), limit as _) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets whether the context should enable delegated credentials.
|
|
||||||
#[corresponds(SSL_CTX_set_delegated_credentials)]
|
|
||||||
pub fn set_delegated_credentials(&mut self, sigalgs: &str) -> Result<(), ErrorStack> {
|
|
||||||
let sigalgs = CString::new(sigalgs).unwrap();
|
|
||||||
unsafe {
|
|
||||||
cvt(ffi::SSL_CTX_set_delegated_credentials(self.as_ptr(), sigalgs.as_ptr()) as c_int)
|
|
||||||
.map(|_| ())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets whether the context should enable there key share extension.
|
|
||||||
#[corresponds(SSL_CTX_set_key_shares_limit)]
|
|
||||||
pub fn set_key_shares_limit(&mut self, limit: u8) {
|
|
||||||
unsafe { ffi::SSL_CTX_set_key_shares_limit(self.as_ptr(), limit as _) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets whether the aes hardware override should be enabled.
|
|
||||||
#[cfg(not(feature = "fips"))]
|
|
||||||
#[corresponds(SSL_CTX_set_aes_hw_override)]
|
|
||||||
pub fn set_aes_hw_override(&mut self, enable: bool) {
|
|
||||||
unsafe { ffi::SSL_CTX_set_aes_hw_override(self.as_ptr(), enable as _) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets whether to preserve the TLS 1.3 cipher list as configured by [`Self::set_cipher_list`].
|
|
||||||
///
|
|
||||||
/// By default, BoringSSL does not preserve the TLS 1.3 cipher list. When this option is disabled
|
|
||||||
/// (the default), BoringSSL uses its internal default TLS 1.3 cipher suites in its default order,
|
|
||||||
/// regardless of what is set via [`Self::set_cipher_list`].
|
|
||||||
///
|
|
||||||
/// When enabled, this option ensures that the TLS 1.3 cipher suites explicitly set via
|
|
||||||
/// [`Self::set_cipher_list`] are retained in their original order, without being reordered or
|
|
||||||
/// modified by BoringSSL's internal logic. This is useful for maintaining specific cipher suite
|
|
||||||
/// priorities for TLS 1.3. Note that if [`Self::set_cipher_list`] does not include any TLS 1.3
|
|
||||||
/// cipher suites, BoringSSL will still fall back to its default TLS 1.3 cipher suites and order.
|
|
||||||
///
|
|
||||||
/// This feature isn't available in the certified version of BoringSSL.
|
|
||||||
///
|
|
||||||
/// # Note
|
|
||||||
///
|
|
||||||
/// This method must be called **before** [`Self::set_cipher_list`] to take effect.
|
|
||||||
/// If called after [`Self::set_cipher_list`], the setting will be ignored.
|
|
||||||
///
|
|
||||||
/// [`Self::set_cipher_list`]: #method.set_cipher_list
|
|
||||||
#[cfg(not(feature = "fips"))]
|
|
||||||
#[corresponds(SSL_CTX_set_preserve_tls13_cipher_list)]
|
|
||||||
pub fn set_preserve_tls13_cipher_list(&mut self, enable: bool) {
|
|
||||||
unsafe { ffi::SSL_CTX_set_preserve_tls13_cipher_list(self.as_ptr(), enable as _) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the indices of the extensions to be permuted.
|
|
||||||
#[corresponds(SSL_CTX_set_extension_order)]
|
|
||||||
pub fn set_extension_permutation(
|
|
||||||
&mut self,
|
|
||||||
indices: &[ExtensionType],
|
|
||||||
) -> Result<(), ErrorStack> {
|
|
||||||
unsafe {
|
|
||||||
cvt(ffi::SSL_CTX_set_extension_order(
|
|
||||||
self.as_ptr(),
|
|
||||||
indices.as_ptr() as *const _,
|
|
||||||
indices.len() as _,
|
|
||||||
))
|
|
||||||
.map(|_| ())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configures whether ClientHello extensions should be permuted.
|
/// Configures whether ClientHello extensions should be permuted.
|
||||||
#[corresponds(SSL_CTX_set_permute_extensions)]
|
#[corresponds(SSL_CTX_set_permute_extensions)]
|
||||||
pub fn set_permute_extensions(&mut self, enabled: bool) {
|
pub fn set_permute_extensions(&mut self, enabled: bool) {
|
||||||
|
|
@ -2108,6 +2152,9 @@ impl SslContextRef {
|
||||||
#[corresponds(SSL_CTX_get0_certificate)]
|
#[corresponds(SSL_CTX_get0_certificate)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn certificate(&self) -> Option<&X509Ref> {
|
pub fn certificate(&self) -> Option<&X509Ref> {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk(), "This API is not supported for RPK");
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = ffi::SSL_CTX_get0_certificate(self.as_ptr());
|
let ptr = ffi::SSL_CTX_get0_certificate(self.as_ptr());
|
||||||
if ptr.is_null() {
|
if ptr.is_null() {
|
||||||
|
|
@ -2136,6 +2183,9 @@ impl SslContextRef {
|
||||||
#[corresponds(SSL_CTX_get_cert_store)]
|
#[corresponds(SSL_CTX_get_cert_store)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn cert_store(&self) -> &X509StoreRef {
|
pub fn cert_store(&self) -> &X509StoreRef {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk(), "This API is not supported for RPK");
|
||||||
|
|
||||||
unsafe { X509StoreRef::from_ptr(ffi::SSL_CTX_get_cert_store(self.as_ptr())) }
|
unsafe { X509StoreRef::from_ptr(ffi::SSL_CTX_get_cert_store(self.as_ptr())) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2244,10 +2294,19 @@ impl SslContextRef {
|
||||||
#[corresponds(SSL_CTX_get_verify_mode)]
|
#[corresponds(SSL_CTX_get_verify_mode)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn verify_mode(&self) -> SslVerifyMode {
|
pub fn verify_mode(&self) -> SslVerifyMode {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk(), "This API is not supported for RPK");
|
||||||
|
|
||||||
let mode = unsafe { ffi::SSL_CTX_get_verify_mode(self.as_ptr()) };
|
let mode = unsafe { ffi::SSL_CTX_get_verify_mode(self.as_ptr()) };
|
||||||
SslVerifyMode::from_bits(mode).expect("SSL_CTX_get_verify_mode returned invalid mode")
|
SslVerifyMode::from_bits(mode).expect("SSL_CTX_get_verify_mode returned invalid mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if context was created for Raw Public Key verification
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
pub fn is_rpk(&self) -> bool {
|
||||||
|
self.ex_data(*RPK_FLAG_INDEX).copied().unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
/// Registers a list of ECH keys on the context. This list should contain new and old
|
/// Registers a list of ECH keys on the context. This list should contain new and old
|
||||||
/// ECHConfigs to allow stale DNS caches to update. Unlike most `SSL_CTX` APIs, this function
|
/// ECHConfigs to allow stale DNS caches to update. Unlike most `SSL_CTX` APIs, this function
|
||||||
/// is safe to call even after the `SSL_CTX` has been associated with connections on various
|
/// is safe to call even after the `SSL_CTX` has been associated with connections on various
|
||||||
|
|
@ -2714,6 +2773,21 @@ impl Ssl {
|
||||||
where
|
where
|
||||||
S: Read + Write,
|
S: Read + Write,
|
||||||
{
|
{
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
{
|
||||||
|
let ctx = self.ssl_context();
|
||||||
|
|
||||||
|
if ctx.is_rpk() {
|
||||||
|
unsafe {
|
||||||
|
ffi::SSL_CTX_set_custom_verify(
|
||||||
|
ctx.as_ptr(),
|
||||||
|
SslVerifyMode::PEER.bits(),
|
||||||
|
Some(rpk_verify_failure_callback),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SslStreamBuilder::new(self, stream).setup_accept()
|
SslStreamBuilder::new(self, stream).setup_accept()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2742,6 +2816,12 @@ impl fmt::Debug for SslRef {
|
||||||
|
|
||||||
builder.field("state", &self.state_string_long());
|
builder.field("state", &self.state_string_long());
|
||||||
|
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
if !self.ssl_context().is_rpk() {
|
||||||
|
builder.field("verify_result", &self.verify_result());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "rpk"))]
|
||||||
builder.field("verify_result", &self.verify_result());
|
builder.field("verify_result", &self.verify_result());
|
||||||
|
|
||||||
builder.finish()
|
builder.finish()
|
||||||
|
|
@ -2823,6 +2903,12 @@ impl SslRef {
|
||||||
/// [`SslContextBuilder::set_verify`]: struct.SslContextBuilder.html#method.set_verify
|
/// [`SslContextBuilder::set_verify`]: struct.SslContextBuilder.html#method.set_verify
|
||||||
#[corresponds(SSL_set_verify)]
|
#[corresponds(SSL_set_verify)]
|
||||||
pub fn set_verify(&mut self, mode: SslVerifyMode) {
|
pub fn set_verify(&mut self, mode: SslVerifyMode) {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(
|
||||||
|
!self.ssl_context().is_rpk(),
|
||||||
|
"This API is not supported for RPK"
|
||||||
|
);
|
||||||
|
|
||||||
unsafe { ffi::SSL_set_verify(self.as_ptr(), mode.bits() as c_int, None) }
|
unsafe { ffi::SSL_set_verify(self.as_ptr(), mode.bits() as c_int, None) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2831,6 +2917,12 @@ impl SslRef {
|
||||||
/// If the peer's certificate chain is longer than this value, verification will fail.
|
/// If the peer's certificate chain is longer than this value, verification will fail.
|
||||||
#[corresponds(SSL_set_verify_depth)]
|
#[corresponds(SSL_set_verify_depth)]
|
||||||
pub fn set_verify_depth(&mut self, depth: u32) {
|
pub fn set_verify_depth(&mut self, depth: u32) {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(
|
||||||
|
!self.ssl_context().is_rpk(),
|
||||||
|
"This API is not supported for RPK"
|
||||||
|
);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
ffi::SSL_set_verify_depth(self.as_ptr(), depth as c_int);
|
ffi::SSL_set_verify_depth(self.as_ptr(), depth as c_int);
|
||||||
}
|
}
|
||||||
|
|
@ -2840,6 +2932,12 @@ impl SslRef {
|
||||||
#[corresponds(SSL_get_verify_mode)]
|
#[corresponds(SSL_get_verify_mode)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn verify_mode(&self) -> SslVerifyMode {
|
pub fn verify_mode(&self) -> SslVerifyMode {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(
|
||||||
|
!self.ssl_context().is_rpk(),
|
||||||
|
"This API is not supported for RPK"
|
||||||
|
);
|
||||||
|
|
||||||
let mode = unsafe { ffi::SSL_get_verify_mode(self.as_ptr()) };
|
let mode = unsafe { ffi::SSL_get_verify_mode(self.as_ptr()) };
|
||||||
SslVerifyMode::from_bits(mode).expect("SSL_get_verify_mode returned invalid mode")
|
SslVerifyMode::from_bits(mode).expect("SSL_get_verify_mode returned invalid mode")
|
||||||
}
|
}
|
||||||
|
|
@ -2865,6 +2963,12 @@ impl SslRef {
|
||||||
where
|
where
|
||||||
F: Fn(bool, &mut X509StoreContextRef) -> bool + 'static + Sync + Send,
|
F: Fn(bool, &mut X509StoreContextRef) -> bool + 'static + Sync + Send,
|
||||||
{
|
{
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(
|
||||||
|
!self.ssl_context().is_rpk(),
|
||||||
|
"This API is not supported for RPK"
|
||||||
|
);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
// this needs to be in an Arc since the callback can register a new callback!
|
// this needs to be in an Arc since the callback can register a new callback!
|
||||||
self.replace_ex_data(Ssl::cached_ex_index(), Arc::new(callback));
|
self.replace_ex_data(Ssl::cached_ex_index(), Arc::new(callback));
|
||||||
|
|
@ -2879,6 +2983,12 @@ impl SslRef {
|
||||||
/// Sets a custom certificate store for verifying peer certificates.
|
/// Sets a custom certificate store for verifying peer certificates.
|
||||||
#[corresponds(SSL_set0_verify_cert_store)]
|
#[corresponds(SSL_set0_verify_cert_store)]
|
||||||
pub fn set_verify_cert_store(&mut self, cert_store: X509Store) -> Result<(), ErrorStack> {
|
pub fn set_verify_cert_store(&mut self, cert_store: X509Store) -> Result<(), ErrorStack> {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(
|
||||||
|
!self.ssl_context().is_rpk(),
|
||||||
|
"This API is not supported for RPK"
|
||||||
|
);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
cvt(ffi::SSL_set0_verify_cert_store(self.as_ptr(), cert_store.into_ptr()) as c_int)?;
|
cvt(ffi::SSL_set0_verify_cert_store(self.as_ptr(), cert_store.into_ptr()) as c_int)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -2895,6 +3005,12 @@ impl SslRef {
|
||||||
where
|
where
|
||||||
F: Fn(&mut SslRef) -> Result<(), SslVerifyError> + 'static + Sync + Send,
|
F: Fn(&mut SslRef) -> Result<(), SslVerifyError> + 'static + Sync + Send,
|
||||||
{
|
{
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(
|
||||||
|
!self.ssl_context().is_rpk(),
|
||||||
|
"This API is not supported for RPK"
|
||||||
|
);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
// this needs to be in an Arc since the callback can register a new callback!
|
// this needs to be in an Arc since the callback can register a new callback!
|
||||||
self.replace_ex_data(Ssl::cached_ex_index(), Arc::new(callback));
|
self.replace_ex_data(Ssl::cached_ex_index(), Arc::new(callback));
|
||||||
|
|
@ -3024,6 +3140,12 @@ impl SslRef {
|
||||||
#[corresponds(SSL_get_peer_certificate)]
|
#[corresponds(SSL_get_peer_certificate)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn peer_certificate(&self) -> Option<X509> {
|
pub fn peer_certificate(&self) -> Option<X509> {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(
|
||||||
|
!self.ssl_context().is_rpk(),
|
||||||
|
"This API is not supported for RPK"
|
||||||
|
);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = ffi::SSL_get_peer_certificate(self.as_ptr());
|
let ptr = ffi::SSL_get_peer_certificate(self.as_ptr());
|
||||||
if ptr.is_null() {
|
if ptr.is_null() {
|
||||||
|
|
@ -3041,6 +3163,12 @@ impl SslRef {
|
||||||
#[corresponds(SSL_get_peer_certificate)]
|
#[corresponds(SSL_get_peer_certificate)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn peer_cert_chain(&self) -> Option<&StackRef<X509>> {
|
pub fn peer_cert_chain(&self) -> Option<&StackRef<X509>> {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(
|
||||||
|
!self.ssl_context().is_rpk(),
|
||||||
|
"This API is not supported for RPK"
|
||||||
|
);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = ffi::SSL_get_peer_cert_chain(self.as_ptr());
|
let ptr = ffi::SSL_get_peer_cert_chain(self.as_ptr());
|
||||||
if ptr.is_null() {
|
if ptr.is_null() {
|
||||||
|
|
@ -3055,6 +3183,12 @@ impl SslRef {
|
||||||
#[corresponds(SSL_get_certificate)]
|
#[corresponds(SSL_get_certificate)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn certificate(&self) -> Option<&X509Ref> {
|
pub fn certificate(&self) -> Option<&X509Ref> {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(
|
||||||
|
!self.ssl_context().is_rpk(),
|
||||||
|
"This API is not supported for RPK"
|
||||||
|
);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = ffi::SSL_get_certificate(self.as_ptr());
|
let ptr = ffi::SSL_get_certificate(self.as_ptr());
|
||||||
if ptr.is_null() {
|
if ptr.is_null() {
|
||||||
|
|
@ -3308,6 +3442,12 @@ impl SslRef {
|
||||||
/// Returns a mutable reference to the X509 verification configuration.
|
/// Returns a mutable reference to the X509 verification configuration.
|
||||||
#[corresponds(SSL_get0_param)]
|
#[corresponds(SSL_get0_param)]
|
||||||
pub fn verify_param_mut(&mut self) -> &mut X509VerifyParamRef {
|
pub fn verify_param_mut(&mut self) -> &mut X509VerifyParamRef {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(
|
||||||
|
!self.ssl_context().is_rpk(),
|
||||||
|
"This API is not supported for RPK"
|
||||||
|
);
|
||||||
|
|
||||||
unsafe { X509VerifyParamRef::from_ptr_mut(ffi::SSL_get0_param(self.as_ptr())) }
|
unsafe { X509VerifyParamRef::from_ptr_mut(ffi::SSL_get0_param(self.as_ptr())) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3319,6 +3459,12 @@ impl SslRef {
|
||||||
/// Returns the certificate verification result.
|
/// Returns the certificate verification result.
|
||||||
#[corresponds(SSL_get_verify_result)]
|
#[corresponds(SSL_get_verify_result)]
|
||||||
pub fn verify_result(&self) -> X509VerifyResult {
|
pub fn verify_result(&self) -> X509VerifyResult {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(
|
||||||
|
!self.ssl_context().is_rpk(),
|
||||||
|
"This API is not supported for RPK"
|
||||||
|
);
|
||||||
|
|
||||||
unsafe { X509VerifyError::from_raw(ffi::SSL_get_verify_result(self.as_ptr()) as c_int) }
|
unsafe { X509VerifyError::from_raw(ffi::SSL_get_verify_result(self.as_ptr()) as c_int) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3573,6 +3719,12 @@ impl SslRef {
|
||||||
/// as trusted by this method.
|
/// as trusted by this method.
|
||||||
#[corresponds(SSL_set_client_CA_list)]
|
#[corresponds(SSL_set_client_CA_list)]
|
||||||
pub fn set_client_ca_list(&mut self, list: Stack<X509Name>) {
|
pub fn set_client_ca_list(&mut self, list: Stack<X509Name>) {
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(
|
||||||
|
!self.ssl_context().is_rpk(),
|
||||||
|
"This API is not supported for RPK"
|
||||||
|
);
|
||||||
|
|
||||||
unsafe { ffi::SSL_set_client_CA_list(self.as_ptr(), list.as_ptr()) }
|
unsafe { ffi::SSL_set_client_CA_list(self.as_ptr(), list.as_ptr()) }
|
||||||
mem::forget(list);
|
mem::forget(list);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1118,84 +1118,3 @@ fn test_ssl_set_compliance() {
|
||||||
ssl.set_compliance_policy(CompliancePolicy::NONE)
|
ssl.set_compliance_policy(CompliancePolicy::NONE)
|
||||||
.expect_err("Testing expect err if set compliance policy to NONE");
|
.expect_err("Testing expect err if set compliance policy to NONE");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_extension_permutation() {
|
|
||||||
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
|
|
||||||
// firefox extension permutation
|
|
||||||
ctx.set_extension_permutation(&[
|
|
||||||
ExtensionType::SERVER_NAME,
|
|
||||||
ExtensionType::EXTENDED_MASTER_SECRET,
|
|
||||||
ExtensionType::RENEGOTIATE,
|
|
||||||
ExtensionType::SUPPORTED_GROUPS,
|
|
||||||
ExtensionType::EC_POINT_FORMATS,
|
|
||||||
ExtensionType::SESSION_TICKET,
|
|
||||||
ExtensionType::APPLICATION_LAYER_PROTOCOL_NEGOTIATION,
|
|
||||||
ExtensionType::STATUS_REQUEST,
|
|
||||||
ExtensionType::DELEGATED_CREDENTIAL,
|
|
||||||
ExtensionType::CERTIFICATE_TIMESTAMP,
|
|
||||||
ExtensionType::KEY_SHARE,
|
|
||||||
ExtensionType::SUPPORTED_VERSIONS,
|
|
||||||
ExtensionType::SIGNATURE_ALGORITHMS,
|
|
||||||
ExtensionType::PSK_KEY_EXCHANGE_MODES,
|
|
||||||
ExtensionType::RECORD_SIZE_LIMIT,
|
|
||||||
ExtensionType::CERT_COMPRESSION,
|
|
||||||
ExtensionType::ENCRYPTED_CLIENT_HELLO,
|
|
||||||
])
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_connect_with_set_extension_permutation_client_ctx() {
|
|
||||||
let server = Server::builder();
|
|
||||||
let server = server.build();
|
|
||||||
|
|
||||||
let mut client = server.client();
|
|
||||||
// firefox extension permutation
|
|
||||||
client
|
|
||||||
.ctx()
|
|
||||||
.set_extension_permutation(&[
|
|
||||||
ExtensionType::SERVER_NAME,
|
|
||||||
ExtensionType::EXTENDED_MASTER_SECRET,
|
|
||||||
ExtensionType::RENEGOTIATE,
|
|
||||||
ExtensionType::SUPPORTED_GROUPS,
|
|
||||||
ExtensionType::EC_POINT_FORMATS,
|
|
||||||
ExtensionType::SESSION_TICKET,
|
|
||||||
ExtensionType::APPLICATION_LAYER_PROTOCOL_NEGOTIATION,
|
|
||||||
ExtensionType::STATUS_REQUEST,
|
|
||||||
ExtensionType::DELEGATED_CREDENTIAL,
|
|
||||||
ExtensionType::CERTIFICATE_TIMESTAMP,
|
|
||||||
ExtensionType::KEY_SHARE,
|
|
||||||
ExtensionType::SUPPORTED_VERSIONS,
|
|
||||||
ExtensionType::SIGNATURE_ALGORITHMS,
|
|
||||||
ExtensionType::PSK_KEY_EXCHANGE_MODES,
|
|
||||||
ExtensionType::RECORD_SIZE_LIMIT,
|
|
||||||
ExtensionType::CERT_COMPRESSION,
|
|
||||||
ExtensionType::ENCRYPTED_CLIENT_HELLO,
|
|
||||||
])
|
|
||||||
.unwrap();
|
|
||||||
let _ = client.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_connect_with_set_extension_permutation_empty_client_ctx() {
|
|
||||||
let server = Server::builder();
|
|
||||||
let server = server.build();
|
|
||||||
|
|
||||||
let mut client = server.client();
|
|
||||||
client.ctx().set_extension_permutation(&[]).unwrap();
|
|
||||||
let _ = client.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_connect_with_set_extension_permutation_server_name_client_ctx() {
|
|
||||||
let server = Server::builder();
|
|
||||||
let server = server.build();
|
|
||||||
|
|
||||||
let mut client = server.client();
|
|
||||||
client
|
|
||||||
.ctx()
|
|
||||||
.set_extension_permutation(&[ExtensionType::SERVER_NAME])
|
|
||||||
.unwrap();
|
|
||||||
let _ = client.connect();
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
//! Encrypt data in AES128 CBC mode
|
//! Encrypt data in AES128 CBC mode
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use boring2::symm::{encrypt, Cipher};
|
//! use boring::symm::{encrypt, Cipher};
|
||||||
//!
|
//!
|
||||||
//! let cipher = Cipher::aes_128_cbc();
|
//! let cipher = Cipher::aes_128_cbc();
|
||||||
//! let data = b"Some Crypto Text";
|
//! let data = b"Some Crypto Text";
|
||||||
|
|
@ -26,8 +26,8 @@
|
||||||
//! Encrypting an asymmetric key with a symmetric cipher
|
//! Encrypting an asymmetric key with a symmetric cipher
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use boring2::rsa::{Padding, Rsa};
|
//! use boring::rsa::{Padding, Rsa};
|
||||||
//! use boring2::symm::Cipher;
|
//! use boring::symm::Cipher;
|
||||||
//!
|
//!
|
||||||
//! // Generate keypair and encrypt private key:
|
//! // Generate keypair and encrypt private key:
|
||||||
//! let keypair = Rsa::generate(2048).unwrap();
|
//! let keypair = Rsa::generate(2048).unwrap();
|
||||||
|
|
@ -318,7 +318,7 @@ unsafe impl Send for Cipher {}
|
||||||
/// CBC mode.
|
/// CBC mode.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use boring2::symm::{Cipher, Mode, Crypter};
|
/// use boring::symm::{Cipher, Mode, Crypter};
|
||||||
///
|
///
|
||||||
/// let plaintexts: [&[u8]; 2] = [b"Some Stream of", b" Crypto Text"];
|
/// let plaintexts: [&[u8]; 2] = [b"Some Stream of", b" Crypto Text"];
|
||||||
/// let key = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F";
|
/// let key = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F";
|
||||||
|
|
@ -642,7 +642,7 @@ impl Drop for Crypter {
|
||||||
/// Encrypt data in AES128 CBC mode
|
/// Encrypt data in AES128 CBC mode
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use boring2::symm::{encrypt, Cipher};
|
/// use boring::symm::{encrypt, Cipher};
|
||||||
///
|
///
|
||||||
/// let cipher = Cipher::aes_128_cbc();
|
/// let cipher = Cipher::aes_128_cbc();
|
||||||
/// let data = b"Some Crypto Text";
|
/// let data = b"Some Crypto Text";
|
||||||
|
|
@ -681,7 +681,7 @@ pub fn encrypt(
|
||||||
/// Decrypt data in AES128 CBC mode
|
/// Decrypt data in AES128 CBC mode
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use boring2::symm::{decrypt, Cipher};
|
/// use boring::symm::{decrypt, Cipher};
|
||||||
///
|
///
|
||||||
/// let cipher = Cipher::aes_128_cbc();
|
/// let cipher = Cipher::aes_128_cbc();
|
||||||
/// let data = b"\xB4\xB9\xE7\x30\xD6\xD6\xF7\xDE\x77\x3F\x1C\xFF\xB3\x3E\x44\x5A\x91\xD7\x27\x62\
|
/// let data = b"\xB4\xB9\xE7\x30\xD6\xD6\xF7\xDE\x77\x3F\x1C\xFF\xB3\x3E\x44\x5A\x91\xD7\x27\x62\
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@
|
||||||
//! # Example
|
//! # Example
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use boring2::x509::extension::BasicConstraints;
|
//! use boring::x509::extension::BasicConstraints;
|
||||||
//! use boring2::x509::X509Extension;
|
//! use boring::x509::X509Extension;
|
||||||
//!
|
//!
|
||||||
//! let mut bc = BasicConstraints::new();
|
//! let mut bc = BasicConstraints::new();
|
||||||
//! let bc = bc.critical().ca().pathlen(1);
|
//! let bc = bc.critical().ca().pathlen(1);
|
||||||
|
|
|
||||||
|
|
@ -416,16 +416,16 @@ impl X509Builder {
|
||||||
/// The `CN` field is used for the common name, such as a DNS name.
|
/// The `CN` field is used for the common name, such as a DNS name.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use boring2::x509::{X509, X509NameBuilder};
|
/// use boring::x509::{X509, X509NameBuilder};
|
||||||
///
|
///
|
||||||
/// let mut x509_name = boring2::x509::X509NameBuilder::new().unwrap();
|
/// let mut x509_name = boring::x509::X509NameBuilder::new().unwrap();
|
||||||
/// x509_name.append_entry_by_text("C", "US").unwrap();
|
/// x509_name.append_entry_by_text("C", "US").unwrap();
|
||||||
/// x509_name.append_entry_by_text("ST", "CA").unwrap();
|
/// x509_name.append_entry_by_text("ST", "CA").unwrap();
|
||||||
/// x509_name.append_entry_by_text("O", "Some organization").unwrap();
|
/// x509_name.append_entry_by_text("O", "Some organization").unwrap();
|
||||||
/// x509_name.append_entry_by_text("CN", "www.example.com").unwrap();
|
/// x509_name.append_entry_by_text("CN", "www.example.com").unwrap();
|
||||||
/// let x509_name = x509_name.build();
|
/// let x509_name = x509_name.build();
|
||||||
///
|
///
|
||||||
/// let mut x509 = boring2::x509::X509::builder().unwrap();
|
/// let mut x509 = boring::x509::X509::builder().unwrap();
|
||||||
/// x509.set_subject_name(&x509_name).unwrap();
|
/// x509.set_subject_name(&x509_name).unwrap();
|
||||||
/// ```
|
/// ```
|
||||||
#[corresponds(X509_set_subject_name)]
|
#[corresponds(X509_set_subject_name)]
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@
|
||||||
//! # Example
|
//! # Example
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use boring2::x509::store::{X509StoreBuilder, X509Store};
|
//! use boring::x509::store::{X509StoreBuilder, X509Store};
|
||||||
//! use boring2::x509::{X509, X509Name};
|
//! use boring::x509::{X509, X509Name};
|
||||||
//! use boring2::asn1::Asn1Time;
|
//! use boring::asn1::Asn1Time;
|
||||||
//! use boring2::pkey::PKey;
|
//! use boring::pkey::PKey;
|
||||||
//! use boring2::hash::MessageDigest;
|
//! use boring::hash::MessageDigest;
|
||||||
//! use boring2::rsa::Rsa;
|
//! use boring::rsa::Rsa;
|
||||||
//! use boring2::nid::Nid;
|
//! use boring::nid::Nid;
|
||||||
//!
|
//!
|
||||||
//! let rsa = Rsa::generate(2048).unwrap();
|
//! let rsa = Rsa::generate(2048).unwrap();
|
||||||
//! let pkey = PKey::from_rsa(rsa).unwrap();
|
//! let pkey = PKey::from_rsa(rsa).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "compio-boring2"
|
|
||||||
version = { workspace = true }
|
|
||||||
authors = ["0x676e67 <gngppz@gmail.com>"]
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
edition = { workspace = true }
|
|
||||||
repository = { workspace = true }
|
|
||||||
homepage = "https://github.com/0x676e67/boring2"
|
|
||||||
documentation = "https://docs.rs/compio-boring2"
|
|
||||||
description = """
|
|
||||||
An implementation of SSL streams for Compio backed by BoringSSL
|
|
||||||
"""
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
|
|
||||||
[features]
|
|
||||||
# Use a FIPS-validated version of boringssl.
|
|
||||||
fips = ["boring/fips", "boring-sys/fips"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
boring = { workspace = true }
|
|
||||||
boring-sys = { workspace = true }
|
|
||||||
compio = { workspace = true }
|
|
||||||
compio-io = { workspace = true, features = ["compat"]}
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
futures = { workspace = true }
|
|
||||||
compio = { workspace = true, features = [ "macros"] }
|
|
||||||
anyhow = { workspace = true }
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2016 Tokio contributors
|
|
||||||
Copyright (c) 2020 Ivan Nikulin <ifaaan@gmail.com>
|
|
||||||
Copyright (c) 2025 0x676e67 <gngppz@gmail.com>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any
|
|
||||||
person obtaining a copy of this software and associated
|
|
||||||
documentation files (the "Software"), to deal in the
|
|
||||||
Software without restriction, including without
|
|
||||||
limitation the rights to use, copy, modify, merge,
|
|
||||||
publish, distribute, sublicense, and/or sell copies of
|
|
||||||
the Software, and to permit persons to whom the Software
|
|
||||||
is furnished to do so, subject to the following
|
|
||||||
conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice
|
|
||||||
shall be included in all copies or substantial portions
|
|
||||||
of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
|
||||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
||||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
|
||||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
||||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
# compio-boring2
|
|
||||||
|
|
||||||
An implementation of SSL streams for Compio built on top of the BoringSSL.
|
|
||||||
|
|
||||||
[Documentation](https://docs.rs/compio-boring2)
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
First, add this to your `Cargo.toml`:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
compio-boring2 = "5.0.0-alpha.4"
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, use either `accept` or `connect` as appropriate.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use boring::ssl;
|
|
||||||
use compio::net::TcpListener;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
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 mut ssl_builder = boring2::ssl::SslAcceptor::mozilla_modern(server)?;
|
|
||||||
ssl_builder.set_default_verify_paths()?;
|
|
||||||
ssl_builder.set_verify(ssl::SslVerifyMode::PEER);
|
|
||||||
let acceptor = ssl_builder.build();
|
|
||||||
let _ssl_stream = compio_boring2::accept(&acceptor, tcp_stream).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This library is an implementation of TLS streams using BoringSSL for
|
|
||||||
negotiating the connection. Each TLS stream implements the `Read` and
|
|
||||||
`Write` traits to interact and interoperate with the rest of the futures I/O
|
|
||||||
ecosystem. Client connections initiated from this crate verify hostnames
|
|
||||||
automatically and by default.
|
|
||||||
|
|
||||||
`compio-boring2` exports this ability through [`accept`] and [`connect`]. `accept` should
|
|
||||||
be used by servers, and `connect` by clients. These augment the functionality provided by the
|
|
||||||
[`boring2`] crate, on which this crate is built. Configuration of TLS parameters is still
|
|
||||||
primarily done through the [`boring2`] crate.
|
|
||||||
|
|
||||||
# License
|
|
||||||
|
|
||||||
This project is licensed under either of
|
|
||||||
|
|
||||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0)
|
|
||||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
|
||||||
http://opensource.org/licenses/MIT)
|
|
||||||
|
|
||||||
at your option.
|
|
||||||
|
|
||||||
### Contribution
|
|
||||||
|
|
||||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
|
||||||
for inclusion in Serde by you, as defined in the Apache-2.0 license, shall be
|
|
||||||
dual licensed as above, without any additional terms or conditions.
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
use boring::ssl::{
|
|
||||||
AsyncPrivateKeyMethod, AsyncSelectCertError, BoxGetSessionFuture, BoxSelectCertFuture,
|
|
||||||
ClientHello, SslContextBuilder, SslRef,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Extensions to [`SslContextBuilder`].
|
|
||||||
///
|
|
||||||
/// This trait provides additional methods to use async callbacks with boring.
|
|
||||||
pub trait SslContextBuilderExt: private::Sealed {
|
|
||||||
/// Sets a callback that is called before most [`ClientHello`] processing
|
|
||||||
/// and before the decision whether to resume a session is made. The
|
|
||||||
/// callback may inspect the [`ClientHello`] and configure the connection.
|
|
||||||
///
|
|
||||||
/// This method uses a function that returns a future whose output is
|
|
||||||
/// itself a closure that will be passed [`ClientHello`] to configure
|
|
||||||
/// the connection based on the computations done in the future.
|
|
||||||
///
|
|
||||||
/// See [`SslContextBuilder::set_select_certificate_callback`] for the sync
|
|
||||||
/// setter of this callback.
|
|
||||||
fn set_async_select_certificate_callback<F>(&mut self, callback: F)
|
|
||||||
where
|
|
||||||
F: Fn(&mut ClientHello<'_>) -> Result<BoxSelectCertFuture, AsyncSelectCertError>
|
|
||||||
+ Send
|
|
||||||
+ Sync
|
|
||||||
+ 'static;
|
|
||||||
|
|
||||||
/// Configures a custom private key method on the context.
|
|
||||||
///
|
|
||||||
/// See [`AsyncPrivateKeyMethod`] for more details.
|
|
||||||
fn set_async_private_key_method(&mut self, method: impl AsyncPrivateKeyMethod);
|
|
||||||
|
|
||||||
/// Sets a callback that is called when a client proposed to resume a session
|
|
||||||
/// but it was not found in the internal cache.
|
|
||||||
///
|
|
||||||
/// The callback is passed a reference to the session ID provided by the client.
|
|
||||||
/// It should return the session corresponding to that ID if available. This is
|
|
||||||
/// only used for servers, not clients.
|
|
||||||
///
|
|
||||||
/// See [`SslContextBuilder::set_get_session_callback`] for the sync setter
|
|
||||||
/// of this callback.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// The returned [`SslSession`] must not be associated with a different [`SslContext`].
|
|
||||||
unsafe fn set_async_get_session_callback<F>(&mut self, callback: F)
|
|
||||||
where
|
|
||||||
F: Fn(&mut SslRef, &[u8]) -> Option<BoxGetSessionFuture> + Send + Sync + 'static;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SslContextBuilderExt for SslContextBuilder {
|
|
||||||
fn set_async_select_certificate_callback<F>(&mut self, callback: F)
|
|
||||||
where
|
|
||||||
F: Fn(&mut ClientHello<'_>) -> Result<BoxSelectCertFuture, AsyncSelectCertError>
|
|
||||||
+ Send
|
|
||||||
+ Sync
|
|
||||||
+ 'static,
|
|
||||||
{
|
|
||||||
self.set_async_select_certificate_callback(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_async_private_key_method(&mut self, method: impl AsyncPrivateKeyMethod) {
|
|
||||||
self.set_async_private_key_method(method);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn set_async_get_session_callback<F>(&mut self, callback: F)
|
|
||||||
where
|
|
||||||
F: Fn(&mut SslRef, &[u8]) -> Option<BoxGetSessionFuture> + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
self.set_async_get_session_callback(callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod private {
|
|
||||||
pub trait Sealed {}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl private::Sealed for SslContextBuilder {}
|
|
||||||
|
|
@ -1,329 +0,0 @@
|
||||||
//! Async TLS streams backed by BoringSSL
|
|
||||||
//!
|
|
||||||
//! This library is an implementation of TLS streams using BoringSSL for
|
|
||||||
//! negotiating the connection. Each TLS stream implements the `Read` and
|
|
||||||
//! `Write` traits to interact and interoperate with the rest of the futures I/O
|
|
||||||
//! ecosystem. Client connections initiated from this crate verify hostnames
|
|
||||||
//! automatically and by default.
|
|
||||||
//!
|
|
||||||
//! `compio-boring2` exports this ability through [`accept`] and [`connect`]. `accept` should
|
|
||||||
//! be used by servers, and `connect` by clients. These augment the functionality provided by the
|
|
||||||
//! [`boring2`] crate, on which this crate is built. Configuration of TLS parameters is still
|
|
||||||
//! primarily done through the [`boring2`] crate.
|
|
||||||
#![warn(missing_docs)]
|
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
|
||||||
|
|
||||||
mod async_callbacks;
|
|
||||||
|
|
||||||
use boring::ssl::{self, ConnectConfiguration, ErrorCode, SslAcceptor, SslRef};
|
|
||||||
use boring_sys as ffi;
|
|
||||||
use compio::buf::{IoBuf, IoBufMut};
|
|
||||||
use compio::io::{AsyncRead, AsyncWrite};
|
|
||||||
use compio::BufResult;
|
|
||||||
use compio_io::compat::SyncStream;
|
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt;
|
|
||||||
use std::io;
|
|
||||||
use std::mem::MaybeUninit;
|
|
||||||
|
|
||||||
pub use crate::async_callbacks::SslContextBuilderExt;
|
|
||||||
pub use boring::ssl::{
|
|
||||||
AsyncPrivateKeyMethod, AsyncPrivateKeyMethodError, AsyncSelectCertError, BoxGetSessionFinish,
|
|
||||||
BoxGetSessionFuture, BoxPrivateKeyMethodFinish, BoxPrivateKeyMethodFuture, BoxSelectCertFinish,
|
|
||||||
BoxSelectCertFuture, ExDataFuture,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Asynchronously performs a client-side TLS handshake over the provided stream.
|
|
||||||
///
|
|
||||||
/// This function automatically sets the task waker on the `Ssl` from `config` to
|
|
||||||
/// allow to make use of async callbacks provided by the boring crate.
|
|
||||||
pub async fn connect<S>(
|
|
||||||
config: ConnectConfiguration,
|
|
||||||
domain: &str,
|
|
||||||
stream: S,
|
|
||||||
) -> Result<SslStream<S>, HandshakeError<S>>
|
|
||||||
where
|
|
||||||
S: AsyncRead + AsyncWrite + Unpin,
|
|
||||||
{
|
|
||||||
let res = config.connect(domain, SyncStream::new(stream));
|
|
||||||
perform_tls_handshake(res).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Asynchronously performs a server-side TLS handshake over the provided stream.
|
|
||||||
///
|
|
||||||
/// This function automatically sets the task waker on the `Ssl` from `config` to
|
|
||||||
/// allow to make use of async callbacks provided by the boring crate.
|
|
||||||
pub async fn accept<S>(acceptor: &SslAcceptor, stream: S) -> Result<SslStream<S>, HandshakeError<S>>
|
|
||||||
where
|
|
||||||
S: AsyncRead + AsyncWrite + Unpin,
|
|
||||||
{
|
|
||||||
let res = acceptor.accept(SyncStream::new(stream));
|
|
||||||
perform_tls_handshake(res).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A partially constructed `SslStream`, useful for unusual handshakes.
|
|
||||||
pub struct SslStreamBuilder<S> {
|
|
||||||
inner: ssl::SslStreamBuilder<SyncStream<S>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> SslStreamBuilder<S>
|
|
||||||
where
|
|
||||||
S: AsyncRead + AsyncWrite + Unpin,
|
|
||||||
{
|
|
||||||
/// Begins creating an `SslStream` atop `stream`.
|
|
||||||
pub fn new(ssl: ssl::Ssl, stream: S) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: ssl::SslStreamBuilder::new(ssl, SyncStream::new(stream)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initiates a client-side TLS handshake.
|
|
||||||
pub async fn accept(self) -> Result<SslStream<S>, HandshakeError<S>> {
|
|
||||||
let res = self.inner.connect();
|
|
||||||
perform_tls_handshake(res).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initiates a server-side TLS handshake.
|
|
||||||
pub async fn connect(self) -> Result<SslStream<S>, HandshakeError<S>> {
|
|
||||||
let res = self.inner.connect();
|
|
||||||
perform_tls_handshake(res).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a mutable reference to the `Ssl` object associated with this builder.
|
|
||||||
pub fn ssl_mut(&mut self) -> &mut SslRef {
|
|
||||||
self.inner.ssl_mut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A wrapper around an underlying raw stream which implements the SSL
|
|
||||||
/// protocol.
|
|
||||||
///
|
|
||||||
/// A `SslStream<S>` represents a handshake that has been completed successfully
|
|
||||||
/// and both the server and the client are ready for receiving and sending
|
|
||||||
/// data. Bytes read from a `SslStream` are decrypted from `S` and bytes written
|
|
||||||
/// to a `SslStream` are encrypted when passing through to `S`.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct SslStream<S>(ssl::SslStream<SyncStream<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()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a mutable reference to the `Ssl` object associated with this stream.
|
|
||||||
pub fn ssl_mut(&mut self) -> &mut SslRef {
|
|
||||||
self.0.ssl_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a shared reference to the underlying stream.
|
|
||||||
#[must_use]
|
|
||||||
pub fn get_ref(&self) -> &S {
|
|
||||||
self.0.get_ref().get_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a mutable reference to the underlying stream.
|
|
||||||
pub fn get_mut(&mut self) -> &mut S {
|
|
||||||
self.0.get_mut().get_mut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> SslStream<S>
|
|
||||||
where
|
|
||||||
S: AsyncRead + AsyncWrite + Unpin,
|
|
||||||
{
|
|
||||||
/// Constructs an `SslStream` from a pointer to the underlying OpenSSL `SSL` struct.
|
|
||||||
///
|
|
||||||
/// This is useful if the handshake has already been completed elsewhere.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// The caller must ensure the pointer is valid.
|
|
||||||
pub unsafe fn from_raw_parts(ssl: *mut ffi::SSL, stream: S) -> Self {
|
|
||||||
Self(ssl::SslStream::from_raw_parts(ssl, SyncStream::new(stream)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: AsyncRead> AsyncRead for SslStream<S> {
|
|
||||||
async fn read<B: IoBufMut>(&mut self, mut buf: B) -> BufResult<usize, B> {
|
|
||||||
let slice = buf.as_mut_slice();
|
|
||||||
|
|
||||||
let mut f = {
|
|
||||||
slice.fill(MaybeUninit::new(0));
|
|
||||||
// SAFETY: The memory has been initialized
|
|
||||||
let slice =
|
|
||||||
unsafe { std::slice::from_raw_parts_mut(slice.as_mut_ptr().cast(), slice.len()) };
|
|
||||||
|s: &mut _| std::io::Read::read(s, slice)
|
|
||||||
};
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match f(&mut self.0) {
|
|
||||||
Ok(res) => {
|
|
||||||
unsafe { buf.set_buf_init(res) };
|
|
||||||
return BufResult(Ok(res), buf);
|
|
||||||
}
|
|
||||||
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
|
|
||||||
match self.0.get_mut().fill_read_buf().await {
|
|
||||||
Ok(_) => continue,
|
|
||||||
Err(e) => return BufResult(Err(e), buf),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res => return BufResult(res, buf),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `AsyncRead` is needed for shutting down stream.
|
|
||||||
impl<S: AsyncWrite + AsyncRead> AsyncWrite for SslStream<S> {
|
|
||||||
async fn write<T: IoBuf>(&mut self, buf: T) -> BufResult<usize, T> {
|
|
||||||
let slice = buf.as_slice();
|
|
||||||
loop {
|
|
||||||
let res = io::Write::write(&mut self.0, slice);
|
|
||||||
match res {
|
|
||||||
Err(e) if e.kind() == io::ErrorKind::WouldBlock => match self.flush().await {
|
|
||||||
Ok(_) => continue,
|
|
||||||
Err(e) => return BufResult(Err(e), buf),
|
|
||||||
},
|
|
||||||
_ => return BufResult(res, buf),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn flush(&mut self) -> io::Result<()> {
|
|
||||||
loop {
|
|
||||||
match io::Write::flush(&mut self.0) {
|
|
||||||
Ok(()) => break,
|
|
||||||
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
|
|
||||||
self.0.get_mut().flush_write_buf().await?;
|
|
||||||
}
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.0.get_mut().flush_write_buf().await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn shutdown(&mut self) -> io::Result<()> {
|
|
||||||
self.flush().await?;
|
|
||||||
self.0.get_mut().get_mut().shutdown().await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error type returned after a failed handshake.
|
|
||||||
pub enum HandshakeError<S> {
|
|
||||||
/// An error that occurred during the handshake.
|
|
||||||
Inner(ssl::HandshakeError<SyncStream<S>>),
|
|
||||||
/// An I/O error that occurred during the handshake.
|
|
||||||
Io(io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
HandshakeError::Inner(ssl::HandshakeError::Failure(s)) => Some(s.ssl()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the error code, if any.
|
|
||||||
#[must_use]
|
|
||||||
pub fn code(&self) -> Option<ErrorCode> {
|
|
||||||
match self {
|
|
||||||
HandshakeError::Inner(ssl::HandshakeError::Failure(s)) => Some(s.error().code()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference to the inner I/O error, if any.
|
|
||||||
#[must_use]
|
|
||||||
pub fn as_io_error(&self) -> Option<&io::Error> {
|
|
||||||
match self {
|
|
||||||
HandshakeError::Inner(ssl::HandshakeError::Failure(s)) => s.error().io_error(),
|
|
||||||
HandshakeError::Io(e) => Some(e),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> fmt::Debug for HandshakeError<S>
|
|
||||||
where
|
|
||||||
S: fmt::Debug,
|
|
||||||
{
|
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
HandshakeError::Inner(e) => fmt::Debug::fmt(e, fmt),
|
|
||||||
HandshakeError::Io(e) => fmt::Debug::fmt(e, fmt),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> fmt::Display for HandshakeError<S> {
|
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
HandshakeError::Inner(e) => fmt::Display::fmt(e, fmt),
|
|
||||||
HandshakeError::Io(e) => fmt::Display::fmt(e, fmt),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> Error for HandshakeError<S>
|
|
||||||
where
|
|
||||||
S: fmt::Debug,
|
|
||||||
{
|
|
||||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
|
||||||
match self {
|
|
||||||
HandshakeError::Inner(e) => e.source(),
|
|
||||||
HandshakeError::Io(e) => Some(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn perform_tls_handshake<S: AsyncRead + AsyncWrite>(
|
|
||||||
mut res: Result<ssl::SslStream<SyncStream<S>>, ssl::HandshakeError<SyncStream<S>>>,
|
|
||||||
) -> Result<SslStream<S>, HandshakeError<S>> {
|
|
||||||
loop {
|
|
||||||
match res {
|
|
||||||
Ok(mut s) => {
|
|
||||||
s.get_mut()
|
|
||||||
.flush_write_buf()
|
|
||||||
.await
|
|
||||||
.map_err(HandshakeError::Io)?;
|
|
||||||
return Ok(SslStream(s));
|
|
||||||
}
|
|
||||||
Err(e) => match e {
|
|
||||||
ssl::HandshakeError::Failure(_) => return Err(HandshakeError::Inner(e)),
|
|
||||||
ssl::HandshakeError::SetupFailure(_) => {
|
|
||||||
return Err(HandshakeError::Inner(e));
|
|
||||||
}
|
|
||||||
ssl::HandshakeError::WouldBlock(mut mid_stream) => {
|
|
||||||
if mid_stream
|
|
||||||
.get_mut()
|
|
||||||
.flush_write_buf()
|
|
||||||
.await
|
|
||||||
.map_err(HandshakeError::Io)?
|
|
||||||
== 0
|
|
||||||
{
|
|
||||||
mid_stream
|
|
||||||
.get_mut()
|
|
||||||
.fill_read_buf()
|
|
||||||
.await
|
|
||||||
.map_err(HandshakeError::Io)?;
|
|
||||||
}
|
|
||||||
res = mid_stream.handshake();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
use boring::ssl::{SslConnector, SslMethod};
|
|
||||||
use compio::io::AsyncReadExt;
|
|
||||||
use compio::net::TcpStream;
|
|
||||||
use compio_io::AsyncWrite;
|
|
||||||
use std::net::ToSocketAddrs;
|
|
||||||
|
|
||||||
#[compio::test]
|
|
||||||
async fn google() {
|
|
||||||
let addr = "google.com:443".to_socket_addrs().unwrap().next().unwrap();
|
|
||||||
let stream = TcpStream::connect(&addr).await.unwrap();
|
|
||||||
|
|
||||||
let config = SslConnector::builder(SslMethod::tls())
|
|
||||||
.unwrap()
|
|
||||||
.build()
|
|
||||||
.configure()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut stream = compio_boring2::connect(config, "google.com", stream)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
stream.write(b"GET / HTTP/1.0\r\n\r\n").await.unwrap();
|
|
||||||
stream.flush().await.unwrap();
|
|
||||||
let (_, buf) = stream.read_to_end(vec![]).await.unwrap();
|
|
||||||
stream.shutdown().await.unwrap();
|
|
||||||
let response = String::from_utf8_lossy(&buf);
|
|
||||||
let response = response.trim_end();
|
|
||||||
|
|
||||||
// any response code is fine
|
|
||||||
assert!(response.starts_with("HTTP/1.0 "));
|
|
||||||
assert!(response.ends_with("</html>") || response.ends_with("</HTML>"));
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
target/
|
||||||
|
Cargo.lock
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
[package]
|
||||||
|
name = "hyper-boring"
|
||||||
|
version = { workspace = true }
|
||||||
|
authors = ["Steven Fackler <sfackler@gmail.com>", "Ivan Nikulin <ifaaan@gmail.com>"]
|
||||||
|
edition = { workspace = true }
|
||||||
|
description = "Hyper TLS support via BoringSSL"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
repository = { workspace = true }
|
||||||
|
documentation = "https://docs.rs/hyper-boring"
|
||||||
|
readme = "README.md"
|
||||||
|
exclude = ["test/*"]
|
||||||
|
rust-version = "1.80"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
features = ["pq-experimental"]
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# Use a FIPS-validated version of boringssl.
|
||||||
|
fips = ["boring/fips", "tokio-boring/fips"]
|
||||||
|
|
||||||
|
# Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/)
|
||||||
|
pq-experimental = ["tokio-boring/pq-experimental"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
antidote = { workspace = true }
|
||||||
|
http = { workspace = true }
|
||||||
|
hyper = { workspace = true }
|
||||||
|
hyper-util = { workspace = true, features = ["client", "client-legacy"] }
|
||||||
|
linked_hash_set = { workspace = true }
|
||||||
|
boring = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
tokio-boring = { workspace = true }
|
||||||
|
tower-layer = { workspace = true }
|
||||||
|
tower-service = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
bytes = { workspace = true }
|
||||||
|
http-body-util = { workspace = true }
|
||||||
|
hyper-util = { workspace = true, features = ["http1", "http2", "service", "tokio"] }
|
||||||
|
hyper = { workspace = true, features = ["server"] }
|
||||||
|
tokio = { workspace = true, features = [ "full" ] }
|
||||||
|
tower = { workspace = true, features = ["util"] }
|
||||||
|
futures = { workspace = true }
|
||||||
|
|
||||||
|
[package.metadata.release]
|
||||||
|
pre-release-hook = [
|
||||||
|
"git-cliff",
|
||||||
|
"--workdir", "..",
|
||||||
|
"-o", "../RELEASE_NOTES",
|
||||||
|
"--tag", "{{version}}"
|
||||||
|
]
|
||||||
|
|
@ -186,8 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
|
||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright 2016 Tokio contributors
|
Copyright [yyyy] [name of copyright owner]
|
||||||
Copyright 2025 0x676e67 <gngppz@gmail.com>
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-2016 Steven Fackler
|
||||||
|
Copyright (c) 2020 Ivan Nikulin <ifaaan@gmail.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
# hyper-boring
|
||||||
|
|
||||||
|
[Documentation](https://docs.rs/hyper-boring)
|
||||||
|
|
||||||
|
Hyper SSL support via BoringSSL.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Licensed under either of
|
||||||
|
|
||||||
|
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
at your option.
|
||||||
|
|
||||||
|
### 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 as above, without any additional terms or
|
||||||
|
conditions.
|
||||||
|
|
||||||
|
## Accolades
|
||||||
|
|
||||||
|
The project is based on a fork of [hyper-openssl](https://github.com/sfackler/hyper-openssl).
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
use boring::ssl::SslVersion;
|
||||||
|
use boring::ssl::{SslSession, SslSessionRef};
|
||||||
|
use linked_hash_set::LinkedHashSet;
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
use std::collections::hash_map::{Entry, HashMap};
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
|
#[derive(Hash, PartialEq, Eq, Clone)]
|
||||||
|
pub struct SessionKey {
|
||||||
|
pub host: String,
|
||||||
|
pub port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct HashSession(SslSession);
|
||||||
|
|
||||||
|
impl PartialEq for HashSession {
|
||||||
|
fn eq(&self, other: &HashSession) -> bool {
|
||||||
|
self.0.id() == other.0.id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for HashSession {}
|
||||||
|
|
||||||
|
impl Hash for HashSession {
|
||||||
|
fn hash<H>(&self, state: &mut H)
|
||||||
|
where
|
||||||
|
H: Hasher,
|
||||||
|
{
|
||||||
|
self.0.id().hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Borrow<[u8]> for HashSession {
|
||||||
|
fn borrow(&self) -> &[u8] {
|
||||||
|
self.0.id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SessionCache {
|
||||||
|
sessions: HashMap<SessionKey, LinkedHashSet<HashSession>>,
|
||||||
|
reverse: HashMap<HashSession, SessionKey>,
|
||||||
|
/// Maximum capacity of LinkedHashSet per SessionKey
|
||||||
|
per_key_session_capacity: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionCache {
|
||||||
|
pub fn with_capacity(per_key_session_capacity: usize) -> SessionCache {
|
||||||
|
SessionCache {
|
||||||
|
sessions: HashMap::new(),
|
||||||
|
reverse: HashMap::new(),
|
||||||
|
per_key_session_capacity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, key: SessionKey, session: SslSession) {
|
||||||
|
let session = HashSession(session);
|
||||||
|
|
||||||
|
let sessions = self.sessions.entry(key.clone()).or_default();
|
||||||
|
|
||||||
|
// if sessions exceed capacity, discard oldest
|
||||||
|
if sessions.len() >= self.per_key_session_capacity {
|
||||||
|
if let Some(hash) = sessions.pop_front() {
|
||||||
|
self.reverse.remove(&hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sessions.insert(session.clone());
|
||||||
|
self.reverse.insert(session, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&mut self, key: &SessionKey) -> Option<SslSession> {
|
||||||
|
let session = {
|
||||||
|
let sessions = self.sessions.get_mut(key)?;
|
||||||
|
sessions.front().cloned()?.0
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/rfc8446#appendix-C.4
|
||||||
|
// OpenSSL will remove the session from its cache after the handshake completes anyway, but this ensures
|
||||||
|
// that concurrent handshakes don't end up with the same session.
|
||||||
|
if session.protocol_version() == SslVersion::TLS1_3 {
|
||||||
|
self.remove(&session);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, session: &SslSessionRef) {
|
||||||
|
let key = match self.reverse.remove(session.id()) {
|
||||||
|
Some(key) => key,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Entry::Occupied(mut sessions) = self.sessions.entry(key) {
|
||||||
|
sessions.get_mut().remove(session.id());
|
||||||
|
if sessions.get().is_empty() {
|
||||||
|
sessions.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
//! Hyper SSL support via BoringSSL.
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
|
||||||
|
use crate::cache::SessionKey;
|
||||||
|
use boring::error::ErrorStack;
|
||||||
|
use boring::ex_data::Index;
|
||||||
|
use boring::ssl::Ssl;
|
||||||
|
use std::fmt;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
use tokio_boring::SslStream;
|
||||||
|
|
||||||
|
mod cache;
|
||||||
|
mod v1;
|
||||||
|
|
||||||
|
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());
|
||||||
|
Ok(*IDX)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Settings for [`HttpsLayer`]
|
||||||
|
pub struct HttpsLayerSettings {
|
||||||
|
session_cache_capacity: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpsLayerSettings {
|
||||||
|
/// Constructs an [`HttpsLayerSettingsBuilder`] for configuring settings
|
||||||
|
#[must_use]
|
||||||
|
pub fn builder() -> HttpsLayerSettingsBuilder {
|
||||||
|
HttpsLayerSettingsBuilder(HttpsLayerSettings::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HttpsLayerSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
session_cache_capacity: 8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builder for [`HttpsLayerSettings`]
|
||||||
|
pub struct HttpsLayerSettingsBuilder(HttpsLayerSettings);
|
||||||
|
|
||||||
|
impl HttpsLayerSettingsBuilder {
|
||||||
|
/// Sets maximum number of sessions to cache. Session capacity is per session key (domain).
|
||||||
|
/// Defaults to 8.
|
||||||
|
pub fn set_session_cache_capacity(&mut self, capacity: usize) {
|
||||||
|
self.0.session_cache_capacity = capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes the builder, returning a new [`HttpsLayerSettings`]
|
||||||
|
#[must_use]
|
||||||
|
pub fn build(self) -> HttpsLayerSettings {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A stream which may be wrapped with TLS.
|
||||||
|
pub enum MaybeHttpsStream<T> {
|
||||||
|
/// A raw HTTP stream.
|
||||||
|
Http(T),
|
||||||
|
/// An SSL-wrapped HTTP stream.
|
||||||
|
Https(SslStream<T>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fmt::Debug for MaybeHttpsStream<T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
MaybeHttpsStream::Http(..) => f.pad("Http(..)"),
|
||||||
|
MaybeHttpsStream::Https(..) => f.pad("Https(..)"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,347 @@
|
||||||
|
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::uri::Scheme;
|
||||||
|
use http::Uri;
|
||||||
|
use hyper::rt::{Read, ReadBufCursor, Write};
|
||||||
|
use hyper_util::client::legacy::connect::{Connected, Connection, HttpConnector};
|
||||||
|
use hyper_util::rt::TokioIo;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use std::{io, net};
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
use tower_layer::Layer;
|
||||||
|
use tower_service::Service;
|
||||||
|
|
||||||
|
/// A Connector using BoringSSL to support `http` and `https` schemes.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HttpsConnector<T> {
|
||||||
|
http: T,
|
||||||
|
inner: Inner,
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
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 = TokioIo<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<T, S> Service<Uri> for HttpsConnector<S>
|
||||||
|
where
|
||||||
|
S: Service<Uri, Response = TokioIo<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,
|
||||||
|
{
|
||||||
|
type Response = MaybeHttpsStream<T>;
|
||||||
|
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)?.into_inner();
|
||||||
|
|
||||||
|
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> Read for MaybeHttpsStream<T>
|
||||||
|
where
|
||||||
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
{
|
||||||
|
fn poll_read(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: ReadBufCursor<'_>,
|
||||||
|
) -> Poll<Result<(), std::io::Error>> {
|
||||||
|
match &mut *self {
|
||||||
|
MaybeHttpsStream::Http(inner) => Pin::new(&mut TokioIo::new(inner)).poll_read(cx, buf),
|
||||||
|
MaybeHttpsStream::Https(inner) => Pin::new(&mut TokioIo::new(inner)).poll_read(cx, buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Write 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(inner) => {
|
||||||
|
Pin::new(&mut TokioIo::new(inner)).poll_write(ctx, buf)
|
||||||
|
}
|
||||||
|
MaybeHttpsStream::Https(inner) => {
|
||||||
|
Pin::new(&mut TokioIo::new(inner)).poll_write(ctx, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||||
|
match &mut *self {
|
||||||
|
MaybeHttpsStream::Http(inner) => Pin::new(&mut TokioIo::new(inner)).poll_flush(ctx),
|
||||||
|
MaybeHttpsStream::Https(inner) => Pin::new(&mut TokioIo::new(inner)).poll_flush(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_shutdown(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||||
|
match &mut *self {
|
||||||
|
MaybeHttpsStream::Http(inner) => Pin::new(&mut TokioIo::new(inner)).poll_shutdown(ctx),
|
||||||
|
MaybeHttpsStream::Https(inner) => Pin::new(&mut TokioIo::new(inner)).poll_shutdown(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDGzCCAgMCCQCHcfe97pgvpTANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB
|
||||||
|
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
|
||||||
|
cyBQdHkgTHRkMB4XDTE2MDgxNDE3MDAwM1oXDTI2MDgxMjE3MDAwM1owWjELMAkG
|
||||||
|
A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
|
||||||
|
IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKZm9vYmFyLmNvbTCCASIwDQYJKoZI
|
||||||
|
hvcNAQEBBQADggEPADCCAQoCggEBAKj0JYxEsxejUIX+I5GH0Hg2G0kX/y1H0+Ub
|
||||||
|
3mw2/Ja5BD/yN96/7zMSumXF8uS3SkmpyiJkbyD01TSRTqjlP7/VCBlyUIChlpLQ
|
||||||
|
mrGaijZiT/VCyPXqmcwFzXS5IOTpX1olJfW8rA41U1LCIcDUyFf6LtZ/v8rSeKr6
|
||||||
|
TuE6SGV4WRaBm1SrjWBeHVV866CRrtSS1ieT2asFsAyOZqWhk2fakwwBDFWDhOGI
|
||||||
|
ubfO+5aq9cBJbNRlzsgB3UZs3gC0O6GzbnZ6oT0TiJMeTsXXjABLUlaq/rrqFF4Y
|
||||||
|
euZkkbHTFBMz288PUc3m3ZTcpN+E7+ZOUBRZXKD20K07NugqCzUCAwEAATANBgkq
|
||||||
|
hkiG9w0BAQsFAAOCAQEASvYHuIl5C0NHBELPpVHNuLbQsDQNKVj3a54+9q1JkiMM
|
||||||
|
6taEJYfw7K1Xjm4RoiFSHpQBh+PWZS3hToToL2Zx8JfMR5MuAirdPAy1Sia/J/qE
|
||||||
|
wQdJccqmvuLkLTSlsGbEJ/LUUgOAgrgHOZM5lUgIhCneA0/dWJ3PsN0zvn69/faY
|
||||||
|
oo1iiolWiIHWWBUSdr3jM2AJaVAsTmLh00cKaDNk37JB940xConBGSl98JPrNrf9
|
||||||
|
dUAiT0iIBngDBdHnn/yTj+InVEFyZSKrNtiDSObFHxPcxGteHNrCPJdP1e+GqkHp
|
||||||
|
HJMRZVCQpSMzvHlofHSNgzWV1MX5h1CP4SGZdBDTfA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCo9CWMRLMXo1CF
|
||||||
|
/iORh9B4NhtJF/8tR9PlG95sNvyWuQQ/8jfev+8zErplxfLkt0pJqcoiZG8g9NU0
|
||||||
|
kU6o5T+/1QgZclCAoZaS0Jqxmoo2Yk/1Qsj16pnMBc10uSDk6V9aJSX1vKwONVNS
|
||||||
|
wiHA1MhX+i7Wf7/K0niq+k7hOkhleFkWgZtUq41gXh1VfOugka7UktYnk9mrBbAM
|
||||||
|
jmaloZNn2pMMAQxVg4ThiLm3zvuWqvXASWzUZc7IAd1GbN4AtDuhs252eqE9E4iT
|
||||||
|
Hk7F14wAS1JWqv666hReGHrmZJGx0xQTM9vPD1HN5t2U3KTfhO/mTlAUWVyg9tCt
|
||||||
|
OzboKgs1AgMBAAECggEBAKLj6IOJBKXolczpzb8UkyAjAkGBektcseV07gelJ/fk
|
||||||
|
3z0LuWPv5p12E/HlXB24vU2x/ikUbbP3eMsawRzDEahQqmNmPEkYAYUAy/Qpi9GN
|
||||||
|
DYvn3LqDec4jVgeQKS+p9H2DzUpTogp8zR2//yzbuWBg2+F//xh7vU0S0RQCziPM
|
||||||
|
x7RSBgbhxSfChfEJbS2sDnzfh0jRQmoY95iFv7puet1FJtzdZ4fgCd1RqmC2lFM5
|
||||||
|
H0eZtN/Cz19lieVs0b996DErdEBqClVZO00eYbRozCDaBzRU3ybB/dMrGJxhkkXm
|
||||||
|
wb3kWMtziH9qOYsostuHIFu8eKFLloKxFnq2R4DGxOECgYEA2KUIZISOeGJSBcLJ
|
||||||
|
JAUK2gvgXPNo4HHWIwOA9xeN3ZJlsnPlffXQNnm6t1st1V2gfMm9I2n0m/F0y2B/
|
||||||
|
n/XGSa8bghfPA9l0c2h58lkL3JQJR/paa8ycTz+YZPrznEyN7Qa0RrJXUvZv9lQL
|
||||||
|
Hc3+FHcSHgMqDV2f2bHAEu9YGi0CgYEAx6VEIPNvrHFgjo/jk1RTuk+m0xEWQsZL
|
||||||
|
Cs+izQMr2TaeJn8LG+93AvFuYn0J0nT3WuStLPrUg8i4IhSS6lf1tId5ivIZPm4r
|
||||||
|
YwMyblBJXhnHbk7Uqodjfw/3s6V2HAu++B7hTdyVr9DFuST9uv4m8bkPV8rfX1jE
|
||||||
|
I2rAPVWvgikCgYB+wNAQP547wQrMZBLbCDg5KwmyWJfb+b6X7czexOEz6humNTjo
|
||||||
|
YZHYzY/5B1fhpk3ntQD8X1nGg5caBvOk21+QbOtjShrM3cXMYCw5JvBRtitX+Zo9
|
||||||
|
yBEMLOE0877ki8XeEDYZxu5gk98d+D4oygUGZEQtWxyXhVepPt5qNa8OYQKBgQDH
|
||||||
|
RVgZI6KFlqzv3wMh3PutbS9wYQ+9GrtwUQuIYe/0YSW9+vSVr5E0qNKrD28sV39F
|
||||||
|
hBauXLady0yvB6YUrjMbPFW+sCMuQzyfGWPO4+g3OrfqjFiM1ZIkE0YEU9Tt7XNx
|
||||||
|
qTDtTI1D7bhNMnTnniI1B6ge0und+3XafAThs5L48QKBgQCTTpfqMt8kU3tcI9sf
|
||||||
|
0MK03y7kA76d5uw0pZbWFy7KI4qnzWutCzb+FMPWWsoFtLJLPZy//u/ZCUVFVa4d
|
||||||
|
0Y/ASNQIESVPXFLAltlLo4MSmsg1vCBsbviEEaPeEjvMrgki93pYtd/aOSgkYC1T
|
||||||
|
mEq154s5rmqh+h+XRIf7Au0SLw==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDXTCCAkWgAwIBAgIJAOIvDiVb18eVMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
||||||
|
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||||
|
aWRnaXRzIFB0eSBMdGQwHhcNMTYwODE0MTY1NjExWhcNMjYwODEyMTY1NjExWjBF
|
||||||
|
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
|
||||||
|
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||||
|
CgKCAQEArVHWFn52Lbl1l59exduZntVSZyDYpzDND+S2LUcO6fRBWhV/1Kzox+2G
|
||||||
|
ZptbuMGmfI3iAnb0CFT4uC3kBkQQlXonGATSVyaFTFR+jq/lc0SP+9Bd7SBXieIV
|
||||||
|
eIXlY1TvlwIvj3Ntw9zX+scTA4SXxH6M0rKv9gTOub2vCMSHeF16X8DQr4XsZuQr
|
||||||
|
7Cp7j1I4aqOJyap5JTl5ijmG8cnu0n+8UcRlBzy99dLWJG0AfI3VRJdWpGTNVZ92
|
||||||
|
aFff3RpK3F/WI2gp3qV1ynRAKuvmncGC3LDvYfcc2dgsc1N6Ffq8GIrkgRob6eBc
|
||||||
|
klDHp1d023Lwre+VaVDSo1//Y72UFwIDAQABo1AwTjAdBgNVHQ4EFgQUbNOlA6sN
|
||||||
|
XyzJjYqciKeId7g3/ZowHwYDVR0jBBgwFoAUbNOlA6sNXyzJjYqciKeId7g3/Zow
|
||||||
|
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVVaR5QWLZIRR4Dw6TSBn
|
||||||
|
BQiLpBSXN6oAxdDw6n4PtwW6CzydaA+creiK6LfwEsiifUfQe9f+T+TBSpdIYtMv
|
||||||
|
Z2H2tjlFX8VrjUFvPrvn5c28CuLI0foBgY8XGSkR2YMYzWw2jPEq3Th/KM5Catn3
|
||||||
|
AFm3bGKWMtGPR4v+90chEN0jzaAmJYRrVUh9vea27bOCn31Nse6XXQPmSI6Gyncy
|
||||||
|
OAPUsvPClF3IjeL1tmBotWqSGn1cYxLo+Lwjk22A9h6vjcNQRyZF2VLVvtwYrNU3
|
||||||
|
mwJ6GCLsLHpwW/yjyvn8iEltnJvByM/eeRnfXV6WDObyiZsE/n6DxIRJodQzFqy9
|
||||||
|
GA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
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::HttpsConnector;
|
||||||
|
use hyper_util::client::legacy::connect::HttpConnector;
|
||||||
|
use hyper_util::client::legacy::Client;
|
||||||
|
use hyper_util::rt::{TokioExecutor, TokioIo};
|
||||||
|
use std::convert::Infallible;
|
||||||
|
use std::{io, iter};
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn google() {
|
||||||
|
let ssl = HttpsConnector::new().unwrap();
|
||||||
|
let client = Client::builder(TokioExecutor::new())
|
||||||
|
.pool_max_idle_per_host(0)
|
||||||
|
.build::<_, Empty<Bytes>>(ssl);
|
||||||
|
|
||||||
|
for _ in 0..3 {
|
||||||
|
let resp = client
|
||||||
|
.get("https://www.google.com".parse().unwrap())
|
||||||
|
.await
|
||||||
|
.expect("connection should succeed");
|
||||||
|
let mut body = BodyStream::new(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(<Empty<Bytes>>::new()))
|
||||||
|
});
|
||||||
|
|
||||||
|
hyper::server::conn::http1::Builder::new()
|
||||||
|
.keep_alive(false)
|
||||||
|
.serve_connection(TokioIo::new(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(TokioExecutor::new()).build::<_, Empty<Bytes>>(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 = BodyStream::new(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(<Empty<Bytes>>::new()))
|
||||||
|
});
|
||||||
|
|
||||||
|
hyper::server::conn::http2::Builder::new(TokioExecutor::new())
|
||||||
|
.serve_connection(TokioIo::new(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(TokioExecutor::new()).build::<_, Empty<Bytes>>(ssl);
|
||||||
|
|
||||||
|
let resp = client
|
||||||
|
.get(format!("https://foobar.com:{}", port).parse().unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(resp.status().is_success(), "{}", resp.status());
|
||||||
|
let mut body = BodyStream::new(resp.into_body());
|
||||||
|
while body.next().await.transpose().unwrap().is_some() {}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
/target
|
|
||||||
/Cargo.lock
|
|
||||||
|
|
||||||
.idea
|
|
||||||
.DS_Store
|
|
||||||
.vscode
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "quinn-boring2"
|
|
||||||
version = { workspace = true }
|
|
||||||
authors = ["0x676e67 <gngppz@gmail.com>"]
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
edition = { workspace = true }
|
|
||||||
repository = { workspace = true }
|
|
||||||
description = "BoringSSL crypto provider for quinn"
|
|
||||||
keywords = ["quic"]
|
|
||||||
categories = ["network-programming", "asynchronous"]
|
|
||||||
rust-version = "1.85"
|
|
||||||
|
|
||||||
[badges]
|
|
||||||
maintenance = { status = "passively-maintained" }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["runtime-tokio"]
|
|
||||||
runtime-tokio = ["quinn/runtime-tokio"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
boring = { workspace = true }
|
|
||||||
boring-sys = { workspace = true }
|
|
||||||
bytes = "1"
|
|
||||||
foreign-types-shared = "0.3"
|
|
||||||
lru = "0.16"
|
|
||||||
quinn = { version = "0.11.8", default-features = false }
|
|
||||||
quinn-proto = { version = "0.11.12", default-features = false }
|
|
||||||
rand = "0.9.2"
|
|
||||||
tracing = "0.1"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
anyhow = "1.0"
|
|
||||||
assert_matches = "1"
|
|
||||||
clap = { version = "4", features = ["derive"] }
|
|
||||||
directories-next = "2"
|
|
||||||
hex-literal = "1.0"
|
|
||||||
rcgen = "0.14"
|
|
||||||
rustls-pemfile = "2"
|
|
||||||
tokio = { version = "1", features = [
|
|
||||||
"rt",
|
|
||||||
"rt-multi-thread",
|
|
||||||
"time",
|
|
||||||
"macros",
|
|
||||||
"sync",
|
|
||||||
] }
|
|
||||||
tracing-subscriber = { version = "0.3", default-features = false, features = [
|
|
||||||
"fmt",
|
|
||||||
] }
|
|
||||||
url = "2"
|
|
||||||
h3 = "0.0.8"
|
|
||||||
h3-quinn = "0.0.10"
|
|
||||||
http = "1"
|
|
||||||
http-body = "1.0.1"
|
|
||||||
http-body-util = "0.1.3"
|
|
||||||
futures-core = "0.3"
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "client"
|
|
||||||
required-features = ["runtime-tokio"]
|
|
||||||
|
|
@ -1,201 +0,0 @@
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
Copyright (c) 2018 The quinn Developers
|
|
||||||
Copyright (c) 2025 0x676e67 <gngppz@gmail.com>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# quinn-boring2
|
|
||||||
|
|
||||||
A crypto provider for [quinn](https://github.com/quinn-rs/quinn) based on [BoringSSL](https://github.com/google/boringssl).
|
|
||||||
|
|
||||||
## Accolades
|
|
||||||
|
|
||||||
The project is based on a fork of [quinn-boring](https://github.com/quinn-rs/quinn-boring).
|
|
||||||
|
|
@ -1,202 +0,0 @@
|
||||||
//! This example demonstrates an HTTP client that requests files from a server.
|
|
||||||
//!
|
|
||||||
//! Checkout the `README.md` for guidance.
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use boring::x509::X509;
|
|
||||||
use bytes::BytesMut;
|
|
||||||
use bytes::{Buf, Bytes};
|
|
||||||
use clap::Parser;
|
|
||||||
use http::Uri;
|
|
||||||
use http_body::Body;
|
|
||||||
use http_body_util::BodyExt;
|
|
||||||
use quinn_boring2::QuicSslContext;
|
|
||||||
use std::{
|
|
||||||
fs,
|
|
||||||
net::{SocketAddr, ToSocketAddrs},
|
|
||||||
path::PathBuf,
|
|
||||||
pin::Pin,
|
|
||||||
sync::Arc,
|
|
||||||
task::{Context, Poll},
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
use tracing::Level;
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
/// HTTP/0.9 over QUIC client
|
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
#[clap(name = "client")]
|
|
||||||
struct Opt {
|
|
||||||
/// Log level e.g. trace, debug, info, warn, error
|
|
||||||
#[clap(long, default_value = "info")]
|
|
||||||
log: Level,
|
|
||||||
|
|
||||||
url: Url,
|
|
||||||
|
|
||||||
/// Override hostname used for certificate verification
|
|
||||||
#[clap(long)]
|
|
||||||
host: Option<String>,
|
|
||||||
|
|
||||||
/// Custom certificate authority to trust, in DER format
|
|
||||||
#[clap(long)]
|
|
||||||
ca: Option<PathBuf>,
|
|
||||||
|
|
||||||
/// Simulate NAT rebinding after connecting
|
|
||||||
#[clap(long)]
|
|
||||||
rebind: bool,
|
|
||||||
|
|
||||||
/// Address to bind on
|
|
||||||
#[clap(long, default_value = "[::]:0")]
|
|
||||||
bind: SocketAddr,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<()> {
|
|
||||||
tracing_subscriber::fmt()
|
|
||||||
.with_max_level(tracing::Level::INFO)
|
|
||||||
.init();
|
|
||||||
|
|
||||||
let options = Opt::parse();
|
|
||||||
|
|
||||||
let url = options.url;
|
|
||||||
let url_host = strip_ipv6_brackets(url.host_str().unwrap());
|
|
||||||
let remote = (url_host, url.port().unwrap_or(443))
|
|
||||||
.to_socket_addrs()?
|
|
||||||
.next()
|
|
||||||
.ok_or_else(|| anyhow!("couldn't resolve to an address"))?;
|
|
||||||
|
|
||||||
let mut client_crypto = quinn_boring2::ClientConfig::new()?;
|
|
||||||
if let Some(ca_path) = options.ca {
|
|
||||||
client_crypto
|
|
||||||
.ctx_mut()
|
|
||||||
.cert_store_mut()
|
|
||||||
.add_cert(X509::from_der(&fs::read(ca_path)?)?)?;
|
|
||||||
} else {
|
|
||||||
client_crypto
|
|
||||||
.ctx_mut()
|
|
||||||
.cert_store_mut()
|
|
||||||
.set_default_paths()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut endpoint = quinn_boring2::helpers::client_endpoint(options.bind)?;
|
|
||||||
endpoint.set_default_client_config(quinn::ClientConfig::new(Arc::new(client_crypto)));
|
|
||||||
|
|
||||||
let start = Instant::now();
|
|
||||||
let rebind = options.rebind;
|
|
||||||
let host = options.host.as_deref().unwrap_or(url_host);
|
|
||||||
|
|
||||||
tracing::info!("connecting to {host} at {remote}");
|
|
||||||
let conn = endpoint
|
|
||||||
.connect(remote, host)?
|
|
||||||
.await
|
|
||||||
.map_err(|e| anyhow!("failed to connect: {}", e))?;
|
|
||||||
tracing::info!("connected at {:?}", start.elapsed());
|
|
||||||
|
|
||||||
if rebind {
|
|
||||||
let socket = std::net::UdpSocket::bind("[::]:0").unwrap();
|
|
||||||
let addr = socket.local_addr().unwrap();
|
|
||||||
tracing::info!("rebinding to {addr}");
|
|
||||||
endpoint.rebind(socket).expect("rebind failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
let (mut h3_conn, mut tx) = h3::client::new(h3_quinn::Connection::new(conn)).await?;
|
|
||||||
|
|
||||||
let req = http::Request::get(Uri::try_from(url.as_str())?).body(())?;
|
|
||||||
let (mut send, mut recv) = tx.send_request(req).await?.split();
|
|
||||||
if let Err(e) = send.finish().await {
|
|
||||||
tracing::error!("failed to send request: {e}");
|
|
||||||
}
|
|
||||||
|
|
||||||
let resp = {
|
|
||||||
let resp = recv.recv_response().await?;
|
|
||||||
let resp_body = Incoming::new(recv, resp.headers())
|
|
||||||
.map_err(Into::<BoxError>::into)
|
|
||||||
.boxed();
|
|
||||||
resp.map(|_| resp_body)
|
|
||||||
};
|
|
||||||
tracing::info!("response: {:#?}", resp);
|
|
||||||
|
|
||||||
let body = BodyExt::collect(resp.into_body())
|
|
||||||
.await
|
|
||||||
.map(|buf| buf.to_bytes())
|
|
||||||
.map_err(|e| anyhow!("failed to collect response body: {e}"))?;
|
|
||||||
tracing::info!("response body: {}", String::from_utf8_lossy(&body));
|
|
||||||
|
|
||||||
h3_conn.shutdown(0).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn strip_ipv6_brackets(host: &str) -> &str {
|
|
||||||
// An ipv6 url looks like eg https://[::1]:4433/Cargo.toml, wherein the host [::1] is the
|
|
||||||
// ipv6 address ::1 wrapped in brackets, per RFC 2732. This strips those.
|
|
||||||
if host.starts_with('[') && host.ends_with(']') {
|
|
||||||
&host[1..host.len() - 1]
|
|
||||||
} else {
|
|
||||||
host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type BoxError = Box<dyn std::error::Error + Send + Sync>;
|
|
||||||
|
|
||||||
struct Incoming<S, B> {
|
|
||||||
inner: h3::client::RequestStream<S, B>,
|
|
||||||
content_length: Option<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, B> Incoming<S, B> {
|
|
||||||
fn new(stream: h3::client::RequestStream<S, B>, headers: &http::header::HeaderMap) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: stream,
|
|
||||||
content_length: headers
|
|
||||||
.get(http::header::CONTENT_LENGTH)
|
|
||||||
.and_then(|h| h.to_str().ok())
|
|
||||||
.and_then(|v| v.parse().ok()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, B> http_body::Body for Incoming<S, B>
|
|
||||||
where
|
|
||||||
S: h3::quic::RecvStream,
|
|
||||||
{
|
|
||||||
type Data = Bytes;
|
|
||||||
type Error = BoxError;
|
|
||||||
|
|
||||||
fn poll_frame(
|
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context,
|
|
||||||
) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
|
|
||||||
match futures_core::ready!(self.inner.poll_recv_data(cx)) {
|
|
||||||
Ok(Some(mut b)) => Poll::Ready(Some(Ok(http_body::Frame::data(
|
|
||||||
b.copy_to_bytes(b.remaining()),
|
|
||||||
)))),
|
|
||||||
Ok(None) => Poll::Ready(None),
|
|
||||||
Err(e) => Poll::Ready(Some(Err(e.into()))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size_hint(&self) -> http_body::SizeHint {
|
|
||||||
if let Some(content_length) = self.content_length {
|
|
||||||
http_body::SizeHint::with_exact(content_length)
|
|
||||||
} else {
|
|
||||||
http_body::SizeHint::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn body_to_string<B>(
|
|
||||||
mut body: B,
|
|
||||||
) -> Result<String, Box<dyn std::error::Error + Send + Sync>>
|
|
||||||
where
|
|
||||||
B: Body<Data = bytes::Bytes> + Unpin,
|
|
||||||
B::Error: std::error::Error + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
let mut buf = BytesMut::new();
|
|
||||||
while let Some(frame) = body.frame().await {
|
|
||||||
let frame = frame?;
|
|
||||||
if let Some(data) = frame.data_ref() {
|
|
||||||
buf.extend_from_slice(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(String::from_utf8(buf.to_vec())?)
|
|
||||||
}
|
|
||||||
|
|
@ -1,152 +0,0 @@
|
||||||
use crate::error::{map_result, Error, Result};
|
|
||||||
use crate::key::{Key, Nonce, Tag};
|
|
||||||
use boring_sys as bffi;
|
|
||||||
use std::mem::MaybeUninit;
|
|
||||||
use std::sync::LazyLock;
|
|
||||||
|
|
||||||
const AES_128_GCM_KEY_LEN: usize = 16;
|
|
||||||
const AES_256_GCM_KEY_LEN: usize = 32;
|
|
||||||
const CHACHA20_POLY1305_KEY_LEN: usize = 32;
|
|
||||||
|
|
||||||
const AES_GCM_NONCE_LEN: usize = 12;
|
|
||||||
const POLY1305_NONCE_LEN: usize = 12;
|
|
||||||
|
|
||||||
pub(crate) const AES_GCM_TAG_LEN: usize = 16;
|
|
||||||
const POLY1305_TAG_LEN: usize = 16;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub(crate) enum ID {
|
|
||||||
Aes128Gcm,
|
|
||||||
Aes256Gcm,
|
|
||||||
Chacha20Poly1305,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper around a raw BoringSSL EVP_AEAD.
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
|
||||||
struct AeadPtr(*const bffi::EVP_AEAD);
|
|
||||||
|
|
||||||
unsafe impl Send for AeadPtr {}
|
|
||||||
unsafe impl Sync for AeadPtr {}
|
|
||||||
|
|
||||||
impl AeadPtr {
|
|
||||||
fn aes128_gcm() -> Self {
|
|
||||||
unsafe { Self(bffi::EVP_aead_aes_128_gcm()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn aes256_gcm() -> Self {
|
|
||||||
unsafe { Self(bffi::EVP_aead_aes_256_gcm()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn chacha20_poly1305() -> Self {
|
|
||||||
unsafe { Self(bffi::EVP_aead_chacha20_poly1305()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper around an BoringSSL EVP_AEAD.
|
|
||||||
pub(crate) struct Aead {
|
|
||||||
ptr: AeadPtr,
|
|
||||||
pub(crate) id: ID,
|
|
||||||
pub(crate) key_len: usize,
|
|
||||||
pub(crate) tag_len: usize,
|
|
||||||
pub(crate) nonce_len: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Aead {
|
|
||||||
#[inline]
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.id == other.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for Aead {}
|
|
||||||
|
|
||||||
static AES128_GCM: LazyLock<Aead> = LazyLock::new(|| Aead {
|
|
||||||
ptr: AeadPtr::aes128_gcm(),
|
|
||||||
id: ID::Aes128Gcm,
|
|
||||||
key_len: AES_128_GCM_KEY_LEN,
|
|
||||||
tag_len: AES_GCM_TAG_LEN,
|
|
||||||
nonce_len: AES_GCM_NONCE_LEN,
|
|
||||||
});
|
|
||||||
|
|
||||||
static AES256_GCM: LazyLock<Aead> = LazyLock::new(|| Aead {
|
|
||||||
ptr: AeadPtr::aes256_gcm(),
|
|
||||||
id: ID::Aes256Gcm,
|
|
||||||
key_len: AES_256_GCM_KEY_LEN,
|
|
||||||
tag_len: AES_GCM_TAG_LEN,
|
|
||||||
nonce_len: AES_GCM_NONCE_LEN,
|
|
||||||
});
|
|
||||||
|
|
||||||
static CHACHA20_POLY1305: LazyLock<Aead> = LazyLock::new(|| Aead {
|
|
||||||
ptr: AeadPtr::chacha20_poly1305(),
|
|
||||||
id: ID::Chacha20Poly1305,
|
|
||||||
key_len: CHACHA20_POLY1305_KEY_LEN,
|
|
||||||
tag_len: POLY1305_TAG_LEN,
|
|
||||||
nonce_len: POLY1305_NONCE_LEN,
|
|
||||||
});
|
|
||||||
|
|
||||||
impl Aead {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn aes128_gcm() -> &'static Self {
|
|
||||||
&AES128_GCM
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn aes256_gcm() -> &'static Self {
|
|
||||||
&AES256_GCM
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn chacha20_poly1305() -> &'static Self {
|
|
||||||
&CHACHA20_POLY1305
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new zeroed key of the appropriate length for the AEAD algorithm.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn zero_key(&self) -> Key {
|
|
||||||
Key::with_len(self.key_len)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new zeroed nonce of the appropriate length for the AEAD algorithm.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn zero_nonce(&self) -> Nonce {
|
|
||||||
Nonce::with_len(self.nonce_len)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new zeroed tag of the appropriate length for the AEAD algorithm.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn zero_tag(&self) -> Tag {
|
|
||||||
Tag::with_len(self.tag_len)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn as_ptr(&self) -> *const bffi::EVP_AEAD {
|
|
||||||
self.ptr.0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn new_aead_ctx(&self, key: &Key) -> Result<bffi::EVP_AEAD_CTX> {
|
|
||||||
if key.len() != self.key_len {
|
|
||||||
return Err(Error::invalid_input(format!(
|
|
||||||
"key length invalid for AEAD_CTX: {}",
|
|
||||||
key.len()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let ctx = unsafe {
|
|
||||||
let mut ctx = MaybeUninit::uninit();
|
|
||||||
|
|
||||||
map_result(bffi::EVP_AEAD_CTX_init(
|
|
||||||
ctx.as_mut_ptr(),
|
|
||||||
self.as_ptr(),
|
|
||||||
key.as_ptr(),
|
|
||||||
key.len(),
|
|
||||||
self.tag_len,
|
|
||||||
std::ptr::null_mut(),
|
|
||||||
))?;
|
|
||||||
|
|
||||||
ctx.assume_init()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
use boring_sys as bffi;
|
|
||||||
use quinn_proto::{TransportError, TransportErrorCode};
|
|
||||||
use std::ffi::{c_int, CStr};
|
|
||||||
use std::fmt;
|
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
pub(crate) enum AlertType {
|
|
||||||
Warning,
|
|
||||||
Fatal,
|
|
||||||
Unknown,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AlertType {
|
|
||||||
const ALERT_TYPE_WARNING: &'static str = "warning";
|
|
||||||
const ALERT_TYPE_FATAL: &'static str = "fatal";
|
|
||||||
const ALERT_TYPE_UNKNOWN: &'static str = "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for AlertType {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Warning => f.write_str(Self::ALERT_TYPE_WARNING),
|
|
||||||
Self::Fatal => f.write_str(Self::ALERT_TYPE_FATAL),
|
|
||||||
_ => f.write_str(Self::ALERT_TYPE_UNKNOWN),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for AlertType {
|
|
||||||
type Err = ();
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s {
|
|
||||||
Self::ALERT_TYPE_WARNING => Ok(Self::Warning),
|
|
||||||
Self::ALERT_TYPE_FATAL => Ok(Self::Fatal),
|
|
||||||
_ => Ok(Self::Unknown),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub(crate) struct Alert(u8);
|
|
||||||
|
|
||||||
impl Alert {
|
|
||||||
pub(crate) fn from(value: u8) -> Self {
|
|
||||||
Alert(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn handshake_failure() -> Self {
|
|
||||||
Alert(bffi::SSL_AD_HANDSHAKE_FAILURE as u8)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_description(&self) -> &'static str {
|
|
||||||
unsafe {
|
|
||||||
CStr::from_ptr(bffi::SSL_alert_desc_string_long(self.0 as c_int))
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Alert {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "SSL alert [{}]: {}", self.0, self.get_description())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Alert> for TransportErrorCode {
|
|
||||||
fn from(alert: Alert) -> Self {
|
|
||||||
TransportErrorCode::crypto(alert.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Alert> for TransportError {
|
|
||||||
fn from(alert: Alert) -> Self {
|
|
||||||
TransportError {
|
|
||||||
code: alert.into(),
|
|
||||||
frame: None,
|
|
||||||
reason: alert.get_description().to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
use crate::error::{Error, Result};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub(crate) struct AlpnProtocol(Vec<u8>);
|
|
||||||
|
|
||||||
impl AlpnProtocol {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn encode(&self, encoded: &mut Vec<u8>) {
|
|
||||||
encoded.push(self.0.len() as u8);
|
|
||||||
encoded.extend_from_slice(&self.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vec<u8>> for AlpnProtocol {
|
|
||||||
fn from(value: Vec<u8>) -> Self {
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub(crate) struct AlpnProtocols(Vec<AlpnProtocol>);
|
|
||||||
|
|
||||||
impl AlpnProtocols {
|
|
||||||
pub(crate) const H3: &'static [u8; 2] = b"h3";
|
|
||||||
|
|
||||||
/// Performs the server-side ALPN protocol selection.
|
|
||||||
pub(crate) fn select<'a>(&self, offered: &'a [u8]) -> Result<&'a [u8]> {
|
|
||||||
for server_proto in &self.0 {
|
|
||||||
let mut i = 0;
|
|
||||||
while i < offered.len() {
|
|
||||||
let len = offered[i] as usize;
|
|
||||||
i += 1;
|
|
||||||
|
|
||||||
let client_proto = &offered[i..i + len];
|
|
||||||
if server_proto.0 == client_proto {
|
|
||||||
return Ok(client_proto);
|
|
||||||
}
|
|
||||||
i += len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(Error::other("ALPN selection failed".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn encode(&self) -> Vec<u8> {
|
|
||||||
let mut out: Vec<u8> = Vec::new();
|
|
||||||
for proto in &self.0 {
|
|
||||||
proto.encode(&mut out);
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for AlpnProtocols {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::from(&[Self::H3.to_vec()][..])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&[Vec<u8>]> for AlpnProtocols {
|
|
||||||
fn from(protos: &[Vec<u8>]) -> Self {
|
|
||||||
let mut out = Vec::with_capacity(protos.len());
|
|
||||||
for proto in protos {
|
|
||||||
out.push(AlpnProtocol(proto.clone()))
|
|
||||||
}
|
|
||||||
Self(out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Vec<Vec<u8>>> for AlpnProtocols {
|
|
||||||
fn from(protos: &Vec<Vec<u8>>) -> Self {
|
|
||||||
Self::from(protos.as_slice())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,565 +0,0 @@
|
||||||
use crate::error::{br, br_zero_is_success, BoringResult};
|
|
||||||
use boring::error::ErrorStack;
|
|
||||||
use boring::pkey::{HasPrivate, PKey};
|
|
||||||
use boring::ssl::{Ssl, SslContext, SslContextRef, SslSession};
|
|
||||||
use boring::x509::store::X509StoreBuilderRef;
|
|
||||||
use boring::x509::X509;
|
|
||||||
use boring_sys as bffi;
|
|
||||||
use bytes::{Buf, BufMut};
|
|
||||||
use foreign_types_shared::{ForeignType, ForeignTypeRef};
|
|
||||||
use std::ffi::{c_char, c_int, c_uint, c_void, CStr};
|
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
use std::result::Result as StdResult;
|
|
||||||
use std::{ffi, fmt, mem, ptr, slice};
|
|
||||||
|
|
||||||
/// Provides additional methods to [SslContext] needed for QUIC.
|
|
||||||
pub trait QuicSslContext {
|
|
||||||
fn set_options(&mut self, options: u32) -> u32;
|
|
||||||
fn verify_peer(&mut self, verify: bool);
|
|
||||||
fn set_quic_method(&mut self, method: &bffi::SSL_QUIC_METHOD) -> BoringResult;
|
|
||||||
fn set_session_cache_mode(&mut self, mode: c_int) -> c_int;
|
|
||||||
fn set_new_session_callback(
|
|
||||||
&mut self,
|
|
||||||
cb: Option<
|
|
||||||
unsafe extern "C" fn(ssl: *mut bffi::SSL, session: *mut bffi::SSL_SESSION) -> c_int,
|
|
||||||
>,
|
|
||||||
);
|
|
||||||
fn set_info_callback(
|
|
||||||
&mut self,
|
|
||||||
cb: Option<unsafe extern "C" fn(ssl: *const bffi::SSL, type_: c_int, value: c_int)>,
|
|
||||||
);
|
|
||||||
fn set_keylog_callback(
|
|
||||||
&mut self,
|
|
||||||
cb: Option<unsafe extern "C" fn(ssl: *const bffi::SSL, line: *const c_char)>,
|
|
||||||
);
|
|
||||||
fn set_certificate(&mut self, cert: X509) -> BoringResult;
|
|
||||||
fn load_certificate_from_pem_file(&mut self, path: &str) -> BoringResult;
|
|
||||||
fn add_to_cert_chain(&mut self, cert: X509) -> BoringResult;
|
|
||||||
fn load_cert_chain_from_pem_file(&mut self, path: &str) -> BoringResult;
|
|
||||||
fn set_private_key<T: HasPrivate>(&mut self, key: PKey<T>) -> BoringResult;
|
|
||||||
fn load_private_key_from_pem_file(&mut self, path: &str) -> BoringResult;
|
|
||||||
fn check_private_key(&self) -> BoringResult;
|
|
||||||
fn cert_store_mut(&mut self) -> &mut X509StoreBuilderRef;
|
|
||||||
|
|
||||||
fn enable_early_data(&mut self, enable: bool);
|
|
||||||
fn set_alpn_protos(&mut self, protos: &[u8]) -> BoringResult;
|
|
||||||
fn set_alpn_select_cb(
|
|
||||||
&mut self,
|
|
||||||
cb: Option<
|
|
||||||
unsafe extern "C" fn(
|
|
||||||
ssl: *mut bffi::SSL,
|
|
||||||
out: *mut *const u8,
|
|
||||||
out_len: *mut u8,
|
|
||||||
in_: *const u8,
|
|
||||||
in_len: c_uint,
|
|
||||||
arg: *mut c_void,
|
|
||||||
) -> c_int,
|
|
||||||
>,
|
|
||||||
);
|
|
||||||
fn set_server_name_cb(
|
|
||||||
&mut self,
|
|
||||||
cb: Option<
|
|
||||||
unsafe extern "C" fn(
|
|
||||||
ssl: *mut bffi::SSL,
|
|
||||||
out_alert: *mut c_int,
|
|
||||||
arg: *mut c_void,
|
|
||||||
) -> c_int,
|
|
||||||
>,
|
|
||||||
);
|
|
||||||
fn set_select_certificate_cb(
|
|
||||||
&mut self,
|
|
||||||
cb: Option<
|
|
||||||
unsafe extern "C" fn(
|
|
||||||
arg1: *const bffi::SSL_CLIENT_HELLO,
|
|
||||||
) -> bffi::ssl_select_cert_result_t,
|
|
||||||
>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QuicSslContext for SslContext {
|
|
||||||
fn set_options(&mut self, options: u32) -> u32 {
|
|
||||||
unsafe { bffi::SSL_CTX_set_options(self.as_ptr(), options) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn verify_peer(&mut self, verify: bool) {
|
|
||||||
let mode = if verify {
|
|
||||||
bffi::SSL_VERIFY_PEER | bffi::SSL_VERIFY_FAIL_IF_NO_PEER_CERT
|
|
||||||
} else {
|
|
||||||
bffi::SSL_VERIFY_NONE
|
|
||||||
};
|
|
||||||
|
|
||||||
unsafe { bffi::SSL_CTX_set_verify(self.as_ptr(), mode, None) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_quic_method(&mut self, method: &bffi::SSL_QUIC_METHOD) -> BoringResult {
|
|
||||||
unsafe { br(bffi::SSL_CTX_set_quic_method(self.as_ptr(), method)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_session_cache_mode(&mut self, mode: c_int) -> c_int {
|
|
||||||
unsafe { bffi::SSL_CTX_set_session_cache_mode(self.as_ptr(), mode) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_new_session_callback(
|
|
||||||
&mut self,
|
|
||||||
cb: Option<
|
|
||||||
unsafe extern "C" fn(ssl: *mut bffi::SSL, session: *mut bffi::SSL_SESSION) -> c_int,
|
|
||||||
>,
|
|
||||||
) {
|
|
||||||
unsafe {
|
|
||||||
bffi::SSL_CTX_sess_set_new_cb(self.as_ptr(), cb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_info_callback(
|
|
||||||
&mut self,
|
|
||||||
cb: Option<unsafe extern "C" fn(ssl: *const bffi::SSL, type_: c_int, value: c_int)>,
|
|
||||||
) {
|
|
||||||
unsafe { bffi::SSL_CTX_set_info_callback(self.as_ptr(), cb) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_keylog_callback(
|
|
||||||
&mut self,
|
|
||||||
cb: Option<unsafe extern "C" fn(ssl: *const bffi::SSL, line: *const c_char)>,
|
|
||||||
) {
|
|
||||||
unsafe { bffi::SSL_CTX_set_keylog_callback(self.as_ptr(), cb) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_certificate(&mut self, cert: X509) -> BoringResult {
|
|
||||||
unsafe {
|
|
||||||
br(bffi::SSL_CTX_use_certificate(self.as_ptr(), cert.as_ptr()))?;
|
|
||||||
mem::forget(cert);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_certificate_from_pem_file(&mut self, path: &str) -> BoringResult {
|
|
||||||
let path = ffi::CString::new(path).unwrap();
|
|
||||||
unsafe {
|
|
||||||
br(bffi::SSL_CTX_use_certificate_file(
|
|
||||||
self.as_ptr(),
|
|
||||||
path.as_ptr(),
|
|
||||||
bffi::SSL_FILETYPE_PEM,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_to_cert_chain(&mut self, cert: X509) -> BoringResult {
|
|
||||||
unsafe {
|
|
||||||
br(bffi::SSL_CTX_add_extra_chain_cert(self.as_ptr(), cert.as_ptr()) as c_int)?;
|
|
||||||
mem::forget(cert);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_cert_chain_from_pem_file(&mut self, path: &str) -> BoringResult {
|
|
||||||
let path = ffi::CString::new(path).unwrap();
|
|
||||||
unsafe {
|
|
||||||
br(bffi::SSL_CTX_use_certificate_chain_file(
|
|
||||||
self.as_ptr(),
|
|
||||||
path.as_ptr(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_private_key<T: HasPrivate>(&mut self, key: PKey<T>) -> BoringResult {
|
|
||||||
unsafe {
|
|
||||||
br(bffi::SSL_CTX_use_PrivateKey(self.as_ptr(), key.as_ptr()))?;
|
|
||||||
mem::forget(key);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_private_key_from_pem_file(&mut self, path: &str) -> BoringResult {
|
|
||||||
let path = ffi::CString::new(path).unwrap();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
br(bffi::SSL_CTX_use_PrivateKey_file(
|
|
||||||
self.as_ptr(),
|
|
||||||
path.as_ptr(),
|
|
||||||
bffi::SSL_FILETYPE_PEM,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_private_key(&self) -> BoringResult {
|
|
||||||
unsafe { br(bffi::SSL_CTX_check_private_key(self.as_ptr())) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cert_store_mut(&mut self) -> &mut X509StoreBuilderRef {
|
|
||||||
unsafe { X509StoreBuilderRef::from_ptr_mut(bffi::SSL_CTX_get_cert_store(self.as_ptr())) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable_early_data(&mut self, enable: bool) {
|
|
||||||
unsafe { bffi::SSL_CTX_set_early_data_enabled(self.as_ptr(), enable.into()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_alpn_protos(&mut self, protos: &[u8]) -> BoringResult {
|
|
||||||
unsafe {
|
|
||||||
br_zero_is_success(bffi::SSL_CTX_set_alpn_protos(
|
|
||||||
self.as_ptr(),
|
|
||||||
protos.as_ptr(),
|
|
||||||
protos.len() as _,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_alpn_select_cb(
|
|
||||||
&mut self,
|
|
||||||
cb: Option<
|
|
||||||
unsafe extern "C" fn(
|
|
||||||
*mut bffi::SSL,
|
|
||||||
*mut *const u8,
|
|
||||||
*mut u8,
|
|
||||||
*const u8,
|
|
||||||
c_uint,
|
|
||||||
*mut c_void,
|
|
||||||
) -> c_int,
|
|
||||||
>,
|
|
||||||
) {
|
|
||||||
unsafe { bffi::SSL_CTX_set_alpn_select_cb(self.as_ptr(), cb, ptr::null_mut()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_server_name_cb(
|
|
||||||
&mut self,
|
|
||||||
cb: Option<
|
|
||||||
unsafe extern "C" fn(
|
|
||||||
ssl: *mut bffi::SSL,
|
|
||||||
out_alert: *mut c_int,
|
|
||||||
arg: *mut c_void,
|
|
||||||
) -> c_int,
|
|
||||||
>,
|
|
||||||
) {
|
|
||||||
// The function always returns 1.
|
|
||||||
unsafe {
|
|
||||||
let _ = bffi::SSL_CTX_set_tlsext_servername_callback(self.as_ptr(), cb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_select_certificate_cb(
|
|
||||||
&mut self,
|
|
||||||
cb: Option<
|
|
||||||
unsafe extern "C" fn(
|
|
||||||
arg1: *const bffi::SSL_CLIENT_HELLO,
|
|
||||||
) -> bffi::ssl_select_cert_result_t,
|
|
||||||
>,
|
|
||||||
) {
|
|
||||||
unsafe { bffi::SSL_CTX_set_select_certificate_cb(self.as_ptr(), cb) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Provides additional methods to [Ssl] needed for QUIC.
|
|
||||||
pub trait QuicSsl {
|
|
||||||
fn set_connect_state(&mut self);
|
|
||||||
fn set_accept_state(&mut self);
|
|
||||||
fn state_string(&self) -> &'static str;
|
|
||||||
fn set_quic_transport_params(&mut self, params: &[u8]) -> BoringResult;
|
|
||||||
fn get_peer_quic_transport_params(&self) -> Option<&[u8]>;
|
|
||||||
fn get_error(&self, raw: c_int) -> SslError;
|
|
||||||
fn is_handshaking(&self) -> bool;
|
|
||||||
fn do_handshake(&mut self) -> SslError;
|
|
||||||
fn provide_quic_data(&mut self, level: Level, data: &[u8]) -> SslError;
|
|
||||||
fn quic_max_handshake_flight_len(&self, level: Level) -> usize;
|
|
||||||
fn quic_read_level(&self) -> Level;
|
|
||||||
fn quic_write_level(&self) -> Level;
|
|
||||||
fn process_post_handshake(&mut self) -> SslError;
|
|
||||||
fn set_verify_hostname(&mut self, domain: &str) -> BoringResult;
|
|
||||||
fn export_keyring_material(
|
|
||||||
&self,
|
|
||||||
output: &mut [u8],
|
|
||||||
label: &[u8],
|
|
||||||
context: &[u8],
|
|
||||||
) -> BoringResult;
|
|
||||||
|
|
||||||
fn in_early_data(&self) -> bool;
|
|
||||||
fn early_data_accepted(&self) -> bool;
|
|
||||||
fn set_quic_method(&mut self, method: &bffi::SSL_QUIC_METHOD) -> BoringResult;
|
|
||||||
fn set_quic_early_data_context(&mut self, value: &[u8]) -> BoringResult;
|
|
||||||
fn get_early_data_reason(&self) -> bffi::ssl_early_data_reason_t;
|
|
||||||
fn early_data_reason_string(reason: bffi::ssl_early_data_reason_t) -> &'static str;
|
|
||||||
fn reset_early_rejected_data(&mut self);
|
|
||||||
fn set_quic_use_legacy_codepoint(&mut self, use_legacy: bool);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QuicSsl for Ssl {
|
|
||||||
fn set_connect_state(&mut self) {
|
|
||||||
unsafe { bffi::SSL_set_connect_state(self.as_ptr()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_accept_state(&mut self) {
|
|
||||||
unsafe { bffi::SSL_set_accept_state(self.as_ptr()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn state_string(&self) -> &'static str {
|
|
||||||
unsafe {
|
|
||||||
CStr::from_ptr(bffi::SSL_state_string_long(self.as_ptr()))
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_quic_transport_params(&mut self, params: &[u8]) -> BoringResult {
|
|
||||||
unsafe {
|
|
||||||
br(bffi::SSL_set_quic_transport_params(
|
|
||||||
self.as_ptr(),
|
|
||||||
params.as_ptr(),
|
|
||||||
params.len(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_peer_quic_transport_params(&self) -> Option<&[u8]> {
|
|
||||||
let mut ptr: *const u8 = ptr::null();
|
|
||||||
let mut len: usize = 0;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
bffi::SSL_get_peer_quic_transport_params(self.as_ptr(), &mut ptr, &mut len);
|
|
||||||
|
|
||||||
if len == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(slice::from_raw_parts(ptr, len))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn get_error(&self, raw: c_int) -> SslError {
|
|
||||||
unsafe { SslError(bffi::SSL_get_error(self.as_ptr(), raw)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_handshaking(&self) -> bool {
|
|
||||||
unsafe { bffi::SSL_in_init(self.as_ptr()) == 1 }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn do_handshake(&mut self) -> SslError {
|
|
||||||
self.get_error(unsafe { bffi::SSL_do_handshake(self.as_ptr()) })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn provide_quic_data(&mut self, level: Level, plaintext: &[u8]) -> SslError {
|
|
||||||
unsafe {
|
|
||||||
self.get_error(bffi::SSL_provide_quic_data(
|
|
||||||
self.as_ptr(),
|
|
||||||
level.into(),
|
|
||||||
plaintext.as_ptr(),
|
|
||||||
plaintext.len(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn quic_max_handshake_flight_len(&self, level: Level) -> usize {
|
|
||||||
unsafe { bffi::SSL_quic_max_handshake_flight_len(self.as_ptr(), level.into()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn quic_read_level(&self) -> Level {
|
|
||||||
unsafe { bffi::SSL_quic_read_level(self.as_ptr()).into() }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn quic_write_level(&self) -> Level {
|
|
||||||
unsafe { bffi::SSL_quic_write_level(self.as_ptr()).into() }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn process_post_handshake(&mut self) -> SslError {
|
|
||||||
self.get_error(unsafe { bffi::SSL_process_quic_post_handshake(self.as_ptr()) })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_verify_hostname(&mut self, domain: &str) -> BoringResult {
|
|
||||||
let param = self.param_mut();
|
|
||||||
param.set_hostflags(boring::x509::verify::X509CheckFlags::NO_PARTIAL_WILDCARDS);
|
|
||||||
match domain.parse() {
|
|
||||||
Ok(ip) => param.set_ip(ip)?,
|
|
||||||
Err(_) => param.set_host(domain)?,
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn export_keyring_material(
|
|
||||||
&self,
|
|
||||||
output: &mut [u8],
|
|
||||||
label: &[u8],
|
|
||||||
context: &[u8],
|
|
||||||
) -> BoringResult {
|
|
||||||
unsafe {
|
|
||||||
br(bffi::SSL_export_keying_material(
|
|
||||||
self.as_ptr(),
|
|
||||||
output.as_mut_ptr(),
|
|
||||||
output.len(),
|
|
||||||
label.as_ptr() as *const c_char,
|
|
||||||
label.len(),
|
|
||||||
context.as_ptr(),
|
|
||||||
context.len(),
|
|
||||||
context.is_empty() as _,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn in_early_data(&self) -> bool {
|
|
||||||
unsafe { bffi::SSL_in_early_data(self.as_ptr()) == 1 }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn early_data_accepted(&self) -> bool {
|
|
||||||
unsafe { bffi::SSL_early_data_accepted(self.as_ptr()) == 1 }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_quic_method(&mut self, method: &bffi::SSL_QUIC_METHOD) -> BoringResult {
|
|
||||||
unsafe { br(bffi::SSL_set_quic_method(self.as_ptr(), method)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_quic_early_data_context(&mut self, value: &[u8]) -> BoringResult {
|
|
||||||
unsafe {
|
|
||||||
br(bffi::SSL_set_quic_early_data_context(
|
|
||||||
self.as_ptr(),
|
|
||||||
value.as_ptr(),
|
|
||||||
value.len(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_early_data_reason(&self) -> bffi::ssl_early_data_reason_t {
|
|
||||||
unsafe { bffi::SSL_get_early_data_reason(self.as_ptr()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn early_data_reason_string(reason: bffi::ssl_early_data_reason_t) -> &'static str {
|
|
||||||
unsafe {
|
|
||||||
bffi::SSL_early_data_reason_string(reason)
|
|
||||||
.as_ref()
|
|
||||||
.map_or("unknown", |reason| CStr::from_ptr(reason).to_str().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn reset_early_rejected_data(&mut self) {
|
|
||||||
unsafe { bffi::SSL_reset_early_data_reject(self.as_ptr()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_quic_use_legacy_codepoint(&mut self, use_legacy: bool) {
|
|
||||||
unsafe { bffi::SSL_set_quic_use_legacy_codepoint(self.as_ptr(), use_legacy as _) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait QuicSslSession {
|
|
||||||
fn early_data_capable(&self) -> bool;
|
|
||||||
fn copy_without_early_data(&mut self) -> SslSession;
|
|
||||||
fn encode<W: BufMut>(&self, out: &mut W) -> BoringResult;
|
|
||||||
fn decode<R: Buf>(ctx: &SslContextRef, r: &mut R) -> StdResult<SslSession, ErrorStack>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QuicSslSession for SslSession {
|
|
||||||
fn early_data_capable(&self) -> bool {
|
|
||||||
unsafe { bffi::SSL_SESSION_early_data_capable(self.as_ptr()) == 1 }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn copy_without_early_data(&mut self) -> SslSession {
|
|
||||||
unsafe { SslSession::from_ptr(bffi::SSL_SESSION_copy_without_early_data(self.as_ptr())) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn encode<W: BufMut>(&self, out: &mut W) -> BoringResult {
|
|
||||||
unsafe {
|
|
||||||
let mut buf: *mut u8 = ptr::null_mut();
|
|
||||||
let mut len = 0usize;
|
|
||||||
br(bffi::SSL_SESSION_to_bytes(
|
|
||||||
self.as_ptr(),
|
|
||||||
&mut buf,
|
|
||||||
&mut len,
|
|
||||||
))?;
|
|
||||||
out.put_slice(slice::from_raw_parts(buf, len));
|
|
||||||
bffi::OPENSSL_free(buf as _);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode<R: Buf>(ctx: &SslContextRef, r: &mut R) -> StdResult<SslSession, ErrorStack> {
|
|
||||||
unsafe {
|
|
||||||
let in_len = r.remaining();
|
|
||||||
let in_ = r.chunk();
|
|
||||||
bffi::SSL_SESSION_from_bytes(in_.as_ptr(), in_len, ctx.as_ptr())
|
|
||||||
.as_mut()
|
|
||||||
.map_or_else(
|
|
||||||
|| Err(ErrorStack::get()),
|
|
||||||
|session| Ok(SslSession::from_ptr(session)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
|
||||||
pub enum Level {
|
|
||||||
Initial = 0,
|
|
||||||
EarlyData = 1,
|
|
||||||
Handshake = 2,
|
|
||||||
Application = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Level {
|
|
||||||
pub const NUM_LEVELS: usize = 4;
|
|
||||||
|
|
||||||
pub fn next(&self) -> Self {
|
|
||||||
match self {
|
|
||||||
Level::Initial => Level::Handshake,
|
|
||||||
Level::EarlyData => Level::Handshake,
|
|
||||||
_ => Level::Application,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<bffi::ssl_encryption_level_t> for Level {
|
|
||||||
fn from(value: bffi::ssl_encryption_level_t) -> Self {
|
|
||||||
match value {
|
|
||||||
bffi::ssl_encryption_level_t::ssl_encryption_initial => Self::Initial,
|
|
||||||
bffi::ssl_encryption_level_t::ssl_encryption_early_data => Self::EarlyData,
|
|
||||||
bffi::ssl_encryption_level_t::ssl_encryption_handshake => Self::Handshake,
|
|
||||||
bffi::ssl_encryption_level_t::ssl_encryption_application => Self::Application,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Level> for bffi::ssl_encryption_level_t {
|
|
||||||
fn from(value: Level) -> Self {
|
|
||||||
match value {
|
|
||||||
Level::Initial => bffi::ssl_encryption_level_t::ssl_encryption_initial,
|
|
||||||
Level::EarlyData => bffi::ssl_encryption_level_t::ssl_encryption_early_data,
|
|
||||||
Level::Handshake => bffi::ssl_encryption_level_t::ssl_encryption_handshake,
|
|
||||||
Level::Application => bffi::ssl_encryption_level_t::ssl_encryption_application,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct SslError(c_int);
|
|
||||||
|
|
||||||
impl SslError {
|
|
||||||
#[inline]
|
|
||||||
pub fn value(&self) -> c_int {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_none(&self) -> bool {
|
|
||||||
self.0 == bffi::SSL_ERROR_NONE
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_description(&self) -> &'static str {
|
|
||||||
unsafe {
|
|
||||||
CStr::from_ptr(bffi::SSL_error_description(self.0))
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for SslError {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "SSL_ERROR[{}]: {}", self.0, self.get_description())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,395 +0,0 @@
|
||||||
use crate::alpn::AlpnProtocols;
|
|
||||||
use crate::bffi_ext::QuicSslContext;
|
|
||||||
use crate::error::{map_result, Result};
|
|
||||||
use crate::session_state::{SessionState, QUIC_METHOD};
|
|
||||||
use crate::version::QuicVersion;
|
|
||||||
use crate::{Entry, QuicSsl, QuicSslSession, SessionCache, SimpleCache};
|
|
||||||
use boring::ssl::{Ssl, SslContext, SslContextBuilder, SslMethod, SslSession, SslVersion};
|
|
||||||
use boring_sys as bffi;
|
|
||||||
use bytes::{Bytes, BytesMut};
|
|
||||||
use foreign_types_shared::ForeignType;
|
|
||||||
use quinn_proto::{
|
|
||||||
crypto, transport_parameters::TransportParameters, ConnectError, ConnectionId, Side,
|
|
||||||
TransportError,
|
|
||||||
};
|
|
||||||
use std::any::Any;
|
|
||||||
use std::ffi::c_int;
|
|
||||||
use std::io::Cursor;
|
|
||||||
use std::result::Result as StdResult;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::LazyLock;
|
|
||||||
use tracing::{trace, warn};
|
|
||||||
|
|
||||||
/// Configuration for a client-side QUIC. Wraps around a BoringSSL [SslContext].
|
|
||||||
pub struct Config {
|
|
||||||
ctx: SslContext,
|
|
||||||
session_cache: Arc<dyn SessionCache>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn new() -> Result<Self> {
|
|
||||||
let mut builder = SslContextBuilder::new(SslMethod::tls())?;
|
|
||||||
|
|
||||||
// QUIC requires TLS 1.3.
|
|
||||||
builder.set_min_proto_version(Some(SslVersion::TLS1_3))?;
|
|
||||||
builder.set_max_proto_version(Some(SslVersion::TLS1_3))?;
|
|
||||||
|
|
||||||
builder.set_default_verify_paths()?;
|
|
||||||
|
|
||||||
// We build the context early, since we are not allowed to further mutate the context
|
|
||||||
// in start_session.
|
|
||||||
let mut ctx = builder.build();
|
|
||||||
|
|
||||||
// By default, enable early data (used for 0-RTT).
|
|
||||||
ctx.enable_early_data(true);
|
|
||||||
|
|
||||||
// Set the default ALPN protocols offered by the client. QUIC requires ALPN be configured
|
|
||||||
// (see <https://www.rfc-editor.org/rfc/rfc9001.html#section-8.1>).
|
|
||||||
ctx.set_alpn_protos(&AlpnProtocols::default().encode())?;
|
|
||||||
|
|
||||||
// Configure session caching.
|
|
||||||
ctx.set_session_cache_mode(bffi::SSL_SESS_CACHE_CLIENT | bffi::SSL_SESS_CACHE_NO_INTERNAL);
|
|
||||||
ctx.set_new_session_callback(Some(Session::new_session_callback));
|
|
||||||
|
|
||||||
// Set callbacks for the SessionState.
|
|
||||||
ctx.set_quic_method(&QUIC_METHOD)?;
|
|
||||||
ctx.set_info_callback(Some(SessionState::info_callback));
|
|
||||||
|
|
||||||
// For clients, verification of the server is on by default.
|
|
||||||
ctx.verify_peer(true);
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
ctx,
|
|
||||||
session_cache: Arc::new(SimpleCache::new(256)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the underlying [SslContext] backing all created sessions.
|
|
||||||
pub fn ctx(&self) -> &SslContext {
|
|
||||||
&self.ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the underlying [SslContext] backing all created sessions. Wherever possible use
|
|
||||||
/// the provided methods to modify settings rather than accessing this directly.
|
|
||||||
///
|
|
||||||
/// Care should be taken to avoid overriding required behavior. In particular, this
|
|
||||||
/// configuration will set callbacks for QUIC events, alpn selection, server name,
|
|
||||||
/// as well as info and key logging.
|
|
||||||
pub fn ctx_mut(&mut self) -> &mut SslContext {
|
|
||||||
&mut self.ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets whether or not the peer certificate should be verified. If `true`, any error
|
|
||||||
/// during verification will be fatal. If not called, verification of the server is
|
|
||||||
/// enabled by default.
|
|
||||||
pub fn verify_peer(&mut self, verify: bool) {
|
|
||||||
self.ctx.verify_peer(verify)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the [SessionCache] used to cache all client sessions.
|
|
||||||
pub fn get_session_cache(&self) -> Arc<dyn SessionCache> {
|
|
||||||
self.session_cache.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the [SessionCache] to be shared by all created client sessions.
|
|
||||||
pub fn set_session_cache(&mut self, session_cache: Arc<dyn SessionCache>) {
|
|
||||||
self.session_cache = session_cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the ALPN protocols supported by the client. QUIC requires that
|
|
||||||
/// ALPN be used (see <https://www.rfc-editor.org/rfc/rfc9001.html#section-8.1>).
|
|
||||||
/// By default, the client will offer "h3".
|
|
||||||
pub fn set_alpn(&mut self, alpn_protocols: &[Vec<u8>]) -> Result<()> {
|
|
||||||
self.ctx
|
|
||||||
.set_alpn_protos(&AlpnProtocols::from(alpn_protocols).encode())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crypto::ClientConfig for Config {
|
|
||||||
fn start_session(
|
|
||||||
self: Arc<Self>,
|
|
||||||
version: u32,
|
|
||||||
server_name: &str,
|
|
||||||
params: &TransportParameters,
|
|
||||||
) -> StdResult<Box<dyn crypto::Session>, ConnectError> {
|
|
||||||
let version = QuicVersion::parse(version).unwrap();
|
|
||||||
|
|
||||||
Ok(Session::new(self, version, server_name, params)
|
|
||||||
.map_err(|_| ConnectError::EndpointStopping)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static SESSION_INDEX: LazyLock<c_int> = LazyLock::new(|| unsafe {
|
|
||||||
bffi::SSL_get_ex_new_index(0, std::ptr::null_mut(), std::ptr::null_mut(), None, None)
|
|
||||||
});
|
|
||||||
|
|
||||||
/// The [crypto::Session] implementation for BoringSSL.
|
|
||||||
struct Session {
|
|
||||||
state: Box<SessionState>,
|
|
||||||
server_name: Bytes,
|
|
||||||
session_cache: Arc<dyn SessionCache>,
|
|
||||||
zero_rtt_peer_params: Option<TransportParameters>,
|
|
||||||
handshake_data_available: bool,
|
|
||||||
handshake_data_sent: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Session {
|
|
||||||
fn new(
|
|
||||||
cfg: Arc<Config>,
|
|
||||||
version: QuicVersion,
|
|
||||||
server_name: &str,
|
|
||||||
params: &TransportParameters,
|
|
||||||
) -> Result<Box<Self>> {
|
|
||||||
let session_cache = cfg.session_cache.clone();
|
|
||||||
let mut ssl = Ssl::new(&cfg.ctx).unwrap();
|
|
||||||
|
|
||||||
// Configure the TLS extension based on the QUIC version used.
|
|
||||||
ssl.set_quic_use_legacy_codepoint(version.uses_legacy_extension());
|
|
||||||
|
|
||||||
// Configure the SSL to be a client.
|
|
||||||
ssl.set_connect_state();
|
|
||||||
|
|
||||||
// Configure verification for the server hostname.
|
|
||||||
ssl.set_verify_hostname(server_name)
|
|
||||||
.map_err(|_| ConnectError::InvalidServerName(server_name.into()))?;
|
|
||||||
|
|
||||||
// Set the SNI hostname.
|
|
||||||
// TODO: should we validate the hostname?
|
|
||||||
ssl.set_hostname(server_name)
|
|
||||||
.map_err(|_| ConnectError::InvalidServerName(server_name.into()))?;
|
|
||||||
|
|
||||||
// Set the transport parameters.
|
|
||||||
ssl.set_quic_transport_params(&encode_params(params))
|
|
||||||
.map_err(|_| ConnectError::EndpointStopping)?;
|
|
||||||
|
|
||||||
let server_name_bytes = Bytes::copy_from_slice(server_name.as_bytes());
|
|
||||||
|
|
||||||
// If we have a cached session, use it.
|
|
||||||
let mut zero_rtt_peer_params = None;
|
|
||||||
if let Some(entry) = session_cache.get(server_name_bytes.clone()) {
|
|
||||||
match Entry::decode(ssl.ssl_context(), entry) {
|
|
||||||
Ok(entry) => {
|
|
||||||
zero_rtt_peer_params = Some(entry.params);
|
|
||||||
match unsafe { ssl.set_session(entry.session.as_ref()) } {
|
|
||||||
Ok(()) => {
|
|
||||||
trace!("attempting resumption (0-RTT) for server: {}.", server_name);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!(
|
|
||||||
"failed setting cached session for server {}: {:?}",
|
|
||||||
server_name, e
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!(
|
|
||||||
"failed decoding session entry for server {}: {:?}",
|
|
||||||
server_name, e
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
trace!(
|
|
||||||
"no cached session found for server: {}. Will continue with 1-RTT.",
|
|
||||||
server_name
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut session = Box::new(Self {
|
|
||||||
state: SessionState::new(ssl, Side::Client, version)?,
|
|
||||||
server_name: server_name_bytes,
|
|
||||||
session_cache,
|
|
||||||
zero_rtt_peer_params,
|
|
||||||
handshake_data_available: false,
|
|
||||||
handshake_data_sent: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register the instance in SSL ex_data. This allows the static callbacks to
|
|
||||||
// reference the instance.
|
|
||||||
unsafe {
|
|
||||||
map_result(bffi::SSL_set_ex_data(
|
|
||||||
session.state.ssl.as_ptr(),
|
|
||||||
*SESSION_INDEX,
|
|
||||||
&mut *session as *mut Self as *mut _,
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the handshake in order to emit the Client Hello on the first
|
|
||||||
// call to write_handshake.
|
|
||||||
session.state.advance_handshake()?;
|
|
||||||
|
|
||||||
Ok(session)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for the rejection of a 0-RTT attempt. Will continue with 1-RTT.
|
|
||||||
fn on_zero_rtt_rejected(&mut self) {
|
|
||||||
trace!(
|
|
||||||
"0-RTT handshake attempted but was rejected by the server: {}",
|
|
||||||
Ssl::early_data_reason_string(self.state.ssl.get_early_data_reason())
|
|
||||||
);
|
|
||||||
|
|
||||||
self.zero_rtt_peer_params = None;
|
|
||||||
|
|
||||||
// Removed the failed cache entry.
|
|
||||||
self.session_cache.remove(self.server_name.clone());
|
|
||||||
|
|
||||||
// Now retry advancing the handshake, this time in 1-RTT mode.
|
|
||||||
if let Err(e) = self.state.advance_handshake() {
|
|
||||||
warn!("failed advancing 1-RTT handshake: {:?}", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Client-side only callback from BoringSSL to allow caching of a new session.
|
|
||||||
fn on_new_session(&mut self, session: SslSession) {
|
|
||||||
if !session.early_data_capable() {
|
|
||||||
warn!("failed caching session: not early data capable");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the server transport parameters.
|
|
||||||
let params = match self.state.ssl.get_peer_quic_transport_params() {
|
|
||||||
Some(params) => {
|
|
||||||
match TransportParameters::read(Side::Client, &mut Cursor::new(¶ms)) {
|
|
||||||
Ok(params) => params,
|
|
||||||
Err(e) => {
|
|
||||||
warn!("failed parsing server transport parameters: {:?}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
warn!("failed caching session: server transport parameters are not available");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Encode the session cache entry, including both the session and the server params.
|
|
||||||
let entry = Entry { session, params };
|
|
||||||
match entry.encode() {
|
|
||||||
Ok(value) => {
|
|
||||||
// Cache the session.
|
|
||||||
self.session_cache.put(self.server_name.clone(), value)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!("failed caching session: unable to encode entry: {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called by the static callbacks to retrieve the instance pointer.
|
|
||||||
#[inline]
|
|
||||||
fn get_instance(ssl: *const bffi::SSL) -> &'static mut Session {
|
|
||||||
unsafe {
|
|
||||||
let data = bffi::SSL_get_ex_data(ssl, *SESSION_INDEX);
|
|
||||||
if data.is_null() {
|
|
||||||
panic!("BUG: Session instance missing")
|
|
||||||
}
|
|
||||||
&mut *(data as *mut Session)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Raw callback from BoringSSL.
|
|
||||||
extern "C" fn new_session_callback(
|
|
||||||
ssl: *mut bffi::SSL,
|
|
||||||
session: *mut bffi::SSL_SESSION,
|
|
||||||
) -> c_int {
|
|
||||||
let inst = Self::get_instance(ssl);
|
|
||||||
let session = unsafe { SslSession::from_ptr(session) };
|
|
||||||
inst.on_new_session(session);
|
|
||||||
|
|
||||||
// Return 1 to indicate we've taken ownership of the session.
|
|
||||||
1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crypto::Session for Session {
|
|
||||||
fn initial_keys(&self, dcid: &ConnectionId, side: Side) -> crypto::Keys {
|
|
||||||
self.state.initial_keys(dcid, side)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handshake_data(&self) -> Option<Box<dyn Any>> {
|
|
||||||
self.state.handshake_data()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn peer_identity(&self) -> Option<Box<dyn Any>> {
|
|
||||||
self.state.peer_identity()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn early_crypto(&self) -> Option<(Box<dyn crypto::HeaderKey>, Box<dyn crypto::PacketKey>)> {
|
|
||||||
self.state.early_crypto()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn early_data_accepted(&self) -> Option<bool> {
|
|
||||||
Some(self.state.ssl.early_data_accepted())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_handshaking(&self) -> bool {
|
|
||||||
self.state.is_handshaking()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_handshake(&mut self, plaintext: &[u8]) -> StdResult<bool, TransportError> {
|
|
||||||
self.state.read_handshake(plaintext)?;
|
|
||||||
|
|
||||||
if self.state.early_data_rejected {
|
|
||||||
self.on_zero_rtt_rejected();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only indicate that handshake data is available once.
|
|
||||||
// On the client side there is no ALPN callback, so we need to manually check
|
|
||||||
// if the ALPN protocol has been selected.
|
|
||||||
if !self.handshake_data_sent {
|
|
||||||
if self.state.ssl.selected_alpn_protocol().is_some() {
|
|
||||||
self.handshake_data_available = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.handshake_data_available {
|
|
||||||
self.handshake_data_sent = true;
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transport_parameters(&self) -> StdResult<Option<TransportParameters>, TransportError> {
|
|
||||||
match self.state.transport_parameters()? {
|
|
||||||
Some(params) => Ok(Some(params)),
|
|
||||||
None => {
|
|
||||||
if self.state.ssl.in_early_data() {
|
|
||||||
Ok(self.zero_rtt_peer_params)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_handshake(&mut self, buf: &mut Vec<u8>) -> Option<crypto::Keys> {
|
|
||||||
self.state.write_handshake(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_1rtt_keys(&mut self) -> Option<crypto::KeyPair<Box<dyn crypto::PacketKey>>> {
|
|
||||||
self.state.next_1rtt_keys()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_valid_retry(&self, orig_dst_cid: &ConnectionId, header: &[u8], payload: &[u8]) -> bool {
|
|
||||||
self.state.is_valid_retry(orig_dst_cid, header, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn export_keying_material(
|
|
||||||
&self,
|
|
||||||
output: &mut [u8],
|
|
||||||
label: &[u8],
|
|
||||||
context: &[u8],
|
|
||||||
) -> StdResult<(), crypto::ExportKeyingMaterialError> {
|
|
||||||
self.state.export_keying_material(output, label, context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn encode_params(params: &TransportParameters) -> Bytes {
|
|
||||||
let mut out = BytesMut::with_capacity(128);
|
|
||||||
params.write(&mut out);
|
|
||||||
out.freeze()
|
|
||||||
}
|
|
||||||
|
|
@ -1,150 +0,0 @@
|
||||||
use boring::error::ErrorStack;
|
|
||||||
use quinn_proto::{crypto, ConnectError, TransportError};
|
|
||||||
use std::ffi::c_int;
|
|
||||||
use std::fmt::{Debug, Display, Formatter};
|
|
||||||
use std::io::ErrorKind;
|
|
||||||
use std::result::Result as StdResult;
|
|
||||||
use std::{fmt, io};
|
|
||||||
|
|
||||||
// Error conversion:
|
|
||||||
pub enum Error {
|
|
||||||
SslError(ErrorStack),
|
|
||||||
IoError(io::Error),
|
|
||||||
ConnectError(ConnectError),
|
|
||||||
TransportError(TransportError),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Error {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::SslError(e) => Debug::fmt(&e, f),
|
|
||||||
Self::IoError(e) => Debug::fmt(&e, f),
|
|
||||||
Self::ConnectError(e) => Debug::fmt(&e, f),
|
|
||||||
Self::TransportError(e) => Debug::fmt(&e, f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Error {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::SslError(e) => Display::fmt(&e, f),
|
|
||||||
Self::IoError(e) => Display::fmt(&e, f),
|
|
||||||
Self::ConnectError(e) => Display::fmt(&e, f),
|
|
||||||
Self::TransportError(e) => Display::fmt(&e, f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for Error {}
|
|
||||||
|
|
||||||
impl Error {
|
|
||||||
pub(crate) fn ssl() -> Self {
|
|
||||||
Error::SslError(ErrorStack::get())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn invalid_input(msg: String) -> Self {
|
|
||||||
Error::IoError(io::Error::new(ErrorKind::InvalidInput, msg))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn other(msg: String) -> Self {
|
|
||||||
Error::IoError(io::Error::other(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Support conversion to CryptoError.
|
|
||||||
impl From<Error> for crypto::CryptoError {
|
|
||||||
fn from(_: Error) -> Self {
|
|
||||||
crypto::CryptoError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Support conversion to ConnectError.
|
|
||||||
impl From<Error> for ConnectError {
|
|
||||||
fn from(e: Error) -> Self {
|
|
||||||
match e {
|
|
||||||
Error::SslError(_) => Self::EndpointStopping,
|
|
||||||
Error::IoError(_) => Self::EndpointStopping,
|
|
||||||
Error::ConnectError(e) => e,
|
|
||||||
Error::TransportError(_) => Self::EndpointStopping,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ErrorStack> for Error {
|
|
||||||
fn from(e: ErrorStack) -> Self {
|
|
||||||
Error::SslError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<io::Error> for Error {
|
|
||||||
fn from(e: io::Error) -> Self {
|
|
||||||
Error::IoError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ConnectError> for Error {
|
|
||||||
fn from(e: ConnectError) -> Self {
|
|
||||||
Error::ConnectError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<TransportError> for Error {
|
|
||||||
fn from(e: TransportError) -> Self {
|
|
||||||
Error::TransportError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The main result type for this (crypto boring) module.
|
|
||||||
pub type Result<T> = StdResult<T, Error>;
|
|
||||||
|
|
||||||
/// The result returned by the Cloudflare Boring library API functions.
|
|
||||||
pub(crate) type BoringResult = StdResult<(), ErrorStack>;
|
|
||||||
|
|
||||||
/// Maps BoringSSL ffi return values to the Result type consistent with the Boring APIs.
|
|
||||||
pub(crate) fn br(bssl_result: c_int) -> BoringResult {
|
|
||||||
match bssl_result {
|
|
||||||
1 => Ok(()),
|
|
||||||
_ => Err(ErrorStack::get()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn br_zero_is_success(bssl_result: c_int) -> BoringResult {
|
|
||||||
match bssl_result {
|
|
||||||
0 => Ok(()),
|
|
||||||
_ => Err(ErrorStack::get()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Maps BoringSSL ffi return values to a Result.
|
|
||||||
pub(crate) fn map_result(bssl_result: c_int) -> Result<()> {
|
|
||||||
match bssl_result {
|
|
||||||
1 => Ok(()),
|
|
||||||
_ => Err(Error::SslError(ErrorStack::get())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Maps a result from a Rust callback to a BoringSSL result error code.
|
|
||||||
pub(crate) fn map_cb_result<T>(result: Result<T>) -> c_int {
|
|
||||||
match result {
|
|
||||||
Ok(_) => 1,
|
|
||||||
_ => 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Like map_result, but for BoringSSL method that break the standard return value convention.
|
|
||||||
pub(crate) fn map_result_zero_is_success(bssl_result: c_int) -> Result<()> {
|
|
||||||
match bssl_result {
|
|
||||||
0 => Ok(()),
|
|
||||||
_ => Err(Error::SslError(ErrorStack::get())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Like map_result, but ensures that the resulting pointer is non-null.
|
|
||||||
pub(crate) fn map_ptr_result<T>(r: *mut T) -> Result<*mut T> {
|
|
||||||
if r.is_null() {
|
|
||||||
Err(Error::ssl())
|
|
||||||
} else {
|
|
||||||
Ok(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
use crate::error::Result;
|
|
||||||
use crate::hkdf::Hkdf;
|
|
||||||
use crate::key::{AeadKey, Key};
|
|
||||||
use crate::secret::Secret;
|
|
||||||
use crate::suite::CipherSuite;
|
|
||||||
use quinn_proto::crypto;
|
|
||||||
|
|
||||||
pub struct HandshakeTokenKey(Key);
|
|
||||||
|
|
||||||
impl HandshakeTokenKey {
|
|
||||||
/// Creates a new randomized HandshakeTokenKey.
|
|
||||||
pub fn new() -> Result<Self> {
|
|
||||||
Self::new_for(Secret::random())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_for(secret: Secret) -> Result<Self> {
|
|
||||||
// Extract the key.
|
|
||||||
let mut key = [0u8; Key::MAX_LEN];
|
|
||||||
let len = Hkdf::sha256().extract(&[], secret.slice(), &mut key)?;
|
|
||||||
Ok(Self(Key::new(key, len)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crypto::HandshakeTokenKey for HandshakeTokenKey {
|
|
||||||
fn aead_from_hkdf(&self, random_bytes: &[u8]) -> Box<dyn crypto::AeadKey> {
|
|
||||||
let suite = CipherSuite::aes256_gcm_sha384();
|
|
||||||
let prk = self.0.slice();
|
|
||||||
let mut key = suite.aead.zero_key();
|
|
||||||
Hkdf::sha256()
|
|
||||||
.expand(prk, random_bytes, key.slice_mut())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Box::new(AeadKey::new(suite, key).unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use hex_literal::hex;
|
|
||||||
use quinn_proto::crypto::HandshakeTokenKey;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn round_trip() {
|
|
||||||
// Create a random token key.
|
|
||||||
let master_key = hex!("ab35ad55e9957c0e67aedbbd76f6a781528a5b43cc57bfd633"
|
|
||||||
"ccca412327aa23e0d7140d5fc290d1637746706c7d703e3bf405687a69ee82284a5ede49f59e19");
|
|
||||||
let htk = super::HandshakeTokenKey::new_for(super::Secret::from(&master_key)).unwrap();
|
|
||||||
|
|
||||||
// Generate an AEAD from the given random.
|
|
||||||
let random_bytes = hex!("b088e52e27da85f8838e163ddb90fd35d633fad44f0ab9c39f05459297178599");
|
|
||||||
let aead_key = htk.aead_from_hkdf(&random_bytes);
|
|
||||||
|
|
||||||
// Inputs for the seal/open operations.
|
|
||||||
let data = hex!("146a6d36221e4f24eda3a16f71a816a8a72dd7efbb0000000064076bde");
|
|
||||||
let additional_data = hex!("00000000000000000000000000000001d60c084b239b74c31de86f");
|
|
||||||
|
|
||||||
// Seal the buffer and verify the expected output.
|
|
||||||
let mut buf = data.to_vec();
|
|
||||||
aead_key.seal(&mut buf, &additional_data).unwrap();
|
|
||||||
let expected = hex!("504a8f0841f3fb7dbf8b3df90b5be913cb5a28000918510baeff64"
|
|
||||||
"5d72b67ab34a47da820a97416d68d0b605af");
|
|
||||||
assert_eq!(&expected, buf.as_slice());
|
|
||||||
|
|
||||||
// Now open and verify we get back the original data.
|
|
||||||
let out = aead_key.open(&mut buf, &additional_data).unwrap();
|
|
||||||
assert_eq!(&data, out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
use crate::error::{map_result, Error, Result};
|
|
||||||
use boring::hash::MessageDigest;
|
|
||||||
use boring_sys as bffi;
|
|
||||||
use bytes::{BufMut, BytesMut};
|
|
||||||
use std::sync::LazyLock;
|
|
||||||
|
|
||||||
/// The block size used by the supported digest algorithms (64).
|
|
||||||
pub(crate) const DIGEST_BLOCK_LEN: usize = bffi::SHA_CBLOCK as _;
|
|
||||||
|
|
||||||
// /// The digest size for SHA256 (32).
|
|
||||||
// pub(crate) const SHA256_DIGEST_LEN: usize = bffi::SHA256_DIGEST_LENGTH as _;
|
|
||||||
//
|
|
||||||
// /// The digest size for SHA384 (48).
|
|
||||||
// pub(crate) const SHA384_DIGEST_LEN: usize = bffi::SHA384_DIGEST_LENGTH as _;
|
|
||||||
|
|
||||||
/// Implementation of [HKDF](https://www.rfc-editor.org/rfc/rfc5869) used for
|
|
||||||
/// creating the initial secrets for
|
|
||||||
/// [QUIC](https://www.rfc-editor.org/rfc/rfc9001#section-5.2) and
|
|
||||||
/// [TLS_1.3](https://datatracker.ietf.org/doc/html/rfc9001#name-initial-secrets).
|
|
||||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
||||||
pub(crate) struct Hkdf(MessageDigest);
|
|
||||||
|
|
||||||
static SHA256: LazyLock<Hkdf> = LazyLock::new(|| Hkdf(MessageDigest::sha256()));
|
|
||||||
|
|
||||||
static SHA384: LazyLock<Hkdf> = LazyLock::new(|| Hkdf(MessageDigest::sha384()));
|
|
||||||
|
|
||||||
impl Hkdf {
|
|
||||||
pub(crate) fn sha256() -> Hkdf {
|
|
||||||
*SHA256
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn sha384() -> Hkdf {
|
|
||||||
*SHA384
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The size of the digest in bytes.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn digest_size(self) -> usize {
|
|
||||||
self.0.size()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Performs an HKDF extract (<https://tools.ietf.org/html/rfc5869#section-2.2>),
|
|
||||||
/// given the salt and the initial key material (IKM). Returns the slice of the 'out'
|
|
||||||
/// array containing the generated pseudorandom key (PRK).
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn extract(self, salt: &[u8], ikm: &[u8], out: &mut [u8]) -> Result<usize> {
|
|
||||||
if out.len() < self.digest_size() {
|
|
||||||
return Err(Error::invalid_input(format!(
|
|
||||||
"HKDF extract output array invalid size: {}",
|
|
||||||
out.len()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut out_len = out.len();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
map_result(bffi::HKDF_extract(
|
|
||||||
out.as_mut_ptr(),
|
|
||||||
&mut out_len,
|
|
||||||
self.0.as_ptr(),
|
|
||||||
ikm.as_ptr(),
|
|
||||||
ikm.len(),
|
|
||||||
salt.as_ptr(),
|
|
||||||
salt.len(),
|
|
||||||
))?;
|
|
||||||
|
|
||||||
Ok(out_len)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Performs the HKDF-Expand-Label function as defined in the
|
|
||||||
/// [TLS-1.3 spec](https://datatracker.ietf.org/doc/html/rfc8446#section-7.1). The
|
|
||||||
/// [HKDF-Expand-Label function](https://www.rfc-editor.org/rfc/rfc5869#section-2.3) takes
|
|
||||||
/// 4 explicit arguments (Secret, Label, Context, and Length), as well as implicit PRF
|
|
||||||
/// which is the hash function negotiated by TLS.
|
|
||||||
///
|
|
||||||
/// Its use in QUIC is only for deriving initial secrets for obfuscation, for calculating
|
|
||||||
/// packet protection keys and IVs from the corresponding packet protection secret and
|
|
||||||
/// key update in the same quic session. None of these uses need a Context (a zero-length
|
|
||||||
/// context is provided), so this argument is omitted here.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn expand_label(self, secret: &[u8], label: &[u8], out: &mut [u8]) -> Result<()> {
|
|
||||||
// Convert the label to a structure required by HKDF_expand. Doing
|
|
||||||
// this inline rather than using the complex openssl Crypto ByteBuilder (CBB).
|
|
||||||
let label = {
|
|
||||||
const TLS_VERSION_LABEL: &[u8] = b"tls13 ";
|
|
||||||
|
|
||||||
// Initialize the builder for the label structure. Breakdown of the required capacity:
|
|
||||||
// 2-byte total length field +
|
|
||||||
// 1-byte for the length of the label +
|
|
||||||
// Label length +
|
|
||||||
// 1-byte for the length of the Context +
|
|
||||||
// 0-bytes for an empty context (QUIC does not use context).
|
|
||||||
let label_len = TLS_VERSION_LABEL.len() + label.len();
|
|
||||||
let builder_capacity = label_len + 4;
|
|
||||||
let mut builder = BytesMut::with_capacity(builder_capacity);
|
|
||||||
|
|
||||||
// Add the length of the output key in big-endian byte order.
|
|
||||||
builder.put_u16(out.len() as u16);
|
|
||||||
|
|
||||||
// Add a child containing the label.
|
|
||||||
builder.put_u8(label_len as u8);
|
|
||||||
builder.put(TLS_VERSION_LABEL);
|
|
||||||
builder.put(label);
|
|
||||||
|
|
||||||
// Add a child containing a zero hash.
|
|
||||||
builder.put_u8(0);
|
|
||||||
builder
|
|
||||||
};
|
|
||||||
|
|
||||||
self.expand(secret, &label, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn expand(&self, prk: &[u8], info: &[u8], out: &mut [u8]) -> Result<()> {
|
|
||||||
unsafe {
|
|
||||||
map_result(bffi::HKDF_expand(
|
|
||||||
out.as_mut_ptr(),
|
|
||||||
out.len(),
|
|
||||||
self.0.as_ptr(),
|
|
||||||
prk.as_ptr(),
|
|
||||||
prk.len(),
|
|
||||||
info.as_ptr(),
|
|
||||||
info.len(),
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
use crate::error::map_ptr_result;
|
|
||||||
use crate::hkdf::DIGEST_BLOCK_LEN;
|
|
||||||
use boring::hash::MessageDigest;
|
|
||||||
use boring_sys as bffi;
|
|
||||||
use quinn_proto::crypto;
|
|
||||||
use rand::RngCore;
|
|
||||||
use std::ffi::{c_uint, c_void};
|
|
||||||
use std::result::Result as StdResult;
|
|
||||||
|
|
||||||
const SIGNATURE_LEN_SHA_256: usize = 32;
|
|
||||||
|
|
||||||
/// Implementation of [crypto::HmacKey] using BoringSSL.
|
|
||||||
pub struct HmacKey {
|
|
||||||
alg: MessageDigest,
|
|
||||||
key: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HmacKey {
|
|
||||||
/// Creates a new randomized SHA-256 HMAC key.
|
|
||||||
pub fn sha256() -> Self {
|
|
||||||
// Create a random key.
|
|
||||||
let mut key = [0u8; DIGEST_BLOCK_LEN];
|
|
||||||
rand::rng().fill_bytes(&mut key);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
alg: MessageDigest::sha256(),
|
|
||||||
key: Vec::from(key),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crypto::HmacKey for HmacKey {
|
|
||||||
fn sign(&self, data: &[u8], out: &mut [u8]) {
|
|
||||||
let mut out_len = out.len() as c_uint;
|
|
||||||
unsafe {
|
|
||||||
map_ptr_result(bffi::HMAC(
|
|
||||||
self.alg.as_ptr(),
|
|
||||||
self.key.as_ptr() as *const c_void,
|
|
||||||
self.key.len(),
|
|
||||||
data.as_ptr(),
|
|
||||||
data.len(),
|
|
||||||
out.as_mut_ptr(),
|
|
||||||
&mut out_len,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the signature length.
|
|
||||||
if out_len as usize != self.signature_len() {
|
|
||||||
panic!("HMAC.sign: generated signature with unexpected length: {out_len}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn signature_len(&self) -> usize {
|
|
||||||
SIGNATURE_LEN_SHA_256
|
|
||||||
}
|
|
||||||
|
|
||||||
fn verify(&self, data: &[u8], signature: &[u8]) -> StdResult<(), crypto::CryptoError> {
|
|
||||||
if signature.len() != self.signature_len() {
|
|
||||||
return Err(crypto::CryptoError {});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign the data.
|
|
||||||
let mut out = [0u8; SIGNATURE_LEN_SHA_256];
|
|
||||||
self.sign(data, &mut out);
|
|
||||||
|
|
||||||
// Compare the output.
|
|
||||||
if out == signature {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Err(crypto::CryptoError {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,529 +0,0 @@
|
||||||
use crate::error::{map_result, map_result_zero_is_success, Result};
|
|
||||||
use crate::macros::bounded_array;
|
|
||||||
use crate::secret::Secret;
|
|
||||||
use crate::suite::{CipherSuite, ID};
|
|
||||||
use crate::{Error, QuicVersion};
|
|
||||||
use boring_sys as bffi;
|
|
||||||
use bytes::BytesMut;
|
|
||||||
use quinn_proto::crypto;
|
|
||||||
use std::ffi::c_uint;
|
|
||||||
use std::fmt::{Debug, Formatter};
|
|
||||||
use std::mem;
|
|
||||||
use std::mem::MaybeUninit;
|
|
||||||
use std::result::Result as StdResult;
|
|
||||||
|
|
||||||
const SAMPLE_LEN: usize = 16; // 128-bits.
|
|
||||||
|
|
||||||
/// The maximum key size used by Quic algorithms.
|
|
||||||
const MAX_KEY_LEN: usize = 32;
|
|
||||||
|
|
||||||
/// The maximum nonce size used by Quic algorithms.
|
|
||||||
const MAX_NONCE_LEN: usize = 12;
|
|
||||||
|
|
||||||
/// The maximum tag size used by Quic algorithms.
|
|
||||||
const MAX_TAG_LEN: usize = 16;
|
|
||||||
|
|
||||||
bounded_array! {
|
|
||||||
/// A buffer that can fit the largest key supported by Quic.
|
|
||||||
pub(crate) struct Key(MAX_KEY_LEN),
|
|
||||||
|
|
||||||
/// A buffer that can fit the largest nonce supported by Quic.
|
|
||||||
pub(crate) struct Nonce(MAX_NONCE_LEN),
|
|
||||||
|
|
||||||
/// A buffer that can fit the largest tag supported by Quic.
|
|
||||||
pub(crate) struct Tag(MAX_TAG_LEN)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A pair of keys for bidirectional communication
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub(crate) struct KeyPair<T> {
|
|
||||||
/// The key for this side, used for encrypting data.
|
|
||||||
pub(crate) local: T,
|
|
||||||
|
|
||||||
/// The key for the other side, used for decrypting data.
|
|
||||||
pub(crate) remote: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyPair<HeaderKey> {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn as_crypto(&self) -> Result<crypto::KeyPair<Box<dyn crypto::HeaderKey>>> {
|
|
||||||
Ok(crypto::KeyPair {
|
|
||||||
local: self.local.as_crypto()?,
|
|
||||||
remote: self.remote.as_crypto()?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyPair<PacketKey> {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn as_crypto(&self) -> Result<crypto::KeyPair<Box<dyn crypto::PacketKey>>> {
|
|
||||||
Ok(crypto::KeyPair {
|
|
||||||
local: Box::new(self.local),
|
|
||||||
remote: Box::new(self.remote),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A complete set of keys for a certain encryption level.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub(crate) struct Keys {
|
|
||||||
/// Header protection keys
|
|
||||||
pub(crate) header: KeyPair<HeaderKey>,
|
|
||||||
/// Packet protection keys
|
|
||||||
pub(crate) packet: KeyPair<PacketKey>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Keys {
|
|
||||||
pub(crate) fn as_crypto(&self) -> Result<crypto::Keys> {
|
|
||||||
Ok(crypto::Keys {
|
|
||||||
header: self.header.as_crypto()?,
|
|
||||||
packet: self.packet.as_crypto()?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal header key representation. Supports conversion to [crypto::HeaderKey]
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub(crate) struct HeaderKey {
|
|
||||||
suite: &'static CipherSuite,
|
|
||||||
key: Key,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HeaderKey {
|
|
||||||
pub(crate) fn new(
|
|
||||||
version: QuicVersion,
|
|
||||||
suite: &'static CipherSuite,
|
|
||||||
secret: &Secret,
|
|
||||||
) -> Result<Self> {
|
|
||||||
let mut key = suite.aead.zero_key();
|
|
||||||
suite
|
|
||||||
.hkdf
|
|
||||||
.expand_label(secret.slice(), version.header_key_label(), key.slice_mut())?;
|
|
||||||
|
|
||||||
Ok(Self { suite, key })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn key(&self) -> &Key {
|
|
||||||
&self.key
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts to a crypto HeaderKey.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn as_crypto(&self) -> Result<Box<dyn crypto::HeaderKey>> {
|
|
||||||
match self.suite.id {
|
|
||||||
ID::Aes128GcmSha256 | ID::Aes256GcmSha384 => {
|
|
||||||
Ok(Box::new(AesHeaderKey::new(self.key())?))
|
|
||||||
}
|
|
||||||
ID::Chacha20Poly1305Sha256 => Ok(Box::new(ChaChaHeaderKey::new(self.key())?)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Base trait for a crypto header protection keys. Implementation copied from rustls.
|
|
||||||
trait CryptoHeaderKey: crypto::HeaderKey {
|
|
||||||
fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5]>;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn sample_len(&self) -> usize {
|
|
||||||
SAMPLE_LEN
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn decrypt_in_place(&self, pn_offset: usize, packet: &mut [u8]) {
|
|
||||||
let (header, sample) = packet.split_at_mut(pn_offset + 4);
|
|
||||||
let (first, rest) = header.split_at_mut(1);
|
|
||||||
let pn_end = Ord::min(pn_offset + 3, rest.len());
|
|
||||||
self.xor_in_place(
|
|
||||||
&sample[..self.sample_len()],
|
|
||||||
&mut first[0],
|
|
||||||
&mut rest[pn_offset - 1..pn_end],
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn encrypt_in_place(&self, pn_offset: usize, packet: &mut [u8]) {
|
|
||||||
let (header, sample) = packet.split_at_mut(pn_offset + 4);
|
|
||||||
let (first, rest) = header.split_at_mut(1);
|
|
||||||
let pn_end = Ord::min(pn_offset + 3, rest.len());
|
|
||||||
self.xor_in_place(
|
|
||||||
&sample[..self.sample_size()],
|
|
||||||
&mut first[0],
|
|
||||||
&mut rest[pn_offset - 1..pn_end],
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn xor_in_place(
|
|
||||||
&self,
|
|
||||||
sample: &[u8],
|
|
||||||
first: &mut u8,
|
|
||||||
packet_number: &mut [u8],
|
|
||||||
masked: bool,
|
|
||||||
) -> Result<()> {
|
|
||||||
// This implements [Header Protection Application] almost verbatim.
|
|
||||||
|
|
||||||
let mask = self.new_mask(sample).unwrap();
|
|
||||||
|
|
||||||
// The `unwrap()` will not panic because `new_mask` returns a
|
|
||||||
// non-empty result.
|
|
||||||
let (first_mask, pn_mask) = mask.split_first().unwrap();
|
|
||||||
|
|
||||||
// It is OK for the `mask` to be longer than `packet_number`,
|
|
||||||
// but a valid `packet_number` will never be longer than `mask`.
|
|
||||||
if packet_number.len() > pn_mask.len() {
|
|
||||||
return Err(Error::other(format!(
|
|
||||||
"packet number too long: {}",
|
|
||||||
packet_number.len()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infallible from this point on. Before this point, `first` and
|
|
||||||
// `packet_number` are unchanged.
|
|
||||||
|
|
||||||
const LONG_HEADER_FORM: u8 = 0x80;
|
|
||||||
let bits = match *first & LONG_HEADER_FORM == LONG_HEADER_FORM {
|
|
||||||
true => 0x0f, // Long header: 4 bits masked
|
|
||||||
false => 0x1f, // Short header: 5 bits masked
|
|
||||||
};
|
|
||||||
|
|
||||||
let first_plain = match masked {
|
|
||||||
// When unmasking, use the packet length bits after unmasking
|
|
||||||
true => *first ^ (first_mask & bits),
|
|
||||||
// When masking, use the packet length bits before masking
|
|
||||||
false => *first,
|
|
||||||
};
|
|
||||||
let pn_len = (first_plain & 0x03) as usize + 1;
|
|
||||||
|
|
||||||
*first ^= first_mask & bits;
|
|
||||||
for (dst, m) in packet_number.iter_mut().zip(pn_mask).take(pn_len) {
|
|
||||||
*dst ^= m;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A [CryptoHeaderKey] for AES ciphers.
|
|
||||||
struct AesHeaderKey(bffi::AES_KEY);
|
|
||||||
|
|
||||||
impl AesHeaderKey {
|
|
||||||
fn new(key: &Key) -> Result<AesHeaderKey> {
|
|
||||||
let hpk = unsafe {
|
|
||||||
let mut hpk = MaybeUninit::uninit();
|
|
||||||
|
|
||||||
// NOTE: this function breaks the usual return value convention.
|
|
||||||
map_result_zero_is_success(bffi::AES_set_encrypt_key(
|
|
||||||
key.as_ptr(),
|
|
||||||
(key.len() * 8) as c_uint,
|
|
||||||
hpk.as_mut_ptr(),
|
|
||||||
))?;
|
|
||||||
|
|
||||||
hpk.assume_init()
|
|
||||||
};
|
|
||||||
Ok(Self(hpk))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CryptoHeaderKey for AesHeaderKey {
|
|
||||||
#[inline]
|
|
||||||
fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5]> {
|
|
||||||
if sample.len() != SAMPLE_LEN {
|
|
||||||
return Err(Error::invalid_input(format!(
|
|
||||||
"invalid sample length: {}",
|
|
||||||
sample.len()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut encrypted: [u8; SAMPLE_LEN] = [0; SAMPLE_LEN];
|
|
||||||
unsafe {
|
|
||||||
bffi::AES_encrypt(sample.as_ptr(), encrypted.as_mut_ptr(), &self.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut out: [u8; 5] = [0; 5];
|
|
||||||
out.copy_from_slice(&encrypted[..5]);
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crypto::HeaderKey for AesHeaderKey {
|
|
||||||
#[inline]
|
|
||||||
fn decrypt(&self, pn_offset: usize, packet: &mut [u8]) {
|
|
||||||
self.decrypt_in_place(pn_offset, packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn encrypt(&self, pn_offset: usize, packet: &mut [u8]) {
|
|
||||||
self.encrypt_in_place(pn_offset, packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn sample_size(&self) -> usize {
|
|
||||||
self.sample_len()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A [CryptoHeaderKey] for ChaCha ciphers.
|
|
||||||
struct ChaChaHeaderKey(Key);
|
|
||||||
|
|
||||||
impl ChaChaHeaderKey {
|
|
||||||
const ZEROS: [u8; 5] = [0; 5];
|
|
||||||
|
|
||||||
fn new(key: &Key) -> Result<ChaChaHeaderKey> {
|
|
||||||
Ok(Self(*key))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CryptoHeaderKey for ChaChaHeaderKey {
|
|
||||||
#[inline]
|
|
||||||
fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5]> {
|
|
||||||
if sample.len() != SAMPLE_LEN {
|
|
||||||
return Err(Error::invalid_input(format!(
|
|
||||||
"sample len invalid: {}",
|
|
||||||
sample.len()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the counter and the nonce from the sample.
|
|
||||||
let (counter, nonce) = sample.split_at(mem::size_of::<u32>());
|
|
||||||
let counter = u32::from_ne_bytes(counter.try_into().unwrap());
|
|
||||||
|
|
||||||
let mut out: [u8; 5] = [0; 5];
|
|
||||||
unsafe {
|
|
||||||
bffi::CRYPTO_chacha_20(
|
|
||||||
out.as_mut_ptr(),
|
|
||||||
Self::ZEROS.as_ptr(),
|
|
||||||
Self::ZEROS.len(),
|
|
||||||
self.0.as_ptr(),
|
|
||||||
nonce.as_ptr(),
|
|
||||||
counter,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crypto::HeaderKey for ChaChaHeaderKey {
|
|
||||||
#[inline]
|
|
||||||
fn decrypt(&self, pn_offset: usize, packet: &mut [u8]) {
|
|
||||||
self.decrypt_in_place(pn_offset, packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn encrypt(&self, pn_offset: usize, packet: &mut [u8]) {
|
|
||||||
self.encrypt_in_place(pn_offset, packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn sample_size(&self) -> usize {
|
|
||||||
self.sample_len()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal key representation.
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub(crate) struct PacketKey {
|
|
||||||
aead_key: AeadKey,
|
|
||||||
iv: Nonce,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PacketKey {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn new(
|
|
||||||
version: QuicVersion,
|
|
||||||
suite: &'static CipherSuite,
|
|
||||||
secret: &Secret,
|
|
||||||
) -> Result<Self> {
|
|
||||||
let mut key = suite.aead.zero_key();
|
|
||||||
suite
|
|
||||||
.hkdf
|
|
||||||
.expand_label(secret.slice(), version.key_label(), key.slice_mut())?;
|
|
||||||
|
|
||||||
let mut iv = suite.aead.zero_nonce();
|
|
||||||
suite
|
|
||||||
.hkdf
|
|
||||||
.expand_label(secret.slice(), version.iv_label(), iv.slice_mut())?;
|
|
||||||
|
|
||||||
let aead_key = AeadKey::new(suite, key)?;
|
|
||||||
|
|
||||||
Ok(Self { aead_key, iv })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) fn key(&self) -> &Key {
|
|
||||||
&self.aead_key.key
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) fn iv(&self) -> &Nonce {
|
|
||||||
&self.iv
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn nonce_for_packet(&self, packet_number: u64) -> Nonce {
|
|
||||||
let mut nonce = self.aead_key.suite.aead.zero_nonce();
|
|
||||||
let slice = nonce.slice_mut();
|
|
||||||
slice[4..].copy_from_slice(&packet_number.to_be_bytes());
|
|
||||||
for (out, inp) in slice.iter_mut().zip(self.iv.slice().iter()) {
|
|
||||||
*out ^= inp;
|
|
||||||
}
|
|
||||||
nonce
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crypto::PacketKey for PacketKey {
|
|
||||||
/// Encrypt a QUIC packet in-place.
|
|
||||||
fn encrypt(&self, packet_number: u64, buf: &mut [u8], header_len: usize) {
|
|
||||||
let (header, payload_tag) = buf.split_at_mut(header_len);
|
|
||||||
|
|
||||||
let nonce = self.nonce_for_packet(packet_number);
|
|
||||||
|
|
||||||
self.aead_key
|
|
||||||
.seal_in_place(&nonce, payload_tag, header)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypt a QUIC packet in-place.
|
|
||||||
fn decrypt(
|
|
||||||
&self,
|
|
||||||
packet_number: u64,
|
|
||||||
header: &[u8],
|
|
||||||
payload: &mut BytesMut,
|
|
||||||
) -> StdResult<(), crypto::CryptoError> {
|
|
||||||
let nonce = self.nonce_for_packet(packet_number);
|
|
||||||
|
|
||||||
let plain_len = self
|
|
||||||
.aead_key
|
|
||||||
.open_in_place(&nonce, payload.as_mut(), header)?;
|
|
||||||
payload.truncate(plain_len);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn tag_len(&self) -> usize {
|
|
||||||
self.aead_key.suite.aead.tag_len
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn confidentiality_limit(&self) -> u64 {
|
|
||||||
self.aead_key.suite.confidentiality_limit
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn integrity_limit(&self) -> u64 {
|
|
||||||
self.aead_key.suite.integrity_limit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A [crypto::PacketKey] that is based on a BoringSSL [bffi::EVP_AEAD_CTX].
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub(crate) struct AeadKey {
|
|
||||||
suite: &'static CipherSuite,
|
|
||||||
key: Key,
|
|
||||||
ctx: bffi::EVP_AEAD_CTX,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for AeadKey {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("AeadKey")
|
|
||||||
.field("suite", self.suite)
|
|
||||||
.field("key", &self.key)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for AeadKey {}
|
|
||||||
|
|
||||||
// EVP_AEAD_CTX_seal & EVP_AEAD_CTX_open allowed to be called concurrently on the same instance of EVP_AEAD_CTX
|
|
||||||
// https://github.com/google/boringssl/blob/master/include/openssl/aead.h#L278
|
|
||||||
unsafe impl Sync for AeadKey {}
|
|
||||||
|
|
||||||
impl AeadKey {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn new(suite: &'static CipherSuite, key: Key) -> Result<Self> {
|
|
||||||
let ctx = suite.aead.new_aead_ctx(&key)?;
|
|
||||||
Ok(Self { suite, key, ctx })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn seal_in_place(
|
|
||||||
&self,
|
|
||||||
nonce: &Nonce,
|
|
||||||
data: &mut [u8],
|
|
||||||
additional_data: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut out_len = data.len() - self.suite.aead.tag_len;
|
|
||||||
unsafe {
|
|
||||||
map_result(bffi::EVP_AEAD_CTX_seal(
|
|
||||||
&self.ctx,
|
|
||||||
data.as_mut_ptr(),
|
|
||||||
&mut out_len,
|
|
||||||
data.len(),
|
|
||||||
nonce.as_ptr(),
|
|
||||||
nonce.len(),
|
|
||||||
data.as_ptr(),
|
|
||||||
out_len,
|
|
||||||
additional_data.as_ptr(),
|
|
||||||
additional_data.len(),
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn open_in_place(
|
|
||||||
&self,
|
|
||||||
nonce: &Nonce,
|
|
||||||
data: &mut [u8],
|
|
||||||
additional_data: &[u8],
|
|
||||||
) -> StdResult<usize, crypto::CryptoError> {
|
|
||||||
let mut out_len = match data.len().checked_sub(self.suite.aead.tag_len) {
|
|
||||||
Some(n) => n,
|
|
||||||
None => return Err(crypto::CryptoError {}),
|
|
||||||
};
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
map_result(bffi::EVP_AEAD_CTX_open(
|
|
||||||
&self.ctx,
|
|
||||||
data.as_mut_ptr(),
|
|
||||||
&mut out_len,
|
|
||||||
out_len,
|
|
||||||
nonce.as_ptr(),
|
|
||||||
nonce.len(),
|
|
||||||
data.as_ptr(),
|
|
||||||
data.len(),
|
|
||||||
additional_data.as_ptr(),
|
|
||||||
additional_data.len(),
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
Ok(out_len)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crypto::AeadKey for AeadKey {
|
|
||||||
#[inline]
|
|
||||||
fn seal(
|
|
||||||
&self,
|
|
||||||
data: &mut Vec<u8>,
|
|
||||||
additional_data: &[u8],
|
|
||||||
) -> StdResult<(), crypto::CryptoError> {
|
|
||||||
data.extend_from_slice(self.suite.aead.zero_tag().slice());
|
|
||||||
self.seal_in_place(&self.suite.aead.zero_nonce(), data, additional_data)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn open<'a>(
|
|
||||||
&self,
|
|
||||||
data: &'a mut [u8],
|
|
||||||
additional_data: &[u8],
|
|
||||||
) -> StdResult<&'a mut [u8], crypto::CryptoError> {
|
|
||||||
let plain_len = self.open_in_place(&self.suite.aead.zero_nonce(), data, additional_data)?;
|
|
||||||
Ok(&mut data[..plain_len])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,301 +0,0 @@
|
||||||
#![allow(unused)]
|
|
||||||
mod aead;
|
|
||||||
mod alert;
|
|
||||||
mod alpn;
|
|
||||||
mod bffi_ext;
|
|
||||||
mod client;
|
|
||||||
mod error;
|
|
||||||
mod handshake_token;
|
|
||||||
mod hkdf;
|
|
||||||
mod hmac;
|
|
||||||
mod key;
|
|
||||||
mod macros;
|
|
||||||
mod retry;
|
|
||||||
mod secret;
|
|
||||||
mod server;
|
|
||||||
mod session_cache;
|
|
||||||
mod session_state;
|
|
||||||
mod suite;
|
|
||||||
mod version;
|
|
||||||
|
|
||||||
// Export the public interface.
|
|
||||||
pub use bffi_ext::*;
|
|
||||||
pub use client::Config as ClientConfig;
|
|
||||||
pub use error::{Error, Result};
|
|
||||||
pub use handshake_token::HandshakeTokenKey;
|
|
||||||
pub use hmac::HmacKey;
|
|
||||||
pub use server::Config as ServerConfig;
|
|
||||||
pub use session_cache::*;
|
|
||||||
pub use version::QuicVersion;
|
|
||||||
|
|
||||||
/// Information available from [quinn_proto::crypto::Session::handshake_data] once the handshake has completed.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct HandshakeData {
|
|
||||||
/// The negotiated application protocol, if ALPN is in use
|
|
||||||
///
|
|
||||||
/// Guaranteed to be set if a nonempty list of protocols was specified for this connection.
|
|
||||||
pub protocol: Option<Vec<u8>>,
|
|
||||||
|
|
||||||
/// The server name specified by the client, if any
|
|
||||||
///
|
|
||||||
/// Always `None` for outgoing connections
|
|
||||||
pub server_name: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod helpers {
|
|
||||||
use super::*;
|
|
||||||
use quinn_proto::crypto;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
/// Create a server config with the given [`crypto::ServerConfig`]
|
|
||||||
///
|
|
||||||
/// Uses a randomized handshake token key.
|
|
||||||
pub fn server_config(crypto: Arc<dyn crypto::ServerConfig>) -> Result<quinn::ServerConfig> {
|
|
||||||
Ok(quinn::ServerConfig::new(
|
|
||||||
crypto,
|
|
||||||
Arc::new(HandshakeTokenKey::new()?),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a default endpoint configuration for BoringSSL.
|
|
||||||
pub fn default_endpoint_config() -> quinn::EndpointConfig {
|
|
||||||
let mut cfg = quinn::EndpointConfig::new(Arc::new(HmacKey::sha256()));
|
|
||||||
cfg.supported_versions(QuicVersion::default_supported_versions());
|
|
||||||
cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper to construct an endpoint for use with outgoing connections only
|
|
||||||
///
|
|
||||||
/// Note that `addr` is the *local* address to bind to, which should usually be a wildcard
|
|
||||||
/// address like `0.0.0.0:0` or `[::]:0`, which allow communication with any reachable IPv4 or
|
|
||||||
/// IPv6 address respectively from an OS-assigned port.
|
|
||||||
///
|
|
||||||
/// Platform defaults for dual-stack sockets vary. For example, any socket bound to a wildcard
|
|
||||||
/// IPv6 address on Windows will not by default be able to communicate with IPv4
|
|
||||||
/// addresses. Portable applications should bind an address that matches the family they wish to
|
|
||||||
/// communicate within.
|
|
||||||
#[cfg(feature = "runtime-tokio")]
|
|
||||||
pub fn client_endpoint(addr: std::net::SocketAddr) -> std::io::Result<quinn::Endpoint> {
|
|
||||||
let socket = std::net::UdpSocket::bind(addr)?;
|
|
||||||
quinn::Endpoint::new(
|
|
||||||
default_endpoint_config(),
|
|
||||||
None,
|
|
||||||
socket,
|
|
||||||
Arc::new(quinn::TokioRuntime),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper to construct an endpoint for use with both incoming and outgoing connections
|
|
||||||
///
|
|
||||||
/// Platform defaults for dual-stack sockets vary. For example, any socket bound to a wildcard
|
|
||||||
/// IPv6 address on Windows will not by default be able to communicate with IPv4
|
|
||||||
/// addresses. Portable applications should bind an address that matches the family they wish to
|
|
||||||
/// communicate within.
|
|
||||||
#[cfg(feature = "runtime-tokio")]
|
|
||||||
pub fn server_endpoint(
|
|
||||||
config: quinn::ServerConfig,
|
|
||||||
addr: std::net::SocketAddr,
|
|
||||||
) -> std::io::Result<quinn::Endpoint> {
|
|
||||||
let socket = std::net::UdpSocket::bind(addr)?;
|
|
||||||
quinn::Endpoint::new(
|
|
||||||
default_endpoint_config(),
|
|
||||||
Some(config),
|
|
||||||
socket,
|
|
||||||
Arc::new(quinn::TokioRuntime),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use crate::error::Result;
|
|
||||||
use crate::secret::{Secret, Secrets};
|
|
||||||
use crate::suite::CipherSuite;
|
|
||||||
use bytes::BytesMut;
|
|
||||||
use hex_literal::hex;
|
|
||||||
use quinn_proto::crypto::PacketKey;
|
|
||||||
use quinn_proto::{ConnectionId, Side};
|
|
||||||
|
|
||||||
/// Copied from quiche.
|
|
||||||
#[test]
|
|
||||||
fn test_initial_keys_v1() -> Result<()> {
|
|
||||||
let dcid: &[u8] = &hex!("8394c8f03e515708");
|
|
||||||
let version = QuicVersion::V1;
|
|
||||||
let suite = CipherSuite::aes128_gcm_sha256();
|
|
||||||
|
|
||||||
let s = Secrets::initial(version, &ConnectionId::new(dcid), Side::Client)?;
|
|
||||||
|
|
||||||
let expected_enc_key: &[u8] = &hex!("1f369613dd76d5467730efcbe3b1a22d");
|
|
||||||
assert_eq!(
|
|
||||||
s.local.packet_key(version, suite)?.key().slice(),
|
|
||||||
expected_enc_key
|
|
||||||
);
|
|
||||||
let expected_enc_iv: &[u8] = &hex!("fa044b2f42a3fd3b46fb255c");
|
|
||||||
assert_eq!(
|
|
||||||
s.local.packet_key(version, suite)?.iv().slice(),
|
|
||||||
expected_enc_iv
|
|
||||||
);
|
|
||||||
let expected_enc_hdr_key: &[u8] = &hex!("9f50449e04a0e810283a1e9933adedd2");
|
|
||||||
assert_eq!(
|
|
||||||
s.local.header_key(version, suite)?.key().slice(),
|
|
||||||
expected_enc_hdr_key
|
|
||||||
);
|
|
||||||
let expected_dec_key: &[u8] = &hex!("cf3a5331653c364c88f0f379b6067e37");
|
|
||||||
assert_eq!(
|
|
||||||
s.remote.packet_key(version, suite)?.key().slice(),
|
|
||||||
expected_dec_key
|
|
||||||
);
|
|
||||||
let expected_dec_iv: &[u8] = &hex!("0ac1493ca1905853b0bba03e");
|
|
||||||
assert_eq!(
|
|
||||||
s.remote.packet_key(version, suite)?.iv().slice(),
|
|
||||||
expected_dec_iv
|
|
||||||
);
|
|
||||||
let expected_dec_hdr_key: &[u8] = &hex!("c206b8d9b9f0f37644430b490eeaa314");
|
|
||||||
assert_eq!(
|
|
||||||
s.remote.header_key(version, suite)?.key().slice(),
|
|
||||||
expected_dec_hdr_key
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copied from rustls.
|
|
||||||
#[test]
|
|
||||||
fn short_packet_header_protection() {
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc9001.html#name-chacha20-poly1305-short-hea
|
|
||||||
|
|
||||||
const PN: u64 = 654360564;
|
|
||||||
const SECRET: &[u8] =
|
|
||||||
&hex!("9ac312a7f877468ebe69422748ad00a15443f18203a07d6060f688f30f21632b");
|
|
||||||
|
|
||||||
let version = QuicVersion::V1;
|
|
||||||
let suite = CipherSuite::chacha20_poly1305_sha256();
|
|
||||||
|
|
||||||
let secret = Secret::from(SECRET);
|
|
||||||
let hpk = secret
|
|
||||||
.header_key(version, suite)
|
|
||||||
.unwrap()
|
|
||||||
.as_crypto()
|
|
||||||
.unwrap();
|
|
||||||
let packet = secret.packet_key(version, suite).unwrap();
|
|
||||||
|
|
||||||
const PLAIN: &[u8] = &[0x42, 0x00, 0xbf, 0xf4, b'h', b'e', b'l', b'l', b'o'];
|
|
||||||
|
|
||||||
let mut buf = PLAIN.to_vec();
|
|
||||||
// Make space for the output tag.
|
|
||||||
buf.extend_from_slice(&[0u8; 16]);
|
|
||||||
packet.encrypt(PN, &mut buf, 4);
|
|
||||||
|
|
||||||
let pn_offset = 1;
|
|
||||||
hpk.encrypt(pn_offset, &mut buf);
|
|
||||||
|
|
||||||
const PROTECTED: &[u8] = &hex!("593b46220c4d504a9f1857793356400fc4a784ee309dff98b2");
|
|
||||||
|
|
||||||
assert_eq!(&buf, PROTECTED);
|
|
||||||
|
|
||||||
hpk.decrypt(pn_offset, &mut buf);
|
|
||||||
|
|
||||||
let (header, payload_tag) = buf.split_at(4);
|
|
||||||
let mut payload_tag = BytesMut::from(payload_tag);
|
|
||||||
packet.decrypt(PN, header, &mut payload_tag).unwrap();
|
|
||||||
let plain = payload_tag.as_ref();
|
|
||||||
assert_eq!(plain, &PLAIN[4..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copied from rustls.
|
|
||||||
#[test]
|
|
||||||
fn key_update_test_vector() {
|
|
||||||
let version = QuicVersion::V1;
|
|
||||||
let suite = CipherSuite::aes128_gcm_sha256();
|
|
||||||
let mut secrets = Secrets {
|
|
||||||
version,
|
|
||||||
suite,
|
|
||||||
local: Secret::from(&hex!(
|
|
||||||
"b8767708f8772358a6ea9fc43e4add2c961b3f5287a6d1467ee0aeab33724dbf"
|
|
||||||
)),
|
|
||||||
remote: Secret::from(&hex!(
|
|
||||||
"42dc972140e0f2e39845b767613439dc6758ca43259b878506824eb1e438d855"
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
secrets.update().unwrap();
|
|
||||||
|
|
||||||
let expected = Secrets {
|
|
||||||
version,
|
|
||||||
suite,
|
|
||||||
local: Secret::from(&hex!(
|
|
||||||
"42cac8c91cd5eb40682e432edf2d2be9f41a52ca6b22d8e6cdb1e8aca9061fce"
|
|
||||||
)),
|
|
||||||
remote: Secret::from(&hex!(
|
|
||||||
"eb7f5e2a123f407db499e361cae590d4d992e14b7ace03c244e0422115b6d38a"
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(expected, secrets);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn client_encrypt_header() {
|
|
||||||
let dcid = ConnectionId::new(&hex!("06b858ec6f80452b"));
|
|
||||||
|
|
||||||
let secrets = Secrets::initial(QuicVersion::V1, &dcid, Side::Client).unwrap();
|
|
||||||
let client = secrets.keys().unwrap().as_crypto().unwrap();
|
|
||||||
|
|
||||||
// Client (encrypt)
|
|
||||||
let mut packet: [u8; 51] = hex!(
|
|
||||||
"c0000000010806b858ec6f80452b0000402100c8fb7ffd97230e38b70d86e7ff148afdf88fc21c4426c7d1cec79914c8785757"
|
|
||||||
);
|
|
||||||
let packet_number = 0;
|
|
||||||
let packet_number_pos = 18;
|
|
||||||
let header_len = 19;
|
|
||||||
|
|
||||||
// Encrypt the payload.
|
|
||||||
client
|
|
||||||
.packet
|
|
||||||
.local
|
|
||||||
.encrypt(packet_number, &mut packet, header_len);
|
|
||||||
let expected_after_packet_encrypt: [u8; 51] = hex!(
|
|
||||||
"c0000000010806b858ec6f80452b0000402100f60e77fa2f629f9921fae64125c5632cf769d801a4693af6b949af37c2c45399"
|
|
||||||
);
|
|
||||||
assert_eq!(packet, expected_after_packet_encrypt);
|
|
||||||
|
|
||||||
// Encrypt the header.
|
|
||||||
client.header.local.encrypt(packet_number_pos, &mut packet);
|
|
||||||
let expected_after_header_encrypt: [u8; 51] = hex!(
|
|
||||||
"cd000000010806b858ec6f80452b000040210bf60e77fa2f629f9921fae64125c5632cf769d801a4693af6b949af37c2c45399"
|
|
||||||
);
|
|
||||||
assert_eq!(packet, expected_after_header_encrypt);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn server_decrypt_header() {
|
|
||||||
let dcid = ConnectionId::new(&hex!("06b858ec6f80452b"));
|
|
||||||
let secrets = Secrets::initial(QuicVersion::V1, &dcid, Side::Server).unwrap();
|
|
||||||
let server = secrets.keys().unwrap().as_crypto().unwrap();
|
|
||||||
|
|
||||||
let mut packet = BytesMut::from(
|
|
||||||
&hex!(
|
|
||||||
"c8000000010806b858ec6f80452b00004021be3ef50807b84191a196f760a6dad1e9d1c430c48952cba0148250c21c0a6a70e1"
|
|
||||||
)[..],
|
|
||||||
);
|
|
||||||
let packet_number = 0;
|
|
||||||
let packet_number_pos = 18;
|
|
||||||
let header_len = 19;
|
|
||||||
|
|
||||||
// Decrypt the header.
|
|
||||||
server.header.remote.decrypt(packet_number_pos, &mut packet);
|
|
||||||
let expected_header: [u8; 19] = hex!("c0000000010806b858ec6f80452b0000402100");
|
|
||||||
assert_eq!(packet[..header_len], expected_header);
|
|
||||||
|
|
||||||
// Decrypt the payload.
|
|
||||||
let mut header = packet;
|
|
||||||
let mut packet = header.split_off(header_len);
|
|
||||||
server
|
|
||||||
.packet
|
|
||||||
.remote
|
|
||||||
.decrypt(packet_number, &header, &mut packet)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(packet[..], [0; 16]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
macro_rules! bounded_array {
|
|
||||||
{$(
|
|
||||||
$(#[$struct_docs:meta])*
|
|
||||||
$vis:vis struct $struct_name:ident($max_len:ident)
|
|
||||||
),*} => {
|
|
||||||
$(
|
|
||||||
$(#[$struct_docs])*
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
|
||||||
$vis struct $struct_name {
|
|
||||||
buf: [u8; Self::MAX_LEN],
|
|
||||||
len: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl $struct_name {
|
|
||||||
/// Maximum value allowed.
|
|
||||||
$vis const MAX_LEN: usize = $max_len;
|
|
||||||
|
|
||||||
/// Creates a new instance, taking ownership of the buffer.
|
|
||||||
#[inline]
|
|
||||||
$vis fn new(buf: [u8; Self::MAX_LEN], len: usize) -> Self {
|
|
||||||
Self { buf, len: len as _ }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new instance with an empty buffer of the given size.
|
|
||||||
#[inline]
|
|
||||||
$vis fn with_len(len: usize) -> Self {
|
|
||||||
Self::new([0u8; Self::MAX_LEN], len)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new instance, copying the buffer.
|
|
||||||
#[inline]
|
|
||||||
$vis fn from(input: &[u8]) -> Self {
|
|
||||||
assert!(input.len() <= Self::MAX_LEN);
|
|
||||||
let mut buf = [0u8; Self::MAX_LEN];
|
|
||||||
let len = input.len();
|
|
||||||
buf[..len].copy_from_slice(input);
|
|
||||||
|
|
||||||
Self::new(buf, len)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new instance with random contents.
|
|
||||||
#[inline]
|
|
||||||
$vis fn random() -> Self {
|
|
||||||
let mut buf = [0u8; Self::MAX_LEN];
|
|
||||||
rand::RngCore::fill_bytes(&mut rand::rng(), &mut buf);
|
|
||||||
Self::new(buf, Self::MAX_LEN)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new instance from the parsed hex string.
|
|
||||||
#[inline]
|
|
||||||
$vis fn parse_hex_string(
|
|
||||||
input: &str,
|
|
||||||
) -> crate::error::Result<Self> {
|
|
||||||
if input.len() % 2 != 0 {
|
|
||||||
return Err(crate::error::Error::invalid_input(
|
|
||||||
"hex string with odd length".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let out_len = input.len() / 2;
|
|
||||||
if out_len > Self::MAX_LEN {
|
|
||||||
return Err(crate::error::Error::invalid_input(
|
|
||||||
"hex string value exceeds buffer size".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut out = [0u8; Self::MAX_LEN];
|
|
||||||
|
|
||||||
let mut out_ix = 0;
|
|
||||||
let mut in_ix = 0;
|
|
||||||
while in_ix < input.len() {
|
|
||||||
let next_two_chars = &input[in_ix..in_ix + 2];
|
|
||||||
out[out_ix] = u8::from_str_radix(next_two_chars, 16).unwrap();
|
|
||||||
|
|
||||||
in_ix += 2;
|
|
||||||
out_ix += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok($struct_name {
|
|
||||||
buf: out,
|
|
||||||
len: out_len as _,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the length of the buffer.
|
|
||||||
#[inline]
|
|
||||||
$vis fn len(&self) -> usize {
|
|
||||||
self.len as _
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a slice of the buffer for its length.
|
|
||||||
#[inline]
|
|
||||||
$vis fn slice(&self) -> &[u8] {
|
|
||||||
&self.buf[..self.len as _]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a mutable slice of the buffer for its length.
|
|
||||||
#[inline]
|
|
||||||
$vis fn slice_mut(&mut self) -> &mut [u8] {
|
|
||||||
&mut self.buf[..self.len as _]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a raw pointer to the buffer.
|
|
||||||
#[inline]
|
|
||||||
$vis fn as_ptr(&self) -> *const u8 {
|
|
||||||
self.buf.as_ptr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for $struct_name {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(f, "{:02x?}", self.slice())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) use bounded_array;
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
use crate::key::{AeadKey, Key, Nonce};
|
|
||||||
use crate::suite::CipherSuite;
|
|
||||||
use crate::{aead, QuicVersion};
|
|
||||||
use quinn_proto::ConnectionId;
|
|
||||||
|
|
||||||
const TAG_LEN: usize = aead::AES_GCM_TAG_LEN;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn retry_tag(
|
|
||||||
version: &QuicVersion,
|
|
||||||
orig_dst_cid: &ConnectionId,
|
|
||||||
packet: &[u8],
|
|
||||||
) -> [u8; TAG_LEN] {
|
|
||||||
let suite = CipherSuite::aes128_gcm_sha256();
|
|
||||||
let key = Key::from(version.retry_integrity_key());
|
|
||||||
let nonce = Nonce::from(version.retry_integrity_nonce());
|
|
||||||
let key = AeadKey::new(suite, key).unwrap();
|
|
||||||
|
|
||||||
let mut pseudo_packet = Vec::with_capacity(packet.len() + orig_dst_cid.len() + 1);
|
|
||||||
pseudo_packet.push(orig_dst_cid.len() as u8);
|
|
||||||
pseudo_packet.extend_from_slice(orig_dst_cid);
|
|
||||||
pseudo_packet.extend_from_slice(packet);
|
|
||||||
|
|
||||||
// Encrypt using the packet as additional data.
|
|
||||||
let mut encrypted = Vec::from(&[0; TAG_LEN][..]);
|
|
||||||
key.seal_in_place(&nonce, &mut encrypted, &pseudo_packet)
|
|
||||||
.unwrap();
|
|
||||||
let tag_start = encrypted.len() - TAG_LEN;
|
|
||||||
|
|
||||||
// Now extract the tag that was written.
|
|
||||||
let mut tag = [0; TAG_LEN];
|
|
||||||
tag.copy_from_slice(&encrypted[tag_start..]);
|
|
||||||
tag
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn is_valid_retry(
|
|
||||||
version: &QuicVersion,
|
|
||||||
orig_dst_cid: &ConnectionId,
|
|
||||||
header: &[u8],
|
|
||||||
payload: &[u8],
|
|
||||||
) -> bool {
|
|
||||||
let tag_start = match payload.len().checked_sub(TAG_LEN) {
|
|
||||||
Some(x) => x,
|
|
||||||
None => return false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut pseudo_packet =
|
|
||||||
Vec::with_capacity(header.len() + payload.len() + orig_dst_cid.len() + 1);
|
|
||||||
pseudo_packet.push(orig_dst_cid.len() as u8);
|
|
||||||
pseudo_packet.extend_from_slice(orig_dst_cid);
|
|
||||||
pseudo_packet.extend_from_slice(header);
|
|
||||||
let tag_start = tag_start + pseudo_packet.len();
|
|
||||||
pseudo_packet.extend_from_slice(payload);
|
|
||||||
|
|
||||||
let suite = CipherSuite::aes128_gcm_sha256();
|
|
||||||
let key = Key::from(version.retry_integrity_key());
|
|
||||||
let nonce = Nonce::from(version.retry_integrity_nonce());
|
|
||||||
let key = AeadKey::new(suite, key).unwrap();
|
|
||||||
|
|
||||||
let (aad, tag) = pseudo_packet.split_at_mut(tag_start);
|
|
||||||
key.open_in_place(&nonce, tag, aad).is_ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use hex_literal::hex;
|
|
||||||
use quinn_proto::ConnectionId;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_valid_retry() {
|
|
||||||
let orig_dst_cid = ConnectionId::new(&hex!("e080ab63f82458c1fd4d64f66faa9216f3f8b481"));
|
|
||||||
let header = hex!("f0000000010884d5a4bdfc1811e108648f4abb039d0c0a");
|
|
||||||
let packet = hex!("e9088adb79f9"
|
|
||||||
"7eabc8b5c8e78f4cc23da7a9dfa43a48a9b2dedc00c3a928ce501e2067300f1be896c2bde90af634ea8a"
|
|
||||||
"7fd1bb7ffd7c5ba7087cdb8c2a060eb360017e850bf5d27b063eedffa9"
|
|
||||||
"dfcdb8ebb4499c60cd86a84a9b2a2adf");
|
|
||||||
assert!(is_valid_retry(
|
|
||||||
&QuicVersion::V1,
|
|
||||||
&orig_dst_cid,
|
|
||||||
&header,
|
|
||||||
&packet
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_retry_tag() {
|
|
||||||
let orig_dst_cid = ConnectionId::new(&hex!("e080ab63f82458c1fd4d64f66faa9216f3f8b481"));
|
|
||||||
let packet = hex!("f0000000010884d5a4bdfc1811e108648f4abb039d0c0ae9088adb79f9"
|
|
||||||
"7eabc8b5c8e78f4cc23da7a9dfa43a48a9b2dedc00c3a928ce501e2067300f1be896c2bde90af634ea8a"
|
|
||||||
"7fd1bb7ffd7c5ba7087cdb8c2a060eb360017e850bf5d27b063eedffa9");
|
|
||||||
let expected = hex!("dfcdb8ebb4499c60cd86a84a9b2a2adf");
|
|
||||||
|
|
||||||
let tag = retry_tag(&QuicVersion::V1, &orig_dst_cid, &packet);
|
|
||||||
assert_eq!(expected, tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,207 +0,0 @@
|
||||||
use crate::error::Result;
|
|
||||||
use crate::hkdf;
|
|
||||||
use crate::key::{HeaderKey, KeyPair, Keys, PacketKey};
|
|
||||||
use crate::macros::bounded_array;
|
|
||||||
use crate::suite::CipherSuite;
|
|
||||||
use crate::version::QuicVersion;
|
|
||||||
use quinn_proto::{ConnectionId, Side};
|
|
||||||
|
|
||||||
const MAX_SECRET_LEN: usize = hkdf::DIGEST_BLOCK_LEN;
|
|
||||||
|
|
||||||
bounded_array! {
|
|
||||||
/// A buffer that can fit the largest master secret.
|
|
||||||
pub(crate) struct Secret(MAX_SECRET_LEN)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Secret {
|
|
||||||
/// Performs an in-place key update.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn update(&mut self, version: QuicVersion, suite: &CipherSuite) -> Result<()> {
|
|
||||||
let out = &mut [0u8; Secret::MAX_LEN][..self.len()];
|
|
||||||
suite
|
|
||||||
.hkdf
|
|
||||||
.expand_label(self.slice(), version.key_update_label(), out)?;
|
|
||||||
self.slice_mut().copy_from_slice(out);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn header_key(
|
|
||||||
&self,
|
|
||||||
version: QuicVersion,
|
|
||||||
suite: &'static CipherSuite,
|
|
||||||
) -> Result<HeaderKey> {
|
|
||||||
HeaderKey::new(version, suite, self)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn packet_key(
|
|
||||||
&self,
|
|
||||||
version: QuicVersion,
|
|
||||||
suite: &'static CipherSuite,
|
|
||||||
) -> Result<PacketKey> {
|
|
||||||
PacketKey::new(version, suite, self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A secret pair for reading (decryption) and writing (encryption).
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub(crate) struct Secrets {
|
|
||||||
pub(crate) version: QuicVersion,
|
|
||||||
pub(crate) suite: &'static CipherSuite,
|
|
||||||
pub(crate) local: Secret,
|
|
||||||
pub(crate) remote: Secret,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Secrets {
|
|
||||||
/// Creates the Quic initial secrets.
|
|
||||||
/// See <https://datatracker.ietf.org/doc/html/rfc9001#name-initial-secrets>.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn initial(
|
|
||||||
version: QuicVersion,
|
|
||||||
dst_cid: &ConnectionId,
|
|
||||||
side: Side,
|
|
||||||
) -> Result<Secrets> {
|
|
||||||
// Initial secrets always use AES-128-GCM and SHA256.
|
|
||||||
let suite = CipherSuite::aes128_gcm_sha256();
|
|
||||||
|
|
||||||
// Generate the initial secret.
|
|
||||||
let salt = version.initial_salt();
|
|
||||||
let mut initial_secret = [0u8; Secret::MAX_LEN];
|
|
||||||
let initial_secret_len = suite.hkdf.extract(salt, dst_cid, &mut initial_secret)?;
|
|
||||||
let initial_secret = &initial_secret[..initial_secret_len];
|
|
||||||
|
|
||||||
// Use the appropriate secret labels for "this" side of the connection.
|
|
||||||
const CLIENT_LABEL: &[u8] = b"client in";
|
|
||||||
const SERVER_LABEL: &[u8] = b"server in";
|
|
||||||
let (local_label, remote_label) = match side {
|
|
||||||
Side::Client => (CLIENT_LABEL, SERVER_LABEL),
|
|
||||||
Side::Server => (SERVER_LABEL, CLIENT_LABEL),
|
|
||||||
};
|
|
||||||
|
|
||||||
let len = suite.hkdf.digest_size();
|
|
||||||
let mut local = Secret::with_len(len);
|
|
||||||
suite
|
|
||||||
.hkdf
|
|
||||||
.expand_label(initial_secret, local_label, local.slice_mut())?;
|
|
||||||
|
|
||||||
let mut remote = Secret::with_len(len);
|
|
||||||
suite
|
|
||||||
.hkdf
|
|
||||||
.expand_label(initial_secret, remote_label, remote.slice_mut())?;
|
|
||||||
|
|
||||||
Ok(Secrets {
|
|
||||||
version,
|
|
||||||
suite,
|
|
||||||
local,
|
|
||||||
remote,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn keys(&self) -> Result<Keys> {
|
|
||||||
Ok(Keys {
|
|
||||||
header: self.header_keys()?,
|
|
||||||
packet: self.packet_keys()?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn header_keys(&self) -> Result<KeyPair<HeaderKey>> {
|
|
||||||
Ok(KeyPair {
|
|
||||||
local: self.local.header_key(self.version, self.suite)?,
|
|
||||||
remote: self.remote.header_key(self.version, self.suite)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn packet_keys(&self) -> Result<KeyPair<PacketKey>> {
|
|
||||||
Ok(KeyPair {
|
|
||||||
local: self.local.packet_key(self.version, self.suite)?,
|
|
||||||
remote: self.remote.packet_key(self.version, self.suite)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn update(&mut self) -> Result<()> {
|
|
||||||
// Update the secrets.
|
|
||||||
self.local.update(self.version, self.suite)?;
|
|
||||||
self.remote.update(self.version, self.suite)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn next_packet_keys(&mut self) -> Result<KeyPair<PacketKey>> {
|
|
||||||
// Get the current keys.
|
|
||||||
let keys = self.packet_keys()?;
|
|
||||||
|
|
||||||
// Update the secrets.
|
|
||||||
self.update()?;
|
|
||||||
|
|
||||||
Ok(keys)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct SecretsBuilder {
|
|
||||||
pub(crate) version: QuicVersion,
|
|
||||||
pub(crate) suite: Option<&'static CipherSuite>,
|
|
||||||
pub(crate) local_secret: Option<Secret>,
|
|
||||||
pub(crate) remote_secret: Option<Secret>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SecretsBuilder {
|
|
||||||
pub(crate) fn new(version: QuicVersion) -> Self {
|
|
||||||
Self {
|
|
||||||
version,
|
|
||||||
suite: None,
|
|
||||||
local_secret: None,
|
|
||||||
remote_secret: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn set_suite(&mut self, suite: &'static CipherSuite) {
|
|
||||||
if let Some(prev) = self.suite {
|
|
||||||
// Make sure it doesn't change once set.
|
|
||||||
assert_eq!(prev, suite);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.suite = Some(suite)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn set_remote_secret(&mut self, secret: Secret) {
|
|
||||||
if let Some(prev) = &self.remote_secret {
|
|
||||||
// Make sure it doesn't change once set.
|
|
||||||
assert_eq!(*prev, secret);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.remote_secret = Some(secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn set_local_secret(&mut self, secret: Secret) {
|
|
||||||
if let Some(prev) = &self.local_secret {
|
|
||||||
// Make sure it doesn't change once set.
|
|
||||||
assert_eq!(*prev, secret);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.local_secret = Some(secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn build(&self) -> Option<Secrets> {
|
|
||||||
if let Some(suite) = self.suite {
|
|
||||||
if let Some(local) = self.local_secret {
|
|
||||||
if let Some(remote) = self.remote_secret {
|
|
||||||
return Some(Secrets {
|
|
||||||
version: self.version,
|
|
||||||
suite,
|
|
||||||
local,
|
|
||||||
remote,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,313 +0,0 @@
|
||||||
use crate::alpn::AlpnProtocols;
|
|
||||||
use crate::bffi_ext::QuicSsl;
|
|
||||||
use crate::error::{map_result, Result};
|
|
||||||
use crate::secret::Secrets;
|
|
||||||
use crate::session_state::{SessionState, QUIC_METHOD};
|
|
||||||
use crate::version::QuicVersion;
|
|
||||||
use crate::{retry, QuicSslContext};
|
|
||||||
use boring::ssl::{Ssl, SslContext, SslContextBuilder, SslMethod, SslVersion};
|
|
||||||
use boring_sys as bffi;
|
|
||||||
use bytes::{Bytes, BytesMut};
|
|
||||||
use foreign_types_shared::ForeignType;
|
|
||||||
use quinn_proto::{
|
|
||||||
crypto, transport_parameters::TransportParameters, ConnectionId, Side, TransportError,
|
|
||||||
};
|
|
||||||
use std::any::Any;
|
|
||||||
use std::ffi::{c_int, c_uint, c_void};
|
|
||||||
use std::result::Result as StdResult;
|
|
||||||
use std::slice;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::LazyLock;
|
|
||||||
|
|
||||||
/// Configuration for a server-side QUIC. Wraps around a BoringSSL [SslContext].
|
|
||||||
pub struct Config {
|
|
||||||
ctx: SslContext,
|
|
||||||
alpn_protocols: AlpnProtocols,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn new() -> Result<Self> {
|
|
||||||
let mut builder = SslContextBuilder::new(SslMethod::tls())?;
|
|
||||||
|
|
||||||
// QUIC requires TLS 1.3.
|
|
||||||
builder.set_min_proto_version(Some(SslVersion::TLS1_3))?;
|
|
||||||
builder.set_max_proto_version(Some(SslVersion::TLS1_3))?;
|
|
||||||
|
|
||||||
builder.set_default_verify_paths()?;
|
|
||||||
|
|
||||||
// We build the context early, since we are not allowed to further mutate the context
|
|
||||||
// in start_session.
|
|
||||||
let mut ctx = builder.build();
|
|
||||||
|
|
||||||
// Disable verification of the client by default.
|
|
||||||
ctx.verify_peer(false);
|
|
||||||
|
|
||||||
// By default, enable early data (used for 0-RTT).
|
|
||||||
ctx.enable_early_data(true);
|
|
||||||
|
|
||||||
// Configure default ALPN protocols accepted by the server.QUIC requires ALPN be
|
|
||||||
// configured (see https://www.rfc-editor.org/rfc/rfc9001.html#section-8.1).
|
|
||||||
ctx.set_alpn_select_cb(Some(Session::alpn_select_callback));
|
|
||||||
|
|
||||||
// Set the callback for receipt of the Server Name Indication (SNI) extension.
|
|
||||||
ctx.set_server_name_cb(Some(Session::server_name_callback));
|
|
||||||
|
|
||||||
// Set callbacks for the SessionState.
|
|
||||||
ctx.set_quic_method(&QUIC_METHOD)?;
|
|
||||||
ctx.set_info_callback(Some(SessionState::info_callback));
|
|
||||||
|
|
||||||
ctx.set_options(bffi::SSL_OP_CIPHER_SERVER_PREFERENCE as u32);
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
ctx,
|
|
||||||
alpn_protocols: AlpnProtocols::default(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the underlying [SslContext] backing all created sessions.
|
|
||||||
pub fn ctx(&self) -> &SslContext {
|
|
||||||
&self.ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the underlying [SslContext] backing all created sessions. Wherever possible use
|
|
||||||
/// the provided methods to modify settings rather than accessing this directly.
|
|
||||||
///
|
|
||||||
/// Care should be taken to avoid overriding required behavior. In particular, this
|
|
||||||
/// configuration will set callbacks for QUIC events, alpn selection, server name,
|
|
||||||
/// as well as info and key logging.
|
|
||||||
pub fn ctx_mut(&mut self) -> &mut SslContext {
|
|
||||||
&mut self.ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets whether or not the peer certificate should be verified. If `true`, any error
|
|
||||||
/// during verification will be fatal. If not called, verification of the client is
|
|
||||||
/// disabled by default.
|
|
||||||
pub fn verify_peer(&mut self, verify: bool) {
|
|
||||||
self.ctx.verify_peer(verify)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the ALPN protocols that will be accepted by the server. QUIC requires that
|
|
||||||
/// ALPN be used (see <https://www.rfc-editor.org/rfc/rfc9001.html#section-8.1>).
|
|
||||||
///
|
|
||||||
/// If this method is not called, the server will default to accepting "h3".
|
|
||||||
pub fn set_alpn(&mut self, alpn_protocols: &[Vec<u8>]) -> Result<()> {
|
|
||||||
self.alpn_protocols = alpn_protocols.into();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crypto::ServerConfig for Config {
|
|
||||||
fn initial_keys(
|
|
||||||
&self,
|
|
||||||
version: u32,
|
|
||||||
dst_cid: &ConnectionId,
|
|
||||||
) -> StdResult<crypto::Keys, crypto::UnsupportedVersion> {
|
|
||||||
let version = QuicVersion::parse(version)?;
|
|
||||||
let secrets = Secrets::initial(version, dst_cid, Side::Server).unwrap();
|
|
||||||
Ok(secrets.keys().unwrap().as_crypto().unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn retry_tag(&self, version: u32, orig_dst_cid: &ConnectionId, packet: &[u8]) -> [u8; 16] {
|
|
||||||
let version = QuicVersion::parse(version).unwrap();
|
|
||||||
retry::retry_tag(&version, orig_dst_cid, packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_session(
|
|
||||||
self: Arc<Self>,
|
|
||||||
version: u32,
|
|
||||||
params: &TransportParameters,
|
|
||||||
) -> Box<dyn crypto::Session> {
|
|
||||||
let version = QuicVersion::parse(version).unwrap();
|
|
||||||
Session::new(self, version, params).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static SESSION_INDEX: LazyLock<c_int> = LazyLock::new(|| unsafe {
|
|
||||||
bffi::SSL_get_ex_new_index(0, std::ptr::null_mut(), std::ptr::null_mut(), None, None)
|
|
||||||
});
|
|
||||||
|
|
||||||
/// The [crypto::Session] implementation for BoringSSL.
|
|
||||||
struct Session {
|
|
||||||
state: Box<SessionState>,
|
|
||||||
alpn: AlpnProtocols,
|
|
||||||
handshake_data_available: bool,
|
|
||||||
handshake_data_sent: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Session {
|
|
||||||
fn new(
|
|
||||||
cfg: Arc<Config>,
|
|
||||||
version: QuicVersion,
|
|
||||||
params: &TransportParameters,
|
|
||||||
) -> Result<Box<Self>> {
|
|
||||||
let mut ssl = Ssl::new(&cfg.ctx).unwrap();
|
|
||||||
|
|
||||||
// Configure the TLS extension based on the QUIC version used.
|
|
||||||
ssl.set_quic_use_legacy_codepoint(version.uses_legacy_extension());
|
|
||||||
|
|
||||||
// Configure the SSL to be a server.
|
|
||||||
ssl.set_accept_state();
|
|
||||||
|
|
||||||
// Set the transport parameters.
|
|
||||||
ssl.set_quic_transport_params(&encode_params(params))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Need to se
|
|
||||||
ssl.set_quic_early_data_context(b"quinn-boring").unwrap();
|
|
||||||
|
|
||||||
let mut session = Box::new(Self {
|
|
||||||
state: SessionState::new(ssl, Side::Server, version)?,
|
|
||||||
alpn: cfg.alpn_protocols.clone(),
|
|
||||||
handshake_data_available: false,
|
|
||||||
handshake_data_sent: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register the instance in SSL ex_data. This allows the static callbacks to
|
|
||||||
// reference the instance.
|
|
||||||
unsafe {
|
|
||||||
map_result(bffi::SSL_set_ex_data(
|
|
||||||
session.state.ssl.as_ptr(),
|
|
||||||
*SESSION_INDEX,
|
|
||||||
&mut *session as *mut Self as *mut _,
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(session)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Server-side only callback from BoringSSL to select the ALPN protocol.
|
|
||||||
#[inline]
|
|
||||||
fn on_alpn_select<'a>(&mut self, offered: &'a [u8]) -> Result<&'a [u8]> {
|
|
||||||
// Indicate that we now have handshake data available.
|
|
||||||
self.handshake_data_available = true;
|
|
||||||
|
|
||||||
self.alpn.select(offered)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Server-side only callback from BoringSSL indicating that the Server Name Indication (SNI)
|
|
||||||
/// extension in the client hello was successfully parsed.
|
|
||||||
#[inline]
|
|
||||||
fn on_server_name(&mut self, _: *mut c_int) -> c_int {
|
|
||||||
// Indicate that we now have handshake data available.
|
|
||||||
self.handshake_data_available = true;
|
|
||||||
|
|
||||||
// SSL_TLSEXT_ERR_OK causes the server_name extension to be acked in
|
|
||||||
// ServerHello.
|
|
||||||
bffi::SSL_TLSEXT_ERR_OK
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Raw callbacks from BoringSSL
|
|
||||||
impl Session {
|
|
||||||
#[inline]
|
|
||||||
fn get_instance(ssl: *const bffi::SSL) -> &'static mut Session {
|
|
||||||
unsafe {
|
|
||||||
let data = bffi::SSL_get_ex_data(ssl, *SESSION_INDEX);
|
|
||||||
if data.is_null() {
|
|
||||||
panic!("BUG: Session instance missing")
|
|
||||||
}
|
|
||||||
&mut *(data as *mut Session)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn alpn_select_callback(
|
|
||||||
ssl: *mut bffi::SSL,
|
|
||||||
out: *mut *const u8,
|
|
||||||
out_len: *mut u8,
|
|
||||||
in_: *const u8,
|
|
||||||
in_len: c_uint,
|
|
||||||
_: *mut c_void,
|
|
||||||
) -> c_int {
|
|
||||||
let inst = Self::get_instance(ssl);
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let protos = slice::from_raw_parts(in_, in_len as _);
|
|
||||||
match inst.on_alpn_select(protos) {
|
|
||||||
Ok(proto) => {
|
|
||||||
*out = proto.as_ptr() as _;
|
|
||||||
*out_len = proto.len() as _;
|
|
||||||
bffi::SSL_TLSEXT_ERR_OK
|
|
||||||
}
|
|
||||||
Err(_) => bffi::SSL_TLSEXT_ERR_ALERT_FATAL,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn server_name_callback(
|
|
||||||
ssl: *mut bffi::SSL,
|
|
||||||
out_alert: *mut c_int,
|
|
||||||
_: *mut c_void,
|
|
||||||
) -> c_int {
|
|
||||||
let inst = Self::get_instance(ssl);
|
|
||||||
inst.on_server_name(out_alert)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crypto::Session for Session {
|
|
||||||
fn initial_keys(&self, dcid: &ConnectionId, side: Side) -> crypto::Keys {
|
|
||||||
self.state.initial_keys(dcid, side)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handshake_data(&self) -> Option<Box<dyn Any>> {
|
|
||||||
self.state.handshake_data()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn peer_identity(&self) -> Option<Box<dyn Any>> {
|
|
||||||
self.state.peer_identity()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn early_crypto(&self) -> Option<(Box<dyn crypto::HeaderKey>, Box<dyn crypto::PacketKey>)> {
|
|
||||||
self.state.early_crypto()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn early_data_accepted(&self) -> Option<bool> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_handshaking(&self) -> bool {
|
|
||||||
self.state.is_handshaking()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_handshake(&mut self, plaintext: &[u8]) -> StdResult<bool, TransportError> {
|
|
||||||
self.state.read_handshake(plaintext)?;
|
|
||||||
|
|
||||||
// Only indicate that handshake data is available once.
|
|
||||||
if !self.handshake_data_sent && self.handshake_data_available {
|
|
||||||
self.handshake_data_sent = true;
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transport_parameters(&self) -> StdResult<Option<TransportParameters>, TransportError> {
|
|
||||||
self.state.transport_parameters()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_handshake(&mut self, buf: &mut Vec<u8>) -> Option<crypto::Keys> {
|
|
||||||
self.state.write_handshake(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_1rtt_keys(&mut self) -> Option<crypto::KeyPair<Box<dyn crypto::PacketKey>>> {
|
|
||||||
self.state.next_1rtt_keys()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_valid_retry(&self, orig_dst_cid: &ConnectionId, header: &[u8], payload: &[u8]) -> bool {
|
|
||||||
self.state.is_valid_retry(orig_dst_cid, header, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn export_keying_material(
|
|
||||||
&self,
|
|
||||||
output: &mut [u8],
|
|
||||||
label: &[u8],
|
|
||||||
context: &[u8],
|
|
||||||
) -> StdResult<(), crypto::ExportKeyingMaterialError> {
|
|
||||||
self.state.export_keying_material(output, label, context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn encode_params(params: &TransportParameters) -> Bytes {
|
|
||||||
let mut out = BytesMut::with_capacity(128);
|
|
||||||
params.write(&mut out);
|
|
||||||
out.freeze()
|
|
||||||
}
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
use crate::error::Result;
|
|
||||||
use crate::{Error, QuicSslSession};
|
|
||||||
use boring::ssl::{SslContextRef, SslSession};
|
|
||||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
|
||||||
use lru::LruCache;
|
|
||||||
use quinn_proto::{transport_parameters::TransportParameters, Side};
|
|
||||||
use std::num::NonZeroUsize;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
|
|
||||||
/// A client-side Session cache for the BoringSSL crypto provider.
|
|
||||||
pub trait SessionCache: Send + Sync {
|
|
||||||
/// Adds the given value to the session cache.
|
|
||||||
fn put(&self, key: Bytes, value: Bytes);
|
|
||||||
|
|
||||||
/// Returns the cached session, if it exists.
|
|
||||||
fn get(&self, key: Bytes) -> Option<Bytes>;
|
|
||||||
|
|
||||||
/// Removes the cached session, if it exists.
|
|
||||||
fn remove(&self, key: Bytes);
|
|
||||||
|
|
||||||
/// Removes all entries from the cache.
|
|
||||||
fn clear(&self);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A utility for combining an [SslSession] and server [TransportParameters] as a
|
|
||||||
/// [SessionCache] entry.
|
|
||||||
pub struct Entry {
|
|
||||||
pub session: SslSession,
|
|
||||||
pub params: TransportParameters,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Entry {
|
|
||||||
/// Encodes this [Entry] into a [SessionCache] value.
|
|
||||||
pub fn encode(&self) -> Result<Bytes> {
|
|
||||||
let mut out = BytesMut::with_capacity(2048);
|
|
||||||
|
|
||||||
// Split the buffer in two: the length prefix buffer and the encoded session buffer.
|
|
||||||
// This will be O(1) as both will refer to the same underlying buffer.
|
|
||||||
let mut encoded = out.split_off(8);
|
|
||||||
|
|
||||||
// Store the session in the second buffer.
|
|
||||||
self.session.encode(&mut encoded)?;
|
|
||||||
|
|
||||||
// Go back and write the length to the first buffer.
|
|
||||||
out.put_u64(encoded.len() as u64);
|
|
||||||
|
|
||||||
// Unsplit to merge the two buffers back together. This will be O(1) since
|
|
||||||
// the buffers are already contiguous in memory.
|
|
||||||
out.unsplit(encoded);
|
|
||||||
|
|
||||||
// Now add the transport parameters.
|
|
||||||
out.reserve(128);
|
|
||||||
let mut encoded = out.split_off(out.len() + 8);
|
|
||||||
self.params.write(&mut encoded);
|
|
||||||
out.put_u64(encoded.len() as u64);
|
|
||||||
out.unsplit(encoded);
|
|
||||||
|
|
||||||
Ok(out.freeze())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decodes a [SessionCache] value into an [Entry].
|
|
||||||
pub fn decode(ctx: &SslContextRef, mut encoded: Bytes) -> Result<Self> {
|
|
||||||
// Decode the session.
|
|
||||||
let len = encoded.get_u64() as usize;
|
|
||||||
let mut encoded_session = encoded.split_to(len);
|
|
||||||
let session = SslSession::decode(ctx, &mut encoded_session)?;
|
|
||||||
|
|
||||||
// Decode the transport parameters.
|
|
||||||
let len = encoded.get_u64() as usize;
|
|
||||||
let mut encoded_params = encoded.split_to(len);
|
|
||||||
let params = TransportParameters::read(Side::Client, &mut encoded_params).map_err(|e| {
|
|
||||||
Error::invalid_input(format!("failed parsing cached transport parameters: {e:?}"))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(Self { session, params })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A [SessionCache] implementation that will never cache anything. Requires no storage.
|
|
||||||
pub struct NoSessionCache;
|
|
||||||
|
|
||||||
impl SessionCache for NoSessionCache {
|
|
||||||
fn put(&self, _: Bytes, _: Bytes) {}
|
|
||||||
|
|
||||||
fn get(&self, _: Bytes) -> Option<Bytes> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove(&self, _: Bytes) {}
|
|
||||||
|
|
||||||
fn clear(&self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SimpleCache {
|
|
||||||
cache: Mutex<LruCache<Bytes, Bytes>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SimpleCache {
|
|
||||||
pub fn new(num_entries: usize) -> Self {
|
|
||||||
SimpleCache {
|
|
||||||
cache: Mutex::new(LruCache::new(NonZeroUsize::new(num_entries).unwrap())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SessionCache for SimpleCache {
|
|
||||||
fn put(&self, key: Bytes, value: Bytes) {
|
|
||||||
let _ = self.cache.lock().unwrap().put(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get(&self, key: Bytes) -> Option<Bytes> {
|
|
||||||
self.cache.lock().unwrap().get(&key).cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove(&self, key: Bytes) {
|
|
||||||
let _ = self.cache.lock().unwrap().pop(&key);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear(&self) {
|
|
||||||
self.cache.lock().unwrap().clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,552 +0,0 @@
|
||||||
use crate::alert::Alert;
|
|
||||||
use crate::error::{map_cb_result, map_result, Result};
|
|
||||||
use crate::secret::{Secret, Secrets, SecretsBuilder};
|
|
||||||
use crate::suite::CipherSuite;
|
|
||||||
use crate::{retry, Error, HandshakeData, Level, QuicSsl, QuicVersion, SslError};
|
|
||||||
use boring::error::ErrorStack;
|
|
||||||
use boring::ssl::{NameType, Ssl};
|
|
||||||
use boring_sys as bffi;
|
|
||||||
use bytes::{Buf, BytesMut};
|
|
||||||
use foreign_types_shared::ForeignType;
|
|
||||||
use quinn_proto::{
|
|
||||||
crypto, transport_parameters::TransportParameters, ConnectionId, Side, TransportError,
|
|
||||||
};
|
|
||||||
use std::any::Any;
|
|
||||||
use std::ffi::c_int;
|
|
||||||
use std::io::Cursor;
|
|
||||||
use std::result::Result as StdResult;
|
|
||||||
use std::slice;
|
|
||||||
use std::sync::LazyLock;
|
|
||||||
use tracing::{error, trace, warn};
|
|
||||||
|
|
||||||
pub(crate) static QUIC_METHOD: bffi::SSL_QUIC_METHOD = bffi::SSL_QUIC_METHOD {
|
|
||||||
set_read_secret: Some(SessionState::set_read_secret_callback),
|
|
||||||
set_write_secret: Some(SessionState::set_write_secret_callback),
|
|
||||||
add_handshake_data: Some(SessionState::add_handshake_data_callback),
|
|
||||||
flush_flight: Some(SessionState::flush_flight_callback),
|
|
||||||
send_alert: Some(SessionState::send_alert_callback),
|
|
||||||
};
|
|
||||||
|
|
||||||
static SESSION_INDEX: LazyLock<c_int> = LazyLock::new(|| unsafe {
|
|
||||||
bffi::SSL_get_ex_new_index(0, std::ptr::null_mut(), std::ptr::null_mut(), None, None)
|
|
||||||
});
|
|
||||||
|
|
||||||
pub(crate) struct SessionState {
|
|
||||||
pub(crate) ssl: Ssl,
|
|
||||||
pub(crate) version: QuicVersion,
|
|
||||||
|
|
||||||
/// Indicates that early data was rejected in the last call to [Self::read_handshake].
|
|
||||||
pub(crate) early_data_rejected: bool,
|
|
||||||
|
|
||||||
side: Side,
|
|
||||||
alert: Option<TransportError>,
|
|
||||||
next_secrets: Option<Secrets>,
|
|
||||||
keys_updated: bool,
|
|
||||||
read_level: Level,
|
|
||||||
write_level: Level,
|
|
||||||
levels: [LevelState; Level::NUM_LEVELS],
|
|
||||||
handshaking: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SessionState {
|
|
||||||
pub(crate) fn new(ssl: Ssl, side: Side, version: QuicVersion) -> Result<Box<Self>> {
|
|
||||||
let levels = [
|
|
||||||
LevelState::new(version, Level::Initial, &ssl),
|
|
||||||
LevelState::new(version, Level::EarlyData, &ssl),
|
|
||||||
LevelState::new(version, Level::Handshake, &ssl),
|
|
||||||
LevelState::new(version, Level::Application, &ssl),
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut state = Box::new(Self {
|
|
||||||
ssl,
|
|
||||||
version,
|
|
||||||
side,
|
|
||||||
alert: None,
|
|
||||||
next_secrets: None,
|
|
||||||
keys_updated: false,
|
|
||||||
read_level: Level::Initial,
|
|
||||||
write_level: Level::Initial,
|
|
||||||
levels,
|
|
||||||
early_data_rejected: false,
|
|
||||||
handshaking: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Registers this instance as ex data on the underlying Ssl in order to support
|
|
||||||
// BoringSSL callbacks to this instance.
|
|
||||||
unsafe {
|
|
||||||
map_result(bffi::SSL_set_ex_data(
|
|
||||||
state.ssl.as_ptr(),
|
|
||||||
*SESSION_INDEX,
|
|
||||||
&mut *state as *mut Self as *mut _,
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn level_state(&self, level: Level) -> &LevelState {
|
|
||||||
&self.levels[level as usize]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn level_state_mut(&mut self, level: Level) -> &mut LevelState {
|
|
||||||
&mut self.levels[level as usize]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn is_handshaking(&self) -> bool {
|
|
||||||
self.handshaking
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn handshake_data(&self) -> Option<Box<dyn Any>> {
|
|
||||||
let sni_name = if self.side.is_server() {
|
|
||||||
self.ssl
|
|
||||||
.servername(NameType::HOST_NAME)
|
|
||||||
.map(|server_name| server_name.to_string())
|
|
||||||
} else {
|
|
||||||
// Server name does not apply to the client.
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let alpn_protocol = self.ssl.selected_alpn_protocol().map(Vec::from);
|
|
||||||
|
|
||||||
if sni_name.is_none() && alpn_protocol.is_none() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(Box::new(HandshakeData {
|
|
||||||
protocol: alpn_protocol,
|
|
||||||
server_name: sni_name,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn next_1rtt_keys(&mut self) -> Option<crypto::KeyPair<Box<dyn crypto::PacketKey>>> {
|
|
||||||
self.next_secrets
|
|
||||||
.as_mut()
|
|
||||||
.map(|secrets| secrets.next_packet_keys().unwrap().as_crypto().unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn transport_parameters(
|
|
||||||
&self,
|
|
||||||
) -> StdResult<Option<TransportParameters>, TransportError> {
|
|
||||||
match self.ssl.get_peer_quic_transport_params() {
|
|
||||||
Some(params) => {
|
|
||||||
let params = TransportParameters::read(self.side, &mut Cursor::new(params))
|
|
||||||
.map_err(|e| TransportError {
|
|
||||||
code: Alert::handshake_failure().into(),
|
|
||||||
frame: None,
|
|
||||||
reason: format!("failed parsing transport params: {e:?}"),
|
|
||||||
})?;
|
|
||||||
Ok(Some(params))
|
|
||||||
}
|
|
||||||
None => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn read_handshake(&mut self, plaintext: &[u8]) -> StdResult<(), TransportError> {
|
|
||||||
let ssl_err = self.ssl.provide_quic_data(self.read_level, plaintext);
|
|
||||||
self.check_alert()?;
|
|
||||||
self.check_ssl_error(ssl_err)?;
|
|
||||||
|
|
||||||
self.advance_handshake()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn write_handshake(&mut self, buf: &mut Vec<u8>) -> Option<crypto::Keys> {
|
|
||||||
// Write all available data at the current write level.
|
|
||||||
let write_state = self.level_state_mut(self.write_level);
|
|
||||||
if write_state.write_buffer.has_remaining() {
|
|
||||||
buf.extend_from_slice(&write_state.write_buffer);
|
|
||||||
write_state.write_buffer.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Advance to the next write level.
|
|
||||||
let ssl_engine_write_level = self.ssl.quic_write_level();
|
|
||||||
let next_write_level = self.write_level.next();
|
|
||||||
if next_write_level != self.write_level && next_write_level <= ssl_engine_write_level {
|
|
||||||
self.write_level = next_write_level;
|
|
||||||
|
|
||||||
// Indicate that we're updating the keys.
|
|
||||||
self.keys_updated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let out = if self.keys_updated {
|
|
||||||
self.keys_updated = false;
|
|
||||||
|
|
||||||
if self.next_secrets.is_some() {
|
|
||||||
// Once we've returned the application secrets, stop sending key updates.
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
// Determine if we're transitioning to the application-level keys.
|
|
||||||
let is_app = self.write_level == Level::Application;
|
|
||||||
|
|
||||||
// Build the secrets.
|
|
||||||
let secrets = self
|
|
||||||
.level_state(self.write_level)
|
|
||||||
.builder
|
|
||||||
.build()
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
panic!("failed building secrets for level {:?}", self.write_level)
|
|
||||||
});
|
|
||||||
|
|
||||||
if is_app {
|
|
||||||
// We've transitioned to the application level, we need to set the
|
|
||||||
// next (i.e. application) secrets for use from next_1rtt_keys.
|
|
||||||
|
|
||||||
// Copy the secrets and advance them to the next application secrets.
|
|
||||||
let mut next_app_secrets = secrets;
|
|
||||||
next_app_secrets.update().unwrap();
|
|
||||||
|
|
||||||
self.next_secrets = Some(next_app_secrets);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(secrets.keys().unwrap())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
out.map(|keys| keys.as_crypto().unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn is_valid_retry(
|
|
||||||
&self,
|
|
||||||
orig_dst_cid: &ConnectionId,
|
|
||||||
header: &[u8],
|
|
||||||
payload: &[u8],
|
|
||||||
) -> bool {
|
|
||||||
retry::is_valid_retry(&self.version, orig_dst_cid, header, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn peer_identity(&self) -> Option<Box<dyn Any>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn early_crypto(
|
|
||||||
&self,
|
|
||||||
) -> Option<(Box<dyn crypto::HeaderKey>, Box<dyn crypto::PacketKey>)> {
|
|
||||||
let builder = &self.level_state(Level::EarlyData).builder;
|
|
||||||
let version = builder.version;
|
|
||||||
let suite = builder.suite?;
|
|
||||||
let early_secret = match self.side {
|
|
||||||
Side::Client => builder.local_secret?,
|
|
||||||
Side::Server => builder.remote_secret?,
|
|
||||||
};
|
|
||||||
let header_key = early_secret
|
|
||||||
.header_key(version, suite)
|
|
||||||
.unwrap()
|
|
||||||
.as_crypto()
|
|
||||||
.unwrap();
|
|
||||||
let packet_key = Box::new(early_secret.packet_key(version, suite).unwrap());
|
|
||||||
|
|
||||||
Some((header_key, packet_key))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn initial_keys(&self, dcid: &ConnectionId, side: Side) -> crypto::Keys {
|
|
||||||
let secrets = Secrets::initial(self.version, dcid, side).unwrap();
|
|
||||||
secrets.keys().unwrap().as_crypto().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn export_keying_material(
|
|
||||||
&self,
|
|
||||||
output: &mut [u8],
|
|
||||||
label: &[u8],
|
|
||||||
context: &[u8],
|
|
||||||
) -> StdResult<(), crypto::ExportKeyingMaterialError> {
|
|
||||||
self.ssl
|
|
||||||
.export_keyring_material(output, label, context)
|
|
||||||
.map_err(|_| crypto::ExportKeyingMaterialError {})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn advance_handshake(&mut self) -> StdResult<(), TransportError> {
|
|
||||||
self.early_data_rejected = false;
|
|
||||||
|
|
||||||
if self.handshaking {
|
|
||||||
let rc = self.ssl.do_handshake();
|
|
||||||
|
|
||||||
// Update the state of the handshake.
|
|
||||||
self.handshaking = self.ssl.is_handshaking();
|
|
||||||
|
|
||||||
self.check_alert()?;
|
|
||||||
self.check_ssl_error(rc)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.handshaking {
|
|
||||||
let ssl_err = self.ssl.process_post_handshake();
|
|
||||||
self.check_alert()?;
|
|
||||||
return self.check_ssl_error(ssl_err);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn check_alert(&self) -> StdResult<(), TransportError> {
|
|
||||||
if let Some(alert) = &self.alert {
|
|
||||||
return Err(alert.clone());
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn check_ssl_error(&mut self, ssl_err: SslError) -> StdResult<(), TransportError> {
|
|
||||||
match ssl_err.value() {
|
|
||||||
bffi::SSL_ERROR_NONE => Ok(()),
|
|
||||||
bffi::SSL_ERROR_WANT_READ
|
|
||||||
| bffi::SSL_ERROR_WANT_WRITE
|
|
||||||
| bffi::SSL_ERROR_PENDING_SESSION
|
|
||||||
| bffi::SSL_ERROR_PENDING_CERTIFICATE
|
|
||||||
| bffi::SSL_ERROR_PENDING_TICKET
|
|
||||||
| bffi::SSL_ERROR_WANT_X509_LOOKUP
|
|
||||||
| bffi::SSL_ERROR_WANT_PRIVATE_KEY_OPERATION
|
|
||||||
| bffi::SSL_ERROR_WANT_CERTIFICATE_VERIFY => {
|
|
||||||
// Not an error - retry when we get more data from the peer.
|
|
||||||
trace!("SSL:{}", ssl_err.get_description());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
bffi::SSL_ERROR_EARLY_DATA_REJECTED => {
|
|
||||||
// Reset the state to allow retry with 1-RTT.
|
|
||||||
self.ssl.reset_early_rejected_data();
|
|
||||||
|
|
||||||
// Indicate that the early data has been rejected for the current handshake.
|
|
||||||
self.early_data_rejected = true;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// Everything else is fatal.
|
|
||||||
let reason = if ssl_err.value() == bffi::SSL_ERROR_SSL {
|
|
||||||
// Error occurred within the SSL library. Get details from the ErrorStack.
|
|
||||||
format!("{}: {:?}", ssl_err, ErrorStack::get())
|
|
||||||
} else {
|
|
||||||
format!("{ssl_err}")
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut err: TransportError = Alert::handshake_failure().into();
|
|
||||||
err.reason = reason;
|
|
||||||
Err(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoringSSL event handlers.
|
|
||||||
impl SessionState {
|
|
||||||
/// Callback from BoringSSL that configures the read secret and cipher suite for the given
|
|
||||||
/// encryption level. If an error is returned, the handshake is terminated with an error.
|
|
||||||
/// This function will be called at most once per encryption level.
|
|
||||||
#[inline]
|
|
||||||
fn on_set_read_secret(
|
|
||||||
&mut self,
|
|
||||||
level: Level,
|
|
||||||
suite: &'static CipherSuite,
|
|
||||||
secret: Secret,
|
|
||||||
) -> Result<()> {
|
|
||||||
// Store the secret.
|
|
||||||
let builder = &mut self.level_state_mut(level).builder;
|
|
||||||
builder.set_suite(suite);
|
|
||||||
builder.set_remote_secret(secret);
|
|
||||||
|
|
||||||
// Advance the currently active read level.
|
|
||||||
self.read_level = level;
|
|
||||||
|
|
||||||
// Indicate that the next call to write_handshake should generate new keys.
|
|
||||||
self.keys_updated = true;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Callback from BoringSSL that configures the write secret and cipher suite for the given
|
|
||||||
/// encryption level. If an error is returned, the handshake is terminated with an error.
|
|
||||||
/// This function will be called at most once per encryption level.
|
|
||||||
#[inline]
|
|
||||||
fn on_set_write_secret(
|
|
||||||
&mut self,
|
|
||||||
level: Level,
|
|
||||||
suite: &'static CipherSuite,
|
|
||||||
secret: Secret,
|
|
||||||
) -> Result<()> {
|
|
||||||
// Store the secret.
|
|
||||||
let builder = &mut self.level_state_mut(level).builder;
|
|
||||||
builder.set_suite(suite);
|
|
||||||
builder.set_local_secret(secret);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Callback from BoringSSL that adds handshake data to the current flight at the given
|
|
||||||
/// encryption level. If an error is returned, the handshake is terminated with an error.
|
|
||||||
#[inline]
|
|
||||||
fn on_add_handshake_data(&mut self, level: Level, data: &[u8]) -> Result<()> {
|
|
||||||
if level < self.write_level {
|
|
||||||
return Err(Error::other(format!(
|
|
||||||
"add_handshake_data for previous write level {level:?}"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure we don't exceed the buffer capacity for the level.
|
|
||||||
let state = self.level_state_mut(level);
|
|
||||||
if state.write_buffer.len() + data.len() > state.write_buffer.capacity() {
|
|
||||||
return Err(Error::other(format!(
|
|
||||||
"add_handshake_data exceeded buffer capacity for level {level:?}"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the message to the level.
|
|
||||||
state.write_buffer.extend_from_slice(data);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Callback from BoringSSL called when the current flight is complete and should be
|
|
||||||
/// written to the transport. Note a flight may contain data at several
|
|
||||||
/// encryption levels.
|
|
||||||
#[inline]
|
|
||||||
fn on_flush_flight(&mut self) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Callback from BoringSSL that sends a fatal alert at the specified encryption level.
|
|
||||||
#[inline]
|
|
||||||
fn on_send_alert(&mut self, _: Level, alert: Alert) -> Result<()> {
|
|
||||||
self.alert = Some(alert.into());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Callback from BoringSSL to handle (i.e. log) info events.
|
|
||||||
fn on_info(&self, type_: c_int, value: c_int) {
|
|
||||||
if type_ & bffi::SSL_CB_LOOP > 0 {
|
|
||||||
trace!("SSL:ACCEPT_LOOP:{}", self.ssl.state_string());
|
|
||||||
} else if type_ & bffi::SSL_CB_ALERT > 0 {
|
|
||||||
let prefix = if type_ & bffi::SSL_CB_READ > 0 {
|
|
||||||
"SSL:ALERT:READ:"
|
|
||||||
} else {
|
|
||||||
"SSL:ALERT:WRITE:"
|
|
||||||
};
|
|
||||||
|
|
||||||
if ((type_ & 0xF0) >> 8) == bffi::SSL3_AL_WARNING {
|
|
||||||
warn!("{}{}", prefix, self.ssl.state_string());
|
|
||||||
} else {
|
|
||||||
error!("{}{}", prefix, self.ssl.state_string());
|
|
||||||
}
|
|
||||||
} else if type_ & bffi::SSL_CB_EXIT > 0 {
|
|
||||||
if value == 1 {
|
|
||||||
trace!("SSL:ACCEPT_EXIT_OK:{}", self.ssl.state_string());
|
|
||||||
} else {
|
|
||||||
// Not necessarily an actual error. It could just require additional
|
|
||||||
// data from the other side.
|
|
||||||
trace!("SSL:ACCEPT_EXIT_FAIL:{}", self.ssl.state_string());
|
|
||||||
}
|
|
||||||
} else if type_ & bffi::SSL_CB_HANDSHAKE_START > 0 {
|
|
||||||
trace!("SSL:HANDSHAKE_START:{}", self.ssl.state_string());
|
|
||||||
} else if type_ & bffi::SSL_CB_HANDSHAKE_DONE > 0 {
|
|
||||||
trace!("SSL:HANDSHAKE_DONE:{}", self.ssl.state_string());
|
|
||||||
} else {
|
|
||||||
warn!(
|
|
||||||
"SSL:unknown event type {}:{}",
|
|
||||||
type_,
|
|
||||||
self.ssl.state_string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Raw callbacks from BoringSSL
|
|
||||||
impl SessionState {
|
|
||||||
/// Called by the static callbacks to retrieve the instance pointer.
|
|
||||||
#[inline]
|
|
||||||
fn get_instance(ssl: *const bffi::SSL) -> &'static mut SessionState {
|
|
||||||
unsafe {
|
|
||||||
let data = bffi::SSL_get_ex_data(ssl, *SESSION_INDEX);
|
|
||||||
if data.is_null() {
|
|
||||||
panic!("BUG: SessionState instance missing")
|
|
||||||
}
|
|
||||||
&mut *(data as *mut SessionState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn set_read_secret_callback(
|
|
||||||
ssl: *mut bffi::SSL,
|
|
||||||
level: bffi::ssl_encryption_level_t,
|
|
||||||
cipher: *const bffi::SSL_CIPHER,
|
|
||||||
secret: *const u8,
|
|
||||||
secret_len: usize,
|
|
||||||
) -> c_int {
|
|
||||||
let inst = Self::get_instance(ssl);
|
|
||||||
let level: Level = level.into();
|
|
||||||
let secret = unsafe { slice::from_raw_parts(secret, secret_len) };
|
|
||||||
let suite = CipherSuite::from_cipher(cipher).unwrap();
|
|
||||||
let secret = Secret::from(secret);
|
|
||||||
map_cb_result(inst.on_set_read_secret(level, suite, secret))
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn set_write_secret_callback(
|
|
||||||
ssl: *mut bffi::SSL,
|
|
||||||
level: bffi::ssl_encryption_level_t,
|
|
||||||
cipher: *const bffi::SSL_CIPHER,
|
|
||||||
secret: *const u8,
|
|
||||||
secret_len: usize,
|
|
||||||
) -> c_int {
|
|
||||||
let inst = Self::get_instance(ssl);
|
|
||||||
let level: Level = level.into();
|
|
||||||
let secret = unsafe { slice::from_raw_parts(secret, secret_len) };
|
|
||||||
let suite = CipherSuite::from_cipher(cipher).unwrap();
|
|
||||||
let secret = Secret::from(secret);
|
|
||||||
map_cb_result(inst.on_set_write_secret(level, suite, secret))
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn add_handshake_data_callback(
|
|
||||||
ssl: *mut bffi::SSL,
|
|
||||||
level: bffi::ssl_encryption_level_t,
|
|
||||||
data: *const u8,
|
|
||||||
len: usize,
|
|
||||||
) -> c_int {
|
|
||||||
let inst = Self::get_instance(ssl);
|
|
||||||
let level: Level = level.into();
|
|
||||||
let data = unsafe { slice::from_raw_parts(data, len) };
|
|
||||||
map_cb_result(inst.on_add_handshake_data(level, data))
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn flush_flight_callback(ssl: *mut bffi::SSL) -> c_int {
|
|
||||||
let inst = Self::get_instance(ssl);
|
|
||||||
map_cb_result(inst.on_flush_flight())
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn send_alert_callback(
|
|
||||||
ssl: *mut bffi::SSL,
|
|
||||||
level: bffi::ssl_encryption_level_t,
|
|
||||||
alert: u8,
|
|
||||||
) -> c_int {
|
|
||||||
let inst = Self::get_instance(ssl);
|
|
||||||
let level: Level = level.into();
|
|
||||||
map_cb_result(inst.on_send_alert(level, Alert::from(alert)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) extern "C" fn info_callback(ssl: *const bffi::SSL, type_: c_int, value: c_int) {
|
|
||||||
let inst = Self::get_instance(ssl);
|
|
||||||
inst.on_info(type_, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct LevelState {
|
|
||||||
pub(crate) builder: SecretsBuilder,
|
|
||||||
pub(crate) write_buffer: BytesMut,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LevelState {
|
|
||||||
#[inline]
|
|
||||||
fn new(version: QuicVersion, level: Level, ssl: &Ssl) -> Self {
|
|
||||||
let capacity = ssl.quic_max_handshake_flight_len(level);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
builder: SecretsBuilder::new(version),
|
|
||||||
write_buffer: BytesMut::with_capacity(capacity),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
use crate::aead::Aead;
|
|
||||||
use crate::error::{Error, Result};
|
|
||||||
use crate::hkdf::Hkdf;
|
|
||||||
use boring_sys as bffi;
|
|
||||||
use std::fmt::{Debug, Formatter};
|
|
||||||
use std::sync::LazyLock;
|
|
||||||
|
|
||||||
// For AEAD_AES_128_GCM and AEAD_AES_256_GCM ... endpoints that do not send
|
|
||||||
// packets larger than 2^11 bytes cannot protect more than 2^28 packets.
|
|
||||||
// https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-confidentiality-limit
|
|
||||||
const AES_CONFIDENTIALITY_LIMIT: u64 = 2u64.pow(28);
|
|
||||||
|
|
||||||
// For AEAD_CHACHA20_POLY1305, the confidentiality limit is greater than the
|
|
||||||
// number of possible packets (2^62) and so can be disregarded.
|
|
||||||
// https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-limits-on-aead-usage
|
|
||||||
const CHACHA20_POLY1305_CONFIDENTIALITY_LIMIT: u64 = u64::MAX;
|
|
||||||
|
|
||||||
// For AEAD_AES_128_GCM ... endpoints that do not attempt to remove
|
|
||||||
// protection from packets larger than 2^11 bytes can attempt to remove
|
|
||||||
// protection from at most 2^57 packets.
|
|
||||||
// For AEAD_AES_256_GCM [the limit] is substantially larger than the limit for
|
|
||||||
// AEAD_AES_128_GCM. However, this document recommends that the same limit be
|
|
||||||
// applied to both functions as either limit is acceptably large.
|
|
||||||
// https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-integrity-limit
|
|
||||||
const AES_INTEGRITY_LIMIT: u64 = 2u64.pow(57);
|
|
||||||
|
|
||||||
// For AEAD_CHACHA20_POLY1305, the integrity limit is 2^36 invalid packets.
|
|
||||||
// https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-limits-on-aead-usage
|
|
||||||
const CHACHA20_POLY1305_INTEGRITY_LIMIT: u64 = 2u64.pow(36);
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub(crate) enum ID {
|
|
||||||
Aes128GcmSha256,
|
|
||||||
Aes256GcmSha384,
|
|
||||||
Chacha20Poly1305Sha256,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Eq, PartialEq)]
|
|
||||||
pub(crate) struct CipherSuite {
|
|
||||||
pub(crate) id: ID,
|
|
||||||
pub(crate) hkdf: Hkdf,
|
|
||||||
pub(crate) aead: &'static Aead,
|
|
||||||
pub(crate) confidentiality_limit: u64,
|
|
||||||
pub(crate) integrity_limit: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for CipherSuite {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
Debug::fmt(&self.id, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static AES128_GCM_SHA256: LazyLock<CipherSuite> = LazyLock::new(|| CipherSuite {
|
|
||||||
id: ID::Aes128GcmSha256,
|
|
||||||
hkdf: Hkdf::sha256(),
|
|
||||||
aead: Aead::aes128_gcm(),
|
|
||||||
confidentiality_limit: AES_CONFIDENTIALITY_LIMIT,
|
|
||||||
integrity_limit: AES_INTEGRITY_LIMIT,
|
|
||||||
});
|
|
||||||
|
|
||||||
static AES256_GCM_SHA384: LazyLock<CipherSuite> = LazyLock::new(|| CipherSuite {
|
|
||||||
id: ID::Aes256GcmSha384,
|
|
||||||
hkdf: Hkdf::sha384(),
|
|
||||||
aead: Aead::aes256_gcm(),
|
|
||||||
confidentiality_limit: AES_CONFIDENTIALITY_LIMIT,
|
|
||||||
integrity_limit: AES_INTEGRITY_LIMIT,
|
|
||||||
});
|
|
||||||
|
|
||||||
static CHACHA20_POLY1305_SHA256: LazyLock<CipherSuite> = LazyLock::new(|| CipherSuite {
|
|
||||||
id: ID::Chacha20Poly1305Sha256,
|
|
||||||
hkdf: Hkdf::sha256(),
|
|
||||||
aead: Aead::chacha20_poly1305(),
|
|
||||||
confidentiality_limit: CHACHA20_POLY1305_CONFIDENTIALITY_LIMIT,
|
|
||||||
integrity_limit: CHACHA20_POLY1305_INTEGRITY_LIMIT,
|
|
||||||
});
|
|
||||||
|
|
||||||
unsafe impl Send for CipherSuite {}
|
|
||||||
unsafe impl Sync for CipherSuite {}
|
|
||||||
|
|
||||||
impl CipherSuite {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn aes128_gcm_sha256() -> &'static Self {
|
|
||||||
&AES128_GCM_SHA256
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn aes256_gcm_sha384() -> &'static Self {
|
|
||||||
&AES256_GCM_SHA384
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn chacha20_poly1305_sha256() -> &'static Self {
|
|
||||||
&CHACHA20_POLY1305_SHA256
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn from_cipher(cipher: *const bffi::SSL_CIPHER) -> Result<&'static Self> {
|
|
||||||
match unsafe { bffi::SSL_CIPHER_get_id(cipher) } as i32 {
|
|
||||||
bffi::TLS1_CK_AES_128_GCM_SHA256 => Ok(Self::aes128_gcm_sha256()),
|
|
||||||
bffi::TLS1_CK_AES_256_GCM_SHA384 => Ok(Self::aes256_gcm_sha384()),
|
|
||||||
bffi::TLS1_CK_CHACHA20_POLY1305_SHA256 => Ok(Self::chacha20_poly1305_sha256()),
|
|
||||||
id => Err(Error::invalid_input(format!("invalid cipher id: {id}"))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
use quinn_proto::crypto;
|
|
||||||
use std::result::Result as StdResult;
|
|
||||||
|
|
||||||
/// QUIC protocol version
|
|
||||||
///
|
|
||||||
/// Governs version-specific behavior in the TLS layer
|
|
||||||
// TODO: add support for draft version 2.
|
|
||||||
#[non_exhaustive]
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
pub enum QuicVersion {
|
|
||||||
V1Draft29,
|
|
||||||
V1Draft30,
|
|
||||||
V1Draft31,
|
|
||||||
V1Draft32,
|
|
||||||
V1Draft33,
|
|
||||||
V1Draft34,
|
|
||||||
|
|
||||||
/// First stable RFC version.
|
|
||||||
V1,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for QuicVersion {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::V1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QuicVersion {
|
|
||||||
const DRAFT_INDICATOR: u32 = 0xff00_0000;
|
|
||||||
const VERSION_1_DRAFT_29: u32 = Self::DRAFT_INDICATOR | 29;
|
|
||||||
const VERSION_1_DRAFT_30: u32 = Self::DRAFT_INDICATOR | 30;
|
|
||||||
const VERSION_1_DRAFT_31: u32 = Self::DRAFT_INDICATOR | 31;
|
|
||||||
const VERSION_1_DRAFT_32: u32 = Self::DRAFT_INDICATOR | 32;
|
|
||||||
const VERSION_1_DRAFT_33: u32 = Self::DRAFT_INDICATOR | 33;
|
|
||||||
const VERSION_1_DRAFT_34: u32 = Self::DRAFT_INDICATOR | 34;
|
|
||||||
const VERSION_1: u32 = 1;
|
|
||||||
|
|
||||||
/// Returns the default list of supported quic versions.
|
|
||||||
pub fn default_supported_versions() -> Vec<u32> {
|
|
||||||
let mut out = Vec::new();
|
|
||||||
for v in [
|
|
||||||
Self::V1,
|
|
||||||
Self::V1Draft34,
|
|
||||||
Self::V1Draft33,
|
|
||||||
Self::V1Draft32,
|
|
||||||
Self::V1Draft31,
|
|
||||||
Self::V1Draft30,
|
|
||||||
Self::V1Draft29,
|
|
||||||
] {
|
|
||||||
out.push(v.label());
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn parse(version: u32) -> StdResult<Self, crypto::UnsupportedVersion> {
|
|
||||||
match version {
|
|
||||||
Self::VERSION_1_DRAFT_29 => Ok(Self::V1Draft29),
|
|
||||||
Self::VERSION_1_DRAFT_30 => Ok(Self::V1Draft30),
|
|
||||||
Self::VERSION_1_DRAFT_31 => Ok(Self::V1Draft31),
|
|
||||||
Self::VERSION_1_DRAFT_32 => Ok(Self::V1Draft32),
|
|
||||||
Self::VERSION_1_DRAFT_33 => Ok(Self::V1Draft33),
|
|
||||||
Self::VERSION_1_DRAFT_34 => Ok(Self::V1Draft34),
|
|
||||||
Self::VERSION_1 => Ok(Self::V1),
|
|
||||||
_ => Err(crypto::UnsupportedVersion),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn label(&self) -> u32 {
|
|
||||||
match self {
|
|
||||||
Self::V1Draft29 => Self::VERSION_1_DRAFT_29,
|
|
||||||
Self::V1Draft30 => Self::VERSION_1_DRAFT_30,
|
|
||||||
Self::V1Draft31 => Self::VERSION_1_DRAFT_31,
|
|
||||||
Self::V1Draft32 => Self::VERSION_1_DRAFT_32,
|
|
||||||
Self::V1Draft33 => Self::VERSION_1_DRAFT_33,
|
|
||||||
Self::V1Draft34 => Self::VERSION_1_DRAFT_34,
|
|
||||||
Self::V1 => Self::VERSION_1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn initial_salt(&self) -> &'static [u8] {
|
|
||||||
match self {
|
|
||||||
Self::V1Draft29 | Self::V1Draft30 | Self::V1Draft31 | Self::V1Draft32 => &[
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#section-5.2
|
|
||||||
0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61,
|
|
||||||
0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99,
|
|
||||||
],
|
|
||||||
Self::V1Draft33 | Self::V1Draft34 | Self::V1 => &[
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc9001.html#name-initial-secrets
|
|
||||||
0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8,
|
|
||||||
0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a,
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn retry_integrity_key(&self) -> &'static [u8] {
|
|
||||||
match self {
|
|
||||||
Self::V1Draft29 | Self::V1Draft30 | Self::V1Draft31 | Self::V1Draft32 => &[
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#section-5.8
|
|
||||||
0xcc, 0xce, 0x18, 0x7e, 0xd0, 0x9a, 0x09, 0xd0, 0x57, 0x28, 0x15, 0x5a, 0x6c, 0xb9,
|
|
||||||
0x6b, 0xe1,
|
|
||||||
],
|
|
||||||
Self::V1Draft33 | Self::V1Draft34 | Self::V1 => &[
|
|
||||||
// https://datatracker.ietf.org/doc/html/rfc9001#name-retry-packet-integrity
|
|
||||||
0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a, 0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68,
|
|
||||||
0xc8, 0x4e,
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn retry_integrity_nonce(&self) -> &'static [u8] {
|
|
||||||
match self {
|
|
||||||
Self::V1Draft29 | Self::V1Draft30 | Self::V1Draft31 | Self::V1Draft32 => &[
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#section-5.8
|
|
||||||
0xe5, 0x49, 0x30, 0xf9, 0x7f, 0x21, 0x36, 0xf0, 0x53, 0x0a, 0x8c, 0x1c,
|
|
||||||
],
|
|
||||||
Self::V1Draft33 | Self::V1Draft34 | Self::V1 => &[
|
|
||||||
// https://datatracker.ietf.org/doc/html/rfc9001#name-retry-packet-integrity
|
|
||||||
0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb,
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Indicates whether this version uses the legacy TLS extension codepoint.
|
|
||||||
pub(crate) fn uses_legacy_extension(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::V1Draft29
|
|
||||||
| Self::V1Draft30
|
|
||||||
| Self::V1Draft31
|
|
||||||
| Self::V1Draft32
|
|
||||||
| Self::V1Draft33
|
|
||||||
| Self::V1Draft34 => true,
|
|
||||||
Self::V1 => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn key_label(&self) -> &'static [u8] {
|
|
||||||
b"quic key"
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn iv_label(&self) -> &'static [u8] {
|
|
||||||
b"quic iv"
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn header_key_label(&self) -> &'static [u8] {
|
|
||||||
b"quic hp"
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn key_update_label(&self) -> &'static [u8] {
|
|
||||||
b"quic ku"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -27,3 +27,7 @@ sleep 20
|
||||||
echo "=== Publishing tokio-boring... ==="
|
echo "=== Publishing tokio-boring... ==="
|
||||||
(cd tokio-boring && cargo publish)
|
(cd tokio-boring && cargo publish)
|
||||||
sleep 20
|
sleep 20
|
||||||
|
|
||||||
|
echo "=== Publishing hyper-boring... ==="
|
||||||
|
(cd hyper-boring && cargo publish)
|
||||||
|
sleep 20
|
||||||
|
|
@ -1,23 +1,30 @@
|
||||||
[package]
|
[package]
|
||||||
name = "tokio-boring2"
|
name = "tokio-boring"
|
||||||
version = { workspace = true }
|
version = { workspace = true }
|
||||||
authors = ["Alex Crichton <alex@alexcrichton.com>", "Ivan Nikulin <ifaaan@gmail.com>"]
|
authors = ["Alex Crichton <alex@alexcrichton.com>", "Ivan Nikulin <ifaaan@gmail.com>"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
repository = { workspace = true }
|
repository = { workspace = true }
|
||||||
homepage = "https://github.com/0x676e67/boring2"
|
homepage = "https://github.com/cloudflare/boring"
|
||||||
documentation = "https://docs.rs/tokio-boring2"
|
documentation = "https://docs.rs/tokio-boring"
|
||||||
description = """
|
description = """
|
||||||
An implementation of SSL streams for Tokio backed by BoringSSL
|
An implementation of SSL streams for Tokio backed by BoringSSL
|
||||||
"""
|
"""
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
|
features = ["rpk", "pq-experimental"]
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# Use a FIPS-validated version of boringssl.
|
# Use a FIPS-validated version of boringssl.
|
||||||
fips = ["boring/fips", "boring-sys/fips"]
|
fips = ["boring/fips", "boring-sys/fips"]
|
||||||
|
|
||||||
|
# Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/)
|
||||||
|
pq-experimental = ["boring/pq-experimental"]
|
||||||
|
|
||||||
|
# Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250)
|
||||||
|
rpk = ["boring/rpk"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
boring = { workspace = true }
|
boring = { workspace = true }
|
||||||
boring-sys = { workspace = true }
|
boring-sys = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# tokio-boring2
|
# tokio-boring
|
||||||
|
|
||||||
An implementation of SSL streams for Tokio built on top of the BoringSSL.
|
An implementation of SSL streams for Tokio built on top of the BoringSSL.
|
||||||
|
|
||||||
[Documentation](https://docs.rs/tokio-boring2)
|
[Documentation](https://docs.rs/tokio-boring)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ First, add this to your `Cargo.toml`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio-boring2 = "5.0.0"
|
tokio-boring = "1.0.0"
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, use either `accept` or `connect` as appropriate.
|
Then, use either `accept` or `connect` as appropriate.
|
||||||
|
|
@ -25,11 +25,11 @@ async fn main() -> anyhow::Result<()> {
|
||||||
let (tcp_stream, _addr) = listener.accept().await?;
|
let (tcp_stream, _addr) = listener.accept().await?;
|
||||||
|
|
||||||
let server = ssl::SslMethod::tls_server();
|
let server = ssl::SslMethod::tls_server();
|
||||||
let mut ssl_builder = boring2::ssl::SslAcceptor::mozilla_modern(server)?;
|
let mut ssl_builder = boring::ssl::SslAcceptor::mozilla_modern(server)?;
|
||||||
ssl_builder.set_default_verify_paths()?;
|
ssl_builder.set_default_verify_paths()?;
|
||||||
ssl_builder.set_verify(ssl::SslVerifyMode::PEER);
|
ssl_builder.set_verify(ssl::SslVerifyMode::PEER);
|
||||||
let acceptor = ssl_builder.build();
|
let acceptor = ssl_builder.build();
|
||||||
let _ssl_stream = tokio_boring2::accept(&acceptor, tcp_stream).await?;
|
let _ssl_stream = tokio_boring::accept(&acceptor, tcp_stream).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -40,10 +40,10 @@ negotiating the connection. Each TLS stream implements the `Read` and
|
||||||
ecosystem. Client connections initiated from this crate verify hostnames
|
ecosystem. Client connections initiated from this crate verify hostnames
|
||||||
automatically and by default.
|
automatically and by default.
|
||||||
|
|
||||||
`tokio-boring2` exports this ability through [`accept`] and [`connect`]. `accept` should
|
`tokio-boring` exports this ability through [`accept`] and [`connect`]. `accept` should
|
||||||
be used by servers, and `connect` by clients. These augment the functionality provided by the
|
be used by servers, and `connect` by clients. These augment the functionality provided by the
|
||||||
[`boring2`] crate, on which this crate is built. Configuration of TLS parameters is still
|
[`boring`] crate, on which this crate is built. Configuration of TLS parameters is still
|
||||||
primarily done through the [`boring2`] crate.
|
primarily done through the [`boring`] crate.
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use boring::ssl;
|
use boring::ssl;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use tokio_boring2 as tokio_boring;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,10 @@
|
||||||
//! ecosystem. Client connections initiated from this crate verify hostnames
|
//! ecosystem. Client connections initiated from this crate verify hostnames
|
||||||
//! automatically and by default.
|
//! automatically and by default.
|
||||||
//!
|
//!
|
||||||
//! `tokio-boring2` exports this ability through [`accept`] and [`connect`]. `accept` should
|
//! `tokio-boring` exports this ability through [`accept`] and [`connect`]. `accept` should
|
||||||
//! be used by servers, and `connect` by clients. These augment the functionality provided by the
|
//! be used by servers, and `connect` by clients. These augment the functionality provided by the
|
||||||
//! [`boring2`] crate, on which this crate is built. Configuration of TLS parameters is still
|
//! [`boring`] crate, on which this crate is built. Configuration of TLS parameters is still
|
||||||
//! primarily done through the [`boring2`] crate.
|
//! primarily done through the [`boring`] crate.
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use tokio_boring2 as tokio_boring;
|
|
||||||
|
|
||||||
use boring::ssl::{SslOptions, SslRef, SslSession, SslSessionCacheMode, SslVersion};
|
use boring::ssl::{SslOptions, SslRef, SslSession, SslSessionCacheMode, SslVersion};
|
||||||
use futures::future;
|
use futures::future;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use tokio_boring2 as tokio_boring;
|
|
||||||
|
|
||||||
use boring::hash::MessageDigest;
|
use boring::hash::MessageDigest;
|
||||||
use boring::pkey::PKey;
|
use boring::pkey::PKey;
|
||||||
use boring::rsa::Padding;
|
use boring::rsa::Padding;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use tokio_boring2 as tokio_boring;
|
|
||||||
|
|
||||||
use boring::ssl::ClientHello;
|
use boring::ssl::ClientHello;
|
||||||
use futures::future;
|
use futures::future;
|
||||||
use tokio::task::yield_now;
|
use tokio::task::yield_now;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use tokio_boring2 as tokio_boring;
|
|
||||||
|
|
||||||
use boring::ssl::{SslConnector, SslMethod};
|
use boring::ssl::{SslConnector, SslMethod};
|
||||||
use futures::future;
|
use futures::future;
|
||||||
use std::net::ToSocketAddrs;
|
use std::net::ToSocketAddrs;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ use std::pin::Pin;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
use tokio_boring::{HandshakeError, SslStream};
|
use tokio_boring::{HandshakeError, SslStream};
|
||||||
use tokio_boring2 as tokio_boring;
|
|
||||||
|
|
||||||
pub(crate) fn create_server(
|
pub(crate) fn create_server(
|
||||||
setup: impl FnOnce(&mut SslAcceptorBuilder),
|
setup: impl FnOnce(&mut SslAcceptorBuilder),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
mod test_rpk {
|
||||||
|
use boring::pkey::PKey;
|
||||||
|
use boring::ssl::{SslAcceptor, SslConnector};
|
||||||
|
use futures::future;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||||
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
use tokio_boring::{HandshakeError, SslStream};
|
||||||
|
|
||||||
|
fn create_server() -> (
|
||||||
|
impl Future<Output = Result<SslStream<TcpStream>, HandshakeError<TcpStream>>>,
|
||||||
|
SocketAddr,
|
||||||
|
) {
|
||||||
|
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
|
|
||||||
|
listener.set_nonblocking(true).unwrap();
|
||||||
|
|
||||||
|
let listener = TcpListener::from_std(listener).unwrap();
|
||||||
|
let addr = listener.local_addr().unwrap();
|
||||||
|
|
||||||
|
let server = async move {
|
||||||
|
let mut acceptor = SslAcceptor::rpk().unwrap();
|
||||||
|
let 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();
|
||||||
|
|
||||||
|
acceptor.set_rpk_certificate(&cert).unwrap();
|
||||||
|
acceptor.set_null_chain_private_key(&pkey).unwrap();
|
||||||
|
|
||||||
|
let acceptor = acceptor.build();
|
||||||
|
|
||||||
|
let stream = listener.accept().await.unwrap().0;
|
||||||
|
|
||||||
|
tokio_boring::accept(&acceptor, stream).await
|
||||||
|
};
|
||||||
|
|
||||||
|
(server, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue