From 5a264bdd06bc07f76318acde4b5bce695ee6761b Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 28 Jun 2026 15:20:03 +0800 Subject: [PATCH] add support for key exporters --- session.go | 24 ++++++++++++++++++++++++ session_test.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/session.go b/session.go index e744b6a..ee6597f 100644 --- a/session.go +++ b/session.go @@ -2,6 +2,7 @@ package webtransport import ( "context" + "encoding/binary" "errors" "io" "net" @@ -307,6 +308,29 @@ func (s *Session) ReceiveDatagram(ctx context.Context) ([]byte, error) { return s.str.ReceiveDatagram(ctx) } +// ExportKeyingMaterial exports keying material bound to this WebTransport session. +func (s *Session) ExportKeyingMaterial(label string, context []byte, length int) ([]byte, error) { + if len(label) > 255 { + return nil, errors.New("webtransport: exporter label longer than 255 bytes") + } + if len(context) > 255 { + return nil, errors.New("webtransport: exporter context longer than 255 bytes") + } + if length < 0 { + return nil, errors.New("webtransport: exporter length must be non-negative") + } + + exporterContext := make([]byte, 8, 8+1+len(label)+1+len(context)) + binary.BigEndian.PutUint64(exporterContext, uint64(s.sessionID)) + exporterContext = append(exporterContext, byte(len(label))) + exporterContext = append(exporterContext, label...) + exporterContext = append(exporterContext, byte(len(context))) + exporterContext = append(exporterContext, context...) + + connState := s.conn.ConnectionState() + return connState.TLS.ExportKeyingMaterial("EXPORTER-WebTransport", exporterContext, length) +} + func (s *Session) closeWithError(closeErr error, closeCapsule *closeSessionCapsule) bool { s.closeMx.Lock() // Duplicate call, or the remote already closed this session. diff --git a/session_test.go b/session_test.go index 911ce41..d4bfe2a 100644 --- a/session_test.go +++ b/session_test.go @@ -181,6 +181,43 @@ func TestCloseWithErrorTruncatesSendMessage(t *testing.T) { } } +func TestExportKeyingMaterial(t *testing.T) { + clientConn, serverConn := newConnPair(t, newUDPConnLocalhost(t), newUDPConnLocalhost(t)) + id := sessionID(0x1020304050607080) + clientSess := newSession(context.Background(), id, clientConn, mockHTTP3Stream{bytes.NewReader(nil)}, "") + serverSess := newSession(context.Background(), id, serverConn, mockHTTP3Stream{bytes.NewReader(nil)}, "") + + label := "label" + context := []byte("context") + clientMaterial, err := clientSess.ExportKeyingMaterial(label, context, 32) + require.NoError(t, err) + serverMaterial, err := serverSess.ExportKeyingMaterial(label, context, 32) + require.NoError(t, err) + require.Equal(t, clientMaterial, serverMaterial) + + exporterContext := make([]byte, 8, 8+1+len(label)+1+len(context)) + binary.BigEndian.PutUint64(exporterContext, uint64(id)) + exporterContext = append(exporterContext, byte(len(label))) + exporterContext = append(exporterContext, label...) + exporterContext = append(exporterContext, byte(len(context))) + exporterContext = append(exporterContext, context...) + connState := clientConn.ConnectionState() + expected, err := connState.TLS.ExportKeyingMaterial("EXPORTER-WebTransport", exporterContext, 32) + require.NoError(t, err) + require.Equal(t, expected, clientMaterial) + + otherMaterial, err := clientSess.ExportKeyingMaterial("other", context, 32) + require.NoError(t, err) + require.NotEqual(t, clientMaterial, otherMaterial) + + _, err = clientSess.ExportKeyingMaterial(strings.Repeat("a", 256), nil, 32) + require.ErrorContains(t, err, "exporter label") + _, err = clientSess.ExportKeyingMaterial(label, bytes.Repeat([]byte("a"), 256), 32) + require.ErrorContains(t, err, "exporter context") + _, err = clientSess.ExportKeyingMaterial(label, nil, -1) + require.ErrorContains(t, err, "non-negative") +} + func TestCapsuleParseErrorClosesSessionWithDatagramError(t *testing.T) { b := quicvarint.Append(nil, uint64(maxStreamsBidiCapsuleType)) b = quicvarint.Append(b, uint64(quicvarint.Len(42)+1))