]> git.ipfire.org Git - thirdparty/gnutls.git/commitdiff
crypto-api: add generic crypto functions for KDF
authorDaiki Ueno <dueno@redhat.com>
Sun, 2 Feb 2020 13:44:05 +0000 (14:44 +0100)
committerDaiki Ueno <dueno@redhat.com>
Tue, 4 Feb 2020 09:29:36 +0000 (10:29 +0100)
This exposes HKDF and PBKDF2 functions from the library.  Instead of
defining a single KDF interface as in PKCS #11, this patch defines 3
distinct functions for HKDF-Extract, HKDF-Expand, and PBKDF2
derivation, so that we can take advantage of compile time checking of
necesssary parameters.

Signed-off-by: Daiki Ueno <dueno@redhat.com>
13 files changed:
.gitignore
NEWS
devel/libgnutls-latest-x86_64.abi
devel/symbols.last
doc/Makefile.am
doc/manpages/Makefile.am
lib/crypto-api.c
lib/crypto-backend.h
lib/includes/gnutls/crypto.h
lib/libgnutls.map
lib/nettle/mac.c
tests/Makefile.am
tests/kdf-api.c [new file with mode: 0644]

index 34d9af38a5a80261ef4aedd56fa78994ba63f67e..6c716933fac3ea1f151185fac7b79fcdc0ae8e47 100644 (file)
@@ -436,6 +436,7 @@ tests/insecure_key
 tests/iov
 tests/ip-check
 tests/ip-utils
+tests/kdf-api
 tests/key-export-pkcs8
 tests/key-id/Makefile
 tests/key-id/Makefile.in
diff --git a/NEWS b/NEWS
index 2dcdfe049e84ebee979da64f5a3a9f266cbe6a97..11c860e428baf97d18bc7802aeb0e8ac0eb7834d 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,15 @@ Copyright (C) 2000-2016 Free Software Foundation, Inc.
 Copyright (C) 2013-2019 Nikos Mavrogiannopoulos
 See the end for copying conditions.
 
+* Version 3.6.13 (unreleased)
+
+** libgnutls: Added new APIs to access KDF algorithms (#813).
+
+** API and ABI modifications:
+gnutls_hkdf_extract: Added
+gnutls_hkdf_expand: Added
+gnutls_pbkdf2: Added
+
 * Version 3.6.12 (released 2020-02-01)
 
 ** libgnutls: Introduced TLS session flag (gnutls_session_get_flags())
index cab31da9a8188e2a55029a4441c132333f4b31f8..23d346aefab575de4275f790e4563f4554263bd6 100644 (file)
     <elf-symbol name='gnutls_hex_decode' version='GNUTLS_3_4' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
     <elf-symbol name='gnutls_hex_encode2' version='GNUTLS_3_4' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
     <elf-symbol name='gnutls_hex_encode' version='GNUTLS_3_4' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+    <elf-symbol name='gnutls_hkdf_expand' version='GNUTLS_3_6_13' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+    <elf-symbol name='gnutls_hkdf_extract' version='GNUTLS_3_6_13' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
     <elf-symbol name='gnutls_hmac' version='GNUTLS_3_4' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
     <elf-symbol name='gnutls_hmac_copy' version='GNUTLS_3_6_9' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
     <elf-symbol name='gnutls_hmac_deinit' version='GNUTLS_3_4' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
     <elf-symbol name='gnutls_openpgp_set_recv_key_function' version='GNUTLS_3_4' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
     <elf-symbol name='gnutls_packet_deinit' version='GNUTLS_3_4' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
     <elf-symbol name='gnutls_packet_get' version='GNUTLS_3_4' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+    <elf-symbol name='gnutls_pbkdf2' version='GNUTLS_3_6_13' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
     <elf-symbol name='gnutls_pcert_deinit' version='GNUTLS_3_4' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
     <elf-symbol name='gnutls_pcert_export_openpgp' version='GNUTLS_3_4' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
     <elf-symbol name='gnutls_pcert_export_x509' version='GNUTLS_3_4' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
index 1e0e56d5c0e9e90e34152afff56ae4f076e4a107..51594477055c02b1983bc3f07534fd2fd32d857a 100644 (file)
@@ -2,6 +2,7 @@ GNUTLS_3_4@GNUTLS_3_4
 GNUTLS_3_6_0@GNUTLS_3_6_0
 GNUTLS_3_6_10@GNUTLS_3_6_10
 GNUTLS_3_6_12@GNUTLS_3_6_12
+GNUTLS_3_6_13@GNUTLS_3_6_13
 GNUTLS_3_6_2@GNUTLS_3_6_2
 GNUTLS_3_6_3@GNUTLS_3_6_3
 GNUTLS_3_6_4@GNUTLS_3_6_4
@@ -285,6 +286,8 @@ gnutls_hex_decode2@GNUTLS_3_4
 gnutls_hex_decode@GNUTLS_3_4
 gnutls_hex_encode2@GNUTLS_3_4
 gnutls_hex_encode@GNUTLS_3_4
+gnutls_hkdf_expand@GNUTLS_3_6_13
+gnutls_hkdf_extract@GNUTLS_3_6_13
 gnutls_hmac@GNUTLS_3_4
 gnutls_hmac_copy@GNUTLS_3_6_9
 gnutls_hmac_deinit@GNUTLS_3_4
@@ -431,6 +434,7 @@ gnutls_openpgp_send_cert@GNUTLS_3_4
 gnutls_openpgp_set_recv_key_function@GNUTLS_3_4
 gnutls_packet_deinit@GNUTLS_3_4
 gnutls_packet_get@GNUTLS_3_4
+gnutls_pbkdf2@GNUTLS_3_6_13
 gnutls_pcert_deinit@GNUTLS_3_4
 gnutls_pcert_export_openpgp@GNUTLS_3_4
 gnutls_pcert_export_x509@GNUTLS_3_4
index aa3984ffe10779c6a35241cd9b1a4344b808a097..234cbf315dfa04bea3a9b68c57a4d1df91dbb2b4 100644 (file)
@@ -1121,6 +1121,10 @@ FUNCS += functions/gnutls_hex_encode
 FUNCS += functions/gnutls_hex_encode.short
 FUNCS += functions/gnutls_hex_encode2
 FUNCS += functions/gnutls_hex_encode2.short
+FUNCS += functions/gnutls_hkdf_expand
+FUNCS += functions/gnutls_hkdf_expand.short
+FUNCS += functions/gnutls_hkdf_extract
+FUNCS += functions/gnutls_hkdf_extract.short
 FUNCS += functions/gnutls_hmac
 FUNCS += functions/gnutls_hmac.short
 FUNCS += functions/gnutls_hmac_copy
@@ -1277,6 +1281,8 @@ FUNCS += functions/gnutls_packet_deinit
 FUNCS += functions/gnutls_packet_deinit.short
 FUNCS += functions/gnutls_packet_get
 FUNCS += functions/gnutls_packet_get.short
+FUNCS += functions/gnutls_pbkdf2
+FUNCS += functions/gnutls_pbkdf2.short
 FUNCS += functions/gnutls_pcert_deinit
 FUNCS += functions/gnutls_pcert_deinit.short
 FUNCS += functions/gnutls_pcert_export_openpgp
index 85d0f7f0e4f929ee151ee46f4538d2e20926acfb..f11b070fe0fc3213ddebcfca816f5ba7779acfb6 100644 (file)
@@ -362,6 +362,8 @@ APIMANS += gnutls_hex_decode.3
 APIMANS += gnutls_hex_decode2.3
 APIMANS += gnutls_hex_encode.3
 APIMANS += gnutls_hex_encode2.3
+APIMANS += gnutls_hkdf_expand.3
+APIMANS += gnutls_hkdf_extract.3
 APIMANS += gnutls_hmac.3
 APIMANS += gnutls_hmac_copy.3
 APIMANS += gnutls_hmac_deinit.3
@@ -440,6 +442,7 @@ APIMANS += gnutls_openpgp_privkey_sign_hash.3
 APIMANS += gnutls_openpgp_send_cert.3
 APIMANS += gnutls_packet_deinit.3
 APIMANS += gnutls_packet_get.3
+APIMANS += gnutls_pbkdf2.3
 APIMANS += gnutls_pcert_deinit.3
 APIMANS += gnutls_pcert_export_openpgp.3
 APIMANS += gnutls_pcert_export_x509.3
index 4db6812c2908a92f4b7b3910feb60c601dcad118..45be64ed1f3d0d236155006a28a3c433b18bacbe 100644 (file)
@@ -1401,3 +1401,98 @@ void gnutls_aead_cipher_deinit(gnutls_aead_cipher_hd_t handle)
        _gnutls_aead_cipher_deinit(handle);
        gnutls_free(handle);
 }
+
+extern gnutls_crypto_kdf_st _gnutls_kdf_ops;
+
+/**
+ * gnutls_hkdf_extract:
+ * @mac: the mac algorithm used internally
+ * @key: the initial keying material
+ * @salt: the optional salt
+ * @output: the output value of the extract operation
+ *
+ * This function will derive a fixed-size key using the HKDF-Extract
+ * function as defined in RFC 5869.
+ *
+ * Returns: Zero or a negative error code on error.
+ *
+ * Since: 3.6.13
+ */
+int
+gnutls_hkdf_extract(gnutls_mac_algorithm_t mac,
+                   const gnutls_datum_t *key,
+                   const gnutls_datum_t *salt,
+                   void *output)
+{
+       /* MD5 is only allowed internally for TLS */
+       if (is_mac_algo_forbidden(mac))
+               return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM);
+
+       return _gnutls_kdf_ops.hkdf_extract(mac, key->data, key->size,
+                                           salt ? salt->data : NULL,
+                                           salt ? salt->size : 0,
+                                           output);
+}
+
+/**
+ * gnutls_hkdf_expand:
+ * @mac: the mac algorithm used internally
+ * @key: the pseudorandom key created with HKDF-Extract
+ * @info: the optional informational data
+ * @output: the output value of the expand operation
+ * @length: the desired length of the output key
+ *
+ * This function will derive a variable length keying material from
+ * the pseudorandom key using the HKDF-Expand function as defined in
+ * RFC 5869.
+ *
+ * Returns: Zero or a negative error code on error.
+ *
+ * Since: 3.6.13
+ */
+int
+gnutls_hkdf_expand(gnutls_mac_algorithm_t mac,
+                  const gnutls_datum_t *key,
+                  const gnutls_datum_t *info,
+                  void *output, size_t length)
+{
+       /* MD5 is only allowed internally for TLS */
+       if (is_mac_algo_forbidden(mac))
+               return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM);
+
+       return _gnutls_kdf_ops.hkdf_expand(mac, key->data, key->size,
+                                          info->data, info->size,
+                                          output, length);
+}
+
+/**
+ * gnutls_pbkdf2:
+ * @mac: the mac algorithm used internally
+ * @key: the initial keying material
+ * @salt: the salt
+ * @iter_count: the iteration count
+ * @output: the output value
+ * @length: the desired length of the output key
+ *
+ * This function will derive a variable length keying material from
+ * a password according to PKCS #5 PBKDF2.
+ *
+ * Returns: Zero or a negative error code on error.
+ *
+ * Since: 3.6.13
+ */
+int
+gnutls_pbkdf2(gnutls_mac_algorithm_t mac,
+             const gnutls_datum_t *key,
+             const gnutls_datum_t *salt,
+             unsigned iter_count,
+             void *output, size_t length)
+{
+       /* MD5 is only allowed internally for TLS */
+       if (is_mac_algo_forbidden(mac))
+               return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM);
+
+       return _gnutls_kdf_ops.pbkdf2(mac, key->data, key->size,
+                                     salt->data, salt->size, iter_count,
+                                     output, length);
+}
index c083b164983ddbccdbc1a1edc5e4721f9a1472a0..33005e73dd65287595ac0da47572fe8b6890e470 100644 (file)
@@ -75,6 +75,22 @@ typedef struct {
        int (*exists) (gnutls_digest_algorithm_t);
 } gnutls_crypto_digest_st;
 
+typedef struct {
+       int (*hkdf_extract) (gnutls_mac_algorithm_t,
+                            const void *key, size_t keysize,
+                            const void *salt, size_t saltsize,
+                            void *output);
+       int (*hkdf_expand) (gnutls_mac_algorithm_t,
+                           const void *key, size_t keysize,
+                           const void *info, size_t infosize,
+                           void *output, size_t length);
+       int (*pbkdf2) (gnutls_mac_algorithm_t,
+                      const void *key, size_t keysize,
+                      const void *salt, size_t saltsize,
+                      unsigned iter_count,
+                      void *output, size_t length);
+} gnutls_crypto_kdf_st;
+
 typedef struct gnutls_crypto_rnd {
        int (*init) (void **ctx); /* called prior to first usage of randomness */
        int (*rnd) (void *ctx, int level, void *data, size_t datasize);
index 685d9d5d29d03d102ed73777e32a7b0a0c8b4d2d..c878d7dfac95ab5118cf14ca931adc5990e6609c 100644 (file)
@@ -139,6 +139,24 @@ int gnutls_hash_fast(gnutls_digest_algorithm_t algorithm,
                     const void *text, size_t textlen, void *digest);
 gnutls_hash_hd_t gnutls_hash_copy(gnutls_hash_hd_t handle);
 
+/* KDF API */
+
+int gnutls_hkdf_extract(gnutls_mac_algorithm_t mac,
+                       const gnutls_datum_t *key,
+                       const gnutls_datum_t *salt,
+                       void *output);
+
+int gnutls_hkdf_expand(gnutls_mac_algorithm_t mac,
+                      const gnutls_datum_t *key,
+                      const gnutls_datum_t *info,
+                      void *output, size_t length);
+
+int gnutls_pbkdf2(gnutls_mac_algorithm_t mac,
+                 const gnutls_datum_t *key,
+                 const gnutls_datum_t *salt,
+                 unsigned iter_count,
+                 void *output, size_t length);
+
 /* register ciphers */
 
 
index e1878bb00c6efc39e3730d7bc36338c821770580..bf8fff8bc3def8ee4349fa32bd68acd85df4dfe9 100644 (file)
@@ -1309,6 +1309,14 @@ GNUTLS_3_6_12
        gnutls_hmac_get_key_size;
 } GNUTLS_3_6_10;
 
+GNUTLS_3_6_13
+{
+ global:
+       gnutls_hkdf_extract;
+       gnutls_hkdf_expand;
+       gnutls_pbkdf2;
+} GNUTLS_3_6_12;
+
 GNUTLS_FIPS140_3_4 {
   global:
        gnutls_cipher_self_test;
index 25054dc2675ec38460d0891273b653660745775d..f997cf3d46ddf7968b84cde37799ca83eabd56db 100644 (file)
@@ -32,6 +32,8 @@
 #include <nettle/sha3.h>
 #include <nettle/hmac.h>
 #include <nettle/umac.h>
+#include <nettle/hkdf.h>
+#include <nettle/pbkdf2.h>
 #if ENABLE_GOST
 #include "gost/hmac-gost.h"
 #ifndef HAVE_NETTLE_GOSTHASH94CP_UPDATE
@@ -825,6 +827,69 @@ wrap_nettle_hash_output(void *src_ctx, void *digest, size_t digestsize)
        return 0;
 }
 
+/* KDF functions based on MAC
+ */
+static int
+wrap_nettle_hkdf_extract (gnutls_mac_algorithm_t mac,
+                         const void *key, size_t keysize,
+                         const void *salt, size_t saltsize,
+                         void *output)
+{
+       struct nettle_mac_ctx ctx;
+       int ret;
+
+       ret = _mac_ctx_init(mac, &ctx);
+       if (ret < 0)
+               return gnutls_assert_val(ret);
+
+       ctx.set_key(&ctx, saltsize, salt);
+       hkdf_extract(&ctx.ctx, ctx.update, ctx.digest, ctx.length,
+                    keysize, key, output);
+
+       return 0;
+}
+
+static int
+wrap_nettle_hkdf_expand (gnutls_mac_algorithm_t mac,
+                        const void *key, size_t keysize,
+                        const void *info, size_t infosize,
+                        void *output, size_t length)
+{
+       struct nettle_mac_ctx ctx;
+       int ret;
+
+       ret = _mac_ctx_init(mac, &ctx);
+       if (ret < 0)
+               return gnutls_assert_val(ret);
+
+       ctx.set_key(&ctx, keysize, key);
+       hkdf_expand(&ctx.ctx, ctx.update, ctx.digest, ctx.length,
+                   infosize, info, length, output);
+
+       return 0;
+}
+
+static int
+wrap_nettle_pbkdf2 (gnutls_mac_algorithm_t mac,
+                   const void *key, size_t keysize,
+                   const void *salt, size_t saltsize,
+                   unsigned iter_count,
+                   void *output, size_t length)
+{
+       struct nettle_mac_ctx ctx;
+       int ret;
+
+       ret = _mac_ctx_init(mac, &ctx);
+       if (ret < 0)
+               return gnutls_assert_val(ret);
+
+       ctx.set_key(&ctx, keysize, key);
+       pbkdf2(&ctx.ctx, ctx.update, ctx.digest, ctx.length,
+              iter_count, saltsize, salt, length, output);
+
+       return 0;
+}
+
 gnutls_crypto_mac_st _gnutls_mac_ops = {
        .init = wrap_nettle_mac_init,
        .setkey = wrap_nettle_mac_set_key,
@@ -846,3 +911,13 @@ gnutls_crypto_digest_st _gnutls_digest_ops = {
        .exists = wrap_nettle_hash_exists,
        .copy = wrap_nettle_hash_copy,
 };
+
+/* These names are clashing with nettle's name mangling. */
+#undef hkdf_extract
+#undef hkdf_expand
+#undef pbkdf2
+gnutls_crypto_kdf_st _gnutls_kdf_ops = {
+       .hkdf_extract = wrap_nettle_hkdf_extract,
+       .hkdf_expand = wrap_nettle_hkdf_expand,
+       .pbkdf2 = wrap_nettle_pbkdf2,
+};
index 4e12bc802eb5dc2bde40df4e2aae45b9b23e8e1f..764db8c33a1d931b7cfacda76217023b508316b6 100644 (file)
@@ -217,7 +217,7 @@ ctests += mini-record-2 simple gnutls_hmac_fast set_pkcs12_cred cert certuniquei
         tls-record-size-limit-asym dh-compute ecdh-compute sign-verify-data-newapi \
         sign-verify-newapi sign-verify-deterministic iov aead-cipher-vec \
         tls13-without-timeout-func buffer status-request-revoked \
-        set_x509_ocsp_multi_cli
+        set_x509_ocsp_multi_cli kdf-api
 
 if HAVE_SECCOMP_TESTS
 ctests += dtls-with-seccomp tls-with-seccomp dtls-client-with-seccomp tls-client-with-seccomp
diff --git a/tests/kdf-api.c b/tests/kdf-api.c
new file mode 100644 (file)
index 0000000..ec74f44
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2020 Red Hat, Inc.
+ *
+ * Author: Daiki Ueno
+ *
+ * This file is part of GnuTLS.
+ *
+ * The GnuTLS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>
+ *
+ */
+
+#include "config.h"
+
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+#include <assert.h>
+#include <stdint.h>
+
+#include "utils.h"
+
+#define MAX_BUF 1024
+
+static void
+test_hkdf(gnutls_mac_algorithm_t mac,
+         const char *ikm_hex,
+         const char *salt_hex,
+         const char *info_hex,
+         size_t length,
+         const char *prk_hex,
+         const char *okm_hex)
+{
+       gnutls_datum_t hex;
+       gnutls_datum_t ikm;
+       gnutls_datum_t salt;
+       gnutls_datum_t info;
+       gnutls_datum_t prk;
+       gnutls_datum_t okm;
+       uint8_t buf[MAX_BUF];
+
+       success("HKDF test with %s\n", gnutls_mac_get_name(mac));
+
+       /* Test HKDF-Extract */
+       hex.data = (void *)ikm_hex;
+       hex.size = strlen(ikm_hex);
+       assert(gnutls_hex_decode2(&hex, &ikm) >= 0);
+
+       hex.data = (void *)salt_hex;
+       hex.size = strlen(salt_hex);
+       assert(gnutls_hex_decode2(&hex, &salt) >= 0);
+
+       assert(gnutls_hkdf_extract(mac, &ikm, &salt, buf) >= 0);
+       gnutls_free(ikm.data);
+       gnutls_free(salt.data);
+
+       prk.data = buf;
+       prk.size = strlen(prk_hex) / 2;
+       assert(gnutls_hex_encode2(&prk, &hex) >= 0);
+
+       if (strcmp((char *)hex.data, prk_hex))
+               fail("prk doesn't match: %s != %s\n",
+                    (char *)hex.data, prk_hex);
+
+       gnutls_free(hex.data);
+
+       /* Test HKDF-Expand */
+       hex.data = (void *)info_hex;
+       hex.size = strlen(info_hex);
+       assert(gnutls_hex_decode2(&hex, &info) >= 0);
+
+       assert(gnutls_hkdf_expand(mac, &prk, &info, buf, length) >= 0);
+       gnutls_free(info.data);
+
+       okm.data = buf;
+       okm.size = strlen(okm_hex) / 2;
+       assert(gnutls_hex_encode2(&okm, &hex) >= 0);
+
+       if (strcmp((char *)hex.data, okm_hex))
+               fail("okm doesn't match: %s != %s\n",
+                    (char *)hex.data, okm_hex);
+
+       gnutls_free(hex.data);
+}
+
+static void
+test_pbkdf2(gnutls_mac_algorithm_t mac,
+           const char *ikm_hex,
+           const char *salt_hex,
+           unsigned iter_count,
+           size_t length,
+           const char *okm_hex)
+{
+       gnutls_datum_t hex;
+       gnutls_datum_t ikm;
+       gnutls_datum_t salt;
+       gnutls_datum_t okm;
+       uint8_t buf[MAX_BUF];
+
+       success("PBKDF2 test with %s\n", gnutls_mac_get_name(mac));
+
+       hex.data = (void *)ikm_hex;
+       hex.size = strlen(ikm_hex);
+       assert(gnutls_hex_decode2(&hex, &ikm) >= 0);
+
+       hex.data = (void *)salt_hex;
+       hex.size = strlen(salt_hex);
+       assert(gnutls_hex_decode2(&hex, &salt) >= 0);
+
+       assert(gnutls_pbkdf2(mac, &ikm, &salt, iter_count, buf, length) >= 0);
+       gnutls_free(ikm.data);
+       gnutls_free(salt.data);
+
+       okm.data = buf;
+       okm.size = length;
+       assert(gnutls_hex_encode2(&okm, &hex) >= 0);
+
+       if (strcmp((char *)hex.data, okm_hex))
+               fail("okm doesn't match: %s != %s\n",
+                    (char *)hex.data, okm_hex);
+
+       gnutls_free(hex.data);
+}
+
+void
+doit(void)
+{
+       /* Test vector from RFC 5869.  More thorough testing is done
+        * in nettle. */
+       test_hkdf(GNUTLS_MAC_SHA256,
+                 "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"
+                 "0b0b0b0b0b0b",
+                 "000102030405060708090a0b0c",
+                 "f0f1f2f3f4f5f6f7f8f9",
+                 42,
+                 "077709362c2e32df0ddc3f0dc47bba63"
+                 "90b6c73bb50f9c3122ec844ad7c2b3e5",
+                 "3cb25f25faacd57a90434f64d0362f2a"
+                 "2d2d0a90cf1a5a4c5db02d56ecc4c5bf"
+                 "34007208d5b887185865");
+
+       /* Test vector from RFC 6070.  More thorough testing is done
+        * in nettle. */
+       test_pbkdf2(GNUTLS_MAC_SHA1,
+                   "70617373776f7264", /* "password" */
+                   "73616c74",         /* "salt" */
+                   4096,
+                   20,
+                   "4b007901b765489abead49d926f721d065a429c1");
+}