Skip to content

Commit da5fdca

Browse files
guhetierCopilot
andauthored
Add support for keying material export (#6134)
## Description Adds a new preview API, `ConnectionExportKeyingMaterial`, letting applications export TLS keying material per RFC 5705 / RFC 8446 §7.5 (label + optional context). Requested by Azure SQL to authenticate connections. Supported by the user-mode TLS providers (Schannel, OpenSSL/quictls); kernel mode returns `QUIC_STATUS_NOT_SUPPORTED`. The API runs inline when the calling thread is safe, otherwise queues a connection operation, and exits early with `QUIC_STATUS_INVALID_STATE` if the TLS context is gone. ## Testing New tests: parameter/state validation (`ApiTest.cpp`), functional cross-peer export over a live connection (`HandshakeTest.cpp`), and RFC 5705 exporter properties at the TLS layer (`TlsTest.cpp`). All run in UM and KM (KM validates the `NOT_SUPPORTED` path). ## Documentation Public API declared in `msquic.h` (preview) with a C++ wrapper in `msquic.hpp`. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4c67926 commit da5fdca

30 files changed

Lines changed: 1017 additions & 3 deletions
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
ConnectionExportKeyingMaterial function
2+
======
3+
4+
Exports keying material derived from the connection's TLS session, as described in [RFC 5705](https://www.rfc-editor.org/rfc/rfc5705) and [RFC 8446 Section 7.5](https://www.rfc-editor.org/rfc/rfc8446#section-7.5).
5+
The exported material is bound to the TLS session and can be used by the app for its own purposes (for example, to authenticate the peer at the application layer).
6+
7+
> **Note** - This API is in [preview](../PreviewFeatures.md). It should be considered unstable and can be subject to breaking changes.
8+
9+
# Syntax
10+
11+
```C
12+
typedef
13+
_IRQL_requires_max_(PASSIVE_LEVEL)
14+
QUIC_STATUS
15+
(QUIC_API * QUIC_CONNECTION_EXPORT_KEYING_MATERIAL_FN)(
16+
_In_ _Pre_defensive_ HQUIC Connection,
17+
_In_ _Pre_defensive_ const QUIC_KEYING_MATERIAL_CONFIG* Config,
18+
_Out_writes_bytes_(Config->OutputLength)
19+
uint8_t* Output
20+
);
21+
```
22+
23+
# Parameters
24+
25+
`Connection`
26+
27+
The valid handle to an open and connected connection object.
28+
29+
`Config`
30+
31+
A pointer to a [QUIC_KEYING_MATERIAL_CONFIG](QUIC_KEYING_MATERIAL_CONFIG.md) that specifies the label, optional context, and the number of bytes to export.
32+
33+
`Output`
34+
35+
A caller-allocated buffer of at least `Config->OutputLength` bytes that receives the exported keying material on success. Its contents are undefined if the call fails.
36+
37+
# Return Value
38+
39+
The function returns a [QUIC_STATUS](QUIC_STATUS.md). The app may use `QUIC_FAILED` or `QUIC_SUCCEEDED` to determine if the function failed or succeeded.
40+
41+
The following errors are the most likely to be returned:
42+
43+
Value | Meaning
44+
--- | ---
45+
**QUIC_STATUS_SUCCESS** | The keying material was exported into `Output`.
46+
**QUIC_STATUS_INVALID_PARAMETER** | `Config`, `Config->Label`, or `Output` is NULL; `Config->OutputLength` is 0; `Config->Context` is NULL while `Config->ContextLength` is non-zero; or `Connection` is not a connection handle.
47+
**QUIC_STATUS_INVALID_STATE** | The connection's TLS context is no longer available. This happens if the handshake has not completed yet, or if it has already been released after handshake completion (see Remarks).
48+
**QUIC_STATUS_NOT_SUPPORTED** | The underlying TLS provider does not support exporting keying material. This is the case in kernel mode.
49+
50+
# Remarks
51+
52+
The keying material can only be exported once the handshake is completed and while the connection's TLS context is alive.
53+
On a server, the TLS context is is released shortly after the handshake completes (unless resumption tickets are used)
54+
55+
This API is best called inline while handling the `QUIC_CONNECTION_EVENT_CONNECTED` event to ensure the TLS context is still alive.
56+
Calling it later may return `QUIC_STATUS_INVALID_STATE` if the context has already been released.
57+
58+
This API is not supported for Windows Kernel Mode, where it returns `QUIC_STATUS_NOT_SUPPORTED`.
59+
60+
# See Also
61+
62+
[QUIC_KEYING_MATERIAL_CONFIG](QUIC_KEYING_MATERIAL_CONFIG.md)<br>
63+
[QUIC_CONNECTION_EVENT](QUIC_CONNECTION_EVENT.md)<br>

docs/api/ExecutionCreate.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
ExecutionCreate function
2+
======
3+
4+
TODO
5+
6+
# See Also
7+
8+
[QUIC_API_TABLE](QUIC_API_TABLE.md)<br>

docs/api/ExecutionDelete.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
ExecutionDelete function
2+
======
3+
4+
TODO
5+
6+
# See Also
7+
8+
[QUIC_API_TABLE](QUIC_API_TABLE.md)<br>

docs/api/ExecutionPoll.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
ExecutionPoll function
2+
======
3+
4+
TODO
5+
6+
# See Also
7+
8+
[QUIC_API_TABLE](QUIC_API_TABLE.md)<br>

docs/api/QUIC_API_TABLE.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,30 @@ typedef struct QUIC_API_TABLE {
4747

4848
QUIC_DATAGRAM_SEND_FN DatagramSend;
4949

50+
QUIC_CONNECTION_COMP_RESUMPTION_FN ConnectionResumptionTicketValidationComplete; // Available from v2.2
51+
QUIC_CONNECTION_COMP_CERT_FN ConnectionCertificateValidationComplete; // Available from v2.2
52+
53+
QUIC_CONNECTION_OPEN_IN_PARTITION_FN
54+
ConnectionOpenInPartition; // Available from v2.5
55+
56+
#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES
57+
QUIC_STREAM_PROVIDE_RECEIVE_BUFFERS_FN
58+
StreamProvideReceiveBuffers; // Available from v2.5
59+
60+
QUIC_CONN_POOL_CREATE_FN ConnectionPoolCreate; // Available from v2.5
61+
62+
#ifndef _KERNEL_MODE
63+
#define QUIC_API_EXECUTION_CONTEXT
64+
QUIC_EXECUTION_CREATE_FN ExecutionCreate; // Available from v2.5
65+
QUIC_EXECUTION_DELETE_FN ExecutionDelete; // Available from v2.5
66+
QUIC_EXECUTION_POLL_FN ExecutionPoll; // Available from v2.5
67+
#endif // _KERNEL_MODE
68+
QUIC_REGISTRATION_CLOSE2_FN RegistrationClose2; // Available from v2.6
69+
70+
QUIC_CONNECTION_EXPORT_KEYING_MATERIAL_FN
71+
ConnectionExportKeyingMaterial; // Available from v2.6
72+
#endif // QUIC_API_ENABLE_PREVIEW_FEATURES
73+
5074
} QUIC_API_TABLE;
5175
```
5276

@@ -172,6 +196,42 @@ See [StreamReceiveSetEnabled](StreamReceiveSetEnabled.md)
172196

173197
See [DatagramSend](DatagramSend.md)
174198

199+
`ConnectionResumptionTicketValidationComplete`
200+
201+
See [ConnectionResumptionTicketValidationComplete](ConnectionResumptionTicketValidationComplete.md)
202+
203+
`ConnectionCertificateValidationComplete`
204+
205+
See [ConnectionCertificateValidationComplete](ConnectionCertificateValidationComplete.md)
206+
207+
`ConnectionOpenInPartition`
208+
209+
See [ConnectionOpenInPartition](ConnectionOpenInPartition.md)
210+
211+
`StreamProvideReceiveBuffers`
212+
213+
See (Preview) [StreamProvideReceiveBuffers](StreamProvideReceiveBuffers.md)
214+
215+
`ConnectionPoolCreate`
216+
217+
See (Preview) [ConnectionPoolCreate](ConnectionPoolCreate.md)
218+
219+
`ExecutionCreate`
220+
221+
See (Preview) [ExecutionCreate](ExecutionCreate.md)
222+
223+
`ExecutionDelete`
224+
225+
See (Preview) [ExecutionDelete](ExecutionDelete.md)
226+
227+
`ExecutionPoll`
228+
229+
See (Preview) [ExecutionPoll](ExecutionPoll.md)
230+
231+
`ConnectionExportKeyingMaterial`
232+
233+
See (Preview) [ConnectionExportKeyingMaterial](ConnectionExportKeyingMaterial.md)
234+
175235
# See Also
176236

177237
[MsQuicOpen2](MsQuicOpen2.md)<br>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
QUIC_KEYING_MATERIAL_CONFIG structure
2+
======
3+
4+
The structure used to configure a call to [ConnectionExportKeyingMaterial](ConnectionExportKeyingMaterial.md).
5+
6+
> **Note** - This API is in [preview](../PreviewFeatures.md). It should be considered unstable and can be subject to breaking changes.
7+
8+
# Syntax
9+
10+
```C
11+
typedef struct QUIC_KEYING_MATERIAL_CONFIG {
12+
_Field_z_ const char* Label;
13+
uint32_t ContextLength;
14+
_Field_size_bytes_opt_(ContextLength) const uint8_t* Context;
15+
uint32_t OutputLength;
16+
} QUIC_KEYING_MATERIAL_CONFIG;
17+
```
18+
19+
# Members
20+
21+
`Label`
22+
23+
A non-NULL, null-terminated ASCII string that disambiguates the exported keying material between different uses. Both peers must use the same label to derive the same material. See [RFC 5705](https://www.rfc-editor.org/rfc/rfc5705) for guidance on choosing a label.
24+
25+
`ContextLength`
26+
27+
The length, in bytes, of the buffer pointed to by `Context`. May be 0.
28+
29+
`Context`
30+
31+
An optional pointer to a `ContextLength`-byte context buffer that is mixed into the derivation, further scoping the exported material. May be NULL, in which case `ContextLength` must be 0.
32+
33+
`OutputLength`
34+
35+
The number of bytes of keying material to export. Must be non-zero. The `Output` buffer passed to [ConnectionExportKeyingMaterial](ConnectionExportKeyingMaterial.md) must be at least this large.
36+
37+
# Remarks
38+
39+
Both peers of a connection derive identical keying material for the same `Label` and `Context`. Different labels or contexts produce independent, unrelated material.
40+
41+
# See Also
42+
43+
[ConnectionExportKeyingMaterial](ConnectionExportKeyingMaterial.md)<br>

src/core/api.c

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,94 @@ MsQuicConnectionSendResumptionTicket(
676676
return Status;
677677
}
678678

679+
_IRQL_requires_max_(PASSIVE_LEVEL)
680+
QUIC_STATUS
681+
QUIC_API
682+
MsQuicConnectionExportKeyingMaterial(
683+
_In_ _Pre_defensive_ HQUIC Handle,
684+
_In_ _Pre_defensive_ const QUIC_KEYING_MATERIAL_CONFIG* Config,
685+
_Out_writes_bytes_(Config->OutputLength)
686+
uint8_t* Output
687+
)
688+
{
689+
QUIC_STATUS Status;
690+
QUIC_CONNECTION* Connection;
691+
692+
CXPLAT_PASSIVE_CODE();
693+
694+
QuicTraceEvent(
695+
ApiEnter,
696+
"[ api] Enter %u (%p).",
697+
QUIC_TRACE_API_CONNECTION_EXPORT_KEYING_MATERIAL,
698+
Handle);
699+
700+
if (Config == NULL ||
701+
Config->Label == NULL ||
702+
Config->OutputLength == 0 ||
703+
Output == NULL ||
704+
(Config->Context == NULL && Config->ContextLength != 0) ||
705+
!IS_CONN_HANDLE(Handle)) {
706+
Status = QUIC_STATUS_INVALID_PARAMETER;
707+
goto Error;
708+
}
709+
710+
#pragma prefast(suppress: __WARNING_25024, "Pointer cast already validated.")
711+
Connection = (QUIC_CONNECTION*)Handle;
712+
713+
QUIC_CONN_VERIFY(Connection, !Connection->State.Freed);
714+
715+
if (MsQuicLib.CustomExecutions ||
716+
Connection->WorkerThreadID == CxPlatCurThreadID()) {
717+
//
718+
// Execute this blocking API call inline if called on the worker thread.
719+
//
720+
BOOLEAN AlreadyInline = Connection->State.InlineApiExecution;
721+
if (!AlreadyInline) {
722+
Connection->State.InlineApiExecution = TRUE;
723+
}
724+
Status =
725+
QuicConnExportKeyingMaterial(Connection, Config, Output);
726+
if (!AlreadyInline) {
727+
Connection->State.InlineApiExecution = FALSE;
728+
}
729+
goto Error;
730+
}
731+
732+
//
733+
// Queue the operation and wait for the worker to process it.
734+
//
735+
CXPLAT_EVENT CompletionEvent;
736+
CxPlatEventInitialize(&CompletionEvent, TRUE, FALSE);
737+
738+
QUIC_API_CONTEXT ApiCtx;
739+
ApiCtx.Type = QUIC_API_TYPE_CONN_EXPORT_KEYING_MATERIAL;
740+
ApiCtx.Completed = &CompletionEvent;
741+
ApiCtx.Status = &Status;
742+
ApiCtx.CONN_EXPORT_KEYING_MATERIAL.Config = Config;
743+
ApiCtx.CONN_EXPORT_KEYING_MATERIAL.Output = Output;
744+
745+
QUIC_OPERATION Oper = { 0 };
746+
Oper.Type = QUIC_OPER_TYPE_API_CALL;
747+
Oper.FreeAfterProcess = FALSE;
748+
Oper.API_CALL.Context = &ApiCtx;
749+
750+
QuicConnQueueOper(Connection, &Oper);
751+
QuicTraceEvent(
752+
ApiWaitOperation,
753+
"[ api] Waiting on operation");
754+
CxPlatEventWaitForever(CompletionEvent);
755+
CxPlatEventUninitialize(CompletionEvent);
756+
757+
Error:
758+
759+
QuicTraceEvent(
760+
ApiExitStatus,
761+
"[ api] Exit %u",
762+
Status);
763+
764+
return Status;
765+
}
766+
679767
_IRQL_requires_max_(DISPATCH_LEVEL)
680768
QUIC_STATUS
681769
QUIC_API

src/core/api.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,16 @@ MsQuicConnectionSendResumptionTicket(
214214
const uint8_t* ResumptionData
215215
);
216216

217+
_IRQL_requires_max_(PASSIVE_LEVEL)
218+
QUIC_STATUS
219+
QUIC_API
220+
MsQuicConnectionExportKeyingMaterial(
221+
_In_ _Pre_defensive_ HQUIC Handle,
222+
_In_ _Pre_defensive_ const QUIC_KEYING_MATERIAL_CONFIG* Config,
223+
_Out_writes_bytes_(Config->OutputLength)
224+
uint8_t* Output
225+
);
226+
217227
_IRQL_requires_max_(DISPATCH_LEVEL)
218228
QUIC_STATUS
219229
QUIC_API

src/core/connection.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7775,6 +7775,42 @@ QuicConnApplyNewSettings(
77757775
return TRUE;
77767776
}
77777777

7778+
_IRQL_requires_max_(PASSIVE_LEVEL)
7779+
QUIC_STATUS
7780+
QuicConnExportKeyingMaterial(
7781+
_In_ QUIC_CONNECTION* Connection,
7782+
_In_ const QUIC_KEYING_MATERIAL_CONFIG* Config,
7783+
_Out_writes_bytes_(Config->OutputLength)
7784+
uint8_t* Output
7785+
)
7786+
{
7787+
//
7788+
// The keying material is derived from the connection's TLS secrets: the
7789+
// handshake must be complete and the TLS context must still be present.
7790+
//
7791+
if (!Connection->State.Connected ||
7792+
!Connection->Crypto.TlsState.HandshakeComplete ||
7793+
Connection->Crypto.TLS == NULL) {
7794+
QuicTraceLogConnWarning(
7795+
ExportKeyingMaterialInvalidState,
7796+
Connection,
7797+
"Cannot export keying material [Connected=%hhu, HandshakeComplete=%hhu, HasTls=%hhu]",
7798+
Connection->State.Connected,
7799+
Connection->Crypto.TlsState.HandshakeComplete,
7800+
(uint8_t)(Connection->Crypto.TLS != NULL));
7801+
return QUIC_STATUS_INVALID_STATE;
7802+
}
7803+
7804+
return
7805+
CxPlatTlsExportKeyingMaterial(
7806+
Connection->Crypto.TLS,
7807+
Config->Label,
7808+
Config->Context,
7809+
Config->ContextLength,
7810+
Output,
7811+
Config->OutputLength);
7812+
}
7813+
77787814
_IRQL_requires_max_(PASSIVE_LEVEL)
77797815
void
77807816
QuicConnProcessApiOperation(
@@ -7932,6 +7968,14 @@ QuicConnProcessApiOperation(
79327968
QuicDatagramSendFlush(&Connection->Datagram);
79337969
break;
79347970

7971+
case QUIC_API_TYPE_CONN_EXPORT_KEYING_MATERIAL:
7972+
Status =
7973+
QuicConnExportKeyingMaterial(
7974+
Connection,
7975+
ApiCtx->CONN_EXPORT_KEYING_MATERIAL.Config,
7976+
ApiCtx->CONN_EXPORT_KEYING_MATERIAL.Output);
7977+
break;
7978+
79357979
default:
79367980
CXPLAT_TEL_ASSERT(FALSE);
79377981
Status = QUIC_STATUS_INVALID_PARAMETER;

src/core/connection.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,6 +1239,19 @@ QuicConnStart(
12391239
_In_ QUIC_CONN_START_FLAGS StartFlags
12401240
);
12411241

1242+
//
1243+
// Exports keying material derived from the connection's TLS session
1244+
// (RFC 5705 / RFC 8446 section 7.5).
1245+
//
1246+
_IRQL_requires_max_(PASSIVE_LEVEL)
1247+
QUIC_STATUS
1248+
QuicConnExportKeyingMaterial(
1249+
_In_ QUIC_CONNECTION* Connection,
1250+
_In_ const QUIC_KEYING_MATERIAL_CONFIG* Config,
1251+
_Out_writes_bytes_(Config->OutputLength)
1252+
uint8_t* Output
1253+
);
1254+
12421255
//
12431256
// Generates a new source connection ID.
12441257
//

0 commit comments

Comments
 (0)