Skip to content

Commit f149041

Browse files
authored
Use standard KDF for stateless retry (#5232)
* Implement SP800-108 CTR-HMAC KBKDF and use it for stateless retry key generation. * Add tests
1 parent cf131f5 commit f149041

File tree

5 files changed

+269
-9
lines changed

5 files changed

+269
-9
lines changed

src/core/partition.c

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,19 +98,24 @@ QuicPartitionGetStatelessRetryKey(
9898
}
9999

100100
//
101-
// Generate a new key from the base retry key combined with the index.
101+
// Generate a new key from the base retry secret using SP800-108 CTR-HMAC.
102102
//
103-
uint8_t RawKey[CXPLAT_AEAD_AES_256_GCM_SIZE];
104-
CxPlatCopyMemory(
105-
RawKey,
106-
MsQuicLib.StatelessRetry.BaseSecret,
107-
MsQuicLib.StatelessRetry.SecretLength);
108-
for (size_t i = 0; i < sizeof(KeyIndex); ++i) {
109-
RawKey[i] ^= ((uint8_t*)&KeyIndex)[i];
103+
uint8_t RawKey[CXPLAT_AEAD_MAX_SIZE];
104+
QUIC_STATUS Status =
105+
CxPlatKbKdfDerive(
106+
MsQuicLib.StatelessRetry.BaseSecret,
107+
MsQuicLib.StatelessRetry.SecretLength,
108+
"QUIC Stateless Retry Key",
109+
(uint8_t*)&KeyIndex,
110+
sizeof(KeyIndex),
111+
MsQuicLib.StatelessRetry.SecretLength,
112+
RawKey);
113+
if (QUIC_FAILED(Status)) {
114+
return NULL;
110115
}
111116

112117
CXPLAT_KEY* NewKey;
113-
QUIC_STATUS Status =
118+
Status =
114119
CxPlatKeyCreate(
115120
MsQuicLib.StatelessRetry.AeadAlgorithm,
116121
RawKey,
@@ -121,12 +126,14 @@ QuicPartitionGetStatelessRetryKey(
121126
"[ lib] ERROR, %u, %s.",
122127
Status,
123128
"Create stateless retry key");
129+
CxPlatSecureZeroMemory(RawKey, sizeof(RawKey));
124130
return NULL;
125131
}
126132

127133
CxPlatKeyFree(Partition->StatelessRetryKeys[KeyIndex & 1].Key);
128134
Partition->StatelessRetryKeys[KeyIndex & 1].Key = NewKey;
129135
Partition->StatelessRetryKeys[KeyIndex & 1].Index = KeyIndex;
136+
CxPlatSecureZeroMemory(RawKey, sizeof(RawKey));
130137

131138
return NewKey;
132139
}

src/inc/quic_crypt.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,18 @@ CxPlatCryptSupports(
394394
CXPLAT_AEAD_TYPE AeadType
395395
);
396396

397+
_IRQL_requires_max_(PASSIVE_LEVEL)
398+
QUIC_STATUS
399+
CxPlatKbKdfDerive(
400+
_In_reads_(SecretLength) const uint8_t* Secret,
401+
_In_ uint32_t SecretLength,
402+
_In_z_ const char* Label,
403+
_In_reads_opt_(ContextLength) const uint8_t* Context,
404+
_In_ uint32_t ContextLength,
405+
_In_ uint32_t OutputLength,
406+
_Out_writes_(OutputLength) uint8_t* Output
407+
);
408+
397409
#if defined(__cplusplus)
398410
}
399411
#endif

src/platform/crypt_bcrypt.c

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,14 @@ BCRYPT_ALG_HANDLE CXPLAT_HMAC_SHA384_ALG_HANDLE;
4646
BCRYPT_ALG_HANDLE CXPLAT_HMAC_SHA512_ALG_HANDLE;
4747
BCRYPT_ALG_HANDLE CXPLAT_AES_ECB_ALG_HANDLE;
4848
BCRYPT_ALG_HANDLE CXPLAT_AES_GCM_ALG_HANDLE;
49+
BCRYPT_ALG_HANDLE CXPLAT_SP800108_CTR_HMAC_ALG_HANDLE;
4950
#else
5051
BCRYPT_ALG_HANDLE CXPLAT_HMAC_SHA256_ALG_HANDLE = BCRYPT_HMAC_SHA256_ALG_HANDLE;
5152
BCRYPT_ALG_HANDLE CXPLAT_HMAC_SHA384_ALG_HANDLE = BCRYPT_HMAC_SHA384_ALG_HANDLE;
5253
BCRYPT_ALG_HANDLE CXPLAT_HMAC_SHA512_ALG_HANDLE = BCRYPT_HMAC_SHA512_ALG_HANDLE;
5354
BCRYPT_ALG_HANDLE CXPLAT_AES_ECB_ALG_HANDLE = BCRYPT_AES_ECB_ALG_HANDLE;
5455
BCRYPT_ALG_HANDLE CXPLAT_AES_GCM_ALG_HANDLE = BCRYPT_AES_GCM_ALG_HANDLE;
56+
BCRYPT_ALG_HANDLE CXPLAT_SP800108_CTR_HMAC_ALG_HANDLE = BCRYPT_SP800108_CTR_HMAC_ALG_HANDLE;
5557
#endif
5658
BCRYPT_ALG_HANDLE CXPLAT_CHACHA20_POLY1305_ALG_HANDLE = NULL;
5759

@@ -204,6 +206,21 @@ CxPlatCryptInitialize(
204206
}
205207
}
206208

209+
Status =
210+
BCryptOpenAlgorithmProvider(
211+
&CXPLAT_SP800108_CTR_HMAC_ALG_HANDLE,
212+
BCRYPT_SP800108_CTR_HMAC_ALGORITHM,
213+
MS_PRIMITIVE_PROVIDER,
214+
BCRYPT_PROV_DISPATCH);
215+
if (!NT_SUCCESS(Status)) {
216+
QuicTraceEvent(
217+
LibraryErrorStatus,
218+
"[ lib] ERROR, %u, %s.",
219+
Status,
220+
"Open SP800-108 CTR HMAC KDF algorithm");
221+
goto Error;
222+
}
223+
207224
Error:
208225

209226
if (!NT_SUCCESS(Status)) {
@@ -231,6 +248,10 @@ CxPlatCryptInitialize(
231248
BCryptCloseAlgorithmProvider(CXPLAT_CHACHA20_POLY1305_ALG_HANDLE, 0);
232249
CXPLAT_CHACHA20_POLY1305_ALG_HANDLE = NULL;
233250
}
251+
if (CXPLAT_SP800108_CTR_HMAC_ALG_HANDLE) {
252+
BCryptCloseAlgorithmProvider(CXPLAT_SP800108_CTR_HMAC_ALG_HANDLE, 0);
253+
CXPLAT_SP800108_CTR_HMAC_ALG_HANDLE = NULL;
254+
}
234255
}
235256

236257
return NtStatusToQuicStatus(Status);
@@ -304,11 +325,13 @@ CxPlatCryptUninitialize(
304325
)
305326
{
306327
#ifdef _KERNEL_MODE
328+
BCryptCloseAlgorithmProvider(CXPLAT_SP800108_CTR_HMAC_ALG_HANDLE, 0);
307329
BCryptCloseAlgorithmProvider(CXPLAT_HMAC_SHA256_ALG_HANDLE, 0);
308330
BCryptCloseAlgorithmProvider(CXPLAT_HMAC_SHA384_ALG_HANDLE, 0);
309331
BCryptCloseAlgorithmProvider(CXPLAT_HMAC_SHA512_ALG_HANDLE, 0);
310332
BCryptCloseAlgorithmProvider(CXPLAT_AES_ECB_ALG_HANDLE, 0);
311333
BCryptCloseAlgorithmProvider(CXPLAT_AES_GCM_ALG_HANDLE, 0);
334+
CXPLAT_SP800108_CTR_HMAC_ALG_HANDLE = NULL;
312335
CXPLAT_HMAC_SHA256_ALG_HANDLE = NULL;
313336
CXPLAT_HMAC_SHA384_ALG_HANDLE = NULL;
314337
CXPLAT_HMAC_SHA512_ALG_HANDLE = NULL;
@@ -764,3 +787,83 @@ CxPlatHashCompute(
764787

765788
return NtStatusToQuicStatus(Status);
766789
}
790+
791+
_IRQL_requires_max_(PASSIVE_LEVEL)
792+
QUIC_STATUS
793+
CxPlatKbKdfDerive(
794+
_In_reads_(SecretLength) const uint8_t* Secret,
795+
_In_ uint32_t SecretLength,
796+
_In_z_ const char* Label,
797+
_In_reads_opt_(ContextLength) const uint8_t* Context,
798+
_In_ uint32_t ContextLength,
799+
_In_ uint32_t OutputLength,
800+
_Out_writes_(OutputLength) uint8_t* Output
801+
)
802+
{
803+
BCRYPT_KEY_HANDLE KeyHandle = NULL;
804+
NTSTATUS Status =
805+
BCryptGenerateSymmetricKey(
806+
CXPLAT_SP800108_CTR_HMAC_ALG_HANDLE,
807+
&KeyHandle,
808+
NULL, // Let BCrypt manage the memory
809+
0,
810+
(UCHAR*)Secret,
811+
SecretLength,
812+
0);
813+
if (!NT_SUCCESS(Status)) {
814+
QuicTraceEvent(
815+
LibraryErrorStatus,
816+
"[ lib] ERROR, %u, %s.",
817+
Status,
818+
"BCryptGenerateSymmetricKey for KDF");
819+
goto Error;
820+
}
821+
822+
BCryptBuffer ParameterList[3];
823+
ParameterList[0].BufferType = KDF_HASH_ALGORITHM;
824+
ParameterList[0].pvBuffer = (PVOID)BCRYPT_SHA256_ALGORITHM;
825+
ParameterList[0].cbBuffer = sizeof(BCRYPT_SHA256_ALGORITHM);
826+
827+
ParameterList[1].BufferType = KDF_LABEL;
828+
ParameterList[1].pvBuffer = (PVOID)Label;
829+
ParameterList[1].cbBuffer = (ULONG)strnlen_s(Label, 255);
830+
831+
ParameterList[2].BufferType = KDF_CONTEXT;
832+
ParameterList[2].pvBuffer = (PVOID)Context;
833+
ParameterList[2].cbBuffer = ContextLength;
834+
835+
BCryptBufferDesc Parameters;
836+
Parameters.ulVersion = BCRYPTBUFFER_VERSION;
837+
Parameters.cBuffers = ARRAYSIZE(ParameterList);
838+
Parameters.pBuffers = ParameterList;
839+
840+
//
841+
// Derive the key using SP800-108 CTR mode
842+
//
843+
ULONG DerivedKeyLength = 0;
844+
Status =
845+
BCryptKeyDerivation(
846+
KeyHandle,
847+
&Parameters,
848+
Output,
849+
OutputLength,
850+
&DerivedKeyLength,
851+
0);
852+
if (!NT_SUCCESS(Status)) {
853+
QuicTraceEvent(
854+
LibraryErrorStatus,
855+
"[ lib] ERROR, %u, %s.",
856+
Status,
857+
"BCryptKeyDerivation SP800-108 CTR");
858+
goto Error;
859+
}
860+
861+
CXPLAT_DBG_ASSERT(DerivedKeyLength == OutputLength);
862+
863+
Error:
864+
if (KeyHandle) {
865+
BCryptDestroyKey(KeyHandle);
866+
}
867+
868+
return NtStatusToQuicStatus(Status);
869+
}

src/platform/crypt_openssl.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,3 +711,62 @@ CxPlatHashCompute(
711711
CXPLAT_FRE_ASSERT(ActualOutputSize == OutputLength);
712712
return QUIC_STATUS_SUCCESS;
713713
}
714+
715+
_IRQL_requires_max_(PASSIVE_LEVEL)
716+
QUIC_STATUS
717+
CxPlatKbKdfDerive(
718+
_In_reads_(SecretLength) const uint8_t* Secret,
719+
_In_ uint32_t SecretLength,
720+
_In_z_ const char* Label,
721+
_In_reads_opt_(ContextLength) const uint8_t* Context,
722+
_In_ uint32_t ContextLength,
723+
_In_ uint32_t OutputLength,
724+
_Out_writes_(OutputLength) uint8_t* Output
725+
)
726+
{
727+
QUIC_STATUS Status = QUIC_STATUS_SUCCESS;
728+
EVP_KDF* Kdf;
729+
EVP_KDF_CTX* KdfCtx;
730+
OSSL_PARAM Params[6];
731+
732+
Kdf = EVP_KDF_fetch(NULL, "KBKDF", NULL);
733+
KdfCtx = EVP_KDF_CTX_new(Kdf);
734+
EVP_KDF_free(Kdf);
735+
736+
Params[0] =
737+
OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST,"SHA2-256", 0);
738+
739+
Params[1] =
740+
OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_MAC,"HMAC", 0);
741+
742+
Params[2] =
743+
OSSL_PARAM_construct_octet_string(
744+
OSSL_KDF_PARAM_KEY,
745+
(void*)Secret,
746+
SecretLength);
747+
748+
Params[3] =
749+
OSSL_PARAM_construct_octet_string(
750+
OSSL_KDF_PARAM_SALT,
751+
(void*)Label,
752+
strnlen(Label, 255));
753+
754+
Params[4] =
755+
OSSL_PARAM_construct_octet_string(
756+
OSSL_KDF_PARAM_INFO,
757+
(void*)Context,
758+
ContextLength);
759+
760+
Params[5] = OSSL_PARAM_construct_end();
761+
762+
if (EVP_KDF_derive(KdfCtx, Output, OutputLength, Params) <= 0) {
763+
QuicTraceEvent(
764+
LibraryError,
765+
"[ lib] ERROR, %s.",
766+
"EVP_KDF_derive failed");
767+
Status = QUIC_STATUS_INTERNAL_ERROR;
768+
}
769+
770+
EVP_KDF_CTX_free(KdfCtx);
771+
return Status;
772+
}

src/platform/unittest/CryptTest.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,47 @@ struct CryptTest : public ::testing::TestWithParam<int32_t>
348348

349349
QuicPacketKeyFree(PacketKey);
350350
}
351+
352+
bool
353+
TestKbKdfDerive(
354+
_In_ QuicBuffer& Key,
355+
_In_ const uint32_t OutputLength,
356+
_In_ QuicBuffer& ExpectedOutput)
357+
{
358+
uint8_t Output[CXPLAT_AEAD_MAX_SIZE];
359+
if (OutputLength > sizeof(Output)) {
360+
//
361+
// Ensure the test doesn't overrun the buffer.
362+
//
363+
GTEST_LOG_(ERROR) << "OutputLength is larger than buffer.";
364+
return false;
365+
}
366+
CxPlatZeroMemory(Output, OutputLength);
367+
uint64_t Context = 1752112221;
368+
const char* Label = "test";
369+
const uint32_t ContextLength = (uint32_t)sizeof(Context);
370+
371+
QUIC_STATUS Status =
372+
CxPlatKbKdfDerive(
373+
Key.Data,
374+
Key.Length,
375+
Label,
376+
(uint8_t*)&Context,
377+
ContextLength,
378+
OutputLength,
379+
Output);
380+
if (QUIC_FAILED(Status)) {
381+
GTEST_LOG_(ERROR) << "CxPlatKbKdfDerive failed with " << std::hex << Status;
382+
return false;
383+
}
384+
385+
if (memcmp(ExpectedOutput.Data, Output, ExpectedOutput.Length) != 0) {
386+
LogTestBuffer("Expected Output: ", ExpectedOutput.Data, ExpectedOutput.Length);
387+
LogTestBuffer("Calculated Output: ", Output, ExpectedOutput.Length);
388+
return false;
389+
}
390+
return true;
391+
}
351392
};
352393

353394
TEST_F(CryptTest, WellKnownClientInitialv1)
@@ -488,6 +529,44 @@ TEST_F(CryptTest, HpMaskAes128)
488529
CxPlatHpKeyFree(HpKey);
489530
}
490531

532+
TEST_F(CryptTest, KbKdfDerive)
533+
{
534+
QuicBuffer Key256("3edc6b5b8f7aadbd713732b482b8f979286e1ea3b8f8f99c30c884cfe3349b83");
535+
536+
QuicBuffer ExpectedOutput256_Key256("B7BFF374C8928335AA41589D41084B64211876771C459C23B06BA4A2EA89B5AE");
537+
if (!TestKbKdfDerive(
538+
Key256,
539+
ExpectedOutput256_Key256.Length,
540+
ExpectedOutput256_Key256)) {
541+
FAIL();
542+
}
543+
544+
QuicBuffer ExpectedOutput128_Key256("2775BB8F82B3B5EB667C8CF548C2F06F");
545+
if (!TestKbKdfDerive(
546+
Key256,
547+
ExpectedOutput128_Key256.Length,
548+
ExpectedOutput128_Key256)) {
549+
FAIL();
550+
}
551+
552+
QuicBuffer Key128("5ddd79f7b33f1f4a6dd57c34a8eec42e");
553+
QuicBuffer ExpectedOutput256_Key128("0D90AEEEA79D7068283FC33561277AD8B641F5D0F2C47A3360DE8BBCB2D1A6E6");
554+
if (!TestKbKdfDerive(
555+
Key128,
556+
ExpectedOutput256_Key128.Length,
557+
ExpectedOutput256_Key128)) {
558+
FAIL();
559+
}
560+
561+
QuicBuffer ExpectedOutput128_Key128("3D49CD6A27AC5CA5F0F51893A755D160");
562+
if (!TestKbKdfDerive(
563+
Key128,
564+
ExpectedOutput128_Key128.Length,
565+
ExpectedOutput128_Key128)) {
566+
FAIL();
567+
}
568+
}
569+
491570
TEST_P(CryptTest, Encryption)
492571
{
493572

0 commit comments

Comments
 (0)