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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ jobs:
path: target
key: clippy-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }}
- name: Run clippy
run: cargo clippy --all --all-targets --features rpk,mlkem
run: cargo clippy --all --all-targets --features rpk,prf,mlkem
- name: Check docs
run: cargo doc --no-deps -p boring -p boring-sys -p hyper-boring -p tokio-boring --features rpk,underscore-wildcards
env:
Expand Down Expand Up @@ -426,9 +426,9 @@ jobs:
shell: bash
- run: cargo check --no-default-features
name: Check `--no-default-features`
- run: cargo check --features mlkem,credential
- run: cargo check --features prf,mlkem,credential
name: Check `mlkem,credential`
- run: cargo test --features rpk
- run: cargo test --features rpk,prf
name: Run `rpk` tests
- run: cargo test --features underscore-wildcards
name: Run `underscore-wildcards` tests
Expand Down
27 changes: 27 additions & 0 deletions boring-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,30 @@ pub fn init() {
CRYPTO_library_init();
}
}

pub mod internal {
use super::EVP_MD;
use std::os::raw::c_int;

extern "C" {
/// Calculates `out_len` bytes of the TLS 1.2 PRF using `digest` and writes
/// them to `out`.
///
/// This symbol is exported by BoringSSL, but it is declared in an internal
/// header (`crypto/fipsmodule/tls/internal.h`) and is therefore not present
/// in generated bindgen output.
pub fn CRYPTO_tls1_prf(
digest: *const EVP_MD,
out: *mut u8,
out_len: usize,
secret: *const u8,
secret_len: usize,
label: *const u8,
label_len: usize,
seed1: *const u8,
seed1_len: usize,
seed2: *const u8,
seed2_len: usize,
) -> c_int;
}
}
3 changes: 3 additions & 0 deletions boring/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ pq-experimental = []
# Interface for ML-KEM (FIPS 203) post-quantum key encapsulation. Does not affect ciphers used in TLS.
mlkem = []

# Expose internal `tls1_prf`
prf = []

# SslCredential API
credential = []

Expand Down
2 changes: 2 additions & 0 deletions boring/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ pub mod nid;
pub mod pkcs12;
pub mod pkcs5;
pub mod pkey;
#[cfg(feature = "prf")]
pub mod prf;
pub mod rand;
pub mod rsa;
pub mod sha;
Expand Down
210 changes: 210 additions & 0 deletions boring/src/prf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
//! TLS pseudo-random function helpers.
//!
//! This module exposes the TLS 1.2 PRF as defined in RFC 5246, Section 5.
//!
//! # Example
//!
//! ```
//! use boring::hash::MessageDigest;
//!
//! let mut out = [0u8; 48];
//! boring::prf::tls1_prf(
//! MessageDigest::sha256(),
//! &mut out,
//! b"secret",
//! b"label",
//! b"seed",
//! )
//! .unwrap();
//! ```

use std::ptr;

use openssl_macros::corresponds;

use crate::cvt;
use crate::error::ErrorStack;
use crate::ffi;
use crate::hash::MessageDigest;

/// Computes TLS 1.2 PRF output using `digest`.
///
/// `seed` should contain the full PRF seed for the calling protocol. In TLS
/// 1.2 this is often a concatenation of random values.
#[corresponds(CRYPTO_tls1_prf)]
pub fn tls1_prf(
digest: MessageDigest,
out: &mut [u8],
secret: &[u8],
label: &[u8],
seed: &[u8],
) -> Result<(), ErrorStack> {
unsafe {
ffi::init();

cvt(ffi::internal::CRYPTO_tls1_prf(
digest.as_ptr(),
out.as_mut_ptr(),
out.len(),
secret.as_ptr(),
secret.len(),
label.as_ptr(),
label.len(),
seed.as_ptr(),
seed.len(),
ptr::null(),
0,
))
}
}

#[cfg(test)]
mod tests {
use hex::FromHex;

use crate::hash::MessageDigest;

#[test]
fn tls12_prf_vectors() {
struct TestVector {
name: &'static str,
digest: fn() -> MessageDigest,
secret: &'static str,
label: &'static [u8],
seed: &'static str,
expected: &'static str,
}

// Test vectors from:
// https://mailarchive.ietf.org/arch/msg/tls/fzVCzk-z3FShgGJ6DOXqM1ydxms/
let vectors = [
TestVector {
name: "TLS1.2 PRF-SHA224",
digest: MessageDigest::sha224,
secret: "e1 88 28 74 03 52 b5 30 d6 9b 34 c6 59 7d ea 2e",
label: b"test label",
seed: "f5 a3 fe 6d 34 e2 e2 85 60 fd ca f6 82 3f 90 91",
expected: "
22 4d 8a f3 c0 45 33 93
a9 77 97 89 d2 1c f7 da
5e e6 2a e6 b6 17 87 3d
48 94 28 ef c8 dd 58 d1
56 6e 70 29 e2 ca 3a 5e
cd 35 5d c6 4d 4d 92 7e
2f bd 78 c4 23 3e 86 04
b1 47 49 a7 7a 92 a7 0f
dd f6 14 bc 0d f6 23 d7
98 60 4e 4c a5 51 27 94
d8 02 a2 58 e8 2f 86 cf
",
},
TestVector {
name: "TLS1.2 PRF-SHA256",
digest: MessageDigest::sha256,
secret: "9b be 43 6b a9 40 f0 17 b1 76 52 84 9a 71 db 35",
label: b"test label",
seed: "a0 ba 9f 93 6c da 31 18 27 a6 f7 96 ff d5 19 8c",
expected: "
e3 f2 29 ba 72 7b e1 7b
8d 12 26 20 55 7c d4 53
c2 aa b2 1d 07 c3 d4 95
32 9b 52 d4 e6 1e db 5a
6b 30 17 91 e9 0d 35 c9
c9 a4 6b 4e 14 ba f9 af
0f a0 22 f7 07 7d ef 17
ab fd 37 97 c0 56 4b ab
4f bc 91 66 6e 9d ef 9b
97 fc e3 4f 79 67 89 ba
a4 80 82 d1 22 ee 42 c5
a7 2e 5a 51 10 ff f7 01
87 34 7b 66
",
},
TestVector {
name: "TLS1.2 PRF-SHA512",
digest: MessageDigest::sha512,
secret: "b0 32 35 23 c1 85 35 99 58 4d 88 56 8b bb 05 eb",
label: b"test label",
seed: "d4 64 0e 12 e4 bc db fb 43 7f 03 e6 ae 41 8e e5",
expected: "
12 61 f5 88 c7 98 c5 c2
01 ff 03 6e 7a 9c b5 ed
cd 7f e3 f9 4c 66 9a 12
2a 46 38 d7 d5 08 b2 83
04 2d f6 78 98 75 c7 14
7e 90 6d 86 8b c7 5c 45
e2 0e b4 0c 1c f4 a1 71
3b 27 37 1f 68 43 25 92
f7 dc 8e a8 ef 22 3e 12
ea 85 07 84 13 11 bf 68
65 3d 0c fc 40 56 d8 11
f0 25 c4 5d df a6 e6 fe
c7 02 f0 54 b4 09 d6 f2
8d d0 a3 23 3e 49 8d a4
1a 3e 75 c5 63 0e ed be
22 fe 25 4e 33 a1 b0 e9
f6 b9 82 66 75 be c7 d0
1a 84 56 58 dc 9c 39 75
45 40 1d 40 b9 f4 6c 7a
40 0e e1 b8 f8 1c a0 a6
0d 1a 39 7a 10 28 bf f5
d2 ef 50 66 12 68 42 fb
8d a4 19 76 32 bd b5 4f
f6 63 3f 86 bb c8 36 e6
40 d4 d8 98
",
},
TestVector {
name: "TLS1.2 PRF-SHA384",
digest: MessageDigest::sha384,
secret: "b8 0b 73 3d 6c ee fc dc 71 56 6e a4 8e 55 67 df",
label: b"test label",
seed: "cd 66 5c f6 a8 44 7d d6 ff 8b 27 55 5e db 74 65",
expected: "
7b 0c 18 e9 ce d4 10 ed
18 04 f2 cf a3 4a 33 6a
1c 14 df fb 49 00 bb 5f
d7 94 21 07 e8 1c 83 cd
e9 ca 0f aa 60 be 9f e3
4f 82 b1 23 3c 91 46 a0
e5 34 cb 40 0f ed 27 00
88 4f 9d c2 36 f8 0e dd
8b fa 96 11 44 c9 e8 d7
92 ec a7 22 a7 b3 2f c3
d4 16 d4 73 eb c2 c5 fd
4a bf da d0 5d 91 84 25
9b 5b f8 cd 4d 90 fa 0d
31 e2 de c4 79 e4 f1 a2
60 66 f2 ee a9 a6 92 36
a3 e5 26 55 c9 e9 ae e6
91 c8 f3 a2 68 54 30 8d
5e aa 3b e8 5e 09 90 70
3d 73 e5 6f
",
},
];

for vector in vectors {
let secret = decode_hex(vector.secret);
let seed = decode_hex(vector.seed);
let expected = decode_hex(vector.expected);
let mut out = vec![0u8; expected.len()];

super::tls1_prf(
(vector.digest)(),
out.as_mut_slice(),
secret.as_slice(),
vector.label,
seed.as_slice(),
)
.unwrap();

assert_eq!(out, expected, "{}", vector.name);
}
}

fn decode_hex(input: &str) -> Vec<u8> {
let compact: String = input.chars().filter(|c| !c.is_whitespace()).collect();
Vec::from_hex(compact).unwrap()
}
}
Loading