Added generic HKDF implementation.
authorThomas Pornin <pornin@bolet.org>
Tue, 14 Aug 2018 15:02:22 +0000 (17:02 +0200)
committerThomas Pornin <pornin@bolet.org>
Tue, 14 Aug 2018 15:02:22 +0000 (17:02 +0200)
inc/bearssl.h
inc/bearssl_hmac.h
inc/bearssl_kdf.h [new file with mode: 0644]
mk/Rules.mk
mk/mkrules.sh
src/kdf/hkdf.c [new file with mode: 0644]
test/test_crypto.c

index 3d5e63a..4f4797c 100644 (file)
@@ -39,6 +39,7 @@
  * | :-------------- | :------------------------------------------------ |
  * | bearssl_hash.h  | Hash functions                                    |
  * | bearssl_hmac.h  | HMAC                                              |
+ * | bearssl_kdf.h   | Key Derivation Functions                          |
  * | bearssl_rand.h  | Pseudorandom byte generators                      |
  * | bearssl_prf.h   | PRF implementations (for SSL/TLS)                 |
  * | bearssl_block.h | Symmetric encryption                              |
 
 #include "bearssl_hash.h"
 #include "bearssl_hmac.h"
+#include "bearssl_kdf.h"
 #include "bearssl_rand.h"
 #include "bearssl_prf.h"
 #include "bearssl_block.h"
index 14147d8..4dc01ca 100644 (file)
@@ -84,6 +84,21 @@ typedef struct {
 void br_hmac_key_init(br_hmac_key_context *kc,
        const br_hash_class *digest_vtable, const void *key, size_t key_len);
 
+/*
+ * \brief Get the underlying hash function.
+ *
+ * This function returns a pointer to the implementation vtable of the
+ * hash function used for this HMAC key context.
+ *
+ * \param kc   HMAC key context.
+ * \return  the hash function implementation.
+ */
+static inline const br_hash_class *br_hmac_key_get_digest(
+       const br_hmac_key_context *kc)
+{
+       return kc->dig_vtable;
+}
+
 /**
  * \brief HMAC computation context.
  *
@@ -142,6 +157,21 @@ br_hmac_size(br_hmac_context *ctx)
        return ctx->out_len;
 }
 
+/*
+ * \brief Get the underlying hash function.
+ *
+ * This function returns a pointer to the implementation vtable of the
+ * hash function used for this HMAC context.
+ *
+ * \param hc   HMAC context.
+ * \return  the hash function implementation.
+ */
+static inline const br_hash_class *br_hmac_get_digest(
+       const br_hmac_context *hc)
+{
+       return hc->dig.vtable;
+}
+
 /**
  * \brief Inject some bytes in HMAC.
  *
diff --git a/inc/bearssl_kdf.h b/inc/bearssl_kdf.h
new file mode 100644 (file)
index 0000000..f018d7e
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2018 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef BR_BEARSSL_KDF_H__
+#define BR_BEARSSL_KDF_H__
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "bearssl_hash.h"
+#include "bearssl_hmac.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \file bearssl_kdf.h
+ *
+ * # Key Derivation Functions
+ *
+ * KDF are functions that takes a variable length input, and provide a
+ * variable length output, meant to be used to derive subkeys from a
+ * master key.
+ *
+ * ## HKDF
+ *
+ * HKDF is a KDF defined by [RFC 5869](https://tools.ietf.org/html/rfc5869).
+ * It is based on HMAC, itself using an underlying hash function. Any
+ * hash function can be used, as long as it is compatible with the rules
+ * for the HMAC implementation (i.e. output size is 64 bytes or less, hash
+ * internal state size is 64 bytes or less, and the internal block length is
+ * a power of 2 between 16 and 256 bytes). HKDF has two phases:
+ *
+ *  - HKDF-Extract: the input data in ingested, along with a "salt" value.
+ *
+ *  - HKDF-Expand: the output is produced, from the result of processing
+ *    the input and salt, and using an extra non-secret parameter called
+ *    "info".
+ *
+ * The "salt" and "info" strings are non-secret and can be empty. Their role
+ * is normally to bind the input and output, respectively, to conventional
+ * identifiers that qualifu them within the used protocol or application.
+ *
+ * The implementation defined in this file uses the following functions:
+ *
+ *  - `br_hkdf_init()`: initialize an HKDF context, with a hash function,
+ *    and the salt. This starts the HKDF-Extract process.
+ *
+ *  - `br_hkdf_inject()`: inject more input bytes. This function may be
+ *    called repeatedly if the input data is provided by chunks.
+ *
+ *  - `br_hkdf_flip()`: end the HKDF-Extract process, and start the
+ *    HKDF-Expand process.
+ *
+ *  - `br_hkdf_produce()`: get the next bytes of output. This function
+ *    may be called several times to obtain the full output by chunks.
+ *    For correct HKDF processing, the same "info" string must be
+ *    provided for each call.
+ *
+ * Note that the HKDF total output size (the number of bytes that
+ * HKDF-Expand is willing to produce) is limited: if the hash output size
+ * is _n_ bytes, then the maximum output size is _255*n_.
+ */
+
+/**
+ * \brief HKDF context.
+ *
+ * The HKDF context is initialized with a hash function implementation
+ * and a salt value. Contents are opaque (callers should not access them
+ * directly). The caller is responsible for allocating the context where
+ * appropriate. Context initialisation and usage incurs no dynamic
+ * allocation, so there is no release function.
+ */
+typedef struct {
+#ifndef BR_DOXYGEN_IGNORE
+       union {
+               br_hmac_context hmac_ctx;
+               br_hmac_key_context prk_ctx;
+       } u;
+       unsigned char buf[64];
+       size_t ptr;
+       size_t dig_len;
+       unsigned chunk_num;
+#endif
+} br_hkdf_context;
+
+/**
+ * \brief HKDF context initialization.
+ *
+ * The underlying hash function and salt value are provided. Arbitrary
+ * salt lengths can be used.
+ *
+ * HKDF makes a difference between a salt of length zero, and an
+ * absent salt (the latter being equivalent to a salt consisting of
+ * bytes of value zero, of the same length as the hash function output).
+ * If `salt_len` is zero, then this function assumes that the salt is
+ * present but of length zero. To specify an _absent_ salt, use
+ * `BR_HKDF_NO_SALT` as `salt` parameter (`salt_len` is then ignored).
+ *
+ * \param hc              HKDF context to initialise.
+ * \param digest_vtable   pointer to the hash function implementation vtable.
+ * \param salt            HKDF-Extract salt.
+ * \param salt_len        HKDF-Extract salt length (in bytes).
+ */
+void br_hkdf_init(br_hkdf_context *hc, const br_hash_class *digest_vtable,
+       const void *salt, size_t salt_len);
+
+/**
+ * \brief The special "absent salt" value for HKDF.
+ */
+#define BR_HKDF_NO_SALT   (&br_hkdf_no_salt)
+
+#ifndef BR_DOXYGEN_IGNORE
+extern const unsigned char br_hkdf_no_salt;
+#endif
+
+/**
+ * \brief HKDF input injection (HKDF-Extract).
+ *
+ * This function injects some more input bytes ("key material") into
+ * HKDF. This function may be called several times, after `br_hkdf_init()`
+ * but before `br_hkdf_flip()`.
+ *
+ * \param hc        HKDF context.
+ * \param ikm       extra input bytes.
+ * \param ikm_len   number of extra input bytes.
+ */
+void br_hkdf_inject(br_hkdf_context *hc, const void *ikm, size_t ikm_len);
+
+/**
+ * \brief HKDF switch to the HKDF-Expand phase.
+ *
+ * This call terminates the HKDF-Extract process (input injection), and
+ * starts the HKDF-Expand process (output production).
+ *
+ * \param hc   HKDF context.
+ */
+void br_hkdf_flip(br_hkdf_context *hc);
+
+/**
+ * \brief HKDF output production (HKDF-Expand).
+ *
+ * Produce more output bytes from the current state. This function may be
+ * called several times, but only after `br_hkdf_flip()`.
+ *
+ * Returned value is the number of actually produced bytes. The total
+ * output length is limited to 255 times the output length of the
+ * underlying hash function.
+ *
+ * \param hc         HKDF context.
+ * \param info       application specific information string.
+ * \param info_len   application specific information string length (in bytes).
+ * \param out        destination buffer for the HKDF output.
+ * \param out_len    the length of the requested output (in bytes).
+ * \return  the produced output length (in bytes).
+ */
+size_t br_hkdf_produce(br_hkdf_context *hc,
+       const void *info, size_t info_len, void *out, size_t out_len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
index 0a23fa5..b480bd6 100644 (file)
@@ -123,6 +123,7 @@ OBJ = \
  $(OBJDIR)$Pi32_sub$O \
  $(OBJDIR)$Pi32_tmont$O \
  $(OBJDIR)$Pi62_modpow2$O \
+ $(OBJDIR)$Phkdf$O \
  $(OBJDIR)$Phmac$O \
  $(OBJDIR)$Phmac_ct$O \
  $(OBJDIR)$Paesctr_drbg$O \
@@ -301,7 +302,7 @@ OBJTESTSPEED = \
  $(OBJDIR)$Ptest_speed$O
 OBJTESTX509 = \
  $(OBJDIR)$Ptest_x509$O
-HEADERSPUB = inc$Pbearssl.h inc$Pbearssl_aead.h inc$Pbearssl_block.h inc$Pbearssl_ec.h inc$Pbearssl_hash.h inc$Pbearssl_hmac.h inc$Pbearssl_pem.h inc$Pbearssl_prf.h inc$Pbearssl_rand.h inc$Pbearssl_rsa.h inc$Pbearssl_ssl.h inc$Pbearssl_x509.h
+HEADERSPUB = inc$Pbearssl.h inc$Pbearssl_aead.h inc$Pbearssl_block.h inc$Pbearssl_ec.h inc$Pbearssl_hash.h inc$Pbearssl_hmac.h inc$Pbearssl_kdf.h inc$Pbearssl_pem.h inc$Pbearssl_prf.h inc$Pbearssl_rand.h inc$Pbearssl_rsa.h inc$Pbearssl_ssl.h inc$Pbearssl_x509.h
 HEADERSPRIV = $(HEADERSPUB) src$Pconfig.h src$Pinner.h
 HEADERSTOOLS = $(HEADERSPUB) tools$Pbrssl.h
 T0SRC = T0$PBlobWriter.cs T0$PCPU.cs T0$PCodeElement.cs T0$PCodeElementJump.cs T0$PCodeElementUInt.cs T0$PCodeElementUIntExpr.cs T0$PCodeElementUIntInt.cs T0$PCodeElementUIntUInt.cs T0$PConstData.cs T0$POpcode.cs T0$POpcodeCall.cs T0$POpcodeConst.cs T0$POpcodeGetLocal.cs T0$POpcodeJump.cs T0$POpcodeJumpIf.cs T0$POpcodeJumpIfNot.cs T0$POpcodeJumpUncond.cs T0$POpcodePutLocal.cs T0$POpcodeRet.cs T0$PSType.cs T0$PT0Comp.cs T0$PTPointerBase.cs T0$PTPointerBlob.cs T0$PTPointerExpr.cs T0$PTPointerNull.cs T0$PTPointerXT.cs T0$PTValue.cs T0$PWord.cs T0$PWordBuilder.cs T0$PWordData.cs T0$PWordInterpreted.cs T0$PWordNative.cs
@@ -723,6 +724,9 @@ $(OBJDIR)$Pi32_tmont$O: src$Pint$Pi32_tmont.c $(HEADERSPRIV)
 $(OBJDIR)$Pi62_modpow2$O: src$Pint$Pi62_modpow2.c $(HEADERSPRIV)
        $(CC) $(CFLAGS) $(INCFLAGS) $(CCOUT)$(OBJDIR)$Pi62_modpow2$O src$Pint$Pi62_modpow2.c
 
+$(OBJDIR)$Phkdf$O: src$Pkdf$Phkdf.c $(HEADERSPRIV)
+       $(CC) $(CFLAGS) $(INCFLAGS) $(CCOUT)$(OBJDIR)$Phkdf$O src$Pkdf$Phkdf.c
+
 $(OBJDIR)$Phmac$O: src$Pmac$Phmac.c $(HEADERSPRIV)
        $(CC) $(CFLAGS) $(INCFLAGS) $(CCOUT)$(OBJDIR)$Phmac$O src$Pmac$Phmac.c
 
index c72b820..eea159b 100755 (executable)
@@ -171,6 +171,7 @@ coresrc=" \
        src/int/i32_sub.c \
        src/int/i32_tmont.c \
        src/int/i62_modpow2.c \
+       src/kdf/hkdf.c \
        src/mac/hmac.c \
        src/mac/hmac_ct.c \
        src/rand/aesctr_drbg.c \
@@ -366,6 +367,7 @@ headerspub=" \
        inc/bearssl_ec.h \
        inc/bearssl_hash.h \
        inc/bearssl_hmac.h \
+       inc/bearssl_kdf.h \
        inc/bearssl_pem.h \
        inc/bearssl_prf.h \
        inc/bearssl_rand.h \
diff --git a/src/kdf/hkdf.c b/src/kdf/hkdf.c
new file mode 100644 (file)
index 0000000..6a36851
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2018 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "inner.h"
+
+const unsigned char br_hkdf_no_salt = 0;
+
+/* see bearssl_kdf.h */
+void
+br_hkdf_init(br_hkdf_context *hc, const br_hash_class *digest_vtable,
+       const void *salt, size_t salt_len)
+{
+       br_hmac_key_context kc;
+       unsigned char tmp[64];
+
+       if (salt == BR_HKDF_NO_SALT) {
+               salt = tmp;
+               salt_len = br_digest_size(digest_vtable);
+               memset(tmp, 0, salt_len);
+       }
+       br_hmac_key_init(&kc, digest_vtable, salt, salt_len);
+       br_hmac_init(&hc->u.hmac_ctx, &kc, 0);
+       hc->dig_len = br_hmac_size(&hc->u.hmac_ctx);
+}
+
+/* see bearssl_kdf.h */
+void
+br_hkdf_inject(br_hkdf_context *hc, const void *ikm, size_t ikm_len)
+{
+       br_hmac_update(&hc->u.hmac_ctx, ikm, ikm_len);
+}
+
+/* see bearssl_kdf.h */
+void
+br_hkdf_flip(br_hkdf_context *hc)
+{
+       unsigned char tmp[64];
+
+       br_hmac_out(&hc->u.hmac_ctx, tmp);
+       br_hmac_key_init(&hc->u.prk_ctx,
+               br_hmac_get_digest(&hc->u.hmac_ctx), tmp, hc->dig_len);
+       hc->ptr = hc->dig_len;
+       hc->chunk_num = 0;
+}
+
+/* see bearssl_kdf.h */
+size_t
+br_hkdf_produce(br_hkdf_context *hc,
+       const void *info, size_t info_len, void *out, size_t out_len)
+{
+       size_t tlen;
+
+       tlen = 0;
+       while (out_len > 0) {
+               size_t clen;
+
+               if (hc->ptr == hc->dig_len) {
+                       br_hmac_context hmac_ctx;
+                       unsigned char x;
+
+                       hc->chunk_num ++;
+                       if (hc->chunk_num == 256) {
+                               return tlen;
+                       }
+                       x = hc->chunk_num;
+                       br_hmac_init(&hmac_ctx, &hc->u.prk_ctx, 0);
+                       if (x != 1) {
+                               br_hmac_update(&hmac_ctx, hc->buf, hc->dig_len);
+                       }
+                       br_hmac_update(&hmac_ctx, info, info_len);
+                       br_hmac_update(&hmac_ctx, &x, 1);
+                       br_hmac_out(&hmac_ctx, hc->buf);
+                       hc->ptr = 0;
+               }
+               clen = hc->dig_len - hc->ptr;
+               if (clen > out_len) {
+                       clen = out_len;
+               }
+               memcpy(out, hc->buf + hc->ptr, clen);
+               out = (unsigned char *)out + clen;
+               out_len -= clen;
+               hc->ptr += clen;
+               tlen += clen;
+       }
+       return tlen;
+}
index dd2562c..740178d 100644 (file)
@@ -1030,6 +1030,107 @@ test_HMAC(void)
 }
 
 static void
+test_HKDF_inner(const br_hash_class *dig, const char *ikmhex,
+       const char *salthex, const char *infohex, const char *okmhex)
+{
+       unsigned char ikm[100], saltbuf[100], info[100], okm[100], tmp[107];
+       const unsigned char *salt;
+       size_t ikm_len, salt_len, info_len, okm_len;
+       br_hkdf_context hc;
+       size_t u;
+
+       ikm_len = hextobin(ikm, ikmhex);
+       if (salthex == NULL) {
+               salt = BR_HKDF_NO_SALT;
+               salt_len = 0;
+       } else {
+               salt = saltbuf;
+               salt_len = hextobin(saltbuf, salthex);
+       }
+       info_len = hextobin(info, infohex);
+       okm_len = hextobin(okm, okmhex);
+
+       br_hkdf_init(&hc, dig, salt, salt_len);
+       br_hkdf_inject(&hc, ikm, ikm_len);
+       br_hkdf_flip(&hc);
+       br_hkdf_produce(&hc, info, info_len, tmp, okm_len);
+       check_equals("KAT HKDF 1", tmp, okm, okm_len);
+
+       br_hkdf_init(&hc, dig, salt, salt_len);
+       for (u = 0; u < ikm_len; u ++) {
+               br_hkdf_inject(&hc, &ikm[u], 1);
+       }
+       br_hkdf_flip(&hc);
+       for (u = 0; u < okm_len; u ++) {
+               br_hkdf_produce(&hc, info, info_len, &tmp[u], 1);
+       }
+       check_equals("KAT HKDF 2", tmp, okm, okm_len);
+
+       br_hkdf_init(&hc, dig, salt, salt_len);
+       br_hkdf_inject(&hc, ikm, ikm_len);
+       br_hkdf_flip(&hc);
+       for (u = 0; u < okm_len; u += 7) {
+               br_hkdf_produce(&hc, info, info_len, &tmp[u], 7);
+       }
+       check_equals("KAT HKDF 3", tmp, okm, okm_len);
+
+       printf(".");
+       fflush(stdout);
+}
+
+static void
+test_HKDF(void)
+{
+       printf("Test HKDF: ");
+       fflush(stdout);
+
+       test_HKDF_inner(&br_sha256_vtable,
+               "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
+               "000102030405060708090a0b0c",
+               "f0f1f2f3f4f5f6f7f8f9",
+               "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865");
+
+       test_HKDF_inner(&br_sha256_vtable,
+               "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f",
+               "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
+               "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+               "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87");
+
+       test_HKDF_inner(&br_sha256_vtable,
+               "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
+               "",
+               "",
+               "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8");
+
+       test_HKDF_inner(&br_sha1_vtable,
+               "0b0b0b0b0b0b0b0b0b0b0b",
+               "000102030405060708090a0b0c",
+               "f0f1f2f3f4f5f6f7f8f9",
+               "085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2c22e422478d305f3f896");
+
+       test_HKDF_inner(&br_sha1_vtable,
+               "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f",
+               "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
+               "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+               "0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e927336d0441f4c4300e2cff0d0900b52d3b4");
+
+       test_HKDF_inner(&br_sha1_vtable,
+               "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
+               "",
+               "",
+               "0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0ea00033de03984d34918");
+
+       test_HKDF_inner(&br_sha1_vtable,
+               "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c",
+               NULL,
+               "",
+               "2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5673a081d70cce7acfc48");
+
+       printf(" done.\n");
+       fflush(stdout);
+}
+
+static void
 test_HMAC_DRBG(void)
 {
        br_hmac_drbg_context ctx;
@@ -8266,6 +8367,7 @@ static const struct {
        STU(MD5_SHA1),
        STU(multihash),
        STU(HMAC),
+       STU(HKDF),
        STU(HMAC_DRBG),
        STU(AESCTR_DRBG),
        STU(PRF),