Skip to content

Commit 819f23b

Browse files
authored
chore: Add tests for Gecko-specific functions (#3280)
1 parent 5bceefa commit 819f23b

11 files changed

Lines changed: 200 additions & 28 deletions

File tree

neqo-crypto/bindings/bindings.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ types = [
1414
"SSLExtensionHandler",
1515
"SSLExtensionType",
1616
"SSLExtensionWriter",
17+
"SSLExtraServerCertData",
1718
"SSLHelloRetryRequestAction",
1819
"SSLHelloRetryRequestCallback",
1920
"SSLNamedGroup",
@@ -80,12 +81,12 @@ variables = [
8081
]
8182
opaque = [
8283
"CERTCertificate",
84+
"CERTCertificateList",
8385
"PK11SymKey",
8486
"PLArenaPool",
8587
"PRFileDesc",
8688
"SECKEYPrivateKey",
8789
"SECKEYPublicKey",
88-
"SSLExtraServerCertData",
8990
]
9091

9192
[nss_sslopt]

neqo-crypto/src/agent.rs

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use std::{
1313
cell::RefCell,
1414
ffi::{CStr, CString},
1515
fmt::{self, Debug, Display, Formatter},
16-
mem::MaybeUninit,
16+
mem::{size_of, MaybeUninit},
1717
ops::{Deref, DerefMut},
1818
os::raw::{c_uint, c_void},
1919
pin::Pin,
@@ -414,7 +414,6 @@ impl SecretAgentInfo {
414414
pub const fn alpn(&self) -> Option<&String> {
415415
self.alpn.as_ref()
416416
}
417-
// TODO: Not used in neqo, but Gecko calls it. Needs a test to call it.
418417
#[must_use]
419418
pub const fn signature_scheme(&self) -> SignatureScheme {
420419
self.signature_scheme
@@ -1256,6 +1255,17 @@ pub struct Server {
12561255
zero_rtt_check: Option<Pin<Box<ZeroRttCheckState>>>,
12571256
}
12581257

1258+
fn load_cert_and_key(name: &str) -> Res<(p11::Certificate, PrivateKey)> {
1259+
let c = CString::new(name)?;
1260+
let cert = p11::Certificate::from_ptr(unsafe {
1261+
p11::PK11_FindCertFromNickname(c.as_ptr(), null_mut())
1262+
})
1263+
.map_err(|_| Error::CertificateLoading)?;
1264+
let key = PrivateKey::from_ptr(unsafe { p11::PK11_FindKeyByAnyCert(*cert, null_mut()) })
1265+
.map_err(|_| Error::CertificateLoading)?;
1266+
Ok((cert, key))
1267+
}
1268+
12591269
impl Server {
12601270
/// Create a new server agent.
12611271
///
@@ -1264,22 +1274,63 @@ impl Server {
12641274
/// Errors returned when NSS fails.
12651275
pub fn new<A: AsRef<str>>(certificates: &[A]) -> Res<Self> {
12661276
let mut agent = SecretAgent::new()?;
1277+
for n in certificates {
1278+
let (cert, key) = load_cert_and_key(n.as_ref())?;
1279+
secstatus_to_res(unsafe {
1280+
ssl::SSL_ConfigServerCert(agent.fd, (*cert).cast(), (*key).cast(), null(), 0)
1281+
})?;
1282+
}
1283+
agent.ready(true, true)?;
1284+
Ok(Self {
1285+
agent,
1286+
zero_rtt_check: None,
1287+
})
1288+
}
12671289

1290+
/// Create a server with OCSP responses and SCTs configured.
1291+
/// Not suitable for multiple certificates, because it configures the same OCSP/SCT for all
1292+
/// certificates. In other words, this is good for testing that the plumbing works, not for
1293+
/// a real server.
1294+
///
1295+
/// # Errors
1296+
///
1297+
/// Errors returned when NSS fails.
1298+
pub fn new_with_ocsp_and_scts<A: AsRef<str>>(
1299+
certificates: &[A],
1300+
ocsp_responses: &[&[u8]],
1301+
scts: &[u8],
1302+
) -> Res<Self> {
1303+
let mut agent = SecretAgent::new()?;
12681304
for n in certificates {
1269-
let c = CString::new(n.as_ref())?;
1270-
let cert_ptr = unsafe { p11::PK11_FindCertFromNickname(c.as_ptr(), null_mut()) };
1271-
let Ok(cert) = p11::Certificate::from_ptr(cert_ptr) else {
1272-
return Err(Error::CertificateLoading);
1305+
let (cert, key) = load_cert_and_key(n.as_ref())?;
1306+
let ocsp_items: Vec<p11::SECItem> = ocsp_responses
1307+
.iter()
1308+
.map(|b| p11::Item::wrap(b))
1309+
.collect::<Res<_>>()?;
1310+
let ocsp_array = ssl::SECItemArrayStr {
1311+
items: ocsp_items.as_ptr().cast::<ssl::SECItem>().cast_mut(),
1312+
len: c_uint::try_from(ocsp_items.len())?,
12731313
};
1274-
let key_ptr = unsafe { p11::PK11_FindKeyByAnyCert(*cert, null_mut()) };
1275-
let Ok(key) = PrivateKey::from_ptr(key_ptr) else {
1276-
return Err(Error::CertificateLoading);
1314+
let sct_item = p11::Item::wrap(scts)?;
1315+
let extra = ssl::SSLExtraServerCertDataStr {
1316+
// ssl_auth_null means "I don't care what sort of certificate this is".
1317+
authType: ssl::SSLAuthType::ssl_auth_null,
1318+
certChain: null(),
1319+
stapledOCSPResponses: &ocsp_array,
1320+
signedCertTimestamps: std::ptr::from_ref(&sct_item).cast(),
1321+
delegCred: null(),
1322+
delegCredPrivKey: null(),
12771323
};
12781324
secstatus_to_res(unsafe {
1279-
ssl::SSL_ConfigServerCert(agent.fd, (*cert).cast(), (*key).cast(), null(), 0)
1325+
ssl::SSL_ConfigServerCert(
1326+
agent.fd,
1327+
(*cert).cast(),
1328+
(*key).cast(),
1329+
&extra,
1330+
c_uint::try_from(size_of::<ssl::SSLExtraServerCertDataStr>())?,
1331+
)
12801332
})?;
12811333
}
1282-
12831334
agent.ready(true, true)?;
12841335
Ok(Self {
12851336
agent,

neqo-crypto/src/auth.rs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,25 @@ impl From<PRErrorCode> for AuthenticationStatus {
5050
}
5151
}
5252

53-
#[test]
54-
fn authentication_status_from_error_code() {
55-
assert_eq!(AuthenticationStatus::from(0), AuthenticationStatus::Ok);
56-
assert_eq!(
57-
AuthenticationStatus::from(12345),
58-
AuthenticationStatus::Unknown
59-
);
53+
#[cfg(test)]
54+
#[cfg_attr(coverage_nightly, coverage(off))]
55+
mod tests {
56+
use super::*;
57+
58+
#[test]
59+
fn authentication_status_from_error_code() {
60+
assert_eq!(
61+
AuthenticationStatus::from(sec::SEC_ERROR_EXPIRED_CERTIFICATE),
62+
AuthenticationStatus::CertExpired
63+
);
64+
assert_eq!(AuthenticationStatus::from(0), AuthenticationStatus::Ok);
65+
assert_eq!(
66+
AuthenticationStatus::from(12345),
67+
AuthenticationStatus::Unknown
68+
);
69+
assert_eq!(
70+
AuthenticationStatus::from(i32::MIN),
71+
AuthenticationStatus::Unknown
72+
);
73+
}
6074
}

neqo-crypto/src/cert.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,11 @@ impl CertificateInfo {
9191
self.certs.into_iter()
9292
}
9393

94-
// TODO: Not used in neqo, but Gecko calls it. Needs a test to call it.
9594
#[must_use]
9695
pub const fn stapled_ocsp_responses(&self) -> Option<&Vec<Vec<u8>>> {
9796
self.stapled_ocsp_responses.as_ref()
9897
}
9998

100-
// TODO: Not used in neqo, but Gecko calls it. Needs a test to call it.
10199
#[must_use]
102100
pub const fn signed_cert_timestamp(&self) -> Option<&Vec<u8>> {
103101
self.signed_cert_timestamp.as_ref()

neqo-crypto/tests/agent.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use neqo_crypto::{
1010
agent::CertificateCompressor, generate_ech_keys, AuthenticationStatus, Client, Error,
1111
HandshakeState, Res, SecretAgentPreInfo, Server, ZeroRttCheckResult, ZeroRttChecker,
1212
TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256, TLS_GRP_EC_SECP256R1, TLS_GRP_EC_X25519,
13-
TLS_VERSION_1_3,
13+
TLS_SIG_ECDSA_SECP256R1_SHA256, TLS_VERSION_1_3,
1414
};
1515

1616
mod handshake;
@@ -66,6 +66,10 @@ fn basic() {
6666
let client_info = client.info().expect("got info");
6767
assert_eq!(TLS_VERSION_1_3, client_info.version());
6868
assert_eq!(TLS_AES_128_GCM_SHA256, client_info.cipher_suite());
69+
assert_eq!(
70+
TLS_SIG_ECDSA_SECP256R1_SHA256,
71+
client_info.signature_scheme()
72+
);
6973

7074
let bytes = server.handshake(now(), &bytes[..]).expect("finish");
7175
assert!(bytes.is_empty());
@@ -74,6 +78,10 @@ fn basic() {
7478
let server_info = server.info().expect("got info");
7579
assert_eq!(TLS_VERSION_1_3, server_info.version());
7680
assert_eq!(TLS_AES_128_GCM_SHA256, server_info.cipher_suite());
81+
assert_eq!(
82+
TLS_SIG_ECDSA_SECP256R1_SHA256,
83+
server_info.signature_scheme()
84+
);
7785
}
7886

7987
fn check_client_preinfo(client_preinfo: &SecretAgentPreInfo) {
@@ -134,11 +142,35 @@ fn raw() {
134142
// The client should have one certificate for the server.
135143
let certs = client.peer_certificate().unwrap();
136144
assert_eq!(1, certs.into_iter().count());
145+
assert!(certs.stapled_ocsp_responses().unwrap().is_empty());
146+
assert!(certs.signed_cert_timestamp().unwrap().is_empty());
137147

138148
// The server shouldn't have a client certificate.
139149
assert!(server.peer_certificate().is_none());
140150
}
141151

152+
#[test]
153+
fn ocsp_stapling_and_signed_cert_timestamps() {
154+
fixture_init();
155+
let mut client = Client::new("server.example", true).expect("should create client");
156+
client
157+
.set_option(neqo_crypto::Opt::SignedCertificateTimestamps, true)
158+
.unwrap();
159+
let ocsp_response = b"fake ocsp response";
160+
let scts = b"fake signed certificate timestamps";
161+
let mut server = Server::new_with_ocsp_and_scts(&["key"], &[&ocsp_response[..]], scts)
162+
.expect("should create server");
163+
164+
connect(&mut client, &mut server);
165+
166+
let certs = client.peer_certificate().unwrap();
167+
assert_eq!(1, certs.into_iter().count());
168+
let ocsp = certs.stapled_ocsp_responses().unwrap();
169+
assert_eq!(ocsp.len(), 1);
170+
assert_eq!(ocsp[0], ocsp_response);
171+
assert_eq!(certs.signed_cert_timestamp().unwrap(), scts);
172+
}
173+
142174
#[test]
143175
fn chacha_client() {
144176
fixture_init();

neqo-http3/src/conn_params.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@ impl Http3Parameters {
137137
self.connect
138138
}
139139

140-
// TODO: Not used in neqo, but Gecko calls it. Needs a test to call it.
141140
#[must_use]
142141
pub const fn http3_datagram(mut self, http3_datagram: bool) -> Self {
143142
self.http3_datagram = http3_datagram;
@@ -193,4 +192,12 @@ mod tests {
193192
fn max_table_size_decoder_rejects_above_limit() {
194193
_ = Http3Parameters::default().max_table_size_decoder(1 << 30);
195194
}
195+
196+
#[test]
197+
fn http3_datagram_setting() {
198+
let params = Http3Parameters::default()
199+
.connection_parameters(ConnectionParameters::default().datagram_size(1200))
200+
.http3_datagram(true);
201+
assert!(params.get_http3_datagram());
202+
}
196203
}

neqo-http3/src/connection_client.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -876,8 +876,6 @@ impl Http3Client {
876876
/// # Errors
877877
///
878878
/// It may return `InvalidStreamId` if a stream does not exist anymore.
879-
//
880-
// TODO: Not used in neqo, but Gecko calls it. Needs a test to call it.
881879
pub fn webtransport_set_sendorder(
882880
&mut self,
883881
stream_id: StreamId,

neqo-http3/src/features/extended_connect/tests/webtransport/streams.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,3 +1127,16 @@ fn wt_session_close_frame_and_streams_client() {
11271127
)),
11281128
);
11291129
}
1130+
1131+
#[test]
1132+
fn wt_set_sendorder() {
1133+
let mut wt = WtTest::new();
1134+
let wt_session = wt.create_wt_session();
1135+
let wt_stream = wt.create_wt_stream_client(wt_session.stream_id(), StreamType::UniDi);
1136+
wt.client
1137+
.webtransport_set_sendorder(wt_stream, Some(42))
1138+
.unwrap();
1139+
wt.client
1140+
.webtransport_set_sendorder(wt_stream, None)
1141+
.unwrap();
1142+
}

neqo-transport/src/connection/mod.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,8 +490,6 @@ impl Connection {
490490

491491
/// # Errors
492492
/// When the operation fails.
493-
//
494-
// TODO: Not used in neqo, but Gecko calls it. Needs a test to call it.
495493
pub fn set_certificate_compression<T: CertificateCompressor>(&mut self) -> Res<()> {
496494
self.crypto.tls_mut().set_certificate_compression::<T>()?;
497495
Ok(())

neqo-transport/src/connection/params.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,6 @@ impl ConnectionParameters {
449449
self.pmtud_iface_mtu
450450
}
451451

452-
// TODO: Not used in neqo, but Gecko calls it. Needs a test to call it.
453452
#[must_use]
454453
pub const fn pmtud_iface_mtu(mut self, pmtud_iface_mtu: bool) -> Self {
455454
self.pmtud_iface_mtu = pmtud_iface_mtu;
@@ -556,3 +555,17 @@ impl ConnectionParameters {
556555
Ok(tps)
557556
}
558557
}
558+
559+
#[cfg(test)]
560+
#[cfg_attr(coverage_nightly, coverage(off))]
561+
mod tests {
562+
use super::*;
563+
564+
#[test]
565+
fn pmtud_iface_mtu() {
566+
let params = ConnectionParameters::default().pmtud_iface_mtu(true);
567+
assert!(params.pmtud_iface_mtu_enabled());
568+
let params = params.pmtud_iface_mtu(false);
569+
assert!(!params.pmtud_iface_mtu_enabled());
570+
}
571+
}

0 commit comments

Comments
 (0)