diff --git a/src/Cargo.toml b/src/Cargo.toml index deeea8694..7c23071df 100644 --- a/src/Cargo.toml +++ b/src/Cargo.toml @@ -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/ diff --git a/src/qos_p256/fuzz/Cargo.toml b/src/qos_p256/fuzz/Cargo.toml new file mode 100644 index 000000000..6a8cdad3f --- /dev/null +++ b/src/qos_p256/fuzz/Cargo.toml @@ -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 diff --git a/src/qos_p256/fuzz/fuzz_targets/1_sign_then_verify.rs b/src/qos_p256/fuzz/fuzz_targets/1_sign_then_verify.rs new file mode 100644 index 000000000..9dc45d6af --- /dev/null +++ b/src/qos_p256/fuzz/fuzz_targets/1_sign_then_verify.rs @@ -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()); +}); diff --git a/src/qos_p256/fuzz/fuzz_targets/2_public_sign_key_round_trip.rs b/src/qos_p256/fuzz/fuzz_targets/2_public_sign_key_round_trip.rs new file mode 100644 index 000000000..7ec4f52ee --- /dev/null +++ b/src/qos_p256/fuzz/fuzz_targets/2_public_sign_key_round_trip.rs @@ -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()); +}); diff --git a/src/qos_p256/fuzz/fuzz_targets/3_public_sign_key_round_trip.rs b/src/qos_p256/fuzz/fuzz_targets/3_public_sign_key_round_trip.rs new file mode 100644 index 000000000..15e1db713 --- /dev/null +++ b/src/qos_p256/fuzz/fuzz_targets/3_public_sign_key_round_trip.rs @@ -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()); +}); diff --git a/src/qos_p256/fuzz/fuzz_targets/4_public_sign_key_import.rs b/src/qos_p256/fuzz/fuzz_targets/4_public_sign_key_import.rs new file mode 100644 index 000000000..b7897bfb4 --- /dev/null +++ b/src/qos_p256/fuzz/fuzz_targets/4_public_sign_key_import.rs @@ -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()); +}); diff --git a/src/qos_p256/fuzz/fuzz_targets/5_basic_encrypt_decrypt.rs b/src/qos_p256/fuzz/fuzz_targets/5_basic_encrypt_decrypt.rs new file mode 100644 index 000000000..5829bdc34 --- /dev/null +++ b/src/qos_p256/fuzz/fuzz_targets/5_basic_encrypt_decrypt.rs @@ -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); +}); diff --git a/src/qos_p256/fuzz/fuzz_targets/6_basic_encrypt_decrypt_aesgcm.rs b/src/qos_p256/fuzz/fuzz_targets/6_basic_encrypt_decrypt_aesgcm.rs new file mode 100644 index 000000000..1971050ea --- /dev/null +++ b/src/qos_p256/fuzz/fuzz_targets/6_basic_encrypt_decrypt_aesgcm.rs @@ -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); +}); \ No newline at end of file diff --git a/src/qos_p256/fuzz/fuzz_targets/7_decrypt_aesgcm.rs b/src/qos_p256/fuzz/fuzz_targets/7_decrypt_aesgcm.rs new file mode 100644 index 000000000..ce1e0a47f --- /dev/null +++ b/src/qos_p256/fuzz/fuzz_targets/7_decrypt_aesgcm.rs @@ -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; + }, + }; +}); \ No newline at end of file diff --git a/src/qos_p256/fuzz/fuzz_targets/8_decrypt_p256.rs b/src/qos_p256/fuzz/fuzz_targets/8_decrypt_p256.rs new file mode 100644 index 000000000..09e903334 --- /dev/null +++ b/src/qos_p256/fuzz/fuzz_targets/8_decrypt_p256.rs @@ -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; + }, + }; +});