summaryrefslogtreecommitdiffstats
path: root/third_party/rust/neqo-crypto/tests
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/neqo-crypto/tests')
-rw-r--r--third_party/rust/neqo-crypto/tests/aead.rs118
-rw-r--r--third_party/rust/neqo-crypto/tests/agent.rs536
-rw-r--r--third_party/rust/neqo-crypto/tests/ext.rs99
-rw-r--r--third_party/rust/neqo-crypto/tests/handshake.rs157
-rw-r--r--third_party/rust/neqo-crypto/tests/hkdf.rs155
-rw-r--r--third_party/rust/neqo-crypto/tests/hp.rs82
-rw-r--r--third_party/rust/neqo-crypto/tests/init.rs44
-rw-r--r--third_party/rust/neqo-crypto/tests/selfencrypt.rs96
8 files changed, 1287 insertions, 0 deletions
diff --git a/third_party/rust/neqo-crypto/tests/aead.rs b/third_party/rust/neqo-crypto/tests/aead.rs
new file mode 100644
index 0000000000..0ee1e66c38
--- /dev/null
+++ b/third_party/rust/neqo-crypto/tests/aead.rs
@@ -0,0 +1,118 @@
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![warn(clippy::pedantic)]
+#![cfg(not(feature = "fuzzing"))]
+
+use neqo_crypto::{
+ constants::{Cipher, TLS_AES_128_GCM_SHA256, TLS_VERSION_1_3},
+ hkdf, Aead,
+};
+use test_fixture::fixture_init;
+
+const AAD: &[u8] = &[
+ 0xc1, 0xff, 0x00, 0x00, 0x12, 0x05, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, 0x00, 0x40,
+ 0x74, 0x00, 0x01,
+];
+const PLAINTEXT: &[u8] = &[
+ 0x0d, 0x00, 0x00, 0x00, 0x00, 0x18, 0x41, 0x0a, 0x02, 0x00, 0x00, 0x56, 0x03, 0x03, 0xee, 0xfc,
+ 0xe7, 0xf7, 0xb3, 0x7b, 0xa1, 0xd1, 0x63, 0x2e, 0x96, 0x67, 0x78, 0x25, 0xdd, 0xf7, 0x39, 0x88,
+ 0xcf, 0xc7, 0x98, 0x25, 0xdf, 0x56, 0x6d, 0xc5, 0x43, 0x0b, 0x9a, 0x04, 0x5a, 0x12, 0x00, 0x13,
+ 0x01, 0x00, 0x00, 0x2e, 0x00, 0x33, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x9d, 0x3c, 0x94, 0x0d,
+ 0x89, 0x69, 0x0b, 0x84, 0xd0, 0x8a, 0x60, 0x99, 0x3c, 0x14, 0x4e, 0xca, 0x68, 0x4d, 0x10, 0x81,
+ 0x28, 0x7c, 0x83, 0x4d, 0x53, 0x11, 0xbc, 0xf3, 0x2b, 0xb9, 0xda, 0x1a, 0x00, 0x2b, 0x00, 0x02,
+ 0x03, 0x04,
+];
+
+fn make_aead(cipher: Cipher) -> Aead {
+ fixture_init();
+
+ let secret = hkdf::import_key(
+ TLS_VERSION_1_3,
+ &[
+ 0x47, 0xb2, 0xea, 0xea, 0x6c, 0x26, 0x6e, 0x32, 0xc0, 0x69, 0x7a, 0x9e, 0x2a, 0x89,
+ 0x8b, 0xdf, 0x5c, 0x4f, 0xb3, 0xe5, 0xac, 0x34, 0xf0, 0xe5, 0x49, 0xbf, 0x2c, 0x58,
+ 0x58, 0x1a, 0x38, 0x11,
+ ],
+ )
+ .expect("make a secret");
+ Aead::new(
+ false,
+ TLS_VERSION_1_3,
+ cipher,
+ &secret,
+ "quic ", // QUICv1 label prefix; note the trailing space here.
+ )
+ .expect("can make an AEAD")
+}
+
+#[test]
+fn aead_encrypt_decrypt() {
+ const TOGGLE: u8 = 77;
+ let aead = make_aead(TLS_AES_128_GCM_SHA256);
+ let ciphertext_buf = &mut [0; 1024]; // Can't use PLAINTEXT.len() here.
+ let ciphertext = aead
+ .encrypt(1, AAD, PLAINTEXT, ciphertext_buf)
+ .expect("encrypt should work");
+ let expected_ciphertext: &[u8] = &[
+ 0x5f, 0x01, 0xc4, 0xc2, 0xa2, 0x30, 0x3d, 0x29, 0x7e, 0x3c, 0x51, 0x9b, 0xf6, 0xb2, 0x23,
+ 0x86, 0xe3, 0xd0, 0xbd, 0x6d, 0xfc, 0x66, 0x12, 0x16, 0x77, 0x29, 0x80, 0x31, 0x04, 0x1b,
+ 0xb9, 0xa7, 0x9c, 0x9f, 0x0f, 0x9d, 0x4c, 0x58, 0x77, 0x27, 0x0a, 0x66, 0x0f, 0x5d, 0xa3,
+ 0x62, 0x07, 0xd9, 0x8b, 0x73, 0x83, 0x9b, 0x2f, 0xdf, 0x2e, 0xf8, 0xe7, 0xdf, 0x5a, 0x51,
+ 0xb1, 0x7b, 0x8c, 0x68, 0xd8, 0x64, 0xfd, 0x3e, 0x70, 0x8c, 0x6c, 0x1b, 0x71, 0xa9, 0x8a,
+ 0x33, 0x18, 0x15, 0x59, 0x9e, 0xf5, 0x01, 0x4e, 0xa3, 0x8c, 0x44, 0xbd, 0xfd, 0x38, 0x7c,
+ 0x03, 0xb5, 0x27, 0x5c, 0x35, 0xe0, 0x09, 0xb6, 0x23, 0x8f, 0x83, 0x14, 0x20, 0x04, 0x7c,
+ 0x72, 0x71, 0x28, 0x1c, 0xcb, 0x54, 0xdf, 0x78, 0x84,
+ ];
+ assert_eq!(ciphertext, expected_ciphertext);
+
+ let plaintext_buf = &mut [0; 1024]; // Can't use PLAINTEXT.len() here.
+ let plaintext = aead
+ .decrypt(1, AAD, ciphertext, plaintext_buf)
+ .expect("decrypt should also work");
+ assert_eq!(plaintext, PLAINTEXT);
+
+ // Decryption failures...
+ // Different counter.
+ let res = aead.decrypt(2, AAD, ciphertext, plaintext_buf);
+ assert!(res.is_err());
+
+ // Front-truncate ciphertext.
+ let res = aead.decrypt(1, AAD, &ciphertext[1..], plaintext_buf);
+ assert!(res.is_err());
+
+ // End-truncate ciphertext.
+ let ciphertext_last = ciphertext.len() - 1;
+ let res = aead.decrypt(1, AAD, &ciphertext[..ciphertext_last], plaintext_buf);
+ assert!(res.is_err());
+
+ // Mess with the buffer.
+ let mut scratch = Vec::new();
+ scratch.extend_from_slice(ciphertext);
+
+ // Toggle first octet.
+ scratch[0] ^= TOGGLE;
+ let res = aead.decrypt(1, AAD, &scratch[..], plaintext_buf);
+ assert!(res.is_err());
+
+ // Toggle the auth tag.
+ scratch[0] ^= TOGGLE;
+ scratch[ciphertext_last] ^= TOGGLE;
+ let res = aead.decrypt(1, AAD, &scratch[..], plaintext_buf);
+ assert!(res.is_err());
+
+ // Mess with the AAD.
+ scratch.clear();
+ scratch.extend_from_slice(AAD);
+
+ // Front-truncate.
+ let res = aead.decrypt(1, &scratch[1..], ciphertext, plaintext_buf);
+ assert!(res.is_err());
+
+ // End-truncate.
+ let aad_last = AAD.len() - 1;
+ let res = aead.decrypt(1, &scratch[..aad_last], ciphertext, plaintext_buf);
+ assert!(res.is_err());
+
+ scratch[0] ^= TOGGLE;
+ let res = aead.decrypt(1, &scratch[..], ciphertext, plaintext_buf);
+ assert!(res.is_err());
+}
diff --git a/third_party/rust/neqo-crypto/tests/agent.rs b/third_party/rust/neqo-crypto/tests/agent.rs
new file mode 100644
index 0000000000..c2c83c467c
--- /dev/null
+++ b/third_party/rust/neqo-crypto/tests/agent.rs
@@ -0,0 +1,536 @@
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![warn(clippy::pedantic)]
+
+use std::boxed::Box;
+
+use neqo_crypto::{
+ generate_ech_keys, AuthenticationStatus, Client, Error, HandshakeState, SecretAgentPreInfo,
+ Server, ZeroRttCheckResult, ZeroRttChecker, TLS_AES_128_GCM_SHA256,
+ TLS_CHACHA20_POLY1305_SHA256, TLS_GRP_EC_SECP256R1, TLS_GRP_EC_X25519, TLS_VERSION_1_3,
+};
+
+mod handshake;
+use test_fixture::{fixture_init, now};
+
+use crate::handshake::{
+ connect, connect_fail, forward_records, resumption_setup, PermissiveZeroRttChecker, Resumption,
+ ZERO_RTT_TOKEN_DATA,
+};
+
+#[test]
+fn make_client() {
+ fixture_init();
+ let _c = Client::new("server", true).expect("should create client");
+}
+
+#[test]
+fn make_server() {
+ fixture_init();
+ let _s = Server::new(&["key"]).expect("should create server");
+}
+
+#[test]
+fn basic() {
+ fixture_init();
+ let mut client = Client::new("server.example", true).expect("should create client");
+ println!("client {:p}", &client);
+ let mut server = Server::new(&["key"]).expect("should create server");
+ println!("server {:p}", &server);
+
+ let bytes = client.handshake(now(), &[]).expect("send CH");
+ assert!(!bytes.is_empty());
+ assert_eq!(*client.state(), HandshakeState::InProgress);
+
+ let bytes = server
+ .handshake(now(), &bytes[..])
+ .expect("read CH, send SH");
+ assert!(!bytes.is_empty());
+ assert_eq!(*server.state(), HandshakeState::InProgress);
+
+ let bytes = client.handshake(now(), &bytes[..]).expect("send CF");
+ assert!(bytes.is_empty());
+ assert_eq!(*client.state(), HandshakeState::AuthenticationPending);
+
+ client.authenticated(AuthenticationStatus::Ok);
+ assert_eq!(*client.state(), HandshakeState::Authenticated(0));
+
+ // Calling handshake() again indicates that we're happy with the cert.
+ let bytes = client.handshake(now(), &[]).expect("send CF");
+ assert!(!bytes.is_empty());
+ assert!(client.state().is_connected());
+
+ let client_info = client.info().expect("got info");
+ assert_eq!(TLS_VERSION_1_3, client_info.version());
+ assert_eq!(TLS_AES_128_GCM_SHA256, client_info.cipher_suite());
+
+ let bytes = server.handshake(now(), &bytes[..]).expect("finish");
+ assert!(bytes.is_empty());
+ assert!(server.state().is_connected());
+
+ let server_info = server.info().expect("got info");
+ assert_eq!(TLS_VERSION_1_3, server_info.version());
+ assert_eq!(TLS_AES_128_GCM_SHA256, server_info.cipher_suite());
+}
+
+fn check_client_preinfo(client_preinfo: &SecretAgentPreInfo) {
+ assert_eq!(client_preinfo.version(), None);
+ assert_eq!(client_preinfo.cipher_suite(), None);
+ assert!(!client_preinfo.early_data());
+ assert_eq!(client_preinfo.early_data_cipher(), None);
+ assert_eq!(client_preinfo.max_early_data(), 0);
+ assert_eq!(client_preinfo.alpn(), None);
+}
+
+fn check_server_preinfo(server_preinfo: &SecretAgentPreInfo) {
+ assert_eq!(server_preinfo.version(), Some(TLS_VERSION_1_3));
+ assert_eq!(server_preinfo.cipher_suite(), Some(TLS_AES_128_GCM_SHA256));
+ assert!(!server_preinfo.early_data());
+ assert_eq!(server_preinfo.early_data_cipher(), None);
+ assert_eq!(server_preinfo.max_early_data(), 0);
+ assert_eq!(server_preinfo.alpn(), None);
+}
+
+#[test]
+fn raw() {
+ fixture_init();
+ let mut client = Client::new("server.example", true).expect("should create client");
+ println!("client {client:?}");
+ let mut server = Server::new(&["key"]).expect("should create server");
+ println!("server {server:?}");
+
+ let client_records = client.handshake_raw(now(), None).expect("send CH");
+ assert!(!client_records.is_empty());
+ assert_eq!(*client.state(), HandshakeState::InProgress);
+
+ check_client_preinfo(&client.preinfo().expect("get preinfo"));
+
+ let server_records =
+ forward_records(now(), &mut server, client_records).expect("read CH, send SH");
+ assert!(!server_records.is_empty());
+ assert_eq!(*server.state(), HandshakeState::InProgress);
+
+ check_server_preinfo(&server.preinfo().expect("get preinfo"));
+
+ let client_records = forward_records(now(), &mut client, server_records).expect("send CF");
+ assert!(client_records.is_empty());
+ assert_eq!(*client.state(), HandshakeState::AuthenticationPending);
+
+ client.authenticated(AuthenticationStatus::Ok);
+ assert_eq!(*client.state(), HandshakeState::Authenticated(0));
+
+ // Calling handshake() again indicates that we're happy with the cert.
+ let client_records = client.handshake_raw(now(), None).expect("send CF");
+ assert!(!client_records.is_empty());
+ assert!(client.state().is_connected());
+
+ let server_records = forward_records(now(), &mut server, client_records).expect("finish");
+ assert!(server_records.is_empty());
+ assert!(server.state().is_connected());
+
+ // The client should have one certificate for the server.
+ let mut certs = client.peer_certificate().unwrap();
+ assert_eq!(1, certs.count());
+
+ // The server shouldn't have a client certificate.
+ assert!(server.peer_certificate().is_none());
+}
+
+#[test]
+fn chacha_client() {
+ fixture_init();
+ let mut client = Client::new("server.example", true).expect("should create client");
+ let mut server = Server::new(&["key"]).expect("should create server");
+ client
+ .set_ciphers(&[TLS_CHACHA20_POLY1305_SHA256])
+ .expect("ciphers set");
+
+ connect(&mut client, &mut server);
+
+ assert_eq!(
+ client.info().unwrap().cipher_suite(),
+ TLS_CHACHA20_POLY1305_SHA256
+ );
+ assert_eq!(
+ server.info().unwrap().cipher_suite(),
+ TLS_CHACHA20_POLY1305_SHA256
+ );
+}
+
+#[test]
+fn server_prefers_first_client_share() {
+ fixture_init();
+ let mut client = Client::new("server.example", true).expect("should create client");
+ let mut server = Server::new(&["key"]).expect("should create server");
+ server
+ .set_groups(&[TLS_GRP_EC_X25519, TLS_GRP_EC_SECP256R1])
+ .expect("groups set");
+ client
+ .set_groups(&[TLS_GRP_EC_X25519, TLS_GRP_EC_SECP256R1])
+ .expect("groups set");
+ client
+ .send_additional_key_shares(1)
+ .expect("should set additional key share count");
+
+ connect(&mut client, &mut server);
+
+ assert_eq!(client.info().unwrap().key_exchange(), TLS_GRP_EC_X25519);
+ assert_eq!(server.info().unwrap().key_exchange(), TLS_GRP_EC_X25519);
+}
+
+#[test]
+fn server_prefers_second_client_share() {
+ fixture_init();
+ let mut client = Client::new("server.example", true).expect("should create client");
+ let mut server = Server::new(&["key"]).expect("should create server");
+ server
+ .set_groups(&[TLS_GRP_EC_SECP256R1, TLS_GRP_EC_X25519])
+ .expect("groups set");
+ client
+ .set_groups(&[TLS_GRP_EC_X25519, TLS_GRP_EC_SECP256R1])
+ .expect("groups set");
+ client
+ .send_additional_key_shares(1)
+ .expect("should set additional key share count");
+
+ connect(&mut client, &mut server);
+
+ assert_eq!(client.info().unwrap().key_exchange(), TLS_GRP_EC_SECP256R1);
+ assert_eq!(server.info().unwrap().key_exchange(), TLS_GRP_EC_SECP256R1);
+}
+
+#[test]
+fn p256_server() {
+ fixture_init();
+ let mut client = Client::new("server.example", true).expect("should create client");
+ let mut server = Server::new(&["key"]).expect("should create server");
+ server
+ .set_groups(&[TLS_GRP_EC_SECP256R1])
+ .expect("groups set");
+
+ connect(&mut client, &mut server);
+
+ assert_eq!(client.info().unwrap().key_exchange(), TLS_GRP_EC_SECP256R1);
+ assert_eq!(server.info().unwrap().key_exchange(), TLS_GRP_EC_SECP256R1);
+}
+
+#[test]
+fn p256_server_hrr() {
+ fixture_init();
+ let mut client = Client::new("server.example", true).expect("should create client");
+ let mut server = Server::new(&["key"]).expect("should create server");
+ server
+ .set_groups(&[TLS_GRP_EC_SECP256R1])
+ .expect("groups set");
+ client
+ .set_groups(&[TLS_GRP_EC_X25519, TLS_GRP_EC_SECP256R1])
+ .expect("groups set");
+ client
+ .send_additional_key_shares(0)
+ .expect("should set additional key share count");
+
+ connect(&mut client, &mut server);
+
+ assert_eq!(client.info().unwrap().key_exchange(), TLS_GRP_EC_SECP256R1);
+ assert_eq!(server.info().unwrap().key_exchange(), TLS_GRP_EC_SECP256R1);
+}
+
+#[test]
+fn alpn() {
+ fixture_init();
+ let mut client = Client::new("server.example", true).expect("should create client");
+ client.set_alpn(&["alpn"]).expect("should set ALPN");
+ let mut server = Server::new(&["key"]).expect("should create server");
+ server.set_alpn(&["alpn"]).expect("should set ALPN");
+
+ connect(&mut client, &mut server);
+
+ let expected = Some(String::from("alpn"));
+ assert_eq!(expected.as_ref(), client.info().unwrap().alpn());
+ assert_eq!(expected.as_ref(), server.info().unwrap().alpn());
+}
+
+#[test]
+fn alpn_multi() {
+ fixture_init();
+ let mut client = Client::new("server.example", true).expect("should create client");
+ client
+ .set_alpn(&["dummy", "alpn"])
+ .expect("should set ALPN");
+ let mut server = Server::new(&["key"]).expect("should create server");
+ server
+ .set_alpn(&["alpn", "other"])
+ .expect("should set ALPN");
+
+ connect(&mut client, &mut server);
+
+ let expected = Some(String::from("alpn"));
+ assert_eq!(expected.as_ref(), client.info().unwrap().alpn());
+ assert_eq!(expected.as_ref(), server.info().unwrap().alpn());
+}
+
+#[test]
+fn alpn_server_pref() {
+ fixture_init();
+ let mut client = Client::new("server.example", true).expect("should create client");
+ client
+ .set_alpn(&["dummy", "alpn"])
+ .expect("should set ALPN");
+ let mut server = Server::new(&["key"]).expect("should create server");
+ server
+ .set_alpn(&["alpn", "dummy"])
+ .expect("should set ALPN");
+
+ connect(&mut client, &mut server);
+
+ let expected = Some(String::from("alpn"));
+ assert_eq!(expected.as_ref(), client.info().unwrap().alpn());
+ assert_eq!(expected.as_ref(), server.info().unwrap().alpn());
+}
+
+#[test]
+fn alpn_no_protocol() {
+ fixture_init();
+ let mut client = Client::new("server.example", true).expect("should create client");
+ client.set_alpn(&["a"]).expect("should set ALPN");
+ let mut server = Server::new(&["key"]).expect("should create server");
+ server.set_alpn(&["b"]).expect("should set ALPN");
+
+ connect_fail(&mut client, &mut server);
+
+ // TODO(mt) check the error code
+}
+
+#[test]
+fn alpn_client_only() {
+ fixture_init();
+ let mut client = Client::new("server.example", true).expect("should create client");
+ client.set_alpn(&["alpn"]).expect("should set ALPN");
+ let mut server = Server::new(&["key"]).expect("should create server");
+
+ connect(&mut client, &mut server);
+
+ assert_eq!(None, client.info().unwrap().alpn());
+ assert_eq!(None, server.info().unwrap().alpn());
+}
+
+#[test]
+fn alpn_server_only() {
+ fixture_init();
+ let mut client = Client::new("server.example", true).expect("should create client");
+ let mut server = Server::new(&["key"]).expect("should create server");
+ server.set_alpn(&["alpn"]).expect("should set ALPN");
+
+ connect(&mut client, &mut server);
+
+ assert_eq!(None, client.info().unwrap().alpn());
+ assert_eq!(None, server.info().unwrap().alpn());
+}
+
+#[test]
+fn resume() {
+ let (_, token) = resumption_setup(Resumption::WithoutZeroRtt);
+
+ let mut client = Client::new("server.example", true).expect("should create second client");
+ let mut server = Server::new(&["key"]).expect("should create second server");
+
+ client
+ .enable_resumption(token)
+ .expect("should accept token");
+ connect(&mut client, &mut server);
+
+ assert!(client.info().unwrap().resumed());
+ assert!(server.info().unwrap().resumed());
+}
+
+#[test]
+fn zero_rtt() {
+ let (anti_replay, token) = resumption_setup(Resumption::WithZeroRtt);
+
+ // Finally, 0-RTT should succeed.
+ let mut client = Client::new("server.example", true).expect("should create client");
+ let mut server = Server::new(&["key"]).expect("should create server");
+ client
+ .enable_resumption(token)
+ .expect("should accept token");
+ client.enable_0rtt().expect("should enable 0-RTT");
+ server
+ .enable_0rtt(
+ anti_replay.as_ref().unwrap(),
+ 0xffff_ffff,
+ Box::<PermissiveZeroRttChecker>::default(),
+ )
+ .expect("should enable 0-RTT");
+
+ connect(&mut client, &mut server);
+ assert!(client.info().unwrap().early_data_accepted());
+ assert!(server.info().unwrap().early_data_accepted());
+}
+
+#[test]
+fn zero_rtt_no_eoed() {
+ let (anti_replay, token) = resumption_setup(Resumption::WithZeroRtt);
+
+ // Finally, 0-RTT should succeed.
+ let mut client = Client::new("server.example", true).expect("should create client");
+ let mut server = Server::new(&["key"]).expect("should create server");
+ client
+ .enable_resumption(token)
+ .expect("should accept token");
+ client.enable_0rtt().expect("should enable 0-RTT");
+ client
+ .disable_end_of_early_data()
+ .expect("should disable EOED");
+ server
+ .enable_0rtt(
+ anti_replay.as_ref().unwrap(),
+ 0xffff_ffff,
+ Box::<PermissiveZeroRttChecker>::default(),
+ )
+ .expect("should enable 0-RTT");
+ server
+ .disable_end_of_early_data()
+ .expect("should disable EOED");
+
+ connect(&mut client, &mut server);
+ assert!(client.info().unwrap().early_data_accepted());
+ assert!(server.info().unwrap().early_data_accepted());
+}
+
+#[derive(Debug)]
+struct RejectZeroRtt {}
+impl ZeroRttChecker for RejectZeroRtt {
+ fn check(&self, token: &[u8]) -> ZeroRttCheckResult {
+ assert_eq!(ZERO_RTT_TOKEN_DATA, token);
+ ZeroRttCheckResult::Reject
+ }
+}
+
+#[test]
+fn reject_zero_rtt() {
+ let (anti_replay, token) = resumption_setup(Resumption::WithZeroRtt);
+
+ // Finally, 0-RTT should succeed.
+ let mut client = Client::new("server.example", true).expect("should create client");
+ let mut server = Server::new(&["key"]).expect("should create server");
+ client
+ .enable_resumption(token)
+ .expect("should accept token");
+ client.enable_0rtt().expect("should enable 0-RTT");
+ server
+ .enable_0rtt(
+ anti_replay.as_ref().unwrap(),
+ 0xffff_ffff,
+ Box::new(RejectZeroRtt {}),
+ )
+ .expect("should enable 0-RTT");
+
+ connect(&mut client, &mut server);
+ assert!(!client.info().unwrap().early_data_accepted());
+ assert!(!server.info().unwrap().early_data_accepted());
+}
+
+#[test]
+fn close() {
+ fixture_init();
+ let mut client = Client::new("server.example", true).expect("should create client");
+ let mut server = Server::new(&["key"]).expect("should create server");
+ connect(&mut client, &mut server);
+ client.close();
+ server.close();
+}
+
+#[test]
+fn close_client_twice() {
+ fixture_init();
+ let mut client = Client::new("server.example", true).expect("should create client");
+ let mut server = Server::new(&["key"]).expect("should create server");
+ connect(&mut client, &mut server);
+ client.close();
+ client.close(); // Should be a noop.
+}
+
+#[test]
+fn ech() {
+ fixture_init();
+ let mut server = Server::new(&["key"]).expect("should create server");
+ let (sk, pk) = generate_ech_keys().expect("ECH keys");
+ server
+ .enable_ech(88, "public.example", &sk, &pk)
+ .expect("should enable server ECH");
+
+ let mut client = Client::new("server.example", true).expect("should create client");
+ client
+ .enable_ech(server.ech_config())
+ .expect("should enable client ECH");
+
+ connect(&mut client, &mut server);
+ assert!(client.info().unwrap().ech_accepted());
+ assert!(server.info().unwrap().ech_accepted());
+ assert!(client.preinfo().unwrap().ech_accepted().unwrap());
+ assert!(server.preinfo().unwrap().ech_accepted().unwrap());
+}
+
+#[test]
+fn ech_retry() {
+ const PUBLIC_NAME: &str = "public.example";
+ const PRIVATE_NAME: &str = "private.example";
+ const CONFIG_ID: u8 = 7;
+
+ fixture_init();
+ let mut server = Server::new(&["key"]).unwrap();
+ let (sk, pk) = generate_ech_keys().unwrap();
+ server.enable_ech(CONFIG_ID, PUBLIC_NAME, &sk, &pk).unwrap();
+
+ let mut client = Client::new(PRIVATE_NAME, true).unwrap();
+ let mut cfg = Vec::from(server.ech_config());
+ // Ensure that the version and config_id is correct.
+ assert_eq!(cfg[2], 0xfe);
+ assert_eq!(cfg[3], 0x0d);
+ assert_eq!(cfg[6], CONFIG_ID);
+ // Change the config_id so that the server doesn't recognize this.
+ cfg[6] ^= 0x94;
+ client.enable_ech(&cfg).unwrap();
+
+ // Long version of connect() so that we can check the state.
+ let records = client.handshake_raw(now(), None).unwrap(); // ClientHello
+ let records = forward_records(now(), &mut server, records).unwrap(); // ServerHello...
+ let records = forward_records(now(), &mut client, records).unwrap(); // (empty)
+ assert!(records.is_empty());
+
+ // The client should now be expecting authentication.
+ assert_eq!(
+ *client.state(),
+ HandshakeState::EchFallbackAuthenticationPending(String::from(PUBLIC_NAME))
+ );
+ client.authenticated(AuthenticationStatus::Ok);
+ let Err(Error::EchRetry(updated_config)) = client.handshake_raw(now(), None) else {
+ panic!(
+ "Handshake should fail with EchRetry, state is instead {:?}",
+ client.state()
+ );
+ };
+ assert_eq!(
+ client
+ .preinfo()
+ .unwrap()
+ .ech_public_name()
+ .unwrap()
+ .unwrap(),
+ PUBLIC_NAME
+ );
+ // We don't forward alerts, so we can't tell the server about them.
+ // An ech_required alert should be set though.
+ assert_eq!(client.alert(), Some(&121));
+
+ let mut server = Server::new(&["key"]).unwrap();
+ server.enable_ech(CONFIG_ID, PUBLIC_NAME, &sk, &pk).unwrap();
+ let mut client = Client::new(PRIVATE_NAME, true).unwrap();
+ client.enable_ech(&updated_config).unwrap();
+
+ connect(&mut client, &mut server);
+
+ assert!(client.info().unwrap().ech_accepted());
+ assert!(server.info().unwrap().ech_accepted());
+ assert!(client.preinfo().unwrap().ech_accepted().unwrap());
+ assert!(server.preinfo().unwrap().ech_accepted().unwrap());
+}
diff --git a/third_party/rust/neqo-crypto/tests/ext.rs b/third_party/rust/neqo-crypto/tests/ext.rs
new file mode 100644
index 0000000000..9ae81133f5
--- /dev/null
+++ b/third_party/rust/neqo-crypto/tests/ext.rs
@@ -0,0 +1,99 @@
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![warn(clippy::pedantic)]
+
+use std::{cell::RefCell, rc::Rc};
+
+use neqo_crypto::{
+ constants::{HandshakeMessage, TLS_HS_CLIENT_HELLO, TLS_HS_ENCRYPTED_EXTENSIONS},
+ ext::{ExtensionHandler, ExtensionHandlerResult, ExtensionWriterResult},
+ Client, Server,
+};
+use test_fixture::fixture_init;
+
+mod handshake;
+use crate::handshake::connect;
+
+struct NoopExtensionHandler;
+impl ExtensionHandler for NoopExtensionHandler {}
+
+// This test just handshakes. It doesn't really do anything about capturing the
+#[test]
+fn noop_extension_handler() {
+ fixture_init();
+ let mut client = Client::new("server.example", true).expect("should create client");
+ let mut server = Server::new(&["key"]).expect("should create server");
+
+ client
+ .extension_handler(0xffff, Rc::new(RefCell::new(NoopExtensionHandler)))
+ .expect("installed");
+ server
+ .extension_handler(0xffff, Rc::new(RefCell::new(NoopExtensionHandler)))
+ .expect("installed");
+
+ connect(&mut client, &mut server);
+}
+
+#[derive(Debug, Default)]
+struct SimpleExtensionHandler {
+ written: bool,
+ handled: bool,
+}
+
+impl SimpleExtensionHandler {
+ #[allow(dead_code)]
+ pub fn negotiated(&self) -> bool {
+ self.written && self.handled
+ }
+}
+
+impl ExtensionHandler for SimpleExtensionHandler {
+ fn write(&mut self, msg: HandshakeMessage, d: &mut [u8]) -> ExtensionWriterResult {
+ match msg {
+ TLS_HS_CLIENT_HELLO | TLS_HS_ENCRYPTED_EXTENSIONS => {
+ self.written = true;
+ d[0] = 77;
+ ExtensionWriterResult::Write(1)
+ }
+ _ => ExtensionWriterResult::Skip,
+ }
+ }
+
+ fn handle(&mut self, msg: HandshakeMessage, d: &[u8]) -> ExtensionHandlerResult {
+ match msg {
+ TLS_HS_CLIENT_HELLO | TLS_HS_ENCRYPTED_EXTENSIONS => {
+ self.handled = true;
+ if d.len() != 1 {
+ ExtensionHandlerResult::Alert(50) // decode_error
+ } else if d[0] == 77 {
+ ExtensionHandlerResult::Ok
+ } else {
+ ExtensionHandlerResult::Alert(47) // illegal_parameter
+ }
+ }
+ _ => ExtensionHandlerResult::Alert(110), // unsupported_extension
+ }
+ }
+}
+
+#[test]
+fn simple_extension() {
+ fixture_init();
+ let mut client = Client::new("server.example", true).expect("should create client");
+ let mut server = Server::new(&["key"]).expect("should create server");
+
+ let client_handler = Rc::new(RefCell::new(SimpleExtensionHandler::default()));
+ let ch = Rc::clone(&client_handler);
+ client
+ .extension_handler(0xffff, ch)
+ .expect("client handler installed");
+ let server_handler = Rc::new(RefCell::new(SimpleExtensionHandler::default()));
+ let sh = Rc::clone(&server_handler);
+ server
+ .extension_handler(0xffff, sh)
+ .expect("server handler installed");
+
+ connect(&mut client, &mut server);
+
+ assert!(client_handler.borrow().negotiated());
+ assert!(server_handler.borrow().negotiated());
+}
diff --git a/third_party/rust/neqo-crypto/tests/handshake.rs b/third_party/rust/neqo-crypto/tests/handshake.rs
new file mode 100644
index 0000000000..b2d8b9cc34
--- /dev/null
+++ b/third_party/rust/neqo-crypto/tests/handshake.rs
@@ -0,0 +1,157 @@
+#![allow(dead_code)]
+
+use std::{mem, time::Instant};
+
+use neqo_common::qinfo;
+use neqo_crypto::{
+ AntiReplay, AuthenticationStatus, Client, HandshakeState, RecordList, Res, ResumptionToken,
+ SecretAgent, Server, ZeroRttCheckResult, ZeroRttChecker,
+};
+use test_fixture::{anti_replay, fixture_init, now};
+
+/// Consume records until the handshake state changes.
+pub fn forward_records(
+ now: Instant,
+ agent: &mut SecretAgent,
+ records_in: RecordList,
+) -> Res<RecordList> {
+ let mut expected_state = match agent.state() {
+ HandshakeState::New => HandshakeState::New,
+ _ => HandshakeState::InProgress,
+ };
+ let mut records_out = RecordList::default();
+ for record in records_in {
+ assert_eq!(records_out.len(), 0);
+ assert_eq!(*agent.state(), expected_state);
+
+ records_out = agent.handshake_raw(now, Some(record))?;
+ expected_state = HandshakeState::InProgress;
+ }
+ Ok(records_out)
+}
+
+fn handshake(now: Instant, client: &mut SecretAgent, server: &mut SecretAgent) {
+ let mut a = client;
+ let mut b = server;
+ let mut records = a.handshake_raw(now, None).unwrap();
+ let is_done = |agent: &mut SecretAgent| agent.state().is_final();
+ while !is_done(b) {
+ records = if let Ok(r) = forward_records(now, b, records) {
+ r
+ } else {
+ // TODO(mt) take the alert generated by the failed handshake
+ // and allow it to be sent to the peer.
+ return;
+ };
+
+ if *b.state() == HandshakeState::AuthenticationPending {
+ b.authenticated(AuthenticationStatus::Ok);
+ records = if let Ok(r) = b.handshake_raw(now, None) {
+ r
+ } else {
+ // TODO(mt) - as above.
+ return;
+ }
+ }
+ mem::swap(&mut a, &mut b);
+ }
+}
+
+pub fn connect_at(now: Instant, client: &mut SecretAgent, server: &mut SecretAgent) {
+ handshake(now, client, server);
+ qinfo!("client: {:?}", client.state());
+ qinfo!("server: {:?}", server.state());
+ assert!(client.state().is_connected());
+ assert!(server.state().is_connected());
+}
+
+pub fn connect(client: &mut SecretAgent, server: &mut SecretAgent) {
+ connect_at(now(), client, server);
+}
+
+pub fn connect_fail(client: &mut SecretAgent, server: &mut SecretAgent) {
+ handshake(now(), client, server);
+ assert!(!client.state().is_connected());
+ assert!(!server.state().is_connected());
+}
+
+#[derive(Clone, Copy, Debug)]
+pub enum Resumption {
+ WithoutZeroRtt,
+ WithZeroRtt,
+}
+
+pub const ZERO_RTT_TOKEN_DATA: &[u8] = b"zero-rtt-token";
+
+#[derive(Debug)]
+pub struct PermissiveZeroRttChecker {
+ resuming: bool,
+}
+
+impl Default for PermissiveZeroRttChecker {
+ fn default() -> Self {
+ Self { resuming: true }
+ }
+}
+
+impl ZeroRttChecker for PermissiveZeroRttChecker {
+ fn check(&self, token: &[u8]) -> ZeroRttCheckResult {
+ if self.resuming {
+ assert_eq!(ZERO_RTT_TOKEN_DATA, token);
+ } else {
+ assert!(token.is_empty());
+ }
+ ZeroRttCheckResult::Accept
+ }
+}
+
+fn zero_rtt_setup(
+ mode: Resumption,
+ client: &mut Client,
+ server: &mut Server,
+) -> Option<AntiReplay> {
+ if let Resumption::WithZeroRtt = mode {
+ client.enable_0rtt().expect("should enable 0-RTT on client");
+
+ let anti_replay = anti_replay();
+ server
+ .enable_0rtt(
+ &anti_replay,
+ 0xffff_ffff,
+ Box::new(PermissiveZeroRttChecker { resuming: false }),
+ )
+ .expect("should enable 0-RTT on server");
+ Some(anti_replay)
+ } else {
+ None
+ }
+}
+
+pub fn resumption_setup(mode: Resumption) -> (Option<AntiReplay>, ResumptionToken) {
+ fixture_init();
+
+ let mut client = Client::new("server.example", true).expect("should create client");
+ let mut server = Server::new(&["key"]).expect("should create server");
+ let anti_replay = zero_rtt_setup(mode, &mut client, &mut server);
+
+ connect(&mut client, &mut server);
+
+ assert!(!client.info().unwrap().resumed());
+ assert!(!server.info().unwrap().resumed());
+ assert!(!client.info().unwrap().early_data_accepted());
+ assert!(!server.info().unwrap().early_data_accepted());
+
+ let server_records = server
+ .send_ticket(now(), ZERO_RTT_TOKEN_DATA)
+ .expect("ticket sent");
+ assert_eq!(server_records.len(), 1);
+ let client_records = client
+ .handshake_raw(now(), server_records.into_iter().next())
+ .expect("records ingested");
+ assert_eq!(client_records.len(), 0);
+
+ // `client` is about to go out of scope,
+ // but we only need to keep the resumption token, so clone it.
+ let token = client.resumption_token().expect("token is present");
+ (anti_replay, token)
+}
diff --git a/third_party/rust/neqo-crypto/tests/hkdf.rs b/third_party/rust/neqo-crypto/tests/hkdf.rs
new file mode 100644
index 0000000000..b4dde482f8
--- /dev/null
+++ b/third_party/rust/neqo-crypto/tests/hkdf.rs
@@ -0,0 +1,155 @@
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![warn(clippy::pedantic)]
+
+use neqo_crypto::{
+ constants::{
+ Cipher, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256,
+ TLS_VERSION_1_3,
+ },
+ hkdf, SymKey,
+};
+use test_fixture::fixture_init;
+
+const SALT: &[u8] = &[
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+];
+
+const IKM: &[u8] = &[
+ 0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+];
+
+const SESSION_HASH: &[u8] = &[
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
+ 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+ 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+ 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+];
+
+fn cipher_hash_len(cipher: Cipher) -> usize {
+ match cipher {
+ TLS_AES_128_GCM_SHA256 | TLS_CHACHA20_POLY1305_SHA256 => 32,
+ TLS_AES_256_GCM_SHA384 => 48,
+ _ => unreachable!(),
+ }
+}
+
+fn import_keys(cipher: Cipher) -> (SymKey, SymKey) {
+ let l = cipher_hash_len(cipher);
+ (
+ hkdf::import_key(TLS_VERSION_1_3, &SALT[0..l]).expect("import salt"),
+ hkdf::import_key(TLS_VERSION_1_3, &IKM[0..l]).expect("import IKM"),
+ )
+}
+
+fn extract(cipher: Cipher, expected: &[u8]) {
+ fixture_init();
+ let (salt, ikm) = import_keys(cipher);
+ let prk = hkdf::extract(TLS_VERSION_1_3, cipher, Some(&salt), &ikm)
+ .expect("HKDF Extract should work");
+ let raw_prk = prk.as_bytes().expect("key should have bytes");
+ assert_eq!(raw_prk, expected);
+}
+
+#[test]
+fn extract_sha256() {
+ const EXPECTED: &[u8] = &[
+ 0xa5, 0x68, 0x02, 0x5a, 0x95, 0xc9, 0x7f, 0x55, 0x38, 0xbc, 0xf7, 0x97, 0xcc, 0x0f, 0xd5,
+ 0xf6, 0xa8, 0x8d, 0x15, 0xbc, 0x0e, 0x85, 0x74, 0x70, 0x3c, 0xa3, 0x65, 0xbd, 0x76, 0xcf,
+ 0x9f, 0xd3,
+ ];
+ extract(TLS_AES_128_GCM_SHA256, EXPECTED);
+ extract(TLS_CHACHA20_POLY1305_SHA256, EXPECTED);
+}
+
+#[test]
+fn extract_sha384() {
+ extract(
+ TLS_AES_256_GCM_SHA384,
+ &[
+ 0x01, 0x93, 0xc0, 0x07, 0x3f, 0x6a, 0x83, 0x0e, 0x2e, 0x4f, 0xb2, 0x58, 0xe4, 0x00,
+ 0x08, 0x5c, 0x68, 0x9c, 0x37, 0x32, 0x00, 0x37, 0xff, 0xc3, 0x1c, 0x5b, 0x98, 0x0b,
+ 0x02, 0x92, 0x3f, 0xfd, 0x73, 0x5a, 0x6f, 0x2a, 0x95, 0xa3, 0xee, 0xf6, 0xd6, 0x8e,
+ 0x6f, 0x86, 0xea, 0x63, 0xf8, 0x33,
+ ],
+ );
+}
+
+fn derive_secret(cipher: Cipher, expected: &[u8]) {
+ fixture_init();
+
+ // Here we only use the salt as the PRK.
+ let (prk, _) = import_keys(cipher);
+ let secret = hkdf::expand_label(TLS_VERSION_1_3, cipher, &prk, &[], "master secret")
+ .expect("HKDF-Expand-Label should work");
+ let raw_secret = secret.as_bytes().expect("key should have bytes");
+ assert_eq!(raw_secret, expected);
+}
+
+#[test]
+fn derive_secret_sha256() {
+ const EXPECTED: &[u8] = &[
+ 0xb7, 0x08, 0x00, 0xe3, 0x8e, 0x48, 0x68, 0x91, 0xb1, 0x0f, 0x5e, 0x6f, 0x22, 0x53, 0x6b,
+ 0x84, 0x69, 0x75, 0xaa, 0xa3, 0x2a, 0xe7, 0xde, 0xaa, 0xc3, 0xd1, 0xb4, 0x05, 0x22, 0x5c,
+ 0x68, 0xf5,
+ ];
+ derive_secret(TLS_AES_128_GCM_SHA256, EXPECTED);
+ derive_secret(TLS_CHACHA20_POLY1305_SHA256, EXPECTED);
+}
+
+#[test]
+fn derive_secret_sha384() {
+ derive_secret(
+ TLS_AES_256_GCM_SHA384,
+ &[
+ 0x13, 0xd3, 0x36, 0x9f, 0x3c, 0x78, 0xa0, 0x32, 0x40, 0xee, 0x16, 0xe9, 0x11, 0x12,
+ 0x66, 0xc7, 0x51, 0xad, 0xd8, 0x3c, 0xa1, 0xa3, 0x97, 0x74, 0xd7, 0x45, 0xff, 0xa7,
+ 0x88, 0x9e, 0x52, 0x17, 0x2e, 0xaa, 0x3a, 0xd2, 0x35, 0xd8, 0xd5, 0x35, 0xfd, 0x65,
+ 0x70, 0x9f, 0xa9, 0xf9, 0xfa, 0x23,
+ ],
+ );
+}
+
+fn expand_label(cipher: Cipher, expected: &[u8]) {
+ fixture_init();
+
+ let l = cipher_hash_len(cipher);
+ let (prk, _) = import_keys(cipher);
+ let secret = hkdf::expand_label(
+ TLS_VERSION_1_3,
+ cipher,
+ &prk,
+ &SESSION_HASH[0..l],
+ "master secret",
+ )
+ .expect("HKDF-Expand-Label should work");
+ let raw_secret = secret.as_bytes().expect("key should have bytes");
+ assert_eq!(raw_secret, expected);
+}
+
+#[test]
+fn expand_label_sha256() {
+ const EXPECTED: &[u8] = &[
+ 0x3e, 0x4e, 0x6e, 0xd0, 0xbc, 0xc4, 0xf4, 0xff, 0xf0, 0xf5, 0x69, 0xd0, 0x6c, 0x1e, 0x0e,
+ 0x10, 0x32, 0xaa, 0xd7, 0xa3, 0xef, 0xf6, 0xa8, 0x65, 0x8e, 0xbe, 0xee, 0xc7, 0x1f, 0x01,
+ 0x6d, 0x3c,
+ ];
+ expand_label(TLS_AES_128_GCM_SHA256, EXPECTED);
+ expand_label(TLS_CHACHA20_POLY1305_SHA256, EXPECTED);
+}
+
+#[test]
+fn expand_label_sha384() {
+ expand_label(
+ TLS_AES_256_GCM_SHA384,
+ &[
+ 0x41, 0xea, 0x77, 0x09, 0x8c, 0x90, 0x04, 0x10, 0xec, 0xbc, 0x37, 0xd8, 0x5b, 0x54,
+ 0xcd, 0x7b, 0x08, 0x15, 0x13, 0x20, 0xed, 0x1e, 0x3f, 0x54, 0x74, 0xf7, 0x8b, 0x06,
+ 0x38, 0x28, 0x06, 0x37, 0x75, 0x23, 0xa2, 0xb7, 0x34, 0xb1, 0x72, 0x2e, 0x59, 0x6d,
+ 0x5a, 0x31, 0xf5, 0x53, 0xab, 0x99,
+ ],
+ );
+}
diff --git a/third_party/rust/neqo-crypto/tests/hp.rs b/third_party/rust/neqo-crypto/tests/hp.rs
new file mode 100644
index 0000000000..43b96869d8
--- /dev/null
+++ b/third_party/rust/neqo-crypto/tests/hp.rs
@@ -0,0 +1,82 @@
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![warn(clippy::pedantic)]
+
+use std::mem;
+
+use neqo_crypto::{
+ constants::{
+ Cipher, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256,
+ TLS_VERSION_1_3,
+ },
+ hkdf,
+ hp::HpKey,
+};
+use test_fixture::fixture_init;
+
+fn make_hp(cipher: Cipher) -> HpKey {
+ fixture_init();
+ let ikm = hkdf::import_key(TLS_VERSION_1_3, &[0; 16]).expect("import IKM");
+ let prk = hkdf::extract(TLS_VERSION_1_3, cipher, None, &ikm).expect("extract works");
+ HpKey::extract(TLS_VERSION_1_3, cipher, &prk, "hp").expect("extract label works")
+}
+
+fn hp_test(cipher: Cipher, expected: &[u8]) {
+ let hp = make_hp(cipher);
+ let mask = hp.mask(&[0; 16]).expect("should produce a mask");
+ assert_eq!(mask, expected, "first invocation should be correct");
+
+ #[allow(clippy::redundant_clone)] // This is deliberate.
+ let hp2 = hp.clone();
+ let mask = hp2.mask(&[0; 16]).expect("clone produces mask");
+ assert_eq!(mask, expected, "clone should produce the same mask");
+
+ let mask = hp.mask(&[0; 16]).expect("should produce a mask again");
+ assert_eq!(mask, expected, "second invocation should be the same");
+}
+
+#[test]
+fn aes128() {
+ const EXPECTED: &[u8] = &[
+ 0x04, 0x7b, 0xda, 0x65, 0xc3, 0x41, 0xcf, 0xbc, 0x5d, 0xe1, 0x75, 0x2b, 0x9d, 0x7d, 0xc3,
+ 0x14,
+ ];
+
+ hp_test(TLS_AES_128_GCM_SHA256, EXPECTED);
+}
+
+#[test]
+fn aes256() {
+ const EXPECTED: &[u8] = &[
+ 0xb5, 0xea, 0xa2, 0x1c, 0x25, 0x77, 0x48, 0x18, 0xbf, 0x25, 0xea, 0xfa, 0xbd, 0x8d, 0x80,
+ 0x2b,
+ ];
+
+ hp_test(TLS_AES_256_GCM_SHA384, EXPECTED);
+}
+
+#[test]
+fn chacha20_ctr() {
+ const EXPECTED: &[u8] = &[
+ 0x34, 0x11, 0xb3, 0x53, 0x02, 0x0b, 0x16, 0xda, 0x0a, 0x85, 0x5a, 0x52, 0x0d, 0x06, 0x07,
+ 0x1f, 0x4a, 0xb1, 0xaf, 0xf7, 0x83, 0xa8, 0xf0, 0x29, 0xc3, 0x19, 0xef, 0x57, 0x48, 0xe7,
+ 0x8e, 0x3e, 0x11, 0x91, 0xe1, 0xd5, 0x92, 0x8f, 0x61, 0x6d, 0x3f, 0x3d, 0x76, 0xb5, 0x29,
+ 0xf1, 0x62, 0x2f, 0x1e, 0xad, 0xdd, 0x23, 0x59, 0x45, 0xac, 0xd2, 0x19, 0x8a, 0xb4, 0x1f,
+ 0x2f, 0x52, 0x46, 0x89,
+ ];
+
+ hp_test(TLS_CHACHA20_POLY1305_SHA256, EXPECTED);
+}
+
+#[test]
+#[should_panic(expected = "out of range")]
+fn aes_short() {
+ let hp = make_hp(TLS_AES_128_GCM_SHA256);
+ mem::drop(hp.mask(&[0; 15]));
+}
+
+#[test]
+#[should_panic(expected = "out of range")]
+fn chacha20_short() {
+ let hp = make_hp(TLS_CHACHA20_POLY1305_SHA256);
+ mem::drop(hp.mask(&[0; 15]));
+}
diff --git a/third_party/rust/neqo-crypto/tests/init.rs b/third_party/rust/neqo-crypto/tests/init.rs
new file mode 100644
index 0000000000..21291ceebb
--- /dev/null
+++ b/third_party/rust/neqo-crypto/tests/init.rs
@@ -0,0 +1,44 @@
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![warn(clippy::pedantic)]
+
+// This uses external interfaces to neqo_crypto rather than being a module
+// inside of lib.rs. Because all other code uses the test_fixture module,
+// they will be calling into the public version of init_db(). Calling into
+// the version exposed to an inner module in lib.rs would result in calling
+// a different version of init_db. That causes explosions as they get
+// different versions of the Once instance they use and they initialize NSS
+// twice, probably likely in parallel. That doesn't work out well.
+use neqo_crypto::{assert_initialized, init_db};
+
+// Pull in the NSS internals so that we can ask NSS if it thinks that
+// it is properly initialized.
+#[allow(
+ dead_code,
+ non_upper_case_globals,
+ clippy::redundant_static_lifetimes,
+ clippy::unseparated_literal_suffix,
+ clippy::upper_case_acronyms
+)]
+mod nss {
+ include!(concat!(env!("OUT_DIR"), "/nss_init.rs"));
+}
+
+#[cfg(nss_nodb)]
+#[test]
+fn init_nodb() {
+ init();
+ assert_initialized();
+ unsafe {
+ assert!(nss::NSS_IsInitialized() != 0);
+ }
+}
+
+#[cfg(not(nss_nodb))]
+#[test]
+fn init_withdb() {
+ init_db(::test_fixture::NSS_DB_PATH);
+ assert_initialized();
+ unsafe {
+ assert!(nss::NSS_IsInitialized() != 0);
+ }
+}
diff --git a/third_party/rust/neqo-crypto/tests/selfencrypt.rs b/third_party/rust/neqo-crypto/tests/selfencrypt.rs
new file mode 100644
index 0000000000..fd9d4ea1ea
--- /dev/null
+++ b/third_party/rust/neqo-crypto/tests/selfencrypt.rs
@@ -0,0 +1,96 @@
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![warn(clippy::pedantic)]
+#![cfg(not(feature = "fuzzing"))]
+
+use neqo_crypto::{
+ constants::{TLS_AES_128_GCM_SHA256, TLS_VERSION_1_3},
+ init,
+ selfencrypt::SelfEncrypt,
+ Error,
+};
+
+#[test]
+fn se_create() {
+ init();
+ SelfEncrypt::new(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256).expect("constructor works");
+}
+
+const PLAINTEXT: &[u8] = b"PLAINTEXT";
+const AAD: &[u8] = b"AAD";
+
+fn sealed() -> (SelfEncrypt, Vec<u8>) {
+ init();
+ let se = SelfEncrypt::new(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256).unwrap();
+ let sealed = se.seal(AAD, PLAINTEXT).expect("sealing works");
+ (se, sealed)
+}
+
+#[test]
+fn seal_open() {
+ let (se, sealed) = sealed();
+ let opened = se.open(AAD, &sealed).expect("opening works");
+ assert_eq!(&opened[..], PLAINTEXT);
+}
+
+#[test]
+fn seal_rotate_open() {
+ let (mut se, sealed) = sealed();
+ se.rotate().expect("rotate should be infallible");
+ let opened = se.open(AAD, &sealed).expect("opening works");
+ assert_eq!(&opened[..], PLAINTEXT);
+}
+
+#[test]
+fn seal_rotate_twice_open() {
+ let (mut se, sealed) = sealed();
+ se.rotate().expect("rotate should be infallible");
+ se.rotate().expect("rotate should be infallible");
+ let res = se.open(AAD, &sealed);
+ assert_eq!(res.unwrap_err(), Error::SelfEncryptFailure);
+}
+
+#[test]
+fn damage_version() {
+ let (se, mut sealed) = sealed();
+ sealed[0] ^= 0x80;
+ let res = se.open(AAD, &sealed);
+ assert_eq!(res.unwrap_err(), Error::SelfEncryptFailure);
+}
+
+fn assert_bad_data<T>(res: Result<T, Error>) {
+ if let Err(Error::NssError { name, .. }) = res {
+ assert_eq!(name, "SEC_ERROR_BAD_DATA");
+ }
+}
+
+#[test]
+fn damage_salt() {
+ let (se, mut sealed) = sealed();
+ sealed[4] ^= 0x10;
+ let res = se.open(AAD, &sealed);
+ assert_bad_data(res);
+}
+
+#[test]
+fn damage_ciphertext() {
+ let (se, mut sealed) = sealed();
+ sealed[20] ^= 0x2f;
+ let res = se.open(AAD, &sealed);
+ assert_bad_data(res);
+}
+
+#[test]
+fn damage_auth_tag() {
+ let (se, mut sealed) = sealed();
+ let idx = sealed.len() - 1;
+ sealed[idx] ^= 0x3;
+ let res = se.open(AAD, &sealed);
+ assert_bad_data(res);
+}
+
+#[test]
+fn truncate() {
+ let (se, sealed) = sealed();
+ let res = se.open(AAD, &sealed[0..(sealed.len() - 1)]);
+ assert_bad_data(res);
+}