]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[crypto] Allow for input keying material to overlap output
authorMichael Brown <mcb30@ipxe.org>
Sat, 20 Jun 2026 09:00:44 +0000 (10:00 +0100)
committerMichael Brown <mcb30@ipxe.org>
Sat, 20 Jun 2026 13:43:36 +0000 (14:43 +0100)
Calling hkdf_extract() with no salt and with the input keying material
provided in the same buffer that will hold the output pseudorandom key
is a valid potential use case.  This will currently fail silently
since the input keying material would be overwritten by the
constructed all-zero salt before being consumed.

Fix by using a local buffer for the all-zero salt, rather than
constructing the salt in the output buffer.

Document the permitted behaviour in terms of overlapping input and
output buffers for both hkdf_extract() and hkdf_expand(), and extend
the test cases to verify this behaviour.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/crypto/hkdf.c
src/tests/hkdf_test.c

index 97f856df34b8c2926465d495d13bcc8c62b3948e..291eafe64e36a12808442d9f1c05b73de10b8ca5 100644 (file)
@@ -49,18 +49,22 @@ FILE_SECBOOT ( PERMITTED );
  * @v ikm              Input keying material
  * @v ikm_len          Length of input keying material
  * @v prk              Pseudorandom key to fill in
+ *
+ * The salt and input keying material are allowed to overlap the
+ * buffer that will be filled in with the pseudorandom key.
  */
 void hkdf_extract ( struct digest_algorithm *digest, const void *salt,
                    size_t salt_len, const void *ikm, size_t ikm_len,
                    void *prk ) {
        uint8_t ctx[ hmac_ctxsize ( digest ) ];
+       uint8_t zero[ digest->digestsize ];
 
        /* Use all-zero salt if not specified */
        if ( ! salt ) {
                assert ( salt_len == 0 );
-               salt_len = digest->digestsize;
-               memset ( prk, 0, salt_len );
-               salt = prk;
+               memset ( zero, 0, sizeof ( zero ) );
+               salt = zero;
+               salt_len = sizeof ( zero );
        }
 
        /* Calculate pseudorandom key */
@@ -78,6 +82,15 @@ void hkdf_extract ( struct digest_algorithm *digest, const void *salt,
  * @v info_len         Length of additional information
  * @v out              Output keying material
  * @v len              Length of output keying material
+ *
+ * The pseudorandom key and additional information are allowed to
+ * overlap the buffer that will be filled in with the output keying
+ * material, provided that the length of the output keying material is
+ * less than or equal to the length of the pseudorandom key.
+ *
+ * In particular, this allows hkdf_expand() to be used to create a new
+ * generation of pseudorandom key (i.e. to have the output keying
+ * material overwrite the existing pseudorandom key).
  */
 void hkdf_expand ( struct digest_algorithm *digest, const void *prk,
                   const void *info, size_t info_len, void *out, size_t len ) {
index 95b19d10b7ab02f249f402709fd590eb40f60159..9acfa828ae5c697b56db97955d95846c4f4f5a53 100644 (file)
@@ -123,8 +123,16 @@ struct hkdf_test {
 static void hkdf_okx ( struct hkdf_test *test, const char *file,
                       unsigned int line ) {
        size_t digestsize = test->digest->digestsize;
+       union {
+               uint8_t ikm[test->ikm_len];
+               uint8_t salt[test->salt_len];
+               uint8_t info[test->info_len];
+               uint8_t prk[test->prk_len];
+               uint8_t okm[test->okm_len];
+       } overlap;
        uint8_t prk[digestsize];
        uint8_t okm[test->okm_len];
+       size_t check_len;
 
        /* Sanity checks */
        okx ( ( test->salt != NULL ) || ( test->salt_len == 0 ), file, line );
@@ -137,8 +145,41 @@ static void hkdf_okx ( struct hkdf_test *test, const char *file,
 
        /* Test expansion */
        hkdf_expand ( test->digest, test->prk, test->info, test->info_len,
-                     okm, sizeof ( okm ) );
+                     okm, test->okm_len );
        okx ( memcmp ( okm, test->okm, test->okm_len ) == 0, file, line );
+
+       /* Test overlap between salt and pseudorandom key */
+       if ( test->salt ) {
+               memcpy ( overlap.salt, test->salt, test->salt_len );
+               hkdf_extract ( test->digest, overlap.salt, test->salt_len,
+                              test->ikm, test->ikm_len, overlap.prk );
+               okx ( memcmp ( overlap.prk, test->prk, digestsize ) == 0,
+                     file, line );
+       }
+
+       /* Test overlap between input keying material and pseudorandom key */
+       memcpy ( overlap.ikm, test->ikm, test->ikm_len );
+       hkdf_extract ( test->digest, test->salt, test->salt_len, overlap.ikm,
+                      test->ikm_len, overlap.prk );
+       okx ( memcmp ( overlap.prk, test->prk, digestsize ) == 0,
+             file, line );
+
+       /* Calculate length for expansion overlap tests */
+       check_len = test->okm_len;
+       if ( check_len > digestsize )
+               check_len = digestsize;
+
+       /* Test overlap between pseudorandom key and output */
+       memcpy ( overlap.prk, test->prk, test->prk_len );
+       hkdf_expand ( test->digest, overlap.prk, test->info, test->info_len,
+                     overlap.okm, test->okm_len );
+       okx ( memcmp ( overlap.okm, test->okm, check_len ) == 0, file, line );
+
+       /* Test overlap between additional information and output */
+       memcpy ( overlap.info, test->info, test->info_len );
+       hkdf_expand ( test->digest, test->prk, overlap.info, test->info_len,
+                     overlap.okm, test->okm_len );
+       okx ( memcmp ( overlap.okm, test->okm, check_len ) == 0, file, line );
 }
 #define hkdf_ok( test ) hkdf_okx ( test, __FILE__, __LINE__ )