[Openvpn-devel,v6] Prefer TLS libraries TLS PRF function, fix OpenVPN in FIPS mode

Message ID 20210305141352.21847-1-arne@rfc2549.org
State Accepted
Headers show
Series [Openvpn-devel,v6] Prefer TLS libraries TLS PRF function, fix OpenVPN in FIPS mode | expand

Commit Message

Arne Schwabe March 5, 2021, 3:13 a.m. UTC
This moves from using our own copy of the TLS1 PRF function to using
TLS library provided function where possible. This includes currently
OpenSSL 1.1.0+ and mbed TLS 2.18+.

For the libraries where it is not possible to use the library's own
function, we still use our own implementation. mbed TLS will continue
to use our own old PRF function while for OpenSSL we will use a
adapted version from OpenSSL 1.0.2t code. The version allows to be
used in a FIPS enabled environment.

The old OpenSSL and mbed TLS implementation could have shared some
more code but as we will eventually drop support for older TLS
libraries, the separation makes it easier it remove that code
invdidually.

In FIPS mode MD5 is normally forbidden, the TLS1 PRF1 function we
use, makes uses of MD5, which in the past has caused OpenVPN to segfault.
The new implementation for OpenSSL version of our custom implementation
has added the special flags that tell OpenSSL that this specific use
of MD5 is allowed in FIPS mode.

No FIPS conformitiy testing etc has been done, this is only about
allowing OpenVPN on a system where FIPS mode has been enabled system
wide (e.g. on RHEL derivates).

Patch v4: Handle the unlikely case that PRF generation fails. More formatting
          fixes.
Patch v5: v4 with the formatting fixes actually commited. sigh.

Patch v6: More formatting fixes, make OpenSSL fucntion return bool instead
          of int.

Signed-off-by: Arne Schwabe <arne@rfc2549.org>
---
 Changes.rst                            |   6 +
 src/openvpn/crypto_backend.h           |  16 ++
 src/openvpn/crypto_mbedtls.c           | 132 +++++++++++++++
 src/openvpn/crypto_openssl.c           | 216 +++++++++++++++++++++++++
 src/openvpn/ssl.c                      | 206 ++++++-----------------
 tests/unit_tests/openvpn/test_crypto.c |  32 ++++
 6 files changed, 449 insertions(+), 159 deletions(-)

Comments

Antonio Quartulli March 6, 2021, 9:56 a.m. UTC | #1
Hi,

On 05/03/2021 15:13, Arne Schwabe wrote:
> This moves from using our own copy of the TLS1 PRF function to using
> TLS library provided function where possible. This includes currently
> OpenSSL 1.1.0+ and mbed TLS 2.18+.
> 
> For the libraries where it is not possible to use the library's own
> function, we still use our own implementation. mbed TLS will continue
> to use our own old PRF function while for OpenSSL we will use a
> adapted version from OpenSSL 1.0.2t code. The version allows to be
> used in a FIPS enabled environment.
> 
> The old OpenSSL and mbed TLS implementation could have shared some
> more code but as we will eventually drop support for older TLS
> libraries, the separation makes it easier it remove that code
> invdidually.
> 
> In FIPS mode MD5 is normally forbidden, the TLS1 PRF1 function we
> use, makes uses of MD5, which in the past has caused OpenVPN to segfault.
> The new implementation for OpenSSL version of our custom implementation
> has added the special flags that tell OpenSSL that this specific use
> of MD5 is allowed in FIPS mode.
> 
> No FIPS conformitiy testing etc has been done, this is only about
> allowing OpenVPN on a system where FIPS mode has been enabled system
> wide (e.g. on RHEL derivates).
> 
> Patch v4: Handle the unlikely case that PRF generation fails. More formatting
>           fixes.
> Patch v5: v4 with the formatting fixes actually commited. sigh.
> 
> Patch v6: More formatting fixes, make OpenSSL fucntion return bool instead
>           of int.
> 
> Signed-off-by: Arne Schwabe <arne@rfc2549.org>


It looks good and passes my basic tests.

Acked-by: Antonio Quartulli <antonio@openvpn.net>
Gert Doering March 7, 2021, 7:40 a.m. UTC | #2
Hi,

On Sat, Mar 06, 2021 at 09:56:36PM +0100, Antonio Quartulli wrote:
> On 05/03/2021 15:13, Arne Schwabe wrote:
> > This moves from using our own copy of the TLS1 PRF function to using
> > TLS library provided function where possible. This includes currently
> > OpenSSL 1.1.0+ and mbed TLS 2.18+.
[..]
> It looks good and passes my basic tests.
> 
> Acked-by: Antonio Quartulli <antonio@openvpn.net>

ACKs are good, but SIGSEGV are bad.  

This crashes for me on the "--tls-client, but no --client" test case, 
with "Linux, mbed TLS 2.25.0"  (talking to a server with the same code,
"3338f2d5a2b7 + this patch"

 run openvpn --ca /home/gert/src/openvpn.git-keys/ca.crt --cert /home/gert/src/openvpn.git-keys/cron2-gentoo-i386.crt --key /home/gert/src/openvpn.git-keys/cron2-gentoo-i386.key     --remote-cert-tls server --nobind --comp-lzo --verb 3 
         --tls-client --dev tap --proto tcp-client --remote gentoo.ov.greenie.net 51204 --ifconfig 10.204.9.2 255.255.255.0 --comp-lzo --tun-ipv6 --ifconfig-ipv6 fd00:abcd:204:9::2/64 fd00:abcd:204:9::1 --route 10.204.0.0 255.255.0.0 10.204.9.1 --route-ipv6 fd00:abcd:204::/48 
         Segmentation fault

GDB tells me:

...
2021-03-07 19:27:15 ++ Certificate has EKU (str) TLS Web Server Authentication, expects TLS Web Server Authentication
2021-03-07 19:27:15 VERIFY EKU OK
2021-03-07 19:27:15 VERIFY OK: depth=0, C=US, ST=California, L=Pleasanton, O=OpenVPN community project, CN=server, emailAddress=samuli@openvpn.net

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7f34ef8 in ?? () from /usr/lib64/libmbedtls.so.13
(gdb) where
#0  0x00007ffff7f34ef8 in ?? () from /usr/lib64/libmbedtls.so.13
#1  0x00007ffff7f35945 in ?? () from /usr/lib64/libmbedtls.so.13
#2  0x00007ffff7f37234 in ?? () from /usr/lib64/libmbedtls.so.13
#3  0x00007ffff7f3a727 in mbedtls_ssl_handshake_client_step () from /usr/lib64/libmbedtls.so.13
#4  0x00007ffff7f4c12d in mbedtls_ssl_handshake_step () from /usr/lib64/libmbedtls.so.13
#5  0x00007ffff7f4c1b8 in mbedtls_ssl_handshake () from /usr/lib64/libmbedtls.so.13
#6  0x00007ffff7f429b8 in mbedtls_ssl_read () from /usr/lib64/libmbedtls.so.13
#7  0x00005555555d0c38 in key_state_read_plaintext (ks=ks@entry=0x555555627d00, 
    buf=buf@entry=0x555555627eb0, maxlen=maxlen@entry=2048) at ../../../openvpn/src/openvpn/buffer.h:231
#8  0x00005555555c9a38 in tls_process (wakeup=0x7fffffffd8b4, to_link_socket_info=<optimized out>, 
    to_link_addr=0x7fffffffd7c8, to_link=<optimized out>, session=<optimized out>, multi=0x555555627720)
    at ../../../openvpn/src/openvpn/ssl.c:2806
#9  tls_multi_process (multi=0x555555627720, to_link=to_link@entry=0x7fffffffe6d0, 
    to_link_addr=to_link_addr@entry=0x7fffffffe3c0, to_link_socket_info=<optimized out>, 
    wakeup=wakeup@entry=0x7fffffffd8d4) at ../../../openvpn/src/openvpn/ssl.c:3009
#10 0x000055555556c593 in check_tls (c=c@entry=0x7fffffffd910) at ../../../openvpn/src/openvpn/forward.c:148
#11 0x000055555556fc68 in pre_select (c=c@entry=0x7fffffffd910)
    at ../../../openvpn/src/openvpn/forward.c:1837
#12 0x000055555559375e in tunnel_point_to_point (c=0x7fffffffd910)
    at ../../../openvpn/src/openvpn/openvpn.c:79
#13 openvpn_main (argc=35, argv=0x7fffffffe998) at ../../../openvpn/src/openvpn/openvpn.c:283

It passes the test same case (same OS, same build tree) for OpenSSL 1.1.1j
and also for FreeBSD/OpenSSL 1.0.2u and FreeBSD/mbed TLS 2.16.9.

As the commit message talks about "mbed TLS 2.18+", it seems that code 
gets irked by --tls-client/p2p.

I do not understand these code paths enough to do further educated guesses,
but we can't merge it like that.

gert
Gert Doering March 7, 2021, 11:53 p.m. UTC | #3
After some discussion on IRC today, it turns out that I was holding
my test rig wrong.  As in: breakage occurs in the combination of
"mbed TLS 2.25.0, TLS, TCP and --dev tap", but it does not actually 
matter whether this patch is applied or not - 2.5.1 breaks as well.  

Arne's test found the commit in mbedTLS between 2.24 and 2.25 that 
introduces the breakage (360e2c41d8211e43), but this does not really
explain anything - but it's fairly clear "not something broken in
our code", or at least "not recently".

As discussed on IRC, I have fixed what whitespace uncrustify complained
about ("if<blank>(", function return types on the preceding line, #endif
with comment)

This all said, I now do actually understand what the patch does (and it
looks good) and I think I have all versions of old/new mbedtls and
old/new openssl covered, client and server.  All pass, except for 
mbedtls 2.25.0 + tcp + tap.

Your patch has been applied to the master branch.

commit 06f6cf3ff850f2930bf4a864ae9898407e94ffb9
Author: Arne Schwabe
Date:   Fri Mar 5 15:13:52 2021 +0100

     Prefer TLS libraries TLS PRF function, fix OpenVPN in FIPS mode

     Signed-off-by: Arne Schwabe <arne@rfc2549.org>
     Acked-by: Antonio Quartulli <antonio@openvpn.net>
     Message-Id: <20210305141352.21847-1-arne@rfc2549.org>
     URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg21612.html
     Signed-off-by: Gert Doering <gert@greenie.muc.de>


--
kind regards,

Gert Doering
Gert Doering March 8, 2021, 12:19 a.m. UTC | #4
Hi,

On Mon, Mar 08, 2021 at 11:53:09AM +0100, Gert Doering wrote:
> After some discussion on IRC today, it turns out that I was holding
> my test rig wrong.  As in: breakage occurs in the combination of
> "mbed TLS 2.25.0, TLS, TCP and --dev tap", but it does not actually 
> matter whether this patch is applied or not - 2.5.1 breaks as well.  

Arne found the trigger.

It is neither related to TCP nor to "--dev tap", but it needs a server
side with "OpenSSL 1.1.1" *and* a client side with "mbedTLS 2.25" to
trigger this - both ends need to negotiate curve25591, and then mbedTLS
will crash in the debug print function.

  https://github.com/ARMmbed/mbedtls/issues/4208

My current test rigs do not test this combination, except for this
particular test case (tcp+tap towards --inetd server), so we were chasing
red herrings for a while...

I need to think long and hard now how to add meaningful tests with
the new test matrix 

   openssl 1.0.2u <-> openssl 1.1.1
   mbedTLS "oldish" <-> mbedTLS "very new

to the test sets, without making the already-long server side test 
(40 minutes) go totally out of bounds...

gert

Patch

diff --git a/Changes.rst b/Changes.rst
index 2a847564..62008e8d 100644
--- a/Changes.rst
+++ b/Changes.rst
@@ -9,6 +9,12 @@  Keying Material Exporters (RFC 5705) based key generation
     the RFC5705 based key material generation to the current custom
     OpenVPN PRF. This feature requires OpenSSL or mbed TLS 2.18+.
 
+Compatibility with OpenSSL in FIPS mode
+    OpenVPN will now work with OpenSSL in FIPS mode. Note, no effort
+    has been made to check or implement all the
+    requirements/recommendation of FIPS 140-2. This just allows OpenVPN
+    to be run on a system that be configured OpenSSL in FIPS mode.
+
 Deprecated features
 -------------------
 ``inetd`` has been removed
diff --git a/src/openvpn/crypto_backend.h b/src/openvpn/crypto_backend.h
index 85cb084a..384ffc80 100644
--- a/src/openvpn/crypto_backend.h
+++ b/src/openvpn/crypto_backend.h
@@ -699,4 +699,20 @@  const char *translate_cipher_name_from_openvpn(const char *cipher_name);
  */
 const char *translate_cipher_name_to_openvpn(const char *cipher_name);
 
+
+/**
+ * Calculates the TLS 1.0-1.1 PRF function. For the exact specification of the
+ * function definition see the TLS RFCs like RFC 4346.
+ *
+ * @param seed          seed to use
+ * @param seed_len      length of the seed
+ * @param secret        secret to use
+ * @param secret_len    length of the secret
+ * @param output        output destination
+ * @param output_len    length of output/number of bytes to generate
+ *
+ * @return              true if successful, false on any error
+ */
+bool ssl_tls1_PRF(const uint8_t *seed, int seed_len, const uint8_t *secret,
+                  int secret_len, uint8_t *output, int output_len);
 #endif /* CRYPTO_BACKEND_H_ */
diff --git a/src/openvpn/crypto_mbedtls.c b/src/openvpn/crypto_mbedtls.c
index fbb1f120..7ea08de4 100644
--- a/src/openvpn/crypto_mbedtls.c
+++ b/src/openvpn/crypto_mbedtls.c
@@ -54,6 +54,7 @@ 
 #include <mbedtls/pem.h>
 
 #include <mbedtls/entropy.h>
+#include <mbedtls/ssl.h>
 
 
 /*
@@ -984,4 +985,135 @@  memcmp_constant_time(const void *a, const void *b, size_t size)
 
     return diff;
 }
+/* mbedtls-2.18.0 or newer */
+#ifdef HAVE_MBEDTLS_SSL_TLS_PRF
+bool ssl_tls1_PRF(const uint8_t *seed, int seed_len, const uint8_t *secret,
+                  int secret_len, uint8_t *output, int output_len)
+{
+    return mbed_ok(mbedtls_ssl_tls_prf(MBEDTLS_SSL_TLS_PRF_TLS1, secret,
+                                       secret_len, "", seed, seed_len, output,
+                                       output_len));
+}
+#else
+/*
+ * Generate the hash required by for the \c tls1_PRF function.
+ *
+ * @param md_kt         Message digest to use
+ * @param sec           Secret to base the hash on
+ * @param sec_len       Length of the secret
+ * @param seed          Seed to hash
+ * @param seed_len      Length of the seed
+ * @param out           Output buffer
+ * @param olen          Length of the output buffer
+ */
+static void
+tls1_P_hash(const md_kt_t *md_kt, const uint8_t *sec, int sec_len,
+            const uint8_t *seed, int seed_len, uint8_t *out, int olen)
+{
+    struct gc_arena gc = gc_new();
+    uint8_t A1[MAX_HMAC_KEY_LENGTH];
+
+#ifdef ENABLE_DEBUG
+    /* used by the D_SHOW_KEY_SOURCE, guarded with ENABLE_DEBUG to avoid unused
+     * variables warnings if compiled with --enable-small */
+    const int olen_orig = olen;
+    const uint8_t *out_orig = out;
+#endif
+
+    hmac_ctx_t *ctx = hmac_ctx_new();
+    hmac_ctx_t *ctx_tmp = hmac_ctx_new();
+
+    dmsg(D_SHOW_KEY_SOURCE, "tls1_P_hash sec: %s", format_hex(sec, sec_len, 0, &gc));
+    dmsg(D_SHOW_KEY_SOURCE, "tls1_P_hash seed: %s", format_hex(seed, seed_len, 0, &gc));
+
+    int chunk = md_kt_size(md_kt);
+    unsigned int A1_len = md_kt_size(md_kt);
+
+    hmac_ctx_init(ctx, sec, sec_len, md_kt);
+    hmac_ctx_init(ctx_tmp, sec, sec_len, md_kt);
+
+    hmac_ctx_update(ctx,seed,seed_len);
+    hmac_ctx_final(ctx, A1);
+
+    for (;;)
+    {
+        hmac_ctx_reset(ctx);
+        hmac_ctx_reset(ctx_tmp);
+        hmac_ctx_update(ctx, A1, A1_len);
+        hmac_ctx_update(ctx_tmp, A1, A1_len);
+        hmac_ctx_update(ctx, seed, seed_len);
+
+        if (olen > chunk)
+        {
+            hmac_ctx_final(ctx, out);
+            out += chunk;
+            olen -= chunk;
+            hmac_ctx_final(ctx_tmp, A1); /* calc the next A1 value */
+        }
+        else    /* last one */
+        {
+            hmac_ctx_final(ctx, A1);
+            memcpy(out, A1, olen);
+            break;
+        }
+    }
+    hmac_ctx_cleanup(ctx);
+    hmac_ctx_free(ctx);
+    hmac_ctx_cleanup(ctx_tmp);
+    hmac_ctx_free(ctx_tmp);
+    secure_memzero(A1, sizeof(A1));
+
+    dmsg(D_SHOW_KEY_SOURCE, "tls1_P_hash out: %s", format_hex(out_orig, olen_orig, 0, &gc));
+    gc_free(&gc);
+}
+
+/*
+ * Use the TLS PRF function for generating data channel keys.
+ * This code is based on the OpenSSL library.
+ *
+ * TLS generates keys as such:
+ *
+ * master_secret[48] = PRF(pre_master_secret[48], "master secret",
+ *                         ClientHello.random[32] + ServerHello.random[32])
+ *
+ * key_block[] = PRF(SecurityParameters.master_secret[48],
+ *                 "key expansion",
+ *                 SecurityParameters.server_random[32] +
+ *                 SecurityParameters.client_random[32]);
+ *
+ * Notes:
+ *
+ * (1) key_block contains a full set of 4 keys.
+ * (2) The pre-master secret is generated by the client.
+ */
+bool ssl_tls1_PRF(const uint8_t *label, int label_len, const uint8_t *sec,
+                  int slen, uint8_t *out1, int olen)
+{
+    struct gc_arena gc = gc_new();
+    const md_kt_t *md5 = md_kt_get("MD5");
+    const md_kt_t *sha1 = md_kt_get("SHA1");
+
+    uint8_t *out2 = (uint8_t *)gc_malloc(olen, false, &gc);
+
+    int len = slen/2;
+    const uint8_t *S1 = sec;
+    const uint8_t *S2 = &(sec[len]);
+    len += (slen&1); /* add for odd, make longer */
+
+    tls1_P_hash(md5, S1, len, label, label_len, out1, olen);
+    tls1_P_hash(sha1, S2, len, label, label_len, out2, olen);
+
+    for (int i = 0; i<olen; i++)
+    {
+        out1[i] ^= out2[i];
+    }
+
+    secure_memzero(out2, olen);
+
+    dmsg(D_SHOW_KEY_SOURCE, "tls1_PRF out[%d]: %s", olen, format_hex(out1, olen, 0, &gc));
+
+    gc_free(&gc);
+    return true;
+}
+#endif
 #endif /* ENABLE_CRYPTO_MBEDTLS */
diff --git a/src/openvpn/crypto_openssl.c b/src/openvpn/crypto_openssl.c
index 4ea78c01..4f088c3a 100644
--- a/src/openvpn/crypto_openssl.c
+++ b/src/openvpn/crypto_openssl.c
@@ -51,6 +51,10 @@ 
 #include <openssl/rand.h>
 #include <openssl/ssl.h>
 
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+#include <openssl/kdf.h>
+#endif
+
 /*
  * Check for key size creepage.
  */
@@ -1124,4 +1128,216 @@  engine_load_key(const char *file, SSL_CTX *ctx)
 #endif /* if HAVE_OPENSSL_ENGINE */
 }
 
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+bool ssl_tls1_PRF(const uint8_t *seed, int seed_len, const uint8_t *secret,
+                  int secret_len, uint8_t *output, int output_len)
+{
+    EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_TLS1_PRF, NULL);
+    if(!EVP_PKEY_derive_init(pctx))
+    {
+        return false;
+    }
+
+    if(!EVP_PKEY_CTX_set_tls1_prf_md(pctx, EVP_md5_sha1()))
+    {
+        return false;
+    }
+
+    if(!EVP_PKEY_CTX_set1_tls1_prf_secret(pctx, secret, secret_len))
+    {
+        return false;
+    }
+
+    if(!EVP_PKEY_CTX_add1_tls1_prf_seed(pctx, seed, seed_len))
+    {
+        return false;
+    }
+
+    size_t out_len = output_len;
+    if(!EVP_PKEY_derive(pctx, output, &out_len))
+    {
+        return false;
+    }
+    if (out_len != output_len)
+    {
+        return false;
+    }
+    return true;
+}
+#else
+/*
+ * Generate the hash required by for the \c tls1_PRF function.
+ *
+ * We cannot use our normal hmac_* function as they do not work
+ * in a FIPS environment (no MD5 allowed, which we need). Instead
+ * we need to directly use the EVP_MD_* API with the special
+ * EVP_MD_CTX_FLAG_NON_FIPS_ALLOW flag.
+ *
+ * The function below is adapted from OpenSSL 1.0.2t
+ *
+ * @param md_kt         Message digest to use
+ * @param sec           Secret to base the hash on
+ * @param sec_len       Length of the secret
+ * @param seed          Seed to hash
+ * @param seed_len      Length of the seed
+ * @param out           Output buffer
+ * @param olen          Length of the output buffer
+ */
+static
+bool tls1_P_hash(const EVP_MD *md, const unsigned char *sec,
+                 int sec_len, const void *seed, int seed_len,
+                 unsigned char *out, int olen)
+{
+    int chunk;
+    size_t j;
+    EVP_MD_CTX ctx, ctx_tmp, ctx_init;
+    EVP_PKEY *mac_key;
+    unsigned char A1[EVP_MAX_MD_SIZE];
+    size_t A1_len;
+    int ret = false;
+
+    chunk = EVP_MD_size(md);
+    OPENSSL_assert(chunk >= 0);
+
+    EVP_MD_CTX_init(&ctx);
+    EVP_MD_CTX_init(&ctx_tmp);
+    EVP_MD_CTX_init(&ctx_init);
+    EVP_MD_CTX_set_flags(&ctx_init, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW);
+    mac_key = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, sec, sec_len);
+    if (!mac_key)
+    {
+        goto err;
+    }
+    if (!EVP_DigestSignInit(&ctx_init, NULL, md, NULL, mac_key))
+    {
+        goto err;
+    }
+    if (!EVP_MD_CTX_copy_ex(&ctx, &ctx_init))
+    {
+        goto err;
+    }
+    if (!EVP_DigestSignUpdate(&ctx, seed, seed_len))
+    {
+        goto err;
+    }
+    if (!EVP_DigestSignFinal(&ctx, A1, &A1_len))
+    {
+        goto err;
+    }
+
+    for (;;) {
+        /* Reinit mac contexts */
+        if (!EVP_MD_CTX_copy_ex(&ctx, &ctx_init))
+        {
+            goto err;
+        }
+        if (!EVP_DigestSignUpdate(&ctx, A1, A1_len))
+        {
+            goto err;
+        }
+        if (olen > chunk && !EVP_MD_CTX_copy_ex(&ctx_tmp, &ctx))
+        {
+            goto err;
+        }
+        if (!EVP_DigestSignUpdate(&ctx, seed, seed_len))
+        {
+            goto err;
+        }
+
+        if (olen > chunk)
+        {
+            if (!EVP_DigestSignFinal(&ctx, out, &j))
+            {
+                goto err;
+            }
+            out += j;
+            olen -= j;
+            /* calc the next A1 value */
+            if (!EVP_DigestSignFinal(&ctx_tmp, A1, &A1_len))
+            {
+                goto err;
+            }
+        }
+        else
+        {
+            /* last one */
+            if (!EVP_DigestSignFinal(&ctx, A1, &A1_len))
+            {
+                goto err;
+            }
+            memcpy(out, A1, olen);
+            break;
+        }
+    }
+    ret = true;
+err:
+    EVP_PKEY_free(mac_key);
+    EVP_MD_CTX_cleanup(&ctx);
+    EVP_MD_CTX_cleanup(&ctx_tmp);
+    EVP_MD_CTX_cleanup(&ctx_init);
+    OPENSSL_cleanse(A1, sizeof(A1));
+    return ret;
+}
+
+
+/*
+ * Use the TLS PRF function for generating data channel keys.
+ * This code is based on the OpenSSL library.
+ *
+ * TLS generates keys as such:
+ *
+ * master_secret[48] = PRF(pre_master_secret[48], "master secret",
+ *                         ClientHello.random[32] + ServerHello.random[32])
+ *
+ * key_block[] = PRF(SecurityParameters.master_secret[48],
+ *                 "key expansion",
+ *                 SecurityParameters.server_random[32] +
+ *                 SecurityParameters.client_random[32]);
+ *
+ * Notes:
+ *
+ * (1) key_block contains a full set of 4 keys.
+ * (2) The pre-master secret is generated by the client.
+ */
+bool ssl_tls1_PRF(const uint8_t *label, int label_len, const uint8_t *sec,
+                  int slen, uint8_t *out1, int olen)
+{
+    bool ret = true;
+    struct gc_arena gc = gc_new();
+    /* For some reason our md_kt_get("MD5") fails otherwise in the unit test */
+    const md_kt_t *md5 = EVP_md5();
+    const md_kt_t *sha1 = EVP_sha1();
+
+    uint8_t *out2 = (uint8_t *)gc_malloc(olen, false, &gc);
+
+    int len = slen/2;
+    const uint8_t *S1 = sec;
+    const uint8_t *S2 = &(sec[len]);
+    len += (slen&1); /* add for odd, make longer */
+
+    if(!tls1_P_hash(md5, S1, len, label, label_len, out1, olen))
+    {
+        ret = false;
+        goto done;
+    }
+
+    if(!tls1_P_hash(sha1, S2, len, label, label_len, out2, olen))
+    {
+        ret = false;
+        goto done;
+    }
+
+    for (int i = 0; i < olen; i++)
+    {
+        out1[i] ^= out2[i];
+    }
+
+    secure_memzero(out2, olen);
+
+    dmsg(D_SHOW_KEY_SOURCE, "tls1_PRF out[%d]: %s", olen, format_hex(out1, olen, 0, &gc));
+done:
+    gc_free(&gc);
+    return ret;
+}
+#endif
 #endif /* ENABLE_CRYPTO_OPENSSL */
diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
index 5a231387..56cd4c60 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -1580,135 +1580,7 @@  key_source2_print(const struct key_source2 *k)
     key_source_print(&k->server, "Server");
 }
 
-/*
- * Generate the hash required by for the \c tls1_PRF function.
- *
- * @param md_kt         Message digest to use
- * @param sec           Secret to base the hash on
- * @param sec_len       Length of the secret
- * @param seed          Seed to hash
- * @param seed_len      Length of the seed
- * @param out           Output buffer
- * @param olen          Length of the output buffer
- */
-static void
-tls1_P_hash(const md_kt_t *md_kt,
-            const uint8_t *sec,
-            int sec_len,
-            const uint8_t *seed,
-            int seed_len,
-            uint8_t *out,
-            int olen)
-{
-    struct gc_arena gc = gc_new();
-    uint8_t A1[MAX_HMAC_KEY_LENGTH];
-
-#ifdef ENABLE_DEBUG
-    const int olen_orig = olen;
-    const uint8_t *out_orig = out;
-#endif
-
-    hmac_ctx_t *ctx = hmac_ctx_new();
-    hmac_ctx_t *ctx_tmp = hmac_ctx_new();
-
-    dmsg(D_SHOW_KEY_SOURCE, "tls1_P_hash sec: %s", format_hex(sec, sec_len, 0, &gc));
-    dmsg(D_SHOW_KEY_SOURCE, "tls1_P_hash seed: %s", format_hex(seed, seed_len, 0, &gc));
-
-    int chunk = md_kt_size(md_kt);
-    unsigned int A1_len = md_kt_size(md_kt);
-
-    hmac_ctx_init(ctx, sec, sec_len, md_kt);
-    hmac_ctx_init(ctx_tmp, sec, sec_len, md_kt);
-
-    hmac_ctx_update(ctx,seed,seed_len);
-    hmac_ctx_final(ctx, A1);
-
-    for (;; )
-    {
-        hmac_ctx_reset(ctx);
-        hmac_ctx_reset(ctx_tmp);
-        hmac_ctx_update(ctx,A1,A1_len);
-        hmac_ctx_update(ctx_tmp,A1,A1_len);
-        hmac_ctx_update(ctx,seed,seed_len);
-
-        if (olen > chunk)
-        {
-            hmac_ctx_final(ctx, out);
-            out += chunk;
-            olen -= chunk;
-            hmac_ctx_final(ctx_tmp, A1); /* calc the next A1 value */
-        }
-        else    /* last one */
-        {
-            hmac_ctx_final(ctx, A1);
-            memcpy(out,A1,olen);
-            break;
-        }
-    }
-    hmac_ctx_cleanup(ctx);
-    hmac_ctx_free(ctx);
-    hmac_ctx_cleanup(ctx_tmp);
-    hmac_ctx_free(ctx_tmp);
-    secure_memzero(A1, sizeof(A1));
-
-    dmsg(D_SHOW_KEY_SOURCE, "tls1_P_hash out: %s", format_hex(out_orig, olen_orig, 0, &gc));
-    gc_free(&gc);
-}
-
-/*
- * Use the TLS PRF function for generating data channel keys.
- * This code is based on the OpenSSL library.
- *
- * TLS generates keys as such:
- *
- * master_secret[48] = PRF(pre_master_secret[48], "master secret",
- *                         ClientHello.random[32] + ServerHello.random[32])
- *
- * key_block[] = PRF(SecurityParameters.master_secret[48],
- *                 "key expansion",
- *                 SecurityParameters.server_random[32] +
- *                 SecurityParameters.client_random[32]);
- *
- * Notes:
- *
- * (1) key_block contains a full set of 4 keys.
- * (2) The pre-master secret is generated by the client.
- */
-static void
-tls1_PRF(const uint8_t *label,
-         int label_len,
-         const uint8_t *sec,
-         int slen,
-         uint8_t *out1,
-         int olen)
-{
-    struct gc_arena gc = gc_new();
-    const md_kt_t *md5 = md_kt_get("MD5");
-    const md_kt_t *sha1 = md_kt_get("SHA1");
-
-    uint8_t *out2 = (uint8_t *) gc_malloc(olen, false, &gc);
-
-    int len = slen/2;
-    const uint8_t *S1 = sec;
-    const uint8_t *S2 = &(sec[len]);
-    len += (slen&1); /* add for odd, make longer */
-
-    tls1_P_hash(md5,S1,len,label,label_len,out1,olen);
-    tls1_P_hash(sha1,S2,len,label,label_len,out2,olen);
-
-    for (int i = 0; i<olen; i++)
-    {
-        out1[i] ^= out2[i];
-    }
-
-    secure_memzero(out2, olen);
-
-    dmsg(D_SHOW_KEY_SOURCE, "tls1_PRF out[%d]: %s", olen, format_hex(out1, olen, 0, &gc));
-
-    gc_free(&gc);
-}
-
-static void
+static bool
 openvpn_PRF(const uint8_t *secret,
             int secret_len,
             const char *label,
@@ -1721,6 +1593,7 @@  openvpn_PRF(const uint8_t *secret,
             uint8_t *output,
             int output_len)
 {
+    bool ret = true;
     /* concatenate seed components */
 
     struct buffer seed = alloc_buf(strlen(label)
@@ -1742,12 +1615,16 @@  openvpn_PRF(const uint8_t *secret,
     }
 
     /* compute PRF */
-    tls1_PRF(BPTR(&seed), BLEN(&seed), secret, secret_len, output, output_len);
+    if (!ssl_tls1_PRF(BPTR(&seed), BLEN(&seed), secret, secret_len, output, output_len))
+    {
+        ret = false;
+    }
 
     buf_clear(&seed);
     free_buf(&seed);
 
     VALGRIND_MAKE_READABLE((void *)output, output_len);
+    return ret;
 }
 
 static void
@@ -1782,8 +1659,8 @@  generate_key_expansion_tls_export(struct tls_session *session, struct key2 *key2
     return true;
 }
 
-static struct key2
-generate_key_expansion_openvpn_prf(const struct tls_session *session)
+static bool
+generate_key_expansion_openvpn_prf(const struct tls_session *session, struct key2 *key2)
 {
     uint8_t master[48] = { 0 };
 
@@ -1799,33 +1676,40 @@  generate_key_expansion_openvpn_prf(const struct tls_session *session)
     key_source2_print(key_src);
 
     /* compute master secret */
-    openvpn_PRF(key_src->client.pre_master,
-                sizeof(key_src->client.pre_master),
-                KEY_EXPANSION_ID " master secret",
-                key_src->client.random1,
-                sizeof(key_src->client.random1),
-                key_src->server.random1,
-                sizeof(key_src->server.random1),
-                NULL,
-                NULL,
-                master,
-                sizeof(master));
+    if(!openvpn_PRF(key_src->client.pre_master,
+                    sizeof(key_src->client.pre_master),
+                    KEY_EXPANSION_ID " master secret",
+                    key_src->client.random1,
+                    sizeof(key_src->client.random1),
+                    key_src->server.random1,
+                    sizeof(key_src->server.random1),
+                    NULL,
+                    NULL,
+                    master,
+                    sizeof(master)))
+    {
+        return false;
+    }
 
-    struct key2 key2;
     /* compute key expansion */
-    openvpn_PRF(master,
-                sizeof(master),
-                KEY_EXPANSION_ID " key expansion",
-                key_src->client.random2,
-                sizeof(key_src->client.random2),
-                key_src->server.random2,
-                sizeof(key_src->server.random2),
-                client_sid,
-                server_sid,
-                (uint8_t *)key2.keys,
-                sizeof(key2.keys));
+    if (!openvpn_PRF(master,
+                    sizeof(master),
+                    KEY_EXPANSION_ID " key expansion",
+                    key_src->client.random2,
+                    sizeof(key_src->client.random2),
+                    key_src->server.random2,
+                    sizeof(key_src->server.random2),
+                    client_sid,
+                    server_sid,
+                    (uint8_t *)key2->keys,
+                    sizeof(key2->keys)))
+    {
+        return false;
+    }
     secure_memzero(&master, sizeof(master));
 
+
+
     /*
      * fixup_key only correctly sets DES parity bits if the cipher is a
      * DES variant.
@@ -1840,11 +1724,11 @@  generate_key_expansion_openvpn_prf(const struct tls_session *session)
      */
     for (int i = 0; i < 2; ++i)
     {
-        fixup_key(&key2.keys[i], &session->opt->key_type);
+        fixup_key(&key2->keys[i], &session->opt->key_type);
     }
-    key2.n = 2;
+    key2->n = 2;
 
-    return key2;
+    return true;
 }
 
 /*
@@ -1876,7 +1760,11 @@  generate_key_expansion(struct key_ctx_bi *key,
     }
     else
     {
-        key2 = generate_key_expansion_openvpn_prf(session);
+        if (!generate_key_expansion_openvpn_prf(session, &key2))
+        {
+            msg(D_TLS_ERRORS, "TLS Error: PRF calcuation failed");
+            goto exit;
+        }
     }
 
     key2_print(&key2, &session->opt->key_type,
diff --git a/tests/unit_tests/openvpn/test_crypto.c b/tests/unit_tests/openvpn/test_crypto.c
index ea9b99b2..af83da68 100644
--- a/tests/unit_tests/openvpn/test_crypto.c
+++ b/tests/unit_tests/openvpn/test_crypto.c
@@ -38,6 +38,7 @@ 
 #include <cmocka.h>
 
 #include "crypto.h"
+#include "ssl_backend.h"
 
 #include "mock_msg.h"
 
@@ -136,12 +137,43 @@  crypto_translate_cipher_names(void **state)
     test_cipher_names("id-aes256-GCM", "AES-256-GCM");
 }
 
+
+static uint8_t good_prf[32] = {0xd9, 0x8c, 0x85, 0x18, 0xc8, 0x5e, 0x94, 0x69,
+                               0x27, 0x91, 0x6a, 0xcf, 0xc2, 0xd5, 0x92, 0xfb,
+                               0xb1, 0x56, 0x7e, 0x4b, 0x4b, 0x14, 0x59, 0xe6,
+                               0xa9, 0x04, 0xac, 0x2d, 0xda, 0xb7, 0x2d, 0x67};
+static void
+crypto_test_tls_prf(void **state)
+{
+    const char *seedstr = "Quis aute iure reprehenderit in voluptate "
+                          "velit esse cillum dolore";
+    const unsigned char *seed = (const unsigned char *)seedstr;
+    const size_t seed_len = strlen(seedstr);
+
+
+
+
+    const char* ipsumlorem = "Lorem ipsum dolor sit amet, consectetur "
+                             "adipisici elit, sed eiusmod tempor incidunt ut "
+                             "labore et dolore magna aliqua.";
+
+    const unsigned char *secret = (const unsigned char *) ipsumlorem;
+    size_t secret_len = strlen((const char *)secret);
+
+
+    uint8_t out[32];
+    ssl_tls1_PRF(seed, seed_len, secret, secret_len, out, sizeof(out));
+
+    assert_memory_equal(good_prf, out, sizeof(out));
+}
+
 int
 main(void)
 {
     const struct CMUnitTest tests[] = {
         cmocka_unit_test(crypto_pem_encode_decode_loopback),
         cmocka_unit_test(crypto_translate_cipher_names),
+        cmocka_unit_test(crypto_test_tls_prf)
     };
 
 #if defined(ENABLE_CRYPTO_OPENSSL)