Implement HKDF
diff --git a/include/psa/crypto.h b/include/psa/crypto.h
index 9165463..47241a6 100644
--- a/include/psa/crypto.h
+++ b/include/psa/crypto.h
@@ -965,6 +965,36 @@
 #define PSA_ALG_IS_RSA_OAEP(alg)                                \
     (((alg) & ~PSA_ALG_HASH_MASK) == PSA_ALG_RSA_OAEP_BASE)
 
+#define PSA_ALG_HKDF_BASE                       ((psa_algorithm_t)0x30000100)
+/** Macro to build an HKDF algorithm.
+ *
+ * For example, `PSA_ALG_HKDF(PSA_ALG_SHA256)` is HKDF using HMAC-SHA-256.
+ *
+ * \param hash_alg      A hash algorithm (\c PSA_ALG_XXX value such that
+ *                      #PSA_ALG_IS_HASH(\p hash_alg) is true).
+ *
+ * \return              The corresponding HKDF algorithm.
+ * \return              Unspecified if \p alg is not a supported
+ *                      hash algorithm.
+ */
+#define PSA_ALG_HKDF(hash_alg)                                  \
+    (PSA_ALG_HKDF_BASE | ((hash_alg) & PSA_ALG_HASH_MASK))
+/** Whether the specified algorithm is an HKDF algorithm.
+ *
+ * HKDF is a family of key derivation algorithms that are based on a hash
+ * function and the HMAC construction.
+ *
+ * \param alg An algorithm identifier (value of type #psa_algorithm_t).
+ *
+ * \return 1 if \c alg is an HKDF algorithm, 0 otherwise.
+ *         This macro may return either 0 or 1 if \c alg is not a supported
+ *         key derivation algorithm identifier.
+ */
+#define PSA_ALG_IS_HKDF(alg)                            \
+    (((alg) & ~PSA_ALG_HASH_MASK) == PSA_ALG_HKDF_BASE)
+#define PSA_ALG_HKDF_GET_HASH(hkdf_alg)                         \
+    (PSA_ALG_CATEGORY_HASH | ((hkdf_alg) & PSA_ALG_HASH_MASK))
+
 /**@}*/
 
 /** \defgroup key_management Key management
@@ -2638,6 +2668,8 @@
  * be used to produce keys and other cryptographic material.
  *
  * The role of \p label and \p salt is as follows:
+ * - For HKDF (#PSA_ALG_HKDF), \p salt is the salt used in the "extract" step
+ *   and \p label is the info string used in the "expand" step.
  *
  * \param[in,out] generator       The generator object to set up. It must
  *                                have been initialized to .
diff --git a/include/psa/crypto_struct.h b/include/psa/crypto_struct.h
index 27a9f1e..baf5b14 100644
--- a/include/psa/crypto_struct.h
+++ b/include/psa/crypto_struct.h
@@ -130,6 +130,20 @@
     } ctx;
 };
 
+typedef struct
+{
+    uint8_t *info;
+    size_t info_length;
+    psa_hmac_internal_data hmac;
+    uint8_t prk[PSA_HASH_MAX_SIZE];
+    uint8_t output_block[PSA_HASH_MAX_SIZE];
+#if PSA_HASH_MAX_SIZE > 0xff
+#error "PSA_HASH_MAX_SIZE does not fit in uint8_t"
+#endif
+    uint8_t offset_in_block;
+    uint8_t block_number;
+} psa_hkdf_generator_t;
+
 struct psa_crypto_generator_s
 {
     psa_algorithm_t alg;
@@ -141,6 +155,9 @@
             uint8_t *data;
             size_t size;
         } buffer;
+#if defined(MBEDTLS_MD_C)
+        psa_hkdf_generator_t hkdf;
+#endif
     } ctx;
 };
 
diff --git a/library/psa_crypto.c b/library/psa_crypto.c
index cc59ca8..9e8f90b 100644
--- a/library/psa_crypto.c
+++ b/library/psa_crypto.c
@@ -3003,6 +3003,14 @@
          * nothing to do. */
     }
     else
+#if defined(MBEDTLS_MD_C)
+    if( PSA_ALG_IS_HKDF( generator->alg ) )
+    {
+        mbedtls_free( generator->ctx.hkdf.info );
+        status = psa_hmac_abort_internal( &generator->ctx.hkdf.hmac );
+    }
+    else
+#endif /* MBEDTLS_MD_C */
     {
         status = PSA_ERROR_BAD_STATE;
     }
@@ -3018,6 +3026,66 @@
     return( PSA_SUCCESS );
 }
 
+#if defined(MBEDTLS_MD_C)
+/* Read some bytes from an HKDF-based generator. This performs a chunk
+ * of the expand phase of the HKDF algorithm. */
+static psa_status_t psa_generator_hkdf_read( psa_hkdf_generator_t *hkdf,
+                                             psa_algorithm_t hash_alg,
+                                             uint8_t *output,
+                                             size_t output_length )
+{
+    uint8_t hash_length = PSA_HASH_SIZE( hash_alg );
+    psa_status_t status;
+
+    while( output_length != 0 )
+    {
+        /* Copy what remains of the current block */
+        uint8_t n = hash_length - hkdf->offset_in_block;
+        if( n > output_length )
+            n = (uint8_t) output_length;
+        memcpy( output, hkdf->output_block + hkdf->offset_in_block, n );
+        output += n;
+        output_length -= n;
+        hkdf->offset_in_block += n;
+        if( output_length == 0 || hkdf->block_number == 0xff )
+            break;
+
+        /* We need a new block */
+        ++hkdf->block_number;
+        hkdf->offset_in_block = 0;
+        status = psa_hmac_setup_internal( &hkdf->hmac,
+                                          hkdf->prk, hash_length,
+                                          hash_alg );
+        if( status != PSA_SUCCESS )
+            return( status );
+        if( hkdf->block_number != 1 )
+        {
+            status = psa_hash_update( &hkdf->hmac.hash_ctx,
+                                      hkdf->output_block,
+                                      hash_length );
+            if( status != PSA_SUCCESS )
+                return( status );
+        }
+        status = psa_hash_update( &hkdf->hmac.hash_ctx,
+                                  hkdf->info,
+                                  hkdf->info_length );
+        if( status != PSA_SUCCESS )
+            return( status );
+        status = psa_hash_update( &hkdf->hmac.hash_ctx,
+                                  &hkdf->block_number, 1 );
+        if( status != PSA_SUCCESS )
+            return( status );
+        status = psa_hmac_finish_internal( &hkdf->hmac,
+                                           hkdf->output_block,
+                                           sizeof( hkdf->output_block ) );
+        if( status != PSA_SUCCESS )
+            return( status );
+    }
+
+    return( PSA_SUCCESS );
+}
+#endif /* MBEDTLS_MD_C */
+
 psa_status_t psa_generator_read( psa_crypto_generator_t *generator,
                                  uint8_t *output,
                                  size_t output_length )
@@ -3045,6 +3113,15 @@
     }
     generator->capacity -= output_length;
 
+#if defined(MBEDTLS_MD_C)
+    if( PSA_ALG_IS_HKDF( generator->alg ) )
+    {
+        psa_algorithm_t hash_alg = PSA_ALG_HKDF_GET_HASH( generator->alg );
+        status = psa_generator_hkdf_read( &generator->ctx.hkdf, hash_alg,
+                                          output, output_length );
+    }
+    else
+#endif /* MBEDTLS_MD_C */
     {
         return( PSA_ERROR_BAD_STATE );
     }
@@ -3091,6 +3168,45 @@
 /* Key derivation */
 /****************************************************************/
 
+/* Set up an HKDF-based generator. This is exactly the extract phase
+ * of the HKDF algorithm. */
+static psa_status_t psa_generator_hkdf_setup( psa_hkdf_generator_t *hkdf,
+                                              key_slot_t *slot,
+                                              psa_algorithm_t hash_alg,
+                                              const uint8_t *salt,
+                                              size_t salt_length,
+                                              const uint8_t *label,
+                                              size_t label_length )
+{
+    psa_status_t status;
+    status = psa_hmac_setup_internal( &hkdf->hmac,
+                                      salt, salt_length,
+                                      PSA_ALG_HMAC_HASH( hash_alg ) );
+    if( status != PSA_SUCCESS )
+        return( status );
+    status = psa_hash_update( &hkdf->hmac.hash_ctx,
+                              slot->data.raw.data,
+                              slot->data.raw.bytes );
+    if( status != PSA_SUCCESS )
+        return( status );
+    status = psa_hmac_finish_internal( &hkdf->hmac,
+                                       hkdf->prk,
+                                       sizeof( hkdf->prk ) );
+    if( status != PSA_SUCCESS )
+        return( status );
+    hkdf->offset_in_block = PSA_HASH_SIZE( hash_alg );
+    hkdf->block_number = 0;
+    hkdf->info_length = label_length;
+    if( label_length != 0 )
+    {
+        hkdf->info = mbedtls_calloc( 1, label_length );
+        if( hkdf->info == NULL )
+            return( PSA_ERROR_INSUFFICIENT_MEMORY );
+        memcpy( hkdf->info, label, label_length );
+    }
+    return( PSA_SUCCESS );
+}
+
 psa_status_t psa_key_derivation( psa_crypto_generator_t *generator,
                                  psa_key_type_t key,
                                  psa_algorithm_t alg,
@@ -3115,6 +3231,23 @@
     if( ! PSA_ALG_IS_KEY_DERIVATION( alg ) )
         return( PSA_ERROR_INVALID_ARGUMENT );
 
+#if defined(MBEDTLS_MD_C)
+    if( PSA_ALG_IS_HKDF( alg ) )
+    {
+        psa_algorithm_t hash_alg = PSA_ALG_HKDF_GET_HASH( alg );
+        size_t hash_size = PSA_HASH_SIZE( hash_alg );
+        if( hash_size == 0 )
+            return( PSA_ERROR_NOT_SUPPORTED );
+        if( capacity > 255 * hash_size )
+            return( PSA_ERROR_INVALID_ARGUMENT );
+        status = psa_generator_hkdf_setup( &generator->ctx.hkdf,
+                                           slot,
+                                           hash_alg,
+                                           salt, salt_length,
+                                           label, label_length );
+    }
+    else
+#endif
     {
         return( PSA_ERROR_NOT_SUPPORTED );
     }