Skip to content

Commit 6e79d9e

Browse files
authored
Add tests using wycheproof test vectors (#15)
- Remove the hack to patch into rustls, and the use of no_std - Update tls12 to use aead module
1 parent 8c391c3 commit 6e79d9e

23 files changed

+7788
-532
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -45,33 +45,3 @@ jobs:
4545
run: cargo fmt -- --check -l
4646
- name: cargo clippy (warnings)
4747
run: cargo clippy --all-targets --all-features -- -D warnings
48-
rustls_provider_tests:
49-
name: Test using rustls provider
50-
runs-on: ubuntu-latest
51-
defaults:
52-
run:
53-
working-directory: ./rustls
54-
steps:
55-
- name: Check out repository
56-
uses: actions/checkout@v4
57-
with:
58-
path: rustls-openssl
59-
- name: Check out repository
60-
uses: actions/checkout@v4
61-
with:
62-
repository: rustls/rustls
63-
path: rustls
64-
- name: Install toolchain
65-
uses: dtolnay/rust-toolchain@v1
66-
with:
67-
toolchain: stable
68-
- name: Cache build artifacts
69-
uses: Swatinem/rust-cache@v2
70-
with:
71-
workspaces: rustls
72-
- name: Patch rustls
73-
run: ../rustls-openssl/tests/patch.sh
74-
- name: find
75-
run: find .
76-
- name: cargo test test_with_openssl
77-
run: cargo test test_with_openssl

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,6 @@ rstest = "0.23.0"
3030
# Use aws_lc_rs to test our provider
3131
rustls = { version = "0.23.0", features = ["aws_lc_rs"] }
3232
rustls-pemfile = "2"
33+
serde = { version = "1.0.215", features = ["derive"] }
34+
serde_json = "1.0.133"
3335
webpki-roots = "0.26"

README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,3 @@ SECP256R1
4444
X25519 // Requires the `x25519` feature
4545
```
4646

47-
# Tests
48-
49-
In addition to the tests in this repo, CI also runs rustls tests that run against all providers.
50-
This is done by patching this repo as a module into a rustls checkout repo, hence this repo mirroring the `rustls::crypto::<provider>` module structure.

build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ fn main() {
1919

2020
// Enable the `chacha` cfg if the `OPENSSL_NO_CHACHA` OpenSSL config is not set.
2121
if std::env::var("DEP_OPENSSL_CONF")
22-
.map(|conf_string| !conf_string.split(",").any(|conf| conf == OPENSSL_NO_CHACHA))
22+
.map(|conf_string| !conf_string.split(',').any(|conf| conf == OPENSSL_NO_CHACHA))
2323
.unwrap_or(true)
2424
{
2525
println!("cargo:rustc-cfg=chacha");

src/aead.rs

Lines changed: 145 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,7 @@
1-
use alloc::format;
2-
use openssl::{
3-
cipher::{Cipher, CipherRef},
4-
cipher_ctx::CipherCtx,
5-
};
6-
use rustls::{
7-
crypto::cipher::{AeadKey, Iv, Nonce},
8-
Error,
9-
};
10-
11-
pub(crate) struct MessageCrypter {
12-
pub algo: Algorithm,
13-
pub key: AeadKey,
14-
pub iv: Iv,
15-
}
1+
use openssl::cipher::{Cipher, CipherRef};
2+
use openssl::cipher_ctx::CipherCtx;
3+
use rustls::crypto::cipher::NONCE_LEN;
4+
use rustls::Error;
165

176
#[derive(Debug, Clone, Copy)]
187
pub(crate) enum Algorithm {
@@ -26,31 +15,30 @@ pub(crate) enum Algorithm {
2615
pub(crate) const TAG_LEN: usize = 16;
2716

2817
impl Algorithm {
29-
pub(crate) fn openssl_cipher(self) -> &'static CipherRef {
18+
fn openssl_cipher(self) -> &'static CipherRef {
3019
match self {
3120
Self::Aes128Gcm => Cipher::aes_128_gcm(),
3221
Self::Aes256Gcm => Cipher::aes_256_gcm(),
3322
#[cfg(chacha)]
3423
Self::ChaCha20Poly1305 => Cipher::chacha20_poly1305(),
3524
}
3625
}
37-
}
3826

39-
impl MessageCrypter {
40-
/// Encrypts the data in place and returns the tag.
27+
pub(crate) fn key_size(self) -> usize {
28+
self.openssl_cipher().key_length()
29+
}
30+
31+
/// Encrypts data in place and returns the tag.
4132
pub(crate) fn encrypt_in_place(
42-
&self,
43-
sequence_number: u64,
33+
self,
34+
key: &[u8],
35+
nonce: &[u8; NONCE_LEN],
4436
aad: &[u8],
4537
data: &mut [u8],
4638
) -> Result<[u8; TAG_LEN], Error> {
4739
CipherCtx::new()
4840
.and_then(|mut ctx| {
49-
ctx.encrypt_init(
50-
Some(self.algo.openssl_cipher()),
51-
Some(self.key.as_ref()),
52-
Some(&Nonce::new(&self.iv, sequence_number).0),
53-
)?;
41+
ctx.encrypt_init(Some(self.openssl_cipher()), Some(key), Some(nonce))?;
5442
// Providing no output buffer implies input is AAD.
5543
ctx.cipher_update(aad, None)?;
5644
// The ciphers are all stream ciphers, so we shound encrypt the same amount of data...
@@ -70,8 +58,9 @@ impl MessageCrypter {
7058
/// plaintext.
7159
/// The data is expected to be in the form of [ciphertext, tag].
7260
pub(crate) fn decrypt_in_place(
73-
&self,
74-
sequence_number: u64,
61+
self,
62+
key: &[u8],
63+
nonce: &[u8; NONCE_LEN],
7564
aad: &[u8],
7665
data: &mut [u8],
7766
) -> Result<usize, Error> {
@@ -84,11 +73,7 @@ impl MessageCrypter {
8473

8574
CipherCtx::new()
8675
.and_then(|mut ctx| {
87-
ctx.decrypt_init(
88-
Some(self.algo.openssl_cipher()),
89-
Some(self.key.as_ref()),
90-
Some(&Nonce::new(&self.iv, sequence_number).0),
91-
)?;
76+
ctx.decrypt_init(Some(self.openssl_cipher()), Some(key), Some(nonce))?;
9277
ctx.cipher_update(aad, None)?;
9378
ctx.set_tag(tag)?;
9479
let count = ctx.cipher_update_inplace(ciphertext, ciphertext.len())?;
@@ -100,3 +85,130 @@ impl MessageCrypter {
10085
.map_err(|e| Error::General(format!("OpenSSL error: {e}")))
10186
}
10287
}
88+
89+
#[cfg(test)]
90+
mod test {
91+
92+
use crate::test::schemas::aead;
93+
use std::{fs, path::PathBuf};
94+
95+
fn test_aes(alg: super::Algorithm) {
96+
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
97+
.join("src")
98+
.join("test")
99+
.join("vectors")
100+
.join("aes_gcm_test.json");
101+
let tests: aead::AeadTestFile =
102+
serde_json::from_str(&fs::read_to_string(path).unwrap()).unwrap();
103+
104+
for group in tests
105+
.test_groups
106+
.unwrap()
107+
.iter()
108+
.filter(|group| group.key_size.unwrap() == 8 * i64::try_from(alg.key_size()).unwrap())
109+
.filter(|group| group.iv_size.unwrap() == 96)
110+
{
111+
for test in group.tests.as_ref().unwrap() {
112+
dbg!(test.tc_id);
113+
let key = test
114+
.key
115+
.as_deref()
116+
.map(|key| hex::decode(key).unwrap())
117+
.unwrap();
118+
let iv = test
119+
.iv
120+
.as_deref()
121+
.map(|iv| hex::decode(iv).unwrap())
122+
.unwrap();
123+
let aad = test
124+
.aad
125+
.as_deref()
126+
.map(|aad| hex::decode(aad).unwrap())
127+
.unwrap();
128+
let msg = test
129+
.msg
130+
.as_deref()
131+
.map(|msg| hex::decode(msg).unwrap())
132+
.unwrap();
133+
let ciphertext = test
134+
.ct
135+
.as_deref()
136+
.map(|ct| hex::decode(ct).unwrap())
137+
.unwrap();
138+
let tag = test
139+
.tag
140+
.as_deref()
141+
.map(|tag| hex::decode(tag).unwrap())
142+
.unwrap();
143+
144+
let mut iv_bytes = [0u8; 12];
145+
iv_bytes.copy_from_slice(&iv[0..12]);
146+
147+
let mut actual_ciphertext = msg.clone();
148+
let actual_tag = alg
149+
.encrypt_in_place(&key, &iv_bytes, &aad, &mut actual_ciphertext)
150+
.unwrap();
151+
152+
match test.result.as_ref().unwrap() {
153+
aead::Result::Invalid => {
154+
if test
155+
.flags
156+
.as_ref()
157+
.unwrap()
158+
.iter()
159+
.any(|flag| flag == "ModifiedTag")
160+
{
161+
assert_ne!(
162+
actual_tag[..],
163+
tag[..],
164+
"Expected incorrect tag. Id {}: {}",
165+
test.tc_id.unwrap(),
166+
test.comment.as_deref().unwrap()
167+
);
168+
}
169+
}
170+
aead::Result::Valid | aead::Result::Acceptable => {
171+
assert_eq!(
172+
actual_ciphertext,
173+
ciphertext,
174+
"Test case failed {}: {}",
175+
test.tc_id.unwrap(),
176+
test.comment.as_deref().unwrap()
177+
);
178+
assert_eq!(
179+
actual_tag[..],
180+
tag[..],
181+
"Test case failed {}: {}",
182+
test.tc_id.unwrap(),
183+
test.comment.as_deref().unwrap()
184+
);
185+
}
186+
}
187+
188+
let mut data = ciphertext.to_vec();
189+
data.extend_from_slice(&tag);
190+
let res = alg.decrypt_in_place(&key, &iv_bytes, &aad, &mut data);
191+
192+
match test.result.as_ref().unwrap() {
193+
aead::Result::Invalid => {
194+
assert!(res.is_err());
195+
}
196+
aead::Result::Valid | aead::Result::Acceptable => {
197+
assert_eq!(res, Ok(msg.len()));
198+
assert_eq!(&data[..res.unwrap()], &msg[..]);
199+
}
200+
}
201+
}
202+
}
203+
}
204+
205+
#[test]
206+
fn test_aes_128() {
207+
test_aes(super::Algorithm::Aes128Gcm);
208+
}
209+
210+
#[test]
211+
fn test_aes_256() {
212+
test_aes(super::Algorithm::Aes256Gcm);
213+
}
214+
}

src/hash.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
//! Provide Rustls `Hash` implementation using OpenSSL `MessageDigest`.
2-
use alloc::boxed::Box;
32
use openssl::hash::MessageDigest;
43
use openssl::md::{Md, MdRef};
54
use openssl::sha::{self, sha256, sha384};
@@ -23,14 +22,14 @@ enum Context {
2322
}
2423

2524
impl Algorithm {
26-
pub(crate) fn mdref(&self) -> &'static MdRef {
25+
pub(crate) fn mdref(self) -> &'static MdRef {
2726
match &self {
2827
Algorithm::SHA256 => Md::sha256(),
2928
Algorithm::SHA384 => Md::sha384(),
3029
}
3130
}
3231

33-
pub(crate) fn message_digest(&self) -> MessageDigest {
32+
pub(crate) fn message_digest(self) -> MessageDigest {
3433
match &self {
3534
Algorithm::SHA256 => MessageDigest::sha256(),
3635
Algorithm::SHA384 => MessageDigest::sha384(),

0 commit comments

Comments
 (0)