Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
863 changes: 863 additions & 0 deletions WEBTRANSPORT_IMPLEMENTATION_PLAN.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions neqo-crypto/bindings/bindings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ functions = [
"SSL_ConfigServerCert",
"SSL_ConfigServerSessionIDCache",
"SSL_DestroyResumptionTokenInfo",
"SSL_ExportKeyingMaterial",
"SSL_GetChannelInfo",
"SSL_GetExperimentalAPI",
"SSL_GetImplementedCiphers",
Expand Down
44 changes: 44 additions & 0 deletions neqo-crypto/src/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,50 @@ impl SecretAgent {
CertificateInfo::new(self.fd)
}

/// Export keying material per RFC 5705/8446.
///
/// This can only be called after the handshake is complete.
///
/// # Arguments
/// * `label` - The exporter label
/// * `context` - Optional context data
/// * `out_len` - Length of output keying material
///
/// # Errors
///
/// Returns error if handshake is not complete or export fails.
pub fn export_keying_material(
&self,
label: &[u8],
context: Option<&[u8]>,
out_len: usize,
) -> Res<Vec<u8>> {
if !self.state.is_connected() {
return Err(Error::InvalidState);
}

let mut out = vec![0u8; out_len];
let (has_context, ctx_ptr, ctx_len) = match context {
Some(ctx) => (PRBool::from(true), ctx.as_ptr(), ctx.len()),
None => (PRBool::from(false), null(), 0),
};

secstatus_to_res(unsafe {
ssl::SSL_ExportKeyingMaterial(
self.fd,
label.as_ptr().cast(),
c_uint::try_from(label.len())?,
has_context,
ctx_ptr,
c_uint::try_from(ctx_len)?,
out.as_mut_ptr(),
c_uint::try_from(out_len)?,
)
})?;

Ok(out)
}

/// Return any fatal alert that the TLS stack might have sent.
#[must_use]
pub fn alert(&self) -> Option<Alert> {
Expand Down
2 changes: 2 additions & 0 deletions neqo-crypto/src/err.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ pub enum Error {
InvalidAlpn,
#[error("Invalid epoch")]
InvalidEpoch,
#[error("Invalid state for this operation")]
InvalidState,
#[error("Invalid certificate compression ID")]
InvalidCertificateCompressionID,
#[error("Mixed handshake method")]
Expand Down
119 changes: 119 additions & 0 deletions neqo-crypto/tests/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -829,3 +829,122 @@ fn connection_fails_encoder_returned_too_long() {

connect_fail(&mut client, &mut server);
}

#[test]
fn export_keying_material_basic() {
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);

let label = b"EXPORTER-test";
let result = client.export_keying_material(label, None, 32);
assert!(result.is_ok());
let material = result.unwrap();
assert_eq!(material.len(), 32);
}

#[test]
fn export_keying_material_with_context() {
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);

let label = b"EXPORTER-test";
let context = b"context-data";
let result = client.export_keying_material(label, Some(context), 32);
assert!(result.is_ok());
let material = result.unwrap();
assert_eq!(material.len(), 32);
}

#[test]
fn export_keying_material_different_labels() {
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);

let label1 = b"EXPORTER-test1";
let label2 = b"EXPORTER-test2";

let material1 = client
.export_keying_material(label1, None, 32)
.expect("export with label1");
let material2 = client
.export_keying_material(label2, None, 32)
.expect("export with label2");

assert_eq!(material1.len(), 32);
assert_eq!(material2.len(), 32);
assert_ne!(
material1, material2,
"Different labels should produce different output"
);
}

#[test]
fn export_keying_material_different_contexts() {
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);

let label = b"EXPORTER-test";
let context1 = b"context1";
let context2 = b"context2";

let material1 = client
.export_keying_material(label, Some(context1), 32)
.expect("export with context1");
let material2 = client
.export_keying_material(label, Some(context2), 32)
.expect("export with context2");

assert_eq!(material1.len(), 32);
assert_eq!(material2.len(), 32);
assert_ne!(
material1, material2,
"Different contexts should produce different output"
);
}

#[test]
fn export_keying_material_before_handshake() {
fixture_init();
let client = Client::new("server.example", true).expect("should create client");

let label = b"EXPORTER-test";
let result = client.export_keying_material(label, None, 32);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), Error::InvalidState);
}

#[test]
fn export_keying_material_same_for_both_sides() {
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);

let label = b"EXPORTER-test";
let context = b"shared-context";

let client_material = client
.export_keying_material(label, Some(context), 32)
.expect("client export");
let server_material = server
.export_keying_material(label, Some(context), 32)
.expect("server export");

assert_eq!(
client_material, server_material,
"Both sides should export identical material"
);
}
14 changes: 14 additions & 0 deletions neqo-http3/src/client_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ pub enum WebTransportEvent {
reason: extended_connect::session::CloseReason,
headers: Option<Vec<Header>>,
},
Draining {
session_id: StreamId,
},
NewStream {
stream_id: StreamId,
session_id: StreamId,
Expand All @@ -42,6 +45,11 @@ pub enum WebTransportEvent {
session_id: StreamId,
datagram: Bytes,
},
DatagramOutcome {
session_id: StreamId,
tracking_id: u64,
outcome: extended_connect::datagram_queue::DatagramOutcome,
},
}

#[derive(Debug, PartialEq, Eq, Clone)]
Expand Down Expand Up @@ -112,6 +120,8 @@ pub enum Http3ClientEvent {
PushReset { push_id: PushId, error: AppError },
/// New stream can be created
RequestsCreatable,
/// Stream quota increased - more streams of the specified type can be created
StreamCreatable { stream_type: StreamType },
/// Cert authentication needed
AuthenticationNeeded,
/// Encrypted client hello fallback occurred. The certificate for the
Expand Down Expand Up @@ -330,6 +340,10 @@ impl Http3ClientEvents {
}
}

pub(crate) fn stream_creatable(&self, stream_type: StreamType) {
self.insert(Http3ClientEvent::StreamCreatable { stream_type });
}

/// Add a new `AuthenticationNeeded` event
pub(crate) fn authentication_needed(&self) {
self.insert(Http3ClientEvent::AuthenticationNeeded);
Expand Down
Loading
Loading