Skip to content

Commit

Permalink
Add cargo-fuzz test harness for the qos_p256 crate for automated cove…
Browse files Browse the repository at this point in the history
…rage guided testing
  • Loading branch information
cr-tk committed Mar 8, 2024
1 parent f929829 commit 2bb6cc1
Show file tree
Hide file tree
Showing 10 changed files with 300 additions and 1 deletion.
11 changes: 10 additions & 1 deletion src/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,16 @@ members = [
"qos_p256",
"qos_nsm",
]
exclude = ["init", "qos_aws", "qos_system", "toolchain","qos_enclave", "eif_build"]
exclude = [
"init",
"qos_aws",
"qos_system",
"toolchain",
"qos_enclave",
"eif_build",
"eif_build",
"qos_p256/fuzz",
]
# We need this to avoid issues with the mock feature uinintentionally being
# enabled just because some tests need it.
# https://nickb.dev/blog/cargo-workspace-and-the-feature-unification-pitfall/
Expand Down
70 changes: 70 additions & 0 deletions src/qos_p256/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
[package]
name = "qos_p256_fuzz"
version = "0.0.0"
publish = false
edition = "2021"

[package.metadata]
cargo-fuzz = true

[dependencies]
libfuzzer-sys = "0.4"

qos_p256 = { path = "../"}

# arbitrary = { version = "1", features = ["derive"] }

# Prevent this from interfering with workspaces
[workspace]
members = ["."]

[profile.release]
debug = 1

[[bin]]
name = "1_sign_then_verify"
path = "fuzz_targets/1_sign_then_verify.rs"
test = false
doc = false

[[bin]]
name = "2_public_sign_key_round_trip"
path = "fuzz_targets/2_public_sign_key_round_trip.rs"
test = false
doc = false

[[bin]]
name = "3_public_sign_key_round_trip"
path = "fuzz_targets/3_public_sign_key_round_trip.rs"
test = false
doc = false

[[bin]]
name = "4_public_sign_key_import"
path = "fuzz_targets/4_public_sign_key_import.rs"
test = false
doc = false

[[bin]]
name = "5_basic_encrypt_decrypt"
path = "fuzz_targets/5_basic_encrypt_decrypt.rs"
test = false
doc = false

[[bin]]
name = "6_basic_encrypt_decrypt_aesgcm"
path = "fuzz_targets/6_basic_encrypt_decrypt_aesgcm.rs"
test = false
doc = false

[[bin]]
name = "7_decrypt_aesgcm"
path = "fuzz_targets/7_decrypt_aesgcm.rs"
test = false
doc = false

[[bin]]
name = "8_decrypt_p256"
path = "fuzz_targets/8_decrypt_p256.rs"
test = false
doc = false
24 changes: 24 additions & 0 deletions src/qos_p256/fuzz/fuzz_targets/1_sign_then_verify.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#![no_main]

use libfuzzer_sys::fuzz_target;

use qos_p256::P256Pair;

// this harness is based on the sign_and_verification_works() unit test

fuzz_target!(|data: &[u8]| {
// let the fuzzer control data that is going to be signed

// Generate a non-deterministically random P256 key
//
// This deviates from fully deterministic fuzz behavior,
// but gives us a chance to randomly discover key-specific issues
let random_key_pair = P256Pair::generate().unwrap();

// produce a signature over the data input the fuzzer controls
let signature = random_key_pair.sign(data).unwrap();

// verify the just-generated signature
// this should always succeed
assert!(random_key_pair.public_key().verify(data, &signature).is_ok());
});
33 changes: 33 additions & 0 deletions src/qos_p256/fuzz/fuzz_targets/2_public_sign_key_round_trip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#![no_main]

use libfuzzer_sys::fuzz_target;

use qos_p256::sign::P256SignPair;
use qos_p256::sign::P256SignPublic;

// this harness is based on the public_key_round_trip_bytes_works() unit test

fuzz_target!(|data: &[u8]| {

// This setup is not ideal, as the fuzzer-controlled data input only has a
// minor influence on the tested public key round trip check

// Generate a non-deterministically random P256 key
//
// This deviates from fully deterministic fuzz behavior,
// but gives us a chance to randomly discover key-specific issues
let pair = P256SignPair::generate();

// derive public key and export it to bytes
let bytes_public = pair.public_key().to_bytes();

// create valid signature
let signature = pair.sign(data).unwrap();

// re-import public key from bytes
// this should always succeed since we just generated and exported it
let public = P256SignPublic::from_bytes(&bytes_public).unwrap();

// expect the signature verification with the reconstructed pubkey to always succeed
assert!(public.verify(data, &signature).is_ok());
});
39 changes: 39 additions & 0 deletions src/qos_p256/fuzz/fuzz_targets/3_public_sign_key_round_trip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#![no_main]

use libfuzzer_sys::fuzz_target;

use qos_p256::sign::P256SignPair;
use qos_p256::sign::P256SignPublic;

// this harness is based on the public_key_round_trip_bytes_works() unit test

fuzz_target!(|data: &[u8]| {

// let the fuzzer control the P256 secret key

// create private key from bytes, derive public key
// silently abort on failures
// we expect only 32 byte vector inputs to succeed here
let pair = match P256SignPair::from_bytes(data) {
Ok(pair) => pair,
Err(_err) => {
return;
},
};

// derive public key and export it
let bytes_public = pair.public_key().to_bytes();

// static plaintext message
let message = b"a message to authenticate";

// sign with private key
let signature = pair.sign(message).unwrap();

// re-import public key from bytes
// this should always succeed since we just generated it
let public = P256SignPublic::from_bytes(&bytes_public).unwrap();

// expect the signature verification with the reconstructed pubkey to always succeed
assert!(pubkey_special.verify(message, &signature).is_ok());
});
32 changes: 32 additions & 0 deletions src/qos_p256/fuzz/fuzz_targets/4_public_sign_key_import.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#![no_main]

use libfuzzer_sys::fuzz_target;

use qos_p256::sign::P256SignPair;
use qos_p256::sign::P256SignPublic;

// this harness is partially based on the public_key_round_trip_bytes_works() unit test

fuzz_target!(|data: &[u8]| {
// let the fuzzer control the P256 signing pubkey

// import public key from bytes
// silently exit in case of errors
let pubkey_special = match P256SignPublic::from_bytes(data) {
Ok(pubkey) => pubkey,
Err(_err) => {
return;
},
};

// static plaintext message
let message = b"a message to authenticate";

// Improvement: replace this with a static pre-recorded signature, we just need a (wrong) signature
let pair = P256SignPair::generate();
// sign with secret key
let signature = pair.sign(message).unwrap();

// we expect this to not succeed since the pubkeys do not match up
assert!(!pubkey_special.verify(message, &signature).is_ok());
});
26 changes: 26 additions & 0 deletions src/qos_p256/fuzz/fuzz_targets/5_basic_encrypt_decrypt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#![no_main]

use libfuzzer_sys::fuzz_target;

use qos_p256::encrypt::P256EncryptPair;


// this harness is partially based on the basic_encrypt_decrypt_works() unit test

fuzz_target!(|data: &[u8]| {
// let the fuzzer control a message plaintext that is encrypted and then decrypted again

// private key generation is non-deterministic: not ideal
let random_key_pair = P256EncryptPair::generate();
let random_key_public = random_key_pair.public_key();

// the encryption is non-deterministic due to the internal random nonce generation
// not ideal, can't be avoided due to API structure?
let serialized_envelope = random_key_public.encrypt(data).unwrap();

// expected to always succeed
let decrypted_data = random_key_pair.decrypt(&serialized_envelope).unwrap();

// check roundtrip data consistency, assert should always hold
assert_eq!(decrypted_data, data);
});
26 changes: 26 additions & 0 deletions src/qos_p256/fuzz/fuzz_targets/6_basic_encrypt_decrypt_aesgcm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#![no_main]

use libfuzzer_sys::fuzz_target;

use qos_p256::encrypt::AesGcm256Secret;


// this harness is partially based on the encrypt_decrypt_round_trip() unit test

fuzz_target!(|data: &[u8]| {
// let the fuzzer control a message plaintext that is encrypted and then decrypted again

// private key generation is non-deterministic: not ideal
let random_key = AesGcm256Secret::generate();

// the encryption is non-deterministic due to the internal random nonce generation
// not ideal, can't be avoided due to API structure?
// expected to always succeed
let encrypted_envelope = random_key.encrypt(data).unwrap();

// expected to always succeed
let decrypted_data = random_key.decrypt(&encrypted_envelope).unwrap();

// check roundtrip data consistency, assert should always hold
assert_eq!(decrypted_data, data);
});
21 changes: 21 additions & 0 deletions src/qos_p256/fuzz/fuzz_targets/7_decrypt_aesgcm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#![no_main]

use libfuzzer_sys::fuzz_target;

use qos_p256::encrypt::AesGcm256Secret;


fuzz_target!(|data: &[u8]| {
// let the fuzzer create an encrypted envelope to test decrypt() robustness

// private key generation is non-deterministic: not ideal
let random_key = AesGcm256Secret::generate();

// we expect this to fail
match random_key.decrypt(&data) {
Ok(_res) => panic!("the fuzzer can't create valid AEAD protected encrypted messages"),
Err(_err) => {
return;
},
};
});
19 changes: 19 additions & 0 deletions src/qos_p256/fuzz/fuzz_targets/8_decrypt_p256.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#![no_main]

use libfuzzer_sys::fuzz_target;

use qos_p256::encrypt::P256EncryptPair;

fuzz_target!(|data: &[u8]| {
// let the fuzzer control an encrypted message ciphertext to test decrypt() robustness

// private key generation is non-deterministic: not ideal
let random_key_pair = P256EncryptPair::generate();

match random_key_pair.decrypt(&data) {
Ok(_res) => panic!("the fuzzer should be unable to create a validly signed message"),
Err(_err) => {
return;
},
};
});

0 comments on commit 2bb6cc1

Please sign in to comment.