[Openvpn-devel,L] Change in openvpn[master]: Implement HKDF expand function based on RFC 8446

Message ID 737fc6870bbfb1248baa1bacfc448ee774a4f5ea-HTML@gerrit.openvpn.net
State New
Headers show
Series [Openvpn-devel,L] Change in openvpn[master]: Implement HKDF expand function based on RFC 8446 | expand

Commit Message

flichtenheld (Code Review) Nov. 11, 2024, 1:59 a.m. UTC
Attention is currently required from: flichtenheld.

Hello flichtenheld,

I'd like you to do a code review.
Please visit

    http://gerrit.openvpn.net/c/openvpn/+/798?usp=email

to review the following change.


Change subject: Implement HKDF expand function based on RFC 8446
......................................................................

Implement HKDF expand function based on RFC 8446

Use crypto_epoch.c/h for the new functions since they are
linked to the epoch key usage in OpenVPN.

Change-Id: I3a1c6561f4d9a69e2a441d49dff620b4258a1bcc
Signed-off-by: Arne Schwabe <arne@rfc2549.org>
---
M CMakeLists.txt
M src/openvpn/Makefile.am
A src/openvpn/crypto_epoch.c
A src/openvpn/crypto_epoch.h
M src/openvpn/crypto_mbedtls.h
M tests/unit_tests/openvpn/Makefile.am
M tests/unit_tests/openvpn/test_crypto.c
7 files changed, 304 insertions(+), 2 deletions(-)



  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/98/798/1

Patch

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5db207d..cbc93cd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -389,6 +389,8 @@ 
     src/openvpn/crypto.c
     src/openvpn/crypto.h
     src/openvpn/crypto_backend.h
+    src/openvpn/crypto_epoch.c
+    src/openvpn/crypto_epoch.h
     src/openvpn/crypto_openssl.c
     src/openvpn/crypto_openssl.h
     src/openvpn/crypto_mbedtls.c
@@ -717,6 +719,7 @@ 
     target_sources(test_crypto PRIVATE
         src/openvpn/crypto_mbedtls.c
         src/openvpn/crypto_openssl.c
+        src/openvpn/crypto_epoch.c
         src/openvpn/crypto.c
         src/openvpn/otime.c
         src/openvpn/packet_id.c
diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am
index ecb2bcf..d6d6592 100644
--- a/src/openvpn/Makefile.am
+++ b/src/openvpn/Makefile.am
@@ -53,6 +53,7 @@ 
 	crypto.c crypto.h crypto_backend.h \
 	crypto_openssl.c crypto_openssl.h \
 	crypto_mbedtls.c crypto_mbedtls.h \
+	crypto_epoch.c crypto_epoch.h \
 	dco.c dco.h dco_internal.h \
 	dco_freebsd.c dco_freebsd.h \
 	dco_linux.c dco_linux.h \
diff --git a/src/openvpn/crypto_epoch.c b/src/openvpn/crypto_epoch.c
new file mode 100644
index 0000000..7a5b460
--- /dev/null
+++ b/src/openvpn/crypto_epoch.c
@@ -0,0 +1,106 @@ 
+/*
+ *  OpenVPN -- An application to securely tunnel IP networks
+ *             over a single TCP/UDP port, with support for SSL/TLS-based
+ *             session authentication and key exchange,
+ *             packet encryption, packet authentication, and
+ *             packet compression.
+ *
+ *  Copyright (C) 2024 OpenVPN Inc <sales@openvpn.net>
+ *  Copyright (C) 2024 Arne Schwabe <arne@rfc2549.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <inttypes.h>
+#include "crypto_backend.h"
+#include "buffer.h"
+#include "integer.h"
+
+void
+ovpn_hdkf_expand(const uint8_t *secret,
+                 const uint8_t *info, int info_len,
+                 uint8_t *out, int out_len)
+{
+    hmac_ctx_t *hmac_ctx = hmac_ctx_new();
+    hmac_ctx_init(hmac_ctx, secret, "SHA256");
+
+    const int digest_size = SHA256_DIGEST_LENGTH;
+
+    /* T(0) = empty string */
+    uint8_t t_prev[SHA256_DIGEST_LENGTH];
+    int t_prev_len = 0;
+
+    for (uint8_t block = 1; (block - 1) * digest_size < out_len; block++)
+    {
+        hmac_ctx_reset(hmac_ctx);
+
+        /* calculate T(block) */
+        hmac_ctx_update(hmac_ctx, t_prev, t_prev_len);
+        hmac_ctx_update(hmac_ctx, info, info_len);
+        hmac_ctx_update(hmac_ctx, &block, 1);
+        hmac_ctx_final(hmac_ctx, t_prev);
+        t_prev_len = digest_size;
+
+        /* Copy a full hmac output or remaining bytes */
+        int out_offset = (block - 1) * digest_size;
+        int copylen = min_int(digest_size, out_len - out_offset);
+
+        memcpy(out + out_offset, t_prev, copylen);
+    }
+    hmac_ctx_cleanup(hmac_ctx);
+    hmac_ctx_free(hmac_ctx);
+}
+
+bool
+ovpn_expand_label(const uint8_t *secret, size_t secret_len,
+                  const uint8_t *label, size_t label_len,
+                  const uint8_t *context, size_t context_len,
+                  uint8_t *out, uint16_t out_len)
+{
+    if (secret_len != 32)
+    {
+        /* Our current implementation is not a general purpose one
+         * and assume that the secret size matches the size of the
+         * hash (SHA256) key */
+        return false;
+    }
+
+    struct gc_arena gc = gc_new();
+    /* 2 byte for the outlen encoded as uint16, 5 bytes for "ovpn " */
+    int hkdf_label_len = 2 + 5 + label_len + context_len;
+    struct buffer hkdf_label = alloc_buf_gc(hkdf_label_len, &gc);
+
+
+    buf_write_u16(&hkdf_label, out_len);
+    buf_write(&hkdf_label, "ovpn ", 5);
+    buf_write(&hkdf_label, label, label_len);
+    if (context_len > 0)
+    {
+        buf_write(&hkdf_label, context, context_len);
+    }
+
+    ASSERT(buf_len(&hkdf_label) == hkdf_label_len);
+
+    ovpn_hdkf_expand(secret, buf_bptr(&hkdf_label),
+                     buf_len(&hkdf_label), out, out_len);
+
+    gc_free(&gc);
+    return true;
+}
\ No newline at end of file
diff --git a/src/openvpn/crypto_epoch.h b/src/openvpn/crypto_epoch.h
new file mode 100644
index 0000000..dad2473
--- /dev/null
+++ b/src/openvpn/crypto_epoch.h
@@ -0,0 +1,69 @@ 
+/*
+ *  OpenVPN -- An application to securely tunnel IP networks
+ *             over a single TCP/UDP port, with support for SSL/TLS-based
+ *             session authentication and key exchange,
+ *             packet encryption, packet authentication, and
+ *             packet compression.
+ *
+ *  Copyright (C) 2024 OpenVPN Inc <sales@openvpn.net>
+ *  Copyright (C) 2024 Arne Schwabe <arne@rfc2549.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef CRYPTO_EPOCH_H
+#define CRYPTO_EPOCH_H
+
+/**
+ * Implementation of the RFC5869 HKDF-Expand function with the following
+ * restrictions
+ *  - salt is always assumed to be zero length (ie not supported)
+ *  - IKM (secret) is assumed to be always 32 bytes
+ *  - HASH is always SHA256
+ *
+ *  @param secret   the input keying material (HMAC key)
+ *  @param info     context and application specific information
+ *  @param out      output keying material
+ *  @param out_len  length of output keying material
+ */
+void
+ovpn_hdkf_expand(const uint8_t *secret,
+                 const uint8_t *info, int info_len,
+                 uint8_t *out, int out_len);
+
+/**
+ * Variant of the RFC 8446 TLS 1.3  HKDF-Expand-Label function with the
+ * following differences/restrictions:
+ *  - secret must 32 bytes in length
+ *  - label prefix is "ovpn " instead of "tls13 "
+ *  - HASH is always SHA256
+ *
+ * @param secret        Input secret
+ * @param secret_len    length of the input secret
+ * @param label         Label for the exported key material
+ * @param label_len     length of the label
+ * @param context       optional context
+ * @param context_len   length of the context
+ * @param out      output keying material
+ * @param out_len  length of output keying material
+ * @return
+ */
+bool
+ovpn_expand_label(const uint8_t *secret, size_t secret_len,
+                  const uint8_t *label, size_t label_len,
+                  const uint8_t *context, size_t context_len,
+                  uint8_t *out, uint16_t out_len);
+
+#endif
diff --git a/src/openvpn/crypto_mbedtls.h b/src/openvpn/crypto_mbedtls.h
index a966a7a..fe51359 100644
--- a/src/openvpn/crypto_mbedtls.h
+++ b/src/openvpn/crypto_mbedtls.h
@@ -29,6 +29,7 @@ 
 #ifndef CRYPTO_MBEDTLS_H_
 #define CRYPTO_MBEDTLS_H_
 
+#include <stdbool.h>
 #include <mbedtls/cipher.h>
 #include <mbedtls/md.h>
 #include <mbedtls/ctr_drbg.h>
diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am
index a4e6235..307f9ed 100644
--- a/tests/unit_tests/openvpn/Makefile.am
+++ b/tests/unit_tests/openvpn/Makefile.am
@@ -66,6 +66,7 @@ 
 	$(top_srcdir)/src/openvpn/crypto.c \
 	$(top_srcdir)/src/openvpn/crypto_mbedtls.c \
 	$(top_srcdir)/src/openvpn/crypto_openssl.c \
+	$(top_srcdir)/src/openvpn/crypto_epoch.c \
 	$(top_srcdir)/src/openvpn/otime.c \
 	$(top_srcdir)/src/openvpn/packet_id.c \
 	$(top_srcdir)/src/openvpn/platform.c \
diff --git a/tests/unit_tests/openvpn/test_crypto.c b/tests/unit_tests/openvpn/test_crypto.c
index 135cd15..c8d2a10 100644
--- a/tests/unit_tests/openvpn/test_crypto.c
+++ b/tests/unit_tests/openvpn/test_crypto.c
@@ -35,6 +35,7 @@ 
 #include <cmocka.h>
 
 #include "crypto.h"
+#include "crypto_epoch.h"
 #include "options.h"
 #include "ssl_backend.h"
 
@@ -469,6 +470,122 @@ 
     assert_int_equal(aeslimit / L, 122059461);
 }
 
+void
+crypto_test_hkdf_expand_testa1(void **state)
+{
+    /* RFC 5889 A.1 Test Case 1 */
+    uint8_t prk[32] =
+    {0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf,
+     0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63,
+     0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31,
+     0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5};
+
+    uint8_t info[10] = {0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5,
+                        0xf6, 0xf7, 0xf8, 0xf9};
+
+    uint8_t okm[42] =
+    {0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a,
+     0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, 0x2f, 0x2a,
+     0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c,
+     0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4, 0xc5, 0xbf,
+     0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18,
+     0x58, 0x65};
+
+    uint8_t out[42];
+    ovpn_hdkf_expand(prk, info, sizeof(info), out, sizeof(out));
+
+    assert_memory_equal(out, okm, sizeof(out));
+}
+
+void
+crypto_test_hkdf_expand_testa2(void **state)
+{
+    /* RFC 5889 A.2 Test Case 2 */
+    uint8_t prk[32] =
+    {0x06, 0xa6, 0xb8, 0x8c, 0x58, 0x53, 0x36, 0x1a,
+     0x06, 0x10, 0x4c, 0x9c, 0xeb, 0x35, 0xb4, 0x5c,
+     0xef, 0x76, 0x00, 0x14, 0x90, 0x46, 0x71, 0x01,
+     0x4a, 0x19, 0x3f, 0x40, 0xc1, 0x5f, 0xc2, 0x44};
+
+    uint8_t info[80] =
+    {0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+     0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+     0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+     0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
+     0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+     0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+     0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+     0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+     0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+     0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff};
+
+    const int L = 82;
+    uint8_t okm[82] =
+    {0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1,
+     0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a, 0x49, 0x34,
+     0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8,
+     0xa0, 0x50, 0xcc, 0x4c, 0x19, 0xaf, 0xa9, 0x7c,
+     0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72,
+     0x71, 0xcb, 0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09,
+     0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8,
+     0x36, 0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71,
+     0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec, 0x3e, 0x87,
+     0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f,
+     0x1d, 0x87};
+
+    uint8_t out[82] = {0xaa};
+    ovpn_hdkf_expand(prk, info, sizeof(info), out, L);
+
+    assert_memory_equal(out, okm, L);
+}
+
+void
+crypto_test_hkdf_expand_testa3(void **state)
+{
+    /* RFC 5889 A.3 Test Case 3 */
+    uint8_t prk[32] =
+    {0x19, 0xef, 0x24, 0xa3, 0x2c, 0x71, 0x7b, 0x16,
+     0x7f, 0x33, 0xa9, 0x1d, 0x6f, 0x64, 0x8b, 0xdf,
+     0x96, 0x59, 0x67, 0x76, 0xaf, 0xdb, 0x63, 0x77,
+     0xac, 0x43, 0x4c, 0x1c, 0x29, 0x3c, 0xcb, 0x04};
+
+    uint8_t info[0] = {};
+
+    int L = 42;
+    uint8_t okm[42] =
+    {0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f,
+     0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c, 0x5a, 0x31,
+     0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e,
+     0xc3, 0x45, 0x4e, 0x5f, 0x3c, 0x73, 0x8d, 0x2d,
+     0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a,
+     0x96, 0xc8};
+
+    uint8_t out[42];
+    ovpn_hdkf_expand(prk, info, sizeof(info), out, L);
+
+    assert_memory_equal(out, okm, L);
+}
+
+void
+crypto_test_ovpn_label_expand(void **state)
+{
+    uint8_t secret[32] =
+    {0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf,
+     0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63,
+     0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31,
+     0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5};
+
+    const uint8_t *label = (const uint8_t *)("unit test");
+    uint8_t out[16];
+    ovpn_expand_label(secret, sizeof(secret), label, 9, NULL, 0, out, sizeof(out));
+
+    uint8_t out_expected[16] =
+    {0x6, 0x8C, 0x11, 0xA6, 0x48, 0xFE, 0xBC, 0xA0,
+     0x9D, 0x1A, 0xF0, 0xD6, 0xD5, 0xF7, 0xBE, 0xC6};
+
+    assert_memory_equal(out, out_expected, 16);
+}
+
 int
 main(void)
 {
@@ -480,8 +597,12 @@ 
         cmocka_unit_test(crypto_test_hmac),
         cmocka_unit_test(test_occ_mtu_calculation),
         cmocka_unit_test(test_mssfix_mtu_calculation),
-        cmocka_unit_test(crypto_test_aead_limits)
-   };
+        cmocka_unit_test(crypto_test_aead_limits),
+        cmocka_unit_test(crypto_test_hkdf_expand_testa1),
+        cmocka_unit_test(crypto_test_hkdf_expand_testa2),
+        cmocka_unit_test(crypto_test_hkdf_expand_testa3),
+        cmocka_unit_test(crypto_test_ovpn_label_expand),
+    };
 
 #if defined(ENABLE_CRYPTO_OPENSSL)
     OpenSSL_add_all_algorithms();