[Openvpn-devel] Adding support for wolfSSL backend

Message ID bd464d61-d16e-4a9b-de38-f96891e65368@wolfssl.com
State New
Headers show
Series
  • [Openvpn-devel] Adding support for wolfSSL backend
Related show

Commit Message

Juliusz Sosinowicz Aug. 16, 2019, 2:14 p.m.
This patch adds the option to use wolfSSL as the ssl backend. To build 
this patch:

 1. wolfSSL needs to be built with the `--enable-all` configure option.
 2. OpenVPN must be built with the `--with-crypto-library=wolfssl`
    configure option.

Documentation regarding the wolfSSL SSL library may be found here: 
https://www.wolfssl.com/

Sincerely
Juliusz

Comments

Antonio Quartulli Aug. 16, 2019, 3:22 p.m. | #1
Hi Juliusz,
On 16/08/2019 16:14, Juliusz Sosinowicz wrote:
> This patch adds the option to use wolfSSL as the ssl backend. To build
> this patch:
> 
> 1. wolfSSL needs to be built with the `--enable-all` configure option.
> 2. OpenVPN must be built with the `--with-crypto-library=wolfssl`
>    configure option.
> 

first of all thanks a lot for your contribution!

I have looked at wolfSSL more than a year ago and back then it was
implementing an OpenSSL compatibility layer, so that it could be used as
drop-in replacement in all those programs using OpenSSL as main TLS library.

Is this layer still in place?
If so, why not using it to link OpenVPN against wolfSSL rather than
adding yet another backend?

The reason why I ask is that adding a new crypto backend drastically
increases the maintenance cost for us. Therefore, reducing the change
required in OpenVPN would be extremely beneficial.


Thanks a lot.

Best Regards,
Arne Schwabe Aug. 16, 2019, 3:30 p.m. | #2
Am 16.08.19 um 16:14 schrieb Juliusz Sosinowicz:
> This patch adds the option to use wolfSSL as the ssl backend. To build
> this patch:
> 

That is great and it is also a very big patch. I skimmed only through
the patch.


+#ifdef ENABLE_CRYPTO_WOLFSSL
+    o->ciphername = "AES-256-CBC";
+#else
     o->ciphername = "BF-CBC";
+#endif

Such silent changes that OpenVPN behaves different, is something we
would like to avoid. Better to error out in this case than to behave
diffently.

Overall the WolfSSL feels to be a bit similar to OpenSSL. Is there any
compatibility you are aiming at?

Also it would be nice to have a summary for people on the OpenVPN
perspective

- Why WolfSSL in OpenVPN instead of mbed or OpenSSL
- What features does WolfSSL offer in OpenVPN that mbed/OpenSSL don't have
- What is missing with WolfSSL?

That should also good to have in the patch like README.mbedtls.

And one of the important question is:

What are your future plans in terms of involvement in OpenVPN
development and maintaince? I think since you are first time contributer
and this a big patch, that is something resonable to ask.

Arne
Gert Doering Aug. 16, 2019, 5:51 p.m. | #3
Hi,

On Fri, Aug 16, 2019 at 05:22:27PM +0200, Antonio Quartulli wrote:
> The reason why I ask is that adding a new crypto backend drastically
> increases the maintenance cost for us. 

... and since we're already struggling with providing proper maintenance
and getting new stuff integrated in a timely fashion, every new burden
due to extra testing, extra backends to consider when adding crypto-
related patches, etc., is a fairly big no-go today.

Now, if we had 30 contributors that are all sitting idle waiting for
patches to review, test and ACK, I might view this slightly differently...

> Therefore, reducing the change
> required in OpenVPN would be extremely beneficial.

This.

gert
Juliusz Sosinowicz Aug. 23, 2019, 6:22 p.m. | #4
Hi,

thank you for the feedback. To answer your questions:


- Why WolfSSL in OpenVPN instead of mbed or OpenSSL
wolfSSL can be compiled to use very few resources in a wide array of 
embedded environments.
wolfSSL is FIPS ready - that is it has all the code available to be FIPS 
140 validated on a platform.
- What features does WolfSSL offer in OpenVPN that mbed/OpenSSL don't have
wolfSSL has a large customer base and some of them would like to use 
OpenVPN with wolfSSL.
- What is missing with WolfSSL?
wolfSSL doesn’t support some older, weaker algorithms like Blowfish.
wolfSSL also lacks support for CryptoAPI and exporting of keying material.
- What are your future plans in terms of involvement in OpenVPN
development and maintaince?
Our plans are to help support and maintain the wolfSSL component of any 
project, including OpenVPN, that decides to incorporate our technology.

Regarding our OpenSSL compatibility layer: we do have a compatibility 
layer for OpenSSL but it still lacks many features. In addition, using 
wolfSSL directly without an additional layer allows for better 
efficiency and performance.

Sincerely
Juliusz

On 8/16/19 8:30 AM, Arne Schwabe wrote:
> Am 16.08.19 um 16:14 schrieb Juliusz Sosinowicz:
>> This patch adds the option to use wolfSSL as the ssl backend. To build
>> this patch:
>>
> That is great and it is also a very big patch. I skimmed only through
> the patch.
>
>
> +#ifdef ENABLE_CRYPTO_WOLFSSL
> +    o->ciphername = "AES-256-CBC";
> +#else
>       o->ciphername = "BF-CBC";
> +#endif
>
> Such silent changes that OpenVPN behaves different, is something we
> would like to avoid. Better to error out in this case than to behave
> diffently.
>
> Overall the WolfSSL feels to be a bit similar to OpenSSL. Is there any
> compatibility you are aiming at?
>
> Also it would be nice to have a summary for people on the OpenVPN
> perspective
>
> - Why WolfSSL in OpenVPN instead of mbed or OpenSSL
> - What features does WolfSSL offer in OpenVPN that mbed/OpenSSL don't have
> - What is missing with WolfSSL?
>
> That should also good to have in the patch like README.mbedtls.
>
> And one of the important question is:
>
> What are your future plans in terms of involvement in OpenVPN
> development and maintaince? I think since you are first time contributer
> and this a big patch, that is something resonable to ask.
>
> Arne
Arne Schwabe Aug. 24, 2019, 4:04 p.m. | #5
Hey,

thanks for taking your time to answer.

I want to give you an honest opionion of mine to merging WolfSSL in
OpenVPN. Please note, that this is my personal opinion and not to be
confused to be an official OpenVPN community project or OpenVPN Inc
position.

For every patch we have to decide if it worth accepting it and if the
maintainance of the code is worthless and if the code itself is
acceptable. Adding a third crypto library duplicates an already existing
feature. So there needs to a very compelling reason for us to add this
large feature.

We have not seen any involvement from your companay in the OpenVPN
project so far. So accepting this patch and then later finding out that
we have high maintainance cost for it, is something I really would like
to avoid.

OpenVPN runs on small deployment but nothing that is really embedded.
And almost all these already have mbed TLS or OpenSSL in place. In the
main system that OpenVPN targets which are typical Unix systems or
Windows, I have not really seen much use of WolfSSL either or seen a
single email/issue/ticket requesting support for it, so from my personal
impression there seem to be not much need for WolfSSL.

In summary, I personally cannot really find a good reason to incoperate
WolfSSL currently into OpenVPN.

For the future of the patch. I would recommend that you keep maintaining
that patch out of tree. We incorporate the fixes changes in the rest of
OpenVPN to make this a more viable option. If your involvement in
OpenVPN is high enough that I and others do feel that accepting the
patch is a burden anymore, it can be merged in the future.


> thank you for the feedback. To answer your questions:
> 
> 
> - Why WolfSSL in OpenVPN instead of mbed or OpenSSL
> wolfSSL can be compiled to use very few resources in a wide array of
> embedded environments.

That seem to be also the stated goal for mbed TLS.

> wolfSSL is FIPS ready - that is it has all the code available to be FIPS
> 140 validated on a platform.
> - What features does WolfSSL offer in OpenVPN that mbed/OpenSSL don't have
> wolfSSL has a large customer base and some of them would like to use
> OpenVPN with wolfSSL.

I personally do not care for FIPS. Also that feel more like marketing
for WolfSSL rather to what actually is an improvement to OepnVPN.


> - What is missing with WolfSSL?
> wolfSSL doesn’t support some older, weaker algorithms like Blowfish.
> wolfSSL also lacks support for CryptoAPI and exporting of keying material.

Otherwise it is a complete drop in replacement for OpenSSL?

> - What are your future plans in terms of involvement in OpenVPN
> development and maintaince?
> Our plans are to help support and maintain the wolfSSL component of any
> project, including OpenVPN, that decides to incorporate our technology.

When I asked here I meant more involvement in the OpenVPN project. I.e.
helping reviewing patches that are cyrpto related. Looking into bug
fixing etc. Fox It that did the mbed TLS port of OpenVPN did and is
still involved in these kind of things.

Furthermore the commitment here is bit too vague for me even for the
maintaince of the WolfSSL support. It sounds like a PR phrase. "WolfSSL
component" for all that I know could just mean WolfSSL itself.

> Regarding our OpenSSL compatibility layer: we do have a compatibility
> layer for OpenSSL but it still lacks many features. In addition, using
> wolfSSL directly without an additional layer allows for better
> efficiency and performance.

This very vague.

Arne
Gert Doering Aug. 24, 2019, 7:40 p.m. | #6
Hi,

On Sat, Aug 24, 2019 at 06:04:21PM +0200, Arne Schwabe wrote:
> I want to give you an honest opionion of mine to merging WolfSSL in
> OpenVPN. Please note, that this is my personal opinion and not to be
> confused to be an official OpenVPN community project or OpenVPN Inc
> position.

Arne summarized things quite well.  New and large additions need to
balance "what *our* users want/need", "what the core team finds 
interesting enough to spend time on" and "how expensive in terms of 
maintainer lifetime will it be to maintain that stuff".

Since we're currently short on contributors that can review crypto
related code changes, and we do not have anyone in the team today
that can review WolfSSL interface code at all, this isn't likely 
going to happen in the near future.

gert,
   herder of cats, aka "trying to keep development somewhat on track"
Steffan Karger Aug. 24, 2019, 9:14 p.m. | #7
Hi,

On 24-08-19 21:40, Gert Doering wrote:
> On Sat, Aug 24, 2019 at 06:04:21PM +0200, Arne Schwabe wrote:
>> I want to give you an honest opionion of mine to merging WolfSSL in
>> OpenVPN. Please note, that this is my personal opinion and not to be
>> confused to be an official OpenVPN community project or OpenVPN Inc
>> position.
> 
> Arne summarized things quite well.  New and large additions need to
> balance "what *our* users want/need", "what the core team finds 
> interesting enough to spend time on" and "how expensive in terms of 
> maintainer lifetime will it be to maintain that stuff".
> 
> Since we're currently short on contributors that can review crypto
> related code changes, and we do not have anyone in the team today
> that can review WolfSSL interface code at all, this isn't likely 
> going to happen in the near future.

Just like Arne and Gert, I believe we're currently not in the position
to take on the maintenance burden of adding another crypto backend. I'd
rather spend our time on fixing bugs and improving openvpn performance
(the crypto libs are not the bottleneck).

-Steffan
David Sommerseth Aug. 26, 2019, 4:53 p.m. | #8
On 24/08/2019 21:40, Gert Doering wrote:
> Hi,
> 
> On Sat, Aug 24, 2019 at 06:04:21PM +0200, Arne Schwabe wrote:
>> I want to give you an honest opionion of mine to merging WolfSSL in
>> OpenVPN. Please note, that this is my personal opinion and not to be
>> confused to be an official OpenVPN community project or OpenVPN Inc
>> position.
> 
> Arne summarized things quite well.  New and large additions need to
> balance "what *our* users want/need", "what the core team finds 
> interesting enough to spend time on" and "how expensive in terms of 
> maintainer lifetime will it be to maintain that stuff".
> 
> Since we're currently short on contributors that can review crypto
> related code changes, and we do not have anyone in the team today
> that can review WolfSSL interface code at all, this isn't likely 
> going to happen in the near future.

I have to say both Arne and Gert do have some really valid points.

But there is a lot to learn from Fox-ITs involvement.  We *are* open to new
contributors, who can be resources on various segments of OpenVPN.  And we
have few resources who really understands the depths of cryptology; we lean a
lot on Steffan and Arne currently.  But the initial PolarSSL support got
acceptance because they more or less promised to help OpenVPN in the future on
the crypto side.

What I'm saying, if you in WolfSSL are willing to help out, be available and
help out responding to crypto related questions and patches on this -devel
mailing list, be present in the community IRC channels, etc ... this would
make it far easier to accept another crypto backend.  And this is basically
what Fox-IT has done via Steffan (and earlier Adrian).

This is also how we got IPv6 support in OpenVPN too; Gert had patches several
years ago he maintained, he grew trust and with that he got more challenges
and is now a co-maintainer of the OpenVPN community project.  Lev got involved
as well in a similar way, with features F-Secure Freedome needed at that time.
 And you can find that a lot of the active people here get their changes
included, because they're active on a regular bases.

It doesn't mean they need to be active every day, but that they keep in touch
at least every now and then on mailing lists or IRC and join the hackathons
from time to time.

So getting a trust that you're going to be available also after WolfSSL
support is added is kind of the key point; to help maintaining both the
WolfSSL implementation but also helping out on a regular basis, especially on
the crypto side.

Patch

diff --git a/.gitignore b/.gitignore
index 0d68ec4b..d007cf62 100644
--- a/.gitignore
+++ b/.gitignore
@@ -72,3 +72,8 @@  nbproject
 test-driver
 compile
 stamp-h2
+
+\.settings/
+\.project
+\.cproject
+\.autotools
diff --git a/configure.ac b/configure.ac
index e9f8a2f9..1013e5a0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -276,10 +276,10 @@  AC_ARG_WITH(
 
 AC_ARG_WITH(
 	[crypto-library],
-	[AS_HELP_STRING([--with-crypto-library=library], [build with the given crypto library, TYPE=openssl|mbedtls @<:@default=openssl@:>@])],
+	[AS_HELP_STRING([--with-crypto-library=library], [build with the given crypto library, TYPE=openssl|mbedtls|wolfssl @<:@default=openssl@:>@])],
 	[
 		case "${withval}" in
-			openssl|mbedtls) ;;
+			openssl|mbedtls|wolfssl) ;;
 			*) AC_MSG_ERROR([bad value ${withval} for --with-crypto-library]) ;;
 		esac
 	],
@@ -1011,6 +1011,31 @@  elif test "${with_crypto_library}" = "mbedtls"; then
 	AC_DEFINE([ENABLE_CRYPTO_MBEDTLS], [1], [Use mbed TLS library])
 	CRYPTO_CFLAGS="${MBEDTLS_CFLAGS}"
 	CRYPTO_LIBS="${MBEDTLS_LIBS}"
+elif test "${with_crypto_library}" = "wolfssl"; then
+	AC_ARG_VAR([WOLFSSL_CFLAGS], [C compiler flags for wolfssl])
+	AC_ARG_VAR([WOLFSSL_LIBS], [linker flags for wolfssl])
+	
+	saved_CFLAGS="${CFLAGS}"
+	saved_LIBS="${LIBS}"
+	
+	if test -z "${WOLFSSL_CFLAGS}" -a -z "${WOLFSSL_LIBS}"; then
+		# if the user did not explicitly specify flags, try to autodetect
+		LIBS="${LIBS} -lwolfssl -lm -pthread"
+		AC_CHECK_LIB(
+			[wolfssl],
+			[wolfSSL_get_ciphers],
+			[],
+			[AC_MSG_ERROR([Could not link wolfSSL library.])]
+		)
+	fi
+	
+	have_crypto_aead_modes="yes"
+	
+	CFLAGS="${WOLFSSL_CFLAGS} ${CFLAGS}"
+	LIBS="${WOLFSSL_LIBS} ${LIBS}"
+	AC_DEFINE([ENABLE_CRYPTO_WOLFSSL], [1], [Use wolfSSL crypto library])
+	CRYPTO_CFLAGS="${WOLFSSL_CFLAGS}"
+	CRYPTO_LIBS="${WOLFSSL_LIBS}"
 else
 	AC_MSG_ERROR([Invalid crypto library: ${with_crypto_library}])
 fi
diff --git a/include/openvpn-plugin.h.in b/include/openvpn-plugin.h.in
index 103844f7..75b33a62 100644
--- a/include/openvpn-plugin.h.in
+++ b/include/openvpn-plugin.h.in
@@ -32,6 +32,12 @@ 
 #define __OPENVPN_X509_CERT_T_DECLARED
 typedef mbedtls_x509_crt openvpn_x509_cert_t;
 #endif
+#elif defined(ENABLE_CRYPTO_WOLFSSL)  /* ifdef ENABLE_CRYPTO_WOLFSSL */
+#include <wolfssl/ssl.h>
+#ifndef __OPENVPN_X509_CERT_T_DECLARED
+#define __OPENVPN_X509_CERT_T_DECLARED
+typedef WOLFSSL_X509 openvpn_x509_cert_t;
+#endif
 #else  /* ifdef ENABLE_CRYPTO_MBEDTLS */
 #include <openssl/x509.h>
 #ifndef __OPENVPN_X509_CERT_T_DECLARED
@@ -332,7 +338,8 @@  struct openvpn_plugin_callbacks
 typedef enum {
     SSLAPI_NONE,
     SSLAPI_OPENSSL,
-    SSLAPI_MBEDTLS
+    SSLAPI_MBEDTLS,
+    SSLAPI_WOLFSSL
 } ovpnSSLAPI;
 
 /**
diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am
index 30caa01f..5c19384e 100644
--- a/src/openvpn/Makefile.am
+++ b/src/openvpn/Makefile.am
@@ -50,6 +50,7 @@  openvpn_SOURCES = \
 	crypto.c crypto.h crypto_backend.h \
 	crypto_openssl.c crypto_openssl.h \
 	crypto_mbedtls.c crypto_mbedtls.h \
+	crypto_wolfssl.c crypto_wolfssl.h \
 	dhcp.c dhcp.h \
 	env_set.c env_set.h \
 	errlevel.h \
@@ -115,10 +116,12 @@  openvpn_SOURCES = \
 	ssl.c ssl.h  ssl_backend.h \
 	ssl_openssl.c ssl_openssl.h \
 	ssl_mbedtls.c ssl_mbedtls.h \
+	ssl_wolfssl.c ssl_wolfssl.h \
 	ssl_common.h \
 	ssl_verify.c ssl_verify.h ssl_verify_backend.h \
 	ssl_verify_openssl.c ssl_verify_openssl.h \
 	ssl_verify_mbedtls.c ssl_verify_mbedtls.h \
+	ssl_verify_wolfssl.c ssl_verify_wolfssl.h \
 	status.c status.h \
 	syshead.h \
 	tls_crypt.c tls_crypt.h \
diff --git a/src/openvpn/crypto_backend.h b/src/openvpn/crypto_backend.h
index 7e9a4bd2..9699b50c 100644
--- a/src/openvpn/crypto_backend.h
+++ b/src/openvpn/crypto_backend.h
@@ -29,18 +29,21 @@ 
 #ifndef CRYPTO_BACKEND_H_
 #define CRYPTO_BACKEND_H_
 
+/* TLS uses a tag of 128 bytes, let's do the same for OpenVPN */
+#define OPENVPN_AEAD_TAG_LENGTH 16
+
 #ifdef ENABLE_CRYPTO_OPENSSL
 #include "crypto_openssl.h"
 #endif
 #ifdef ENABLE_CRYPTO_MBEDTLS
 #include "crypto_mbedtls.h"
 #endif
+#ifdef ENABLE_CRYPTO_WOLFSSL
+#include "crypto_wolfssl.h"
+#endif
 #include "basic.h"
 #include "buffer.h"
 
-/* TLS uses a tag of 128 bytes, let's do the same for OpenVPN */
-#define OPENVPN_AEAD_TAG_LENGTH 16
-
 /* Maximum cipher block size (bytes) */
 #define OPENVPN_MAX_CIPHER_BLOCK_SIZE 32
 
@@ -355,7 +358,7 @@  void cipher_ctx_free(cipher_ctx_t *ctx);
  * @param key_len       Length of the key, in bytes
  * @param kt            Static cipher parameters to use
  * @param enc           Whether to encrypt or decrypt (either
- *                      \c MBEDTLS_OP_ENCRYPT or \c MBEDTLS_OP_DECRYPT).
+ *                      \c OPENVPN_OP_ENCRYPT or \c OPENVPN_OP_DECRYPT).
  */
 void cipher_ctx_init(cipher_ctx_t *ctx, const uint8_t *key, int key_len,
                      const cipher_kt_t *kt, int enc);
diff --git a/src/openvpn/crypto_wolfssl.c b/src/openvpn/crypto_wolfssl.c
new file mode 100644
index 00000000..99358d6b
--- /dev/null
+++ b/src/openvpn/crypto_wolfssl.c
@@ -0,0 +1,1977 @@ 
+/*
+ *  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) 2002-2019 OpenVPN Inc <sales@openvpn.net>
+ *  Copyright (C) 2010-2019 Fox Crypto B.V. <openvpn@fox-it.com>
+ *
+ *  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.
+ */
+
+/**
+ * @file Data Channel Cryptography wolfSSL-specific backend interface
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#include "syshead.h"
+
+#if defined(ENABLE_CRYPTO_WOLFSSL)
+
+#include "basic.h"
+#include "buffer.h"
+#include "integer.h"
+#include "crypto.h"
+#include "crypto_backend.h"
+
+/*
+ *
+ * Functions related to the core crypto library
+ *
+ */
+
+void crypto_init_lib(void)
+{
+    int ret;
+
+    if ((ret = wolfCrypt_Init()) != 0)
+    {
+        msg(D_CRYPT_ERRORS, "wolfCrypt_Init failed");
+    }
+}
+
+void crypto_uninit_lib(void)
+{
+    int ret;
+    if ((ret = wolfCrypt_Cleanup()) != 0)
+    {
+        msg(D_CRYPT_ERRORS, "wolfCrypt_Cleanup failed");
+    }
+}
+
+void crypto_clear_error(void)
+{
+}
+
+void crypto_init_lib_engine(const char *engine_name)
+{
+    msg(M_INFO, "Note: wolfSSL does not have an engine");
+}
+
+void show_available_ciphers(void)
+{
+    cipher_kt_t cipher;
+    for (cipher = 0; cipher < OV_WC_NULL_CIPHER_TYPE; cipher++)
+    {
+        if (cipher_kt_mode(&cipher) != OPENVPN_MODE_OTHER)
+        { /* Hide other cipher types */
+            print_cipher(&cipher);
+        }
+    }
+}
+
+void show_available_digests(void)
+{
+#ifndef NO_MD4
+    printf("MD4 %d bit digest size\n", wc_HashGetDigestSize(WC_HASH_TYPE_MD4));
+#endif
+#ifndef NO_MD5
+    printf("MD5 %d bit digest size\n", wc_HashGetDigestSize(WC_HASH_TYPE_MD5));
+#endif
+#ifndef NO_SHA
+    printf("SHA1 %d bit digest size\n", wc_HashGetDigestSize(WC_HASH_TYPE_SHA));
+#endif
+#ifdef WOLFSSL_SHA224
+        printf("SHA224 %d bit digest size\n", wc_HashGetDigestSize(WC_HASH_TYPE_SHA224));
+    #endif
+#ifndef NO_SHA256
+    printf("SHA256 %d bit digest size\n",
+            wc_HashGetDigestSize(WC_HASH_TYPE_SHA256));
+#endif
+#ifdef WOLFSSL_SHA384
+        printf("SHA384 %d bit digest size\n", wc_HashGetDigestSize(WC_HASH_TYPE_SHA384));
+    #endif
+#ifdef WOLFSSL_SHA512
+        printf("SHA512 %d bit digest size\n", wc_HashGetDigestSize(WC_HASH_TYPE_SHA512));
+    #endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_224)
+        printf("SHA3-224 %d bit digest size\n", wc_HashGetDigestSize(WC_HASH_TYPE_SHA3_224));
+    #endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_256)
+        printf("SHA3-256 %d bit digest size\n", wc_HashGetDigestSize(WC_HASH_TYPE_SHA3_256));
+    #endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_384)
+        printf("SHA3-384 %d bit digest size\n", wc_HashGetDigestSize(WC_HASH_TYPE_SHA3_384));
+    #endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_512)
+        printf("SHA3-512 %d bit digest size\n", wc_HashGetDigestSize(WC_HASH_TYPE_SHA3_512));
+    #endif
+}
+
+void show_available_engines(void)
+{
+    msg(M_INFO, "Note: wolfSSL does not have an engine");
+}
+
+const cipher_name_pair cipher_name_translation_table[] =
+{ };
+const size_t cipher_name_translation_table_count =
+        sizeof(cipher_name_translation_table)
+                / sizeof(*cipher_name_translation_table);
+
+#define PEM_BEGIN              "-----BEGIN "
+#define PEM_BEGIN_LEN          11
+#define PEM_LINE_END           "-----\n"
+#define PEM_LINE_END_LEN       6
+#define PEM_END                "-----END "
+#define PEM_END_LEN            9
+
+/*
+ * This function calculates the length of the resulting base64 encoded string
+ */
+static const int PEM_LINE_SZ = 64;
+static uint32_t der_to_pem_len(uint32_t der_len)
+{
+    uint32_t pem_len;
+    pem_len = (der_len + 2) / 3 * 4;
+    pem_len += (pem_len + PEM_LINE_SZ - 1) / PEM_LINE_SZ; /* new lines */
+    return pem_len;
+}
+
+bool crypto_pem_encode(const char *name, struct buffer *dst,
+        const struct buffer *src, struct gc_arena *gc)
+{
+    uint8_t* pem_buf = NULL;
+    uint32_t pem_len = der_to_pem_len(BLEN(src));
+    uint8_t* out_buf = NULL;
+    uint8_t* out_buf_ptr;
+    bool ret = false;
+    int err;
+    int name_len = strlen(name);
+    int out_len = PEM_BEGIN_LEN + PEM_LINE_END_LEN + name_len + pem_len +
+    PEM_END_LEN + PEM_LINE_END_LEN + name_len;
+
+    if (!(pem_buf = (uint8_t*) malloc(pem_len)))
+    {
+        return false;
+    }
+
+    if (!(out_buf = (uint8_t*) malloc(out_len)))
+    {
+        goto cleanup;
+    }
+
+    if ((err = Base64_Encode(BPTR(src), BLEN(src), pem_buf, &pem_len)) != 0)
+    {
+        msg(M_INFO, "Base64_Encode failed with Errno: %d", err);
+        goto cleanup;
+    }
+
+    out_buf_ptr = out_buf;
+    memcpy(out_buf_ptr, PEM_BEGIN, PEM_BEGIN_LEN);
+    out_buf_ptr += PEM_BEGIN_LEN;
+    memcpy(out_buf_ptr, name, name_len);
+    out_buf_ptr += name_len;
+    memcpy(out_buf_ptr, PEM_LINE_END, PEM_LINE_END_LEN);
+    out_buf_ptr += PEM_LINE_END_LEN;
+    memcpy(out_buf_ptr, pem_buf, pem_len);
+    out_buf_ptr += pem_len;
+    memcpy(out_buf_ptr, PEM_END, PEM_END_LEN);
+    out_buf_ptr += PEM_END_LEN;
+    memcpy(out_buf_ptr, name, name_len);
+    out_buf_ptr += name_len;
+    memcpy(out_buf_ptr, PEM_LINE_END, PEM_LINE_END_LEN);
+
+    *dst = alloc_buf_gc(out_len + 1, gc);
+    ASSERT(buf_write(dst, out_buf, out_len));
+    buf_null_terminate(dst);
+
+    ret = true;
+
+    cleanup: if (out_buf)
+    {
+        free(out_buf);
+    }
+    if (pem_buf)
+    {
+        free(pem_buf);
+    }
+
+    return ret;
+}
+
+/*
+ * This function calculates the length of the string decoded from base64
+ */
+static uint32_t pem_to_der_len(uint32_t pem_len)
+{
+    int plainSz = pem_len - ((pem_len + (PEM_LINE_SZ - 1)) / PEM_LINE_SZ);
+    return (plainSz * 3 + 3) / 4;
+}
+
+bool crypto_pem_decode(const char *name, struct buffer *dst,
+        const struct buffer *src)
+{
+    int name_len = strlen(name);
+    int err;
+    uint8_t* src_buf;
+    bool ret = false;
+    unsigned int der_len = BLEN(src) - PEM_BEGIN_LEN - PEM_LINE_END_LEN -
+    PEM_END_LEN - PEM_LINE_END_LEN - name_len - name_len - 1;
+    unsigned int pem_len = pem_to_der_len(der_len);
+
+    ASSERT(
+            BLEN(src) > PEM_BEGIN_LEN + PEM_LINE_END_LEN + PEM_END_LEN + PEM_LINE_END_LEN);
+
+    if (!(src_buf = (uint8_t*) malloc(BLEN(src))))
+    {
+        msg(M_FATAL, "Cannot allocate memory for PEM decode");
+        return false;
+    }
+    memcpy(src_buf, BPTR(src), BLEN(src));
+
+    src_buf[PEM_BEGIN_LEN + name_len] = '\0';
+
+    if (strcmp((char*) (src_buf + PEM_BEGIN_LEN), name))
+    {
+        msg(D_CRYPT_ERRORS, "%s: unexpected PEM name (got '%s', expected '%s')",
+                __func__, src_buf + PEM_BEGIN_LEN, name);
+        goto cleanup;
+    }
+
+    if ((err = Base64_Decode(
+    BPTR(src) + PEM_BEGIN_LEN + PEM_LINE_END_LEN + name_len, der_len,
+            src_buf, &pem_len)) != 0)
+    {
+        msg(M_INFO, "Base64_Decode failed with Errno: %d", err);
+        goto cleanup;
+    }
+
+    uint8_t *dst_data = buf_write_alloc(dst, pem_len);
+    if (!dst_data)
+    {
+        msg(D_CRYPT_ERRORS, "%s: dst too small (%i, needs %i)", __func__,
+                BCAP(dst), pem_len);
+        goto cleanup;
+    }
+
+    memcpy(dst_data, src_buf, pem_len);
+
+    ret = true;
+
+    cleanup: free(src_buf);
+    return ret;
+}
+
+/*
+ * Generate strong cryptographic random numbers
+ */
+int rand_bytes(uint8_t *output, int len)
+{
+    static WC_RNG rng;
+    static bool rng_init = false;
+    int ret;
+
+    if (!rng_init)
+    {
+        if ((ret = wc_InitRng(&rng)) != 0)
+        {
+            msg(D_CRYPT_ERRORS, "wc_InitRng failed Errno: %d", ret);
+            return 0;
+        }
+        rng_init = true;
+    }
+
+    if ((ret = wc_RNG_GenerateBlock(&rng, output, len)) != 0)
+    {
+        msg(D_CRYPT_ERRORS, "wc_RNG_GenerateBlock failed Errno: %d", ret);
+        return 0;
+    }
+
+    return 1;
+}
+
+/*
+ *
+ * Key functions, allow manipulation of keys.
+ *
+ */
+
+int key_des_num_cblocks(const cipher_kt_t *kt)
+{
+    int ret = 0;
+
+    if (kt)
+    {
+        switch (*kt)
+        {
+        case OV_WC_DES_CBC_TYPE:
+        case OV_WC_DES_ECB_TYPE:
+            ret = DES_KEY_SIZE / DES_BLOCK_SIZE;
+            break;
+        case OV_WC_DES_EDE3_CBC_TYPE:
+        case OV_WC_DES_EDE3_ECB_TYPE:
+            ret = DES3_KEY_SIZE / DES_BLOCK_SIZE;
+            break;
+        default:
+            ret = 0;
+        }
+    }
+
+    msg(D_CRYPTO_DEBUG, "CRYPTO INFO: n_DES_cblocks=%d", ret);
+    return ret;
+}
+
+static const unsigned char odd_parity[256] =
+{ 1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14, 16, 16, 19, 19, 21, 21,
+        22, 22, 25, 25, 26, 26, 28, 28, 31, 31, 32, 32, 35, 35, 37, 37, 38, 38,
+        41, 41, 42, 42, 44, 44, 47, 47, 49, 49, 50, 50, 52, 52, 55, 55, 56, 56,
+        59, 59, 61, 61, 62, 62, 64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74,
+        76, 76, 79, 79, 81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91, 91, 93, 93,
+        94, 94, 97, 97, 98, 98, 100, 100, 103, 103, 104, 104, 107, 107, 109,
+        109, 110, 110, 112, 112, 115, 115, 117, 117, 118, 118, 121, 121, 122,
+        122, 124, 124, 127, 127, 128, 128, 131, 131, 133, 133, 134, 134, 137,
+        137, 138, 138, 140, 140, 143, 143, 145, 145, 146, 146, 148, 148, 151,
+        151, 152, 152, 155, 155, 157, 157, 158, 158, 161, 161, 162, 162, 164,
+        164, 167, 167, 168, 168, 171, 171, 173, 173, 174, 174, 176, 176, 179,
+        179, 181, 181, 182, 182, 185, 185, 186, 186, 188, 188, 191, 191, 193,
+        193, 194, 194, 196, 196, 199, 199, 200, 200, 203, 203, 205, 205, 206,
+        206, 208, 208, 211, 211, 213, 213, 214, 214, 217, 217, 218, 218, 220,
+        220, 223, 223, 224, 224, 227, 227, 229, 229, 230, 230, 233, 233, 234,
+        234, 236, 236, 239, 239, 241, 241, 242, 242, 244, 244, 247, 247, 248,
+        248, 251, 251, 253, 253, 254, 254 };
+
+static int DES_check_key_parity(const uint8_t *key)
+{
+    unsigned int i;
+
+    for (i = 0; i < DES_BLOCK_SIZE; i++)
+    {
+        if (key[i] != odd_parity[key[i]])
+            return 0;
+    }
+    return 1;
+}
+
+/* return true in fail case (1) */
+static int DES_check(word32 mask, word32 mask2, uint8_t* key)
+{
+    word32 value[2];
+
+    value[0] = mask;
+    value[1] = mask2;
+    return (memcmp(value, key, sizeof(value)) == 0) ? 1 : 0;
+}
+
+static inline uint32_t ByteReverseWord32(uint32_t value)
+{
+    /* 6 instructions with rotate instruction, 8 without */
+    value = ((value & 0xFF00FF00) >> 8) | ((value & 0x00FF00FF) << 8);
+    return value << 16U | value >> 16U;
+}
+
+/* check if not weak. Weak key list from Nist "Recommendation for the Triple
+ * Data Encryption Algorithm (TDEA) Block Cipher"
+ *
+ * returns 1 if is weak 0 if not
+ */
+static int wolfSSL_DES_is_weak_key(uint8_t* key)
+{
+    word32 mask, mask2;
+
+    mask = 0x01010101;
+    mask2 = 0x01010101;
+    if (DES_check(mask, mask2, key))
+    {
+        return 1;
+    }
+
+    mask = 0xFEFEFEFE;
+    mask2 = 0xFEFEFEFE;
+    if (DES_check(mask, mask2, key))
+    {
+        return 1;
+    }
+
+    mask = 0xE0E0E0E0;
+    mask2 = 0xF1F1F1F1;
+    if (DES_check(mask, mask2, key))
+    {
+        return 1;
+    }
+
+    mask = 0x1F1F1F1F;
+    mask2 = 0x0E0E0E0E;
+    if (DES_check(mask, mask2, key))
+    {
+        return 1;
+    }
+
+    /* semi-weak *key check (list from same Nist paper) */
+    mask = 0x011F011F;
+    mask2 = 0x010E010E;
+    if (DES_check(mask, mask2, key)
+            || DES_check(ByteReverseWord32(mask), ByteReverseWord32(mask2),
+                    key))
+    {
+        return 1;
+    }
+
+    mask = 0x01E001E0;
+    mask2 = 0x01F101F1;
+    if (DES_check(mask, mask2, key)
+            || DES_check(ByteReverseWord32(mask), ByteReverseWord32(mask2),
+                    key))
+    {
+        return 1;
+    }
+
+    mask = 0x01FE01FE;
+    mask2 = 0x01FE01FE;
+    if (DES_check(mask, mask2, key)
+            || DES_check(ByteReverseWord32(mask), ByteReverseWord32(mask2),
+                    key))
+    {
+        return 1;
+    }
+
+    mask = 0x1FE01FE0;
+    mask2 = 0x0EF10EF1;
+    if (DES_check(mask, mask2, key)
+            || DES_check(ByteReverseWord32(mask), ByteReverseWord32(mask2),
+                    key))
+    {
+        return 1;
+    }
+
+    mask = 0x1FFE1FFE;
+    mask2 = 0x0EFE0EFE;
+    if (DES_check(mask, mask2, key)
+            || DES_check(ByteReverseWord32(mask), ByteReverseWord32(mask2),
+                    key))
+    {
+        return 1;
+    }
+
+    return 0;
+}
+
+bool key_des_check(uint8_t *key, int key_len, int ndc)
+{
+    int i;
+    struct buffer b;
+
+    buf_set_read(&b, key, key_len);
+
+    for (i = 0; i < ndc; ++i)
+    {
+        uint8_t *dc = (uint8_t *) buf_read_alloc(&b, DES_KEY_SIZE);
+        if (!dc)
+        {
+            msg(D_CRYPT_ERRORS,
+                    "CRYPTO INFO: check_key_DES: insufficient key material");
+            return false;
+        }
+        if (wolfSSL_DES_is_weak_key(dc))
+        {
+            msg(D_CRYPT_ERRORS,
+                    "CRYPTO INFO: check_key_DES: weak key detected");
+            return false;
+        }
+        if (!DES_check_key_parity(dc))
+        {
+            msg(D_CRYPT_ERRORS,
+                    "CRYPTO INFO: check_key_DES: bad parity detected");
+            return false;
+        }
+    }
+    return true;
+
+}
+
+/* Sets the parity of the DES key for use */
+static void wolfSSL_DES_set_odd_parity(uint8_t* myDes)
+{
+    int i;
+
+    for (i = 0; i < DES_BLOCK_SIZE; i++)
+    {
+        myDes[i] = odd_parity[myDes[i]];
+    }
+}
+
+void key_des_fixup(uint8_t *key, int key_len, int ndc)
+{
+    int i;
+    struct buffer b;
+
+    buf_set_read(&b, key, key_len);
+    for (i = 0; i < ndc; ++i)
+    {
+        uint8_t *dc = (uint8_t *) buf_read_alloc(&b, DES_BLOCK_SIZE);
+        if (!dc)
+        {
+            msg(D_CRYPT_ERRORS,
+                    "CRYPTO INFO: fixup_key_DES: insufficient key material");
+            return;
+        }
+        wolfSSL_DES_set_odd_parity(dc);
+    }
+}
+
+void cipher_des_encrypt_ecb(const unsigned char key[DES_KEY_LENGTH],
+        unsigned char src[DES_KEY_LENGTH], unsigned char dst[DES_KEY_LENGTH])
+{
+    Des myDes;
+
+    if (src == NULL || dst == NULL || key == NULL)
+    {
+        msg(D_CRYPT_ERRORS, "Bad argument passed to cipher_des_encrypt_ecb");
+    }
+
+    wc_Des_SetKey(&myDes, key, NULL, DES_ENCRYPTION);
+    wc_Des_EcbEncrypt(&myDes, dst, src, DES_KEY_LENGTH);
+}
+
+/*
+ *
+ * Generic cipher key type functions
+ *
+ */
+
+const cipher_kt_t *cipher_kt_get(const char *ciphername)
+{
+    const struct cipher* cipher;
+
+    for (cipher = cipher_tbl; cipher->name != NULL; cipher++)
+    {
+        if (strncmp(ciphername, cipher->name, strlen(cipher->name) + 1) == 0)
+        {
+            return &cipher_static[cipher->type];
+        }
+    }
+    return NULL;
+}
+
+const char *cipher_kt_name(const cipher_kt_t *cipher_kt)
+{
+    if (!cipher_kt)
+    {
+        return "[null-digest]";
+    }
+    else
+    {
+        return cipher_tbl[*cipher_kt].name;
+    }
+}
+
+int cipher_kt_key_size(const cipher_kt_t *cipher_kt)
+{
+    if (cipher_kt == NULL)
+    {
+        return 0;
+    }
+    switch (*cipher_kt)
+    {
+#ifdef HAVE_AES_CBC
+    case OV_WC_AES_128_CBC_TYPE:
+        return AES_128_KEY_SIZE;
+    case OV_WC_AES_192_CBC_TYPE:
+        return AES_192_KEY_SIZE;
+    case OV_WC_AES_256_CBC_TYPE:
+        return AES_256_KEY_SIZE;
+#endif
+#ifdef WOLFSSL_AES_COUNTER
+    case OV_WC_AES_128_CTR_TYPE:
+        return AES_128_KEY_SIZE;
+    case OV_WC_AES_192_CTR_TYPE:
+        return AES_192_KEY_SIZE;
+    case OV_WC_AES_256_CTR_TYPE:
+        return AES_256_KEY_SIZE;
+#endif
+#ifdef HAVE_AES_ECB
+    case OV_WC_AES_128_ECB_TYPE:
+        return AES_128_KEY_SIZE;
+    case OV_WC_AES_192_ECB_TYPE:
+        return AES_192_KEY_SIZE;
+    case OV_WC_AES_256_ECB_TYPE:
+        return AES_256_KEY_SIZE;
+#endif
+#ifdef WOLFSSL_AES_DIRECT
+    case OV_WC_AES_128_OFB_TYPE:
+        return AES_128_KEY_SIZE;
+    case OV_WC_AES_192_OFB_TYPE:
+        return AES_192_KEY_SIZE;
+    case OV_WC_AES_256_OFB_TYPE:
+        return AES_256_KEY_SIZE;
+#endif
+#ifdef WOLFSSL_AES_CFB
+    case OV_WC_AES_128_CFB_TYPE:
+        return AES_128_KEY_SIZE;
+    case OV_WC_AES_192_CFB_TYPE:
+        return AES_192_KEY_SIZE;
+    case OV_WC_AES_256_CFB_TYPE:
+        return AES_256_KEY_SIZE;
+#endif
+#ifdef HAVE_AESGCM
+    case OV_WC_AES_128_GCM_TYPE:
+        return AES_128_KEY_SIZE;
+    case OV_WC_AES_192_GCM_TYPE:
+        return AES_192_KEY_SIZE;
+    case OV_WC_AES_256_GCM_TYPE:
+        return AES_256_KEY_SIZE;
+#endif
+#ifndef NO_DES3
+    case OV_WC_DES_CBC_TYPE:
+    case OV_WC_DES_ECB_TYPE:
+        return DES_KEY_SIZE;
+    case OV_WC_DES_EDE3_CBC_TYPE:
+    case OV_WC_DES_EDE3_ECB_TYPE:
+        return DES3_KEY_SIZE;
+#endif
+#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
+    case OV_WC_CHACHA20_POLY1305_TYPE:
+        return CHACHA20_POLY1305_AEAD_KEYSIZE;
+#endif
+    case OV_WC_NULL_CIPHER_TYPE:
+        return 0;
+    }
+    return 0;
+}
+
+int cipher_kt_iv_size(const cipher_kt_t *cipher_kt)
+{
+    if (cipher_kt == NULL)
+    {
+        return 0;
+    }
+    switch (*cipher_kt)
+    {
+#ifdef HAVE_AES_CBC
+    case OV_WC_AES_128_CBC_TYPE:
+    case OV_WC_AES_192_CBC_TYPE:
+    case OV_WC_AES_256_CBC_TYPE:
+#endif
+#ifdef WOLFSSL_AES_COUNTER
+    case OV_WC_AES_128_CTR_TYPE:
+    case OV_WC_AES_192_CTR_TYPE:
+    case OV_WC_AES_256_CTR_TYPE:
+#endif
+#ifdef HAVE_AES_ECB
+    case OV_WC_AES_128_ECB_TYPE:
+    case OV_WC_AES_192_ECB_TYPE:
+    case OV_WC_AES_256_ECB_TYPE:
+#endif
+#ifdef WOLFSSL_AES_DIRECT
+    case OV_WC_AES_128_OFB_TYPE:
+    case OV_WC_AES_192_OFB_TYPE:
+    case OV_WC_AES_256_OFB_TYPE:
+#endif
+#ifdef WOLFSSL_AES_CFB
+    case OV_WC_AES_128_CFB_TYPE:
+    case OV_WC_AES_192_CFB_TYPE:
+    case OV_WC_AES_256_CFB_TYPE:
+#endif
+#if defined(HAVE_AES_CBC) || defined(WOLFSSL_AES_COUNTER) || defined(HAVE_AES_ECB) || defined(WOLFSSL_AES_DIRECT) || defined(WOLFSSL_AES_CFB)
+        return AES_IV_SIZE;
+#endif
+#ifdef HAVE_AESGCM
+    case OV_WC_AES_128_GCM_TYPE:
+    case OV_WC_AES_192_GCM_TYPE:
+    case OV_WC_AES_256_GCM_TYPE:
+        return AESGCM_IV_SZ;
+#endif
+#ifndef NO_DES3
+    case OV_WC_DES_CBC_TYPE:
+    case OV_WC_DES_ECB_TYPE:
+    case OV_WC_DES_EDE3_CBC_TYPE:
+    case OV_WC_DES_EDE3_ECB_TYPE:
+        return DES_IV_SIZE;
+#endif
+#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
+    case OV_WC_CHACHA20_POLY1305_TYPE:
+        return CHACHA20_POLY1305_AEAD_IV_SIZE;
+#endif
+    case OV_WC_NULL_CIPHER_TYPE:
+        return 0;
+    }
+    return 0;
+}
+
+static bool needs_padding(const cipher_kt_t *cipher_kt)
+{
+    if (cipher_kt == NULL)
+    {
+        return false;
+    }
+    switch (*cipher_kt)
+    {
+#ifdef HAVE_AES_CBC
+    case OV_WC_AES_128_CBC_TYPE:
+    case OV_WC_AES_192_CBC_TYPE:
+    case OV_WC_AES_256_CBC_TYPE:
+#endif
+#ifdef HAVE_AES_ECB
+    case OV_WC_AES_128_ECB_TYPE:
+    case OV_WC_AES_192_ECB_TYPE:
+    case OV_WC_AES_256_ECB_TYPE:
+#endif
+#ifndef NO_DES3
+    case OV_WC_DES_CBC_TYPE:
+    case OV_WC_DES_ECB_TYPE:
+    case OV_WC_DES_EDE3_CBC_TYPE:
+    case OV_WC_DES_EDE3_ECB_TYPE:
+#endif
+#if defined(HAVE_AES_CBC) || defined(HAVE_AES_ECB) || !defined(NO_DES3)
+        return true;
+#endif
+#ifdef WOLFSSL_AES_DIRECT
+    case OV_WC_AES_128_OFB_TYPE:
+    case OV_WC_AES_192_OFB_TYPE:
+    case OV_WC_AES_256_OFB_TYPE:
+#endif
+#ifdef WOLFSSL_AES_CFB
+    case OV_WC_AES_128_CFB_TYPE:
+    case OV_WC_AES_192_CFB_TYPE:
+    case OV_WC_AES_256_CFB_TYPE:
+#endif
+#ifdef WOLFSSL_AES_COUNTER
+    case OV_WC_AES_128_CTR_TYPE:
+    case OV_WC_AES_192_CTR_TYPE:
+    case OV_WC_AES_256_CTR_TYPE:
+#endif
+#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
+    case OV_WC_CHACHA20_POLY1305_TYPE:
+#endif
+#ifdef HAVE_AESGCM
+    case OV_WC_AES_128_GCM_TYPE:
+    case OV_WC_AES_192_GCM_TYPE:
+    case OV_WC_AES_256_GCM_TYPE:
+#endif
+#if defined(WOLFSSL_AES_DIRECT) || defined(WOLFSSL_AES_CFB) || defined(HAVE_AESGCM) || defined(WOLFSSL_AES_COUNTER) || (defined(HAVE_CHACHA) && defined(HAVE_POLY1305))
+        return false;
+#endif
+    case OV_WC_NULL_CIPHER_TYPE:
+    default:
+        return false;
+    }
+}
+
+int cipher_kt_block_size(const cipher_kt_t *cipher_kt)
+{
+    if (cipher_kt == NULL)
+    {
+        return 0;
+    }
+    if (!needs_padding(cipher_kt))
+    {
+        return 1;
+    }
+
+    switch (*cipher_kt)
+    {
+#ifdef HAVE_AES_CBC
+    case OV_WC_AES_128_CBC_TYPE:
+    case OV_WC_AES_192_CBC_TYPE:
+    case OV_WC_AES_256_CBC_TYPE:
+#endif
+#ifdef HAVE_AES_ECB
+    case OV_WC_AES_128_ECB_TYPE:
+    case OV_WC_AES_192_ECB_TYPE:
+    case OV_WC_AES_256_ECB_TYPE:
+#endif
+#ifdef HAVE_AESGCM
+    case OV_WC_AES_128_GCM_TYPE:
+    case OV_WC_AES_192_GCM_TYPE:
+    case OV_WC_AES_256_GCM_TYPE:
+#endif
+#if defined(HAVE_AES_CBC) || defined(WOLFSSL_AES_COUNTER) || defined(HAVE_AES_ECB) || defined(WOLFSSL_AES_DIRECT) || defined(WOLFSSL_AES_CFB) || defined(HAVE_AESGCM)
+        return AES_BLOCK_SIZE;
+#endif
+#ifndef NO_DES3
+    case OV_WC_DES_CBC_TYPE:
+    case OV_WC_DES_ECB_TYPE:
+    case OV_WC_DES_EDE3_CBC_TYPE:
+    case OV_WC_DES_EDE3_ECB_TYPE:
+        return DES_BLOCK_SIZE;
+#endif
+    case OV_WC_NULL_CIPHER_TYPE:
+    default:
+        return 0;
+    }
+}
+
+int cipher_kt_tag_size(const cipher_kt_t *cipher_kt)
+{
+    if (cipher_kt_mode_aead(cipher_kt))
+    {
+        return OPENVPN_AEAD_TAG_LENGTH;
+    }
+    else
+    {
+        return 0;
+    }
+}
+
+bool cipher_kt_insecure(const cipher_kt_t *cipher)
+{
+    if (needs_padding(cipher))
+    {
+        return !(cipher_kt_block_size(cipher) >= 128 / 8);
+    }
+    else
+    {
+        /* For ciphers without padding check key size instead */
+        return !(cipher_kt_key_size(cipher) >= 128 / 8);
+    }
+}
+
+int cipher_kt_mode(const cipher_kt_t *cipher_kt)
+{
+    if (cipher_kt == NULL)
+    {
+        return 0;
+    }
+    switch (*cipher_kt)
+    {
+    /* Not all cases included since OpenVPN only recognizes CBC, OFB, CFB, and GCM */
+#ifdef HAVE_AES_CBC
+    case OV_WC_AES_128_CBC_TYPE:
+    case OV_WC_AES_192_CBC_TYPE:
+    case OV_WC_AES_256_CBC_TYPE:
+#endif
+#ifndef NO_DES3
+    case OV_WC_DES_CBC_TYPE:
+    case OV_WC_DES_EDE3_CBC_TYPE:
+#endif
+#if defined(HAVE_AES_CBC) || defined(NO_DES3)
+        return OPENVPN_MODE_CBC;
+#endif
+#ifdef WOLFSSL_AES_DIRECT
+    case OV_WC_AES_128_OFB_TYPE:
+    case OV_WC_AES_192_OFB_TYPE:
+    case OV_WC_AES_256_OFB_TYPE:
+        return OPENVPN_MODE_OFB;
+#endif
+#ifdef WOLFSSL_AES_CFB
+    case OV_WC_AES_128_CFB_TYPE:
+    case OV_WC_AES_192_CFB_TYPE:
+    case OV_WC_AES_256_CFB_TYPE:
+        return OPENVPN_MODE_CFB;
+#endif
+#ifdef HAVE_AESGCM
+    case OV_WC_AES_128_GCM_TYPE:
+    case OV_WC_AES_192_GCM_TYPE:
+    case OV_WC_AES_256_GCM_TYPE:
+        return OPENVPN_MODE_GCM;
+#endif
+    case OV_WC_NULL_CIPHER_TYPE:
+    default:
+        return OPENVPN_MODE_OTHER;
+    }
+}
+
+bool cipher_kt_mode_cbc(const cipher_kt_t *cipher)
+{
+    return cipher && cipher_kt_mode(cipher) == OPENVPN_MODE_CBC;
+}
+
+bool cipher_kt_mode_ofb_cfb(const cipher_kt_t *cipher)
+{
+    return cipher
+            && (cipher_kt_mode(cipher) == OPENVPN_MODE_OFB
+                    || cipher_kt_mode(cipher) == OPENVPN_MODE_CFB);
+}
+
+bool cipher_kt_mode_aead(const cipher_kt_t *cipher)
+{
+#ifdef HAVE_AEAD_CIPHER_MODES
+    if (cipher)
+    {
+        switch (*cipher)
+        {
+        case OV_WC_AES_128_GCM_TYPE:
+        case OV_WC_AES_192_GCM_TYPE:
+        case OV_WC_AES_256_GCM_TYPE:
+        case OV_WC_CHACHA20_POLY1305_TYPE:
+            return true;
+        default:
+            return false;
+        }
+    }
+#endif
+    return false;
+}
+
+/*
+ *
+ * Generic cipher context functions
+ *
+ */
+
+static void wc_cipher_init(cipher_ctx_t* ctx)
+{
+    ctx->cipher_type = OV_WC_NULL_CIPHER_TYPE;
+    ctx->enc = -1;
+    ctx->buf_used = 0;
+#ifdef HAVE_AEAD_CIPHER_MODES
+    ctx->aead_buf_len = 0;
+    ctx->aead_buf = NULL;
+    ctx->authInSz = 0;
+    ctx->authIn = NULL;
+    ctx->aead_updated = false;
+#endif
+}
+
+cipher_ctx_t *cipher_ctx_new(void)
+{
+    cipher_ctx_t *ctx = (cipher_ctx_t*) malloc(sizeof *ctx);
+    check_malloc_return(ctx);
+    wc_cipher_init(ctx);
+    return ctx;
+}
+
+void cipher_ctx_free(cipher_ctx_t *ctx)
+{
+    if (ctx)
+    {
+#ifdef HAVE_AEAD_CIPHER_MODES
+        if (ctx->authIn)
+        {
+            free(ctx->authIn);
+            ctx->authIn = NULL;
+            ctx->authInSz = 0;
+        }
+        if (ctx->aead_buf)
+        {
+            free(ctx->aead_buf);
+            ctx->aead_buf = NULL;
+            ctx->aead_buf_len = 0;
+        }
+#endif
+        free(ctx);
+    }
+}
+
+static void check_key_length(const cipher_kt_t kt, int key_len)
+{
+    int correct_key_len;
+
+    if (!kt)
+    {
+        return;
+    }
+
+    correct_key_len = cipher_kt_key_size(&kt);
+
+    if (key_len != correct_key_len)
+    {
+        msg(M_FATAL, "Wrong key length for chosen cipher.\n"
+                "Cipher chosen: %s\n"
+                "Key length expected: %d\n"
+                "Key length provided: %d\n", cipher_kt_name(&kt),
+                correct_key_len, key_len);
+    }
+}
+
+static void reset_aead(cipher_ctx_t *ctx)
+{
+#ifdef HAVE_AEAD_CIPHER_MODES
+    ctx->aead_updated = false;
+    memset(&ctx->aead_tag, 0, sizeof(ctx->aead_tag));
+    if (ctx->authIn)
+    {
+        free(ctx->authIn);
+        ctx->authIn = NULL;
+        ctx->authInSz = 0;
+    }
+    if (ctx->aead_buf)
+    {
+        free(ctx->aead_buf);
+        ctx->aead_buf = NULL;
+        ctx->aead_buf_len = 0;
+    }
+#endif
+}
+
+/*
+ * Function to setup context for cipher streams
+ */
+static int wolfssl_ctx_init(cipher_ctx_t *ctx, const uint8_t *key, int key_len,
+        const uint8_t* iv, const cipher_kt_t *kt, int enc)
+{
+    int ret;
+
+    switch (*kt)
+    {
+#ifdef HAVE_AES_CBC
+    case OV_WC_AES_128_CBC_TYPE:
+    case OV_WC_AES_192_CBC_TYPE:
+    case OV_WC_AES_256_CBC_TYPE:
+        if (key) {
+            if ((ret = wc_AesSetKey(
+                    &ctx->cipher.aes, key, key_len, iv,
+                    enc == OPENVPN_OP_ENCRYPT ? AES_ENCRYPTION : AES_DECRYPTION
+                )) != 0) {
+                msg(M_FATAL, "wc_AesSetKey failed with Errno: %d", ret);
+                return 0;
+            }
+        }
+        if (iv && !key) {
+            if ((ret = wc_AesSetIV(&ctx->cipher.aes, iv))) {
+                msg(M_FATAL, "wc_AesSetIV failed with Errno: %d", ret);
+                return 0;
+            }
+        }
+        break;
+#endif
+#ifdef WOLFSSL_AES_CFB
+    case OV_WC_AES_128_CFB_TYPE:
+    case OV_WC_AES_192_CFB_TYPE:
+    case OV_WC_AES_256_CFB_TYPE:
+#endif
+#ifdef WOLFSSL_AES_COUNTER
+    case OV_WC_AES_128_CTR_TYPE:
+    case OV_WC_AES_192_CTR_TYPE:
+    case OV_WC_AES_256_CTR_TYPE:
+#endif
+#ifdef WOLFSSL_AES_DIRECT
+    case OV_WC_AES_128_OFB_TYPE:
+    case OV_WC_AES_192_OFB_TYPE:
+    case OV_WC_AES_256_OFB_TYPE:
+#endif
+#ifdef HAVE_AES_ECB
+    case OV_WC_AES_128_ECB_TYPE:
+    case OV_WC_AES_192_ECB_TYPE:
+    case OV_WC_AES_256_ECB_TYPE:
+#endif
+#if defined(WOLFSSL_AES_DIRECT) || defined(WOLFSSL_AES_COUNTER) || defined(WOLFSSL_AES_CFB) || defined(HAVE_AES_ECB)
+        if (key) {
+            if ((ret = wc_AesSetKeyDirect(
+                    &ctx->cipher.aes, key, key_len, iv, AES_ENCRYPTION
+                )) != 0) {
+                msg(M_FATAL, "wc_AesSetKey failed with Errno: %d", ret);
+                return 0;
+            }
+        }
+        if (iv && !key) {
+            if ((ret = wc_AesSetIV(&ctx->cipher.aes, iv))) {
+                msg(M_FATAL, "wc_AesSetIV failed with Errno: %d", ret);
+                return 0;
+            }
+        }
+        break;
+#endif
+#ifdef HAVE_AESGCM
+    case OV_WC_AES_128_GCM_TYPE:
+    case OV_WC_AES_192_GCM_TYPE:
+    case OV_WC_AES_256_GCM_TYPE:
+        if (key) {
+            if ((ret = wc_AesGcmSetKey(&ctx->cipher.aes, key, key_len)) != 0) {
+                msg(M_FATAL, "wc_AesGcmSetKey failed with Errno: %d", ret);
+            }
+        }
+        if (iv) {
+            memcpy(ctx->iv.aes, iv, AESGCM_IV_SZ);
+        }
+        break;
+#endif
+#ifndef NO_DES3
+    case OV_WC_DES_CBC_TYPE:
+    case OV_WC_DES_ECB_TYPE:
+        if (key)
+        {
+            if ((ret = wc_Des_SetKey(&ctx->cipher.des, key, iv,
+                    enc == OPENVPN_OP_ENCRYPT ? DES_ENCRYPTION : DES_DECRYPTION))
+                    != 0)
+            {
+                msg(M_FATAL, "wc_Des_SetKey failed with Errno: %d", ret);
+                return 0;
+            }
+        }
+        if (iv && !key)
+        {
+            wc_Des_SetIV(&ctx->cipher.des, iv);
+        }
+        break;
+    case OV_WC_DES_EDE3_CBC_TYPE:
+    case OV_WC_DES_EDE3_ECB_TYPE:
+        if (key)
+        {
+            if ((ret = wc_Des3_SetKey(&ctx->cipher.des3, key, iv,
+                    enc == OPENVPN_OP_ENCRYPT ? DES_ENCRYPTION : DES_DECRYPTION))
+                    != 0)
+            {
+                msg(M_FATAL, "wc_Des3_SetKey failed with Errno: %d", ret);
+                return 0;
+            }
+        }
+        if (iv && !key)
+        {
+            if ((ret = wc_Des3_SetIV(&ctx->cipher.des3, iv)) != 0)
+            {
+                msg(M_FATAL, "wc_Des3_SetIV failed with Errno: %d", ret);
+                return 0;
+            }
+        }
+        break;
+#endif
+#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
+    case OV_WC_CHACHA20_POLY1305_TYPE:
+        ASSERT(CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE == OPENVPN_AEAD_TAG_LENGTH);
+        if (iv) {
+            memcpy(ctx->iv.chacha20_poly1305, iv, CHACHA20_POLY1305_AEAD_IV_SIZE);
+        }
+        break;
+#endif
+    case OV_WC_NULL_CIPHER_TYPE:
+        return 0;
+    }
+
+    if (key)
+    {
+        memcpy(&ctx->key, key, key_len);
+    }
+
+    ctx->cipher_type = *kt;
+    switch (enc)
+    {
+    case OPENVPN_OP_ENCRYPT:
+        ctx->enc = OV_WC_ENCRYPT;
+        break;
+    case OPENVPN_OP_DECRYPT:
+        ctx->enc = OV_WC_DECRYPT;
+        break;
+    }
+    ctx->buf_used = 0;
+#ifdef HAVE_AEAD_CIPHER_MODES
+    ctx->aead_updated = false;
+    if (ctx->aead_buf)
+    {
+        free(ctx->aead_buf);
+        ctx->aead_buf = NULL;
+        ctx->aead_buf_len = 0;
+    }
+#endif
+    return 1;
+}
+
+void cipher_ctx_init(cipher_ctx_t *ctx, const uint8_t *key, int key_len,
+        const cipher_kt_t *kt, int enc)
+{
+    int ret;
+    ASSERT(NULL != kt && NULL != ctx && NULL != key);
+
+    check_key_length(*kt, key_len);
+    if ((ret = wolfssl_ctx_init(ctx, key, key_len, NULL, kt, enc)) != 1)
+    {
+        msg(M_FATAL, "wolfssl_ctx_init failed with Errno: %d", ret);
+    }
+}
+
+/*
+ * Reset and zero values in cipher context
+ */
+void cipher_ctx_cleanup(cipher_ctx_t *ctx)
+{
+    if (ctx)
+    {
+        ctx->cipher_type = OV_WC_NULL_CIPHER_TYPE;
+        ctx->enc = -1;
+        ctx->buf_used = 0;
+        memset(&ctx->cipher, 0, sizeof(ctx->cipher));
+        memset(&ctx->buf, 0, sizeof(ctx->buf));
+        memset(&ctx->key, 0, sizeof(ctx->key));
+        reset_aead(ctx);
+    }
+}
+
+int cipher_ctx_iv_length(const cipher_ctx_t *ctx)
+{
+    return cipher_kt_iv_size(&ctx->cipher_type);
+}
+
+int cipher_ctx_get_tag(cipher_ctx_t *ctx, uint8_t *tag, int tag_len)
+{
+#ifdef HAVE_AEAD_CIPHER_MODES
+    if (!ctx || !tag)
+    {
+        return 0;
+    }
+    ASSERT(tag_len == OPENVPN_AEAD_TAG_LENGTH);
+    memcpy(tag, ctx->aead_tag, OPENVPN_AEAD_TAG_LENGTH);
+    return 1;
+#else
+    msg(M_FATAL, "%s called without AEAD functionality compiled in.", __func__);
+#endif
+}
+
+int cipher_ctx_block_size(const cipher_ctx_t *ctx)
+{
+    return cipher_kt_block_size(&ctx->cipher_type);
+}
+
+int cipher_ctx_mode(const cipher_ctx_t *ctx)
+{
+    return cipher_kt_mode(&ctx->cipher_type);
+}
+
+const cipher_kt_t *cipher_ctx_get_cipher_kt(const cipher_ctx_t *ctx)
+{
+    return ctx ? &ctx->cipher_type : NULL;
+}
+
+/*
+ * Reset the cipher context to the initial settings used in cipher_ctx_init
+ * and set a new IV
+ */
+int cipher_ctx_reset(cipher_ctx_t *ctx, const uint8_t *iv_buf)
+{
+    int ret;
+    if ((ret = wolfssl_ctx_init(ctx, (uint8_t*) &ctx->key,
+            cipher_kt_key_size(&ctx->cipher_type), iv_buf, &ctx->cipher_type,
+            ctx->enc)) != 1)
+    {
+        msg(M_FATAL, "wolfssl_ctx_init failed with Errno: %d", ret);
+    }
+    return 1;
+}
+
+int cipher_ctx_update_ad(cipher_ctx_t *ctx, const uint8_t *src, int src_len)
+{
+#ifdef HAVE_AEAD_CIPHER_MODES
+    if (!ctx || !src || src_len <= 0)
+    {
+        msg(M_FATAL, "Invalid parameter(s) for cipher_ctx_update_ad");
+    }
+    if (ctx->authIn)
+    {
+        free(ctx->authIn);
+    }
+    ctx->authIn = (uint8_t*) malloc(src_len);
+    check_malloc_return(ctx->authIn);
+    memcpy(ctx->authIn, src, src_len);
+    ctx->authInSz = src_len;
+#else
+    msg(M_FATAL, "%s called without AEAD functionality compiiled in.", __func__);
+#endif
+    return 1;
+}
+
+/*
+ * Update cipher blocks. The data stream in src has to be padded outside of
+ * this function. Do not call this function directly, use wolfssl_ctx_update
+ * instead.
+ */
+static int wolfssl_ctx_update_blocks(cipher_ctx_t *ctx, uint8_t *dst,
+        int *dst_len, uint8_t *src, int src_len)
+{
+    int ret, i, j;
+    if (needs_padding(&ctx->cipher_type))
+    {
+        /* make sure src is correctly padded */
+        ASSERT((src_len % cipher_kt_block_size(&ctx->cipher_type)) == 0);
+    }
+
+    switch (ctx->cipher_type)
+    {
+#ifdef HAVE_AES_CBC
+    case OV_WC_AES_128_CBC_TYPE:
+    case OV_WC_AES_192_CBC_TYPE:
+    case OV_WC_AES_256_CBC_TYPE:
+        if (ctx->enc == OV_WC_ENCRYPT) {
+            if ((ret = wc_AesCbcEncrypt(&ctx->cipher.aes, dst, src, src_len)) != 0) {
+                msg(M_FATAL, "wc_AesCbcEncrypt failed with Errno: %d", ret);
+                return 0;
+            }
+        } else {
+            if ((ret = wc_AesCbcDecrypt(&ctx->cipher.aes, dst, src, src_len)) != 0) {
+                msg(M_FATAL, "wc_AesCbcDecrypt failed with Errno: %d", ret);
+                return 0;
+            }
+        }
+        break;
+#endif
+#ifdef WOLFSSL_AES_COUNTER
+    case OV_WC_AES_128_CTR_TYPE:
+    case OV_WC_AES_192_CTR_TYPE:
+    case OV_WC_AES_256_CTR_TYPE:
+        /* encryption and decryption are the same for CTR */
+        if ((ret = wc_AesCtrEncrypt(&ctx->cipher.aes, dst, src, src_len)) != 0) {
+            msg(M_FATAL, "wc_AesCtrEncrypt failed with Errno: %d", ret);
+            return 0;
+        }
+        break;
+#endif
+#ifdef HAVE_AES_ECB
+    case OV_WC_AES_128_ECB_TYPE:
+    case OV_WC_AES_192_ECB_TYPE:
+    case OV_WC_AES_256_ECB_TYPE:
+        if (ctx->enc == OV_WC_ENCRYPT) {
+            if ((ret = wc_AesEcbEncrypt(&ctx->cipher.aes, dst, src, src_len)) != 0) {
+                msg(M_FATAL, "wc_AesEcbEncrypt failed with Errno: %d", ret);
+                return 0;
+            }
+        } else {
+            if ((ret = wc_AesEcbDecrypt(&ctx->cipher.aes, dst, src, src_len)) != 0) {
+                msg(M_FATAL, "wc_AesEcbDecrypt failed with Errno: %d", ret);
+                return 0;
+            }
+        }
+        break;
+#endif
+#ifdef WOLFSSL_AES_DIRECT
+    case OV_WC_AES_128_OFB_TYPE:
+    case OV_WC_AES_192_OFB_TYPE:
+    case OV_WC_AES_256_OFB_TYPE:
+        /* encryption and decryption are the same for OFB */
+        for (i = 0; i < src_len; i += AES_BLOCK_SIZE) {
+            wc_AesEncryptDirect(&ctx->cipher.aes, (uint8_t*)ctx->cipher.aes.reg,
+                                                  (uint8_t*)ctx->cipher.aes.reg);
+            for (j = i; j < MIN(i + AES_BLOCK_SIZE, src_len); j++) {
+                dst[j] = ((uint8_t*)ctx->cipher.aes.reg)[j - i] ^ src[j];
+            }
+        }
+        break;
+#endif
+#ifdef WOLFSSL_AES_CFB
+    case OV_WC_AES_128_CFB_TYPE:
+    case OV_WC_AES_192_CFB_TYPE:
+    case OV_WC_AES_256_CFB_TYPE:
+        if (ctx->enc == OV_WC_ENCRYPT) {
+            if ((ret = wc_AesCfbEncrypt(&ctx->cipher.aes, dst, src, src_len)) != 0) {
+                msg(M_FATAL, "wc_AesCfbEncrypt failed with Errno: %d", ret);
+                return 0;
+            }
+        } else {
+            if ((ret = wc_AesCfbDecrypt(&ctx->cipher.aes, dst, src, src_len)) != 0) {
+                msg(M_FATAL, "wc_AesCfbDecrypt failed with Errno: %d", ret);
+                return 0;
+            }
+        }
+        break;
+#endif
+#ifdef HAVE_AESGCM
+    case OV_WC_AES_128_GCM_TYPE:
+    case OV_WC_AES_192_GCM_TYPE:
+    case OV_WC_AES_256_GCM_TYPE:
+        if (ctx->aead_updated) {
+            msg(M_FATAL, "AEAD ALGORITHMS MAY ONLY CALL UPDATE ONCE");
+        }
+        if (ctx->enc == OV_WC_ENCRYPT) {
+            if ((ret = wc_AesGcmEncrypt(&ctx->cipher.aes, dst, src, src_len,
+                                        ctx->iv.aes, AESGCM_IV_SZ, ctx->aead_tag,
+                                        OPENVPN_AEAD_TAG_LENGTH, ctx->authIn,
+                                        ctx->authInSz)) != 0) {
+                msg(M_FATAL, "wc_AesGcmEncrypt failed with Errno: %d", ret);
+            }
+        } else {
+            /*
+             * Decryption needs to be handled in Final call since wolfSSL also checks
+             * that the auth tag is correct.
+             */
+            ASSERT(!ctx->aead_buf);
+            ctx->aead_buf = (uint8_t*) malloc(src_len);
+            check_malloc_return(ctx->aead_buf);
+            memcpy(ctx->aead_buf, src, src_len);
+            ctx->aead_buf_len = src_len;
+
+            *dst_len -= src_len;
+        }
+        ctx->aead_updated = true;
+        break;
+#endif
+#ifndef NO_DES3
+    case OV_WC_DES_CBC_TYPE:
+        if (ctx->enc == OV_WC_ENCRYPT)
+        {
+            if ((ret = wc_Des_CbcEncrypt(&ctx->cipher.des, dst, src, src_len))
+                    != 0)
+            {
+                msg(M_FATAL, "wc_Des3_CbcEncrypt failed with Errno: %d", ret);
+                return 0;
+            }
+        }
+        else
+        {
+            if ((ret = wc_Des_CbcDecrypt(&ctx->cipher.des, dst, src, src_len))
+                    != 0)
+            {
+                msg(M_FATAL, "wc_Des3_CbcDecrypt failed with Errno: %d", ret);
+                return 0;
+            }
+        }
+        break;
+    case OV_WC_DES_EDE3_CBC_TYPE:
+        if (ctx->enc == OV_WC_ENCRYPT)
+        {
+            if ((ret = wc_Des3_CbcEncrypt(&ctx->cipher.des3, dst, src, src_len))
+                    != 0)
+            {
+                msg(M_FATAL, "wc_Des3_CbcEncrypt failed with Errno: %d", ret);
+                return 0;
+            }
+        }
+        else
+        {
+            if ((ret = wc_Des3_CbcDecrypt(&ctx->cipher.des3, dst, src, src_len))
+                    != 0)
+            {
+                msg(M_FATAL, "wc_Des3_CbcDecrypt failed with Errno: %d", ret);
+                return 0;
+            }
+        }
+        break;
+    case OV_WC_DES_ECB_TYPE:
+        if ((ret = wc_Des_EcbEncrypt(&ctx->cipher.des, dst, src, src_len)) != 0)
+        {
+            msg(M_FATAL, "wc_Des_EcbEncrypt failed with Errno: %d", ret);
+            return 0;
+        }
+        break;
+    case OV_WC_DES_EDE3_ECB_TYPE:
+        if ((ret = wc_Des3_EcbEncrypt(&ctx->cipher.des3, dst, src, src_len))
+                != 0)
+        {
+            msg(M_FATAL, "wc_Des3_EcbEncrypt failed with Errno: %d", ret);
+            return 0;
+        }
+        break;
+#endif
+#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
+    case OV_WC_CHACHA20_POLY1305_TYPE:
+        if (ctx->aead_updated) {
+            msg(M_FATAL, "AEAD ALGORITHMS MAY ONLY CALL UPDATE ONCE");
+            return 0;
+        }
+        if (ctx->enc == OV_WC_ENCRYPT) {
+            if ((ret = wc_ChaCha20Poly1305_Encrypt(ctx->key.chacha20_poly1305_key,
+                                                   ctx->iv.chacha20_poly1305, ctx->authIn, ctx->authInSz,
+                                                   src, src_len, dst, ctx->aead_tag)) != 0) {
+                msg(M_FATAL, "wc_ChaCha20Poly1305_Encrypt failed with Errno: %d", ret);
+                return 0;
+            }
+        } else {
+            /*
+             * Store for later since wc_ChaCha20Poly1305_Decrypt also takes in the
+             * correct tag as a parameter and automatically checks it.
+             */
+            ASSERT(!ctx->aead_buf);
+            ctx->aead_buf = (uint8_t*) malloc(src_len);
+            check_malloc_return(ctx->aead_buf);
+            memcpy(ctx->aead_buf, src, src_len);
+            ctx->aead_buf_len = src_len;
+
+            *dst_len -= src_len;
+        }
+        ctx->aead_updated = true;
+        break;
+#endif
+    case OV_WC_NULL_CIPHER_TYPE:
+        return 0;
+    }
+    *dst_len += src_len;
+    return 1;
+}
+
+/*
+ * This function wraps wolfssl_ctx_update_blocks by checking and storing input data
+ * that is not properly padded. The stored data is later concatenated with new blocks
+ * of data that are passed to this function.
+ */
+static int wolfssl_ctx_update(cipher_ctx_t *ctx, uint8_t *dst, int *dst_len,
+        uint8_t *src, int src_len)
+{
+    int ret;
+    int block_size = cipher_kt_block_size(&ctx->cipher_type);
+    int block_leftover;
+
+    if (!ctx || !src || (src_len < 0) || !dst_len || !dst)
+        return 0;
+
+    *dst_len = 0;
+
+    if (!src_len)
+    {
+        /* nothing to do */
+        return 1;
+    }
+
+    if (!needs_padding(&ctx->cipher_type))
+    {
+        /*
+         * In case of AEAD and no padding needed send data straight to wolfssl_ctx_update_blocks
+         * and don't process padding
+         */
+        if ((ret = wolfssl_ctx_update_blocks(ctx, dst, dst_len, src, src_len)
+                != 1))
+        {
+            msg(M_FATAL, "%s: wolfssl_ctx_update_blocks() failed", __func__);
+        }
+        return 1;
+    }
+
+    if (ctx->buf_used)
+    {
+        if ((ctx->buf_used + src_len) < block_size)
+        {
+            memcpy((&ctx->buf) + ctx->buf_used, src, src_len);
+            ctx->buf_used += src_len;
+            return 1;
+        }
+        else
+        {
+            memcpy((&ctx->buf) + ctx->buf_used, src,
+                    block_size - ctx->buf_used);
+            src += block_size - ctx->buf_used;
+            src_len -= block_size - ctx->buf_used;
+            if ((ret = wolfssl_ctx_update_blocks(ctx, dst, dst_len,
+                    (uint8_t*) &(ctx->buf), block_size) != 1))
+            {
+                msg(M_FATAL, "%s: wolfssl_ctx_update_blocks() failed",
+                        __func__);
+            }
+            ctx->buf_used = 0;
+            dst += block_size;
+            *dst_len += block_size;
+        }
+    }
+
+    ASSERT(ctx->buf_used == 0);
+
+    if (src_len < block_size)
+    {
+        memcpy(&ctx->buf, src, src_len);
+        ctx->buf_used = src_len;
+        return 1;
+    }
+
+    block_leftover = src_len % block_size;
+    if ((ret = wolfssl_ctx_update_blocks(ctx, dst, dst_len, src,
+            src_len - block_leftover) != 1))
+    {
+        msg(M_FATAL, "%s: wolfssl_ctx_update_blocks() failed", __func__);
+    }
+
+    if (block_leftover)
+    {
+        memcpy(&ctx->buf, src + (src_len - block_leftover), block_leftover);
+        ctx->buf_used = block_leftover;
+    }
+    else if (ctx->enc == OV_WC_DECRYPT)
+    {
+        /* copy last decrypted block to check padding in final call */
+        memcpy(&ctx->buf, dst + (src_len - block_size), block_size);
+    }
+
+    return 1;
+}
+
+int cipher_ctx_update(cipher_ctx_t *ctx, uint8_t *dst, int *dst_len,
+        uint8_t *src, int src_len)
+{
+    if (!wolfssl_ctx_update(ctx, dst, dst_len, src, src_len))
+    {
+        msg(M_FATAL, "%s: wolfssl_ctx_update() failed", __func__);
+    }
+    return 1;
+}
+
+/*
+ * Pads the buffer of the cipher context with PKCS#7 padding
+ */
+static void pad_block(cipher_ctx_t *ctx)
+{
+    int i, block_size, n;
+    uint8_t* buf = (uint8_t*) &ctx->buf;
+    block_size = cipher_kt_block_size(&ctx->cipher_type);
+    n = block_size - ctx->buf_used;
+    ASSERT(block_size >= ctx->buf_used);
+    ASSERT(n < 256); // nothing more can fit in a byte
+    for (i = ctx->buf_used; i < block_size; i++)
+    {
+        buf[i] = (uint8_t) (n);
+    }
+}
+
+/*
+ * Verifies the PKCS#7 padding of the block in the cipher context and
+ * returns the number of padding blocks.
+ */
+static int check_pad(cipher_ctx_t *ctx)
+{
+    int i;
+    int n;
+    int block_size = cipher_kt_block_size(&ctx->cipher_type);
+    uint8_t* buf = (uint8_t*) &ctx->buf;
+    n = buf[block_size - 1];
+    if (n > block_size)
+        return -1;
+    for (i = 0; i < n; i++)
+    {
+        if (buf[block_size - i - 1] != n)
+            return -1;
+    }
+    return n;
+}
+
+/*
+ * Verify or add necessary padding and return final block. In case of decryption
+ * the length returned in dst_len is negative so as to remove the final padding
+ * blocks.
+ */
+static int wolfssl_ctx_final(cipher_ctx_t *ctx, uint8_t *dst, int *dst_len)
+{
+    int block_size;
+    int pad_left;
+
+    if (!ctx || !dst_len || !dst)
+    {
+        return 0;
+    }
+
+    *dst_len = 0;
+
+    if (ctx->buf_used == 0 && ctx->enc != OV_WC_DECRYPT
+            && !needs_padding(&ctx->cipher_type))
+    {
+        return 1;
+    }
+
+    block_size = cipher_kt_block_size(&ctx->cipher_type);
+
+    if (!cipher_kt_mode_aead(&ctx->cipher_type))
+    {
+        if (ctx->enc == OV_WC_ENCRYPT)
+        {
+            if (needs_padding(&ctx->cipher_type))
+            {
+                pad_block(ctx);
+            }
+            if (wolfssl_ctx_update_blocks(ctx, dst, dst_len,
+                    (uint8_t*) &ctx->buf, block_size) != 1)
+            {
+                return 0;
+            }
+        }
+        else if (needs_padding(&ctx->cipher_type))
+        {
+            if (ctx->buf_used != 0)
+            {
+                *dst_len = 0;
+                msg(M_FATAL, "%s: not enough padding for decrypt", __func__);
+                return 0;
+            }
+            if ((pad_left = check_pad(ctx)) >= 0)
+            {
+                *dst_len = -pad_left;
+            }
+            else
+            {
+                msg(M_FATAL, "%s: padding is incorrect", __func__);
+                return 0;
+            }
+        }
+    }
+
+    return 1;
+}
+
+int cipher_ctx_final(cipher_ctx_t *ctx, uint8_t *dst, int *dst_len)
+{
+    return wolfssl_ctx_final(ctx, dst, dst_len);
+}
+
+int cipher_ctx_final_check_tag(cipher_ctx_t *ctx, uint8_t *dst, int *dst_len,
+        uint8_t *tag, size_t tag_len)
+{
+#ifdef HAVE_AEAD_CIPHER_MODES
+    int ret;
+
+    if (!ctx || !dst_len || !dst || !tag || tag_len <= 0)
+    {
+        return 0;
+    }
+
+    ASSERT(ctx->enc == OV_WC_DECRYPT);
+
+    switch (ctx->cipher_type)
+    {
+#ifdef HAVE_AESGCM
+    case OV_WC_AES_128_GCM_TYPE:
+    case OV_WC_AES_192_GCM_TYPE:
+    case OV_WC_AES_256_GCM_TYPE:
+        if ((ret = wc_AesGcmDecrypt(&ctx->cipher.aes, dst, ctx->aead_buf, ctx->aead_buf_len,
+                                    ctx->iv.aes, AESGCM_IV_SZ, tag, tag_len, ctx->authIn,
+                                    ctx->authInSz)) != 0) {
+            msg(M_FATAL, "wc_AesGcmDecrypt failed with Errno: %d", ret);
+            return 0;
+        }
+        break;
+#endif
+#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
+    case OV_WC_CHACHA20_POLY1305_TYPE:
+        if (tag_len != CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE) {
+            msg(M_FATAL, "Incorrect tag length for Chacha20_Poly1305. Got: %d ; Need: %d",
+                (int)tag_len, CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE);
+            return 0;
+        }
+        if ((ret = wc_ChaCha20Poly1305_Decrypt(ctx->key.chacha20_poly1305_key, ctx->iv.chacha20_poly1305,
+                                               ctx->authIn, ctx->authInSz, ctx->aead_buf, ctx->aead_buf_len,
+                                               tag, dst)) != 0) {
+            msg(M_FATAL, "wc_ChaCha20Poly1305_Decrypt failed with Errno: %d", ret);
+            return 0;
+        }
+        break;
+#endif
+    default:
+        msg(M_FATAL,
+                "cipher_ctx_final_check_tag called with none AEAD cipher.");
+        return 0;
+    }
+    *dst_len = ctx->aead_buf_len;
+    return 1;
+#else
+    msg(M_FATAL, "%s called without AEAD functionality compiiled in.", __func__);
+#endif
+}
+
+/*
+ *
+ * Generic message digest information functions
+ *
+ */
+
+const md_kt_t *md_kt_get(const char *digest)
+{
+    const struct digest* digest_;
+
+    for (digest_ = digest_tbl; digest_->name != NULL; digest_++)
+    {
+        if (strncmp(digest, digest_->name, strlen(digest_->name) + 1) == 0)
+        {
+#ifndef NO_MD4
+            if (digest_->type == OV_WC_MD4)
+            {
+                msg(M_FATAL, "MD4 not supported in wolfssl generic functions.");
+            }
+#endif
+            return &digest_static[digest_->type];
+        }
+    }
+    return NULL;
+}
+
+const char *md_kt_name(const md_kt_t *kt)
+{
+    if (!kt)
+    {
+        return "[null-digest]";
+    }
+    else
+    {
+        return digest_tbl[*kt].name;
+    }
+}
+
+int md_kt_size(const md_kt_t *kt)
+{
+    if (!kt || *kt >= OV_WC_NULL_DIGEST)
+    {
+        return 0;
+    }
+    return wc_HashGetDigestSize(OV_to_WC_hash_type[*kt]);
+}
+
+/*
+ *
+ * Generic message digest functions
+ *
+ */
+
+int md_full(const md_kt_t *kt, const uint8_t *src, int src_len, uint8_t *dst)
+{
+    int ret;
+
+    if (!kt || !src || !dst)
+    {
+        return 0;
+    }
+
+    if ((ret = wc_Hash(OV_to_WC_hash_type[*kt], src, src_len, dst, -1)) != 0)
+    {
+        msg(M_FATAL, "md_full failed with Errno: %d", ret);
+        return 0;
+    }
+    return 1;
+}
+
+md_ctx_t *md_ctx_new(void)
+{
+    md_ctx_t *ctx = (md_ctx_t*) malloc(sizeof(md_ctx_t));
+    check_malloc_return(ctx);
+    return ctx;
+}
+
+void md_ctx_free(md_ctx_t *ctx)
+{
+    if (ctx)
+    {
+        free(ctx);
+    }
+}
+
+void md_ctx_init(md_ctx_t *ctx, const md_kt_t *kt)
+{
+    ASSERT(NULL != ctx && NULL != kt);
+
+    wc_HashInit(&ctx->hash, OV_to_WC_hash_type[*kt]);
+    ctx->hash_type = *kt;
+}
+
+void md_ctx_cleanup(md_ctx_t *ctx)
+{
+    if (ctx)
+    {
+        wc_HashFree(&ctx->hash, OV_to_WC_hash_type[ctx->hash_type]);
+    }
+}
+
+int md_ctx_size(const md_ctx_t *ctx)
+{
+    return md_kt_size(&ctx->hash_type);
+}
+
+void md_ctx_update(md_ctx_t *ctx, const uint8_t *src, int src_len)
+{
+    int ret;
+
+    if ((ret = wc_HashUpdate(&ctx->hash, OV_to_WC_hash_type[ctx->hash_type],
+            src, src_len)) != 0)
+    {
+        msg(M_FATAL, "wc_HashUpdate failed with Errno: %d", ret);
+    }
+}
+
+void md_ctx_final(md_ctx_t *ctx, uint8_t *dst)
+{
+    int ret;
+
+    if ((ret = wc_HashFinal(&ctx->hash, OV_to_WC_hash_type[ctx->hash_type], dst))
+            != 0)
+    {
+        msg(M_FATAL, "wc_HashFinal failed with Errno: %d", ret);
+    }
+}
+
+/*
+ *
+ * Generic HMAC functions
+ *
+ */
+
+hmac_ctx_t *hmac_ctx_new(void)
+{
+    hmac_ctx_t *ctx = (hmac_ctx_t*) calloc(sizeof(hmac_ctx_t), 1);
+    check_malloc_return(ctx);
+    return ctx;
+}
+
+void hmac_ctx_free(hmac_ctx_t *ctx)
+{
+    if (ctx)
+    {
+        wc_HmacFree(&ctx->hmac);
+    }
+}
+
+void hmac_ctx_init(hmac_ctx_t *ctx, const uint8_t *key, int key_length,
+        const md_kt_t *kt)
+{
+    int ret;
+    ASSERT(NULL != kt && NULL != ctx);
+
+    if ((ret = wc_HmacSetKey(&ctx->hmac, OV_to_WC_hash_type[*kt], key,
+            key_length)) != 0)
+    {
+        msg(M_FATAL, "wc_HmacSetKey failed. Errno: %d", ret);
+    }
+
+    /* Hold key for later reseting of hmac instance */
+    memcpy(&ctx->key, key, key_length);
+    ctx->key_len = key_length;
+
+    /* make sure we used a big enough key */
+    ASSERT(md_kt_size(kt) <= key_length);
+}
+
+void hmac_ctx_cleanup(hmac_ctx_t *ctx)
+{
+    hmac_ctx_free(ctx);
+}
+
+int hmac_ctx_size(const hmac_ctx_t *ctx)
+{
+    if (!ctx)
+    {
+        return 0;
+    }
+    return wc_HashGetDigestSize(ctx->hmac.macType);
+}
+
+void hmac_ctx_reset(hmac_ctx_t *ctx)
+{
+    int ret;
+    if (ctx)
+    {
+        if ((ret = wc_HmacSetKey(&ctx->hmac, ctx->hmac.macType,
+                (uint8_t*) &ctx->key, ctx->key_len)) != 0)
+        {
+            msg(M_FATAL, "wc_HmacSetKey failed. Errno: %d", ret);
+        }
+    }
+}
+
+void hmac_ctx_update(hmac_ctx_t *ctx, const uint8_t *src, int src_len)
+{
+    int ret;
+    if (ctx && src)
+    {
+        if ((ret = wc_HmacUpdate(&ctx->hmac, src, src_len)) != 0)
+        {
+            msg(M_FATAL, "wc_HmacUpdate failed. Errno: %d", ret);
+        }
+    }
+}
+
+void hmac_ctx_final(hmac_ctx_t *ctx, uint8_t *dst)
+{
+    int ret;
+    if (ctx && dst)
+    {
+        if ((ret = wc_HmacFinal(&ctx->hmac, dst)) != 0)
+        {
+            msg(M_FATAL, "wc_HmacFinal failed. Errno: %d", ret);
+        }
+    }
+}
+
+#endif /* ENABLE_CRYPTO_WOLFSSL */
diff --git a/src/openvpn/crypto_wolfssl.h b/src/openvpn/crypto_wolfssl.h
new file mode 100644
index 00000000..69806bce
--- /dev/null
+++ b/src/openvpn/crypto_wolfssl.h
@@ -0,0 +1,522 @@ 
+/*
+ *  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) 2002-2019 OpenVPN Inc <sales@openvpn.net>
+ *  Copyright (C) 2010-2019 Fox Crypto B.V. <openvpn@fox-it.com>
+ *
+ *  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.
+ */
+
+/**
+ * @file Data Channel Cryptography wolfSSL-specific backend interface
+ */
+
+#ifndef CRYPTO_WOLFSSL_H_
+#define CRYPTO_WOLFSSL_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#include <wolfssl/options.h>
+#include <wolfssl/wolfcrypt/logging.h>
+#include <wolfssl/wolfcrypt/wc_port.h>
+#include <wolfssl/wolfcrypt/coding.h>
+#include <wolfssl/wolfcrypt/types.h>
+#include <wolfssl/ssl.h>
+
+// Digests
+#include <wolfssl/wolfcrypt/md4.h>
+#include <wolfssl/wolfcrypt/md5.h>
+#include <wolfssl/wolfcrypt/sha.h>
+#include <wolfssl/wolfcrypt/sha256.h>
+#include <wolfssl/wolfcrypt/sha3.h>
+#include <wolfssl/wolfcrypt/sha512.h>
+
+// Encryption ciphers
+#include <wolfssl/wolfcrypt/aes.h>
+#include <wolfssl/wolfcrypt/des3.h>
+#include <wolfssl/wolfcrypt/chacha.h>
+#include <wolfssl/wolfcrypt/chacha20_poly1305.h>
+#include <wolfssl/wolfcrypt/poly1305.h>
+
+#include <wolfssl/wolfcrypt/hmac.h>
+#include <wolfssl/wolfcrypt/random.h>
+
+#include <wolfssl/wolfcrypt/misc.h>
+
+#include <stdbool.h>
+
+# define SHA_DIGEST_LENGTH       WC_SHA_DIGEST_SIZE
+# define SHA224_DIGEST_LENGTH    WC_SHA224_DIGEST_SIZE
+# define SHA256_DIGEST_LENGTH    WC_SHA256_DIGEST_SIZE
+# define SHA384_DIGEST_LENGTH    WC_SHA384_DIGEST_SIZE
+# define SHA512_DIGEST_LENGTH    WC_SHA512_DIGEST_SIZE
+
+#define NOT_IMPLEMENTED -0x666
+
+/** Cipher should encrypt */
+#define OPENVPN_OP_ENCRYPT      1
+
+/** Cipher should decrypt */
+#define OPENVPN_OP_DECRYPT      0
+
+#ifdef HAVE_AESGCM
+#define AESGCM_IV_SZ GCM_NONCE_MID_SZ
+#endif
+
+/** Generic cipher key type %context. */
+typedef enum
+{
+    /* DO NOT CHANGE ORDER OF ELEMENTS */
+#ifdef HAVE_AES_CBC
+    OV_WC_AES_128_CBC_TYPE,
+    OV_WC_AES_192_CBC_TYPE,
+    OV_WC_AES_256_CBC_TYPE,
+#endif
+#ifdef WOLFSSL_AES_COUNTER
+    OV_WC_AES_128_CTR_TYPE,
+    OV_WC_AES_192_CTR_TYPE,
+    OV_WC_AES_256_CTR_TYPE,
+#endif
+#ifdef HAVE_AES_ECB
+    OV_WC_AES_128_ECB_TYPE,
+    OV_WC_AES_192_ECB_TYPE,
+    OV_WC_AES_256_ECB_TYPE,
+#endif
+#ifdef WOLFSSL_AES_DIRECT
+    OV_WC_AES_128_OFB_TYPE,
+    OV_WC_AES_192_OFB_TYPE,
+    OV_WC_AES_256_OFB_TYPE,
+#endif
+#ifdef WOLFSSL_AES_CFB
+    OV_WC_AES_128_CFB_TYPE,
+    OV_WC_AES_192_CFB_TYPE,
+    OV_WC_AES_256_CFB_TYPE,
+#endif
+#ifdef HAVE_AESGCM
+    OV_WC_AES_128_GCM_TYPE,
+    OV_WC_AES_192_GCM_TYPE,
+    OV_WC_AES_256_GCM_TYPE,
+#endif
+#ifndef NO_DES3
+    OV_WC_DES_CBC_TYPE,
+    OV_WC_DES_ECB_TYPE,
+    OV_WC_DES_EDE3_CBC_TYPE,
+    OV_WC_DES_EDE3_ECB_TYPE,
+#endif
+#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
+    OV_WC_CHACHA20_POLY1305_TYPE,
+#endif
+    /* LEAVE NULL CIPHER AS LAST ELEMENT */
+    OV_WC_NULL_CIPHER_TYPE,
+} cipher_kt_t;
+
+/* Make sure the order is the same as in cipher_kt_t */
+const static cipher_kt_t cipher_static[] =
+{
+#ifdef HAVE_AES_CBC
+        OV_WC_AES_128_CBC_TYPE, OV_WC_AES_192_CBC_TYPE, OV_WC_AES_256_CBC_TYPE,
+#endif
+#ifdef WOLFSSL_AES_COUNTER
+        OV_WC_AES_128_CTR_TYPE, OV_WC_AES_192_CTR_TYPE, OV_WC_AES_256_CTR_TYPE,
+#endif
+#ifdef HAVE_AES_ECB
+        OV_WC_AES_128_ECB_TYPE, OV_WC_AES_192_ECB_TYPE, OV_WC_AES_256_ECB_TYPE,
+#endif
+#ifdef WOLFSSL_AES_DIRECT
+        OV_WC_AES_128_OFB_TYPE, OV_WC_AES_192_OFB_TYPE, OV_WC_AES_256_OFB_TYPE,
+#endif
+#ifdef WOLFSSL_AES_CFB
+        OV_WC_AES_128_CFB_TYPE, OV_WC_AES_192_CFB_TYPE, OV_WC_AES_256_CFB_TYPE,
+#endif
+#ifdef HAVE_AESGCM
+        OV_WC_AES_128_GCM_TYPE, OV_WC_AES_192_GCM_TYPE, OV_WC_AES_256_GCM_TYPE,
+#endif
+#ifndef NO_DES3
+        OV_WC_DES_CBC_TYPE, OV_WC_DES_ECB_TYPE, OV_WC_DES_EDE3_CBC_TYPE,
+        OV_WC_DES_EDE3_ECB_TYPE,
+#endif
+#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
+        OV_WC_CHACHA20_POLY1305_TYPE,
+#endif
+        OV_WC_NULL_CIPHER_TYPE, };
+
+const static struct cipher
+{
+    cipher_kt_t type;
+    const char *name;
+} cipher_tbl[] =
+{
+/* Make sure the order is the same as in cipher_kt_t */
+#ifdef HAVE_AES_CBC
+        { OV_WC_AES_128_CBC_TYPE, "AES-128-CBC" },
+        { OV_WC_AES_192_CBC_TYPE, "AES-192-CBC" },
+        { OV_WC_AES_256_CBC_TYPE, "AES-256-CBC" },
+#endif
+#ifdef WOLFSSL_AES_COUNTER
+        { OV_WC_AES_128_CTR_TYPE, "AES-128-CTR" },
+        { OV_WC_AES_192_CTR_TYPE, "AES-192-CTR" },
+        { OV_WC_AES_256_CTR_TYPE, "AES-256-CTR" },
+#endif
+#ifdef HAVE_AES_ECB
+        { OV_WC_AES_128_ECB_TYPE, "AES-128-ECB" },
+        { OV_WC_AES_192_ECB_TYPE, "AES-192-ECB" },
+        { OV_WC_AES_256_ECB_TYPE, "AES-256-ECB" },
+#endif
+#ifdef WOLFSSL_AES_DIRECT
+        { OV_WC_AES_128_OFB_TYPE, "AES-128-OFB" },
+        { OV_WC_AES_192_OFB_TYPE, "AES-192-OFB" },
+        { OV_WC_AES_256_OFB_TYPE, "AES-256-OFB" },
+#endif
+#ifdef WOLFSSL_AES_CFB
+        { OV_WC_AES_128_CFB_TYPE, "AES-128-CFB" },
+        { OV_WC_AES_192_CFB_TYPE, "AES-192-CFB" },
+        { OV_WC_AES_256_CFB_TYPE, "AES-256-CFB" },
+#endif
+#ifdef HAVE_AESGCM
+        { OV_WC_AES_128_GCM_TYPE, "AES-128-GCM" },
+        { OV_WC_AES_192_GCM_TYPE, "AES-192-GCM" },
+        { OV_WC_AES_256_GCM_TYPE, "AES-256-GCM" },
+#endif
+#ifndef NO_DES3
+        { OV_WC_DES_CBC_TYPE, "DES-CBC" },
+        { OV_WC_DES_ECB_TYPE, "DES-ECB" },
+        { OV_WC_DES_EDE3_CBC_TYPE, "DES-EDE3-CBC" },
+        { OV_WC_DES_EDE3_ECB_TYPE, "DES-EDE3-ECB" },
+#endif
+#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
+        { OV_WC_CHACHA20_POLY1305_TYPE, "CHACHA20-POLY1305" },
+#endif
+        { 0, NULL } };
+
+/** Generic cipher %context. */
+typedef struct
+{
+    union
+    {
+#ifndef NO_AES
+        Aes aes;
+#endif
+#ifndef NO_DES3
+        Des des;
+        Des3 des3;
+#endif
+#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
+        uint8_t chacha20_poly1305_key[CHACHA20_POLY1305_AEAD_KEYSIZE];
+#endif
+    } cipher;
+
+    union
+    {
+#ifndef NO_AES
+        uint8_t aes_128[AES_128_KEY_SIZE];
+        uint8_t aes_192[AES_192_KEY_SIZE];
+        uint8_t aes_256[AES_256_KEY_SIZE];
+#endif
+#ifndef NO_DES3
+        uint8_t des[DES_KEY_SIZE];
+        uint8_t des3[DES3_KEY_SIZE];
+#endif
+#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
+        uint8_t chacha20_poly1305_key[CHACHA20_POLY1305_AEAD_KEYSIZE];
+#endif
+    } key;
+
+    cipher_kt_t cipher_type;
+
+    enum
+    {
+        OV_WC_DECRYPT = OPENVPN_OP_DECRYPT, OV_WC_ENCRYPT = OPENVPN_OP_ENCRYPT,
+    } enc;
+
+    union
+    {
+#ifndef NO_AES
+        uint8_t aes[AES_BLOCK_SIZE];
+#endif
+#ifndef NO_DES3
+        uint8_t des[DES_BLOCK_SIZE];
+#endif
+        uint8_t null_cipher[0];
+    } buf;
+    int buf_used;
+
+    union
+    {
+#ifndef NO_AES
+        uint8_t aes[AES_BLOCK_SIZE];
+#endif
+#ifndef NO_DES3
+        uint8_t des[DES_BLOCK_SIZE];
+#endif
+#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
+        uint8_t chacha20_poly1305[CHACHA20_POLY1305_AEAD_IV_SIZE];
+#endif
+    } iv;
+
+#ifdef HAVE_AEAD_CIPHER_MODES
+bool aead_updated;
+uint8_t aead_tag[OPENVPN_AEAD_TAG_LENGTH];
+
+uint8_t* authIn;
+int authInSz;
+
+uint8_t* aead_buf;
+int aead_buf_len;
+#endif
+} cipher_ctx_t;
+
+/** Generic message digest key type %context. */
+typedef enum
+{
+/* DO NOT CHANGE ORDER OF ELEMENTS */
+#ifndef NO_MD4
+OV_WC_MD4,
+#endif
+#ifndef NO_MD5
+OV_WC_MD5,
+#endif
+#ifndef NO_SHA
+OV_WC_SHA,
+#endif
+#ifdef WOLFSSL_SHA224
+OV_WC_SHA224,
+#endif
+#ifndef NO_SHA256
+OV_WC_SHA256,
+#endif
+#ifdef WOLFSSL_SHA384
+OV_WC_SHA384,
+#endif
+#ifdef WOLFSSL_SHA512
+OV_WC_SHA512,
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_224)
+OV_WC_SHA3_224,
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_256)
+OV_WC_SHA3_256,
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_384)
+OV_WC_SHA3_384,
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_512)
+OV_WC_SHA3_512,
+#endif
+/* LEAVE NULL DIGEST AS LAST ELEMENT */
+OV_WC_NULL_DIGEST,
+} md_kt_t;
+
+static const enum wc_HashType OV_to_WC_hash_type[] =
+{
+/* Make sure the order is the same as in md_kt_t */
+#ifndef NO_MD4
+    WC_HASH_TYPE_MD4,
+#endif
+#ifndef NO_MD5
+    WC_HASH_TYPE_MD5,
+#endif
+#ifndef NO_SHA
+    WC_HASH_TYPE_SHA,
+#endif
+#ifdef WOLFSSL_SHA224
+    WC_HASH_TYPE_SHA224,
+#endif
+#ifndef NO_SHA256
+    WC_HASH_TYPE_SHA256,
+#endif
+#ifdef WOLFSSL_SHA384
+    WC_HASH_TYPE_SHA384,
+#endif
+#ifdef WOLFSSL_SHA512
+    WC_HASH_TYPE_SHA512,
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_224)
+    WC_HASH_TYPE_SHA3_224,
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_256)
+    WC_HASH_TYPE_SHA3_256,
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_384)
+    WC_HASH_TYPE_SHA3_384,
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_512)
+    WC_HASH_TYPE_SHA3_512,
+#endif
+    };
+
+static const md_kt_t digest_static[] =
+{
+/* Make sure the order is the same as in md_kt_t */
+#ifndef NO_MD4
+    OV_WC_MD4,
+#endif
+#ifndef NO_MD5
+    OV_WC_MD5,
+#endif
+#ifndef NO_SHA
+    OV_WC_SHA,
+#endif
+#ifdef WOLFSSL_SHA224
+    OV_WC_SHA224,
+#endif
+#ifndef NO_SHA256
+    OV_WC_SHA256,
+#endif
+#ifdef WOLFSSL_SHA384
+    OV_WC_SHA384,
+#endif
+#ifdef WOLFSSL_SHA512
+    OV_WC_SHA512,
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_224)
+    OV_WC_SHA3_224,
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_256)
+    OV_WC_SHA3_256,
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_384)
+    OV_WC_SHA3_384,
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_512)
+    OV_WC_SHA3_512,
+#endif
+    };
+
+static const struct digest
+{
+md_kt_t type;
+const char *name;
+} digest_tbl[] =
+{
+/* Make sure the order is the same as in md_kt_t */
+#ifndef NO_MD4
+    { OV_WC_MD4, "MD4" },
+#endif
+#ifndef NO_MD5
+    { OV_WC_MD5, "MD5" },
+#endif
+#ifndef NO_SHA
+    { OV_WC_SHA, "SHA1" },
+#endif
+#ifdef WOLFSSL_SHA224
+    { OV_WC_SHA224, "SHA224" },
+#endif
+#ifndef NO_SHA256
+    { OV_WC_SHA256, "SHA256" },
+#endif
+#ifdef WOLFSSL_SHA384
+    { OV_WC_SHA384, "SHA384" },
+#endif
+#ifdef WOLFSSL_SHA512
+    { OV_WC_SHA512, "SHA512" },
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_224)
+    { OV_WC_SHA3_224, "SHA3-224" },
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_256)
+    { OV_WC_SHA3_256, "SHA3-256" },
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_384)
+    { OV_WC_SHA3_384, "SHA3-384" },
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_512)
+    { OV_WC_SHA3_512, "SHA3-512" },
+#endif
+    { 0, NULL } };
+
+/** Generic message digest %context. */
+typedef struct
+{
+wc_HashAlg hash;
+md_kt_t hash_type;
+} md_ctx_t;
+
+/** Generic HMAC %context. */
+typedef struct
+{
+Hmac hmac;
+union
+{
+#ifndef NO_MD4
+    uint8_t md4[MD4_DIGEST_SIZE];
+#endif
+#ifndef NO_MD5
+    uint8_t md5[WC_MD5_DIGEST_SIZE];
+#endif
+#ifndef NO_SHA
+    uint8_t sha[WC_SHA_DIGEST_SIZE];
+#endif
+#ifdef WOLFSSL_SHA224
+    uint8_t sha224[WC_SHA224_DIGEST_SIZE];
+#endif
+#ifndef NO_SHA256
+    uint8_t sha256[WC_SHA256_DIGEST_SIZE];
+#endif
+#ifdef WOLFSSL_SHA384
+    uint8_t sha384[WC_SHA384_DIGEST_SIZE];
+#endif
+#ifdef WOLFSSL_SHA512
+    uint8_t sha512[WC_SHA512_DIGEST_SIZE];
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_224)
+    uint8_t sha3_224[WC_SHA3_224_DIGEST_SIZE];
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_256)
+    uint8_t sha3_256[WC_SHA3_256_DIGEST_SIZE];
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_384)
+    uint8_t sha3_384[WC_SHA3_384_DIGEST_SIZE];
+#endif
+#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_512)
+    uint8_t sha3_512[WC_SHA3_512_DIGEST_SIZE];
+#endif
+} key;
+int key_len;
+} hmac_ctx_t;
+
+/** Maximum length of an IV */
+#define OPENVPN_MAX_IV_LENGTH   16
+
+typedef enum
+{
+OPENVPN_MODE_CBC, OPENVPN_MODE_CFB, OPENVPN_MODE_OFB, // this needs to be implemented using CBC with a stream of 0's
+OPENVPN_MODE_GCM,
+OPENVPN_MODE_OTHER,
+} cipher_modes;
+
+#define DES_KEY_LENGTH          DES_KEY_SIZE
+#define MD4_DIGEST_LENGTH       MD4_DIGEST_SIZE
+#ifndef MD5_DIGEST_LENGTH
+#define MD5_DIGEST_LENGTH       WC_MD5_DIGEST_SIZE
+#endif
+
+/* Set if variable length cipher */
+#define EVP_CIPH_VARIABLE_LENGTH 0x8
+
+static inline bool cipher_kt_var_key_size(const cipher_kt_t *cipher)
+{
+return false;
+}
+
+#define CIPHER_LIST_SIZE 1000
+
+#endif /* CRYPTO_WOLFSSL_H_ */
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index 2d3865a6..97dc92eb 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -862,7 +862,11 @@  init_options(struct options *o, const bool init_gc)
 #if P2MP
     o->scheduled_exit_interval = 5;
 #endif
+#ifdef ENABLE_CRYPTO_WOLFSSL
+    o->ciphername = "AES-256-CBC";
+#else
     o->ciphername = "BF-CBC";
+#endif
 #ifdef HAVE_AEAD_CIPHER_MODES /* IV_NCP=2 requires GCM support */
     o->ncp_enabled = true;
 #else
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index 63f0f4cb..33b76d68 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -79,8 +79,8 @@  struct options_pre_pull
 };
 
 #endif
-#if !defined(ENABLE_CRYPTO_OPENSSL) && !defined(ENABLE_CRYPTO_MBEDTLS)
-#error "At least one of OpenSSL or mbed TLS needs to be defined."
+#if !defined(ENABLE_CRYPTO_OPENSSL) && !defined(ENABLE_CRYPTO_MBEDTLS) && !defined(ENABLE_CRYPTO_WOLFSSL)
+#error "At least one of OpenSSL or mbed TLS or wolfSSL needs to be defined."
 #endif
 
 struct connection_entry
diff --git a/src/openvpn/ssl_backend.h b/src/openvpn/ssl_backend.h
index 1c244ece..c9e9d757 100644
--- a/src/openvpn/ssl_backend.h
+++ b/src/openvpn/ssl_backend.h
@@ -42,6 +42,11 @@ 
 #include "ssl_verify_mbedtls.h"
 #define SSLAPI SSLAPI_MBEDTLS
 #endif
+#ifdef ENABLE_CRYPTO_WOLFSSL
+#include "ssl_wolfssl.h"
+#include "ssl_verify_wolfssl.h"
+#define SSLAPI SSLAPI_WOLFSSL
+#endif
 
 /* Ensure that SSLAPI got a sane value if SSL is disabled or unknown */
 #ifndef SSLAPI
diff --git a/src/openvpn/ssl_verify.h b/src/openvpn/ssl_verify.h
index 64f27efb..8fc07a73 100644
--- a/src/openvpn/ssl_verify.h
+++ b/src/openvpn/ssl_verify.h
@@ -40,6 +40,9 @@ 
 #ifdef ENABLE_CRYPTO_MBEDTLS
 #include "ssl_verify_mbedtls.h"
 #endif
+#ifdef ENABLE_CRYPTO_WOLFSSL
+#include "ssl_verify_wolfssl.h"
+#endif
 
 #include "ssl_verify_backend.h"
 
diff --git a/src/openvpn/ssl_verify_wolfssl.c b/src/openvpn/ssl_verify_wolfssl.c
new file mode 100644
index 00000000..2e283f8c
--- /dev/null
+++ b/src/openvpn/ssl_verify_wolfssl.c
@@ -0,0 +1,654 @@ 
+/*
+ *  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) 2002-2019 OpenVPN Inc <sales@openvpn.net>
+ *  Copyright (C) 2010-2019 Fox Crypto B.V. <openvpn@fox-it.com>
+ *
+ *  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.
+ */
+
+/**
+ * @file Control Channel Verification Module wolfSSL backend
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#include "syshead.h"
+
+#if defined(ENABLE_CRYPTO_WOLFSSL)
+
+#include "ssl_backend.h"
+#include "ssl_verify.h"
+#include "ssl_verify_backend.h"
+
+int verify_callback(int preverify_ok, WOLFSSL_X509_STORE_CTX *store)
+{
+    char buffer[WOLFSSL_MAX_ERROR_SZ];
+    struct key_state_ssl* ks_ssl = store->userCtx;
+
+    if (store->error)
+    {
+        msg(M_INFO, "In verification callback, error = %d, %s\n", store->error,
+                wolfSSL_ERR_error_string(store->error, buffer));
+        return 0;
+    }
+    else
+    {
+        if (verify_cert(ks_ssl->session, store->current_cert,
+                store->error_depth) != SUCCESS)
+        {
+            return 0;
+        }
+        return 1;
+    }
+}
+
+char *x509_get_subject(openvpn_x509_cert_t *cert, struct gc_arena *gc)
+{
+    char *subject = NULL;
+    int subject_len;
+    WOLFSSL_X509_NAME* name = wolfSSL_X509_get_subject_name(cert);
+    if (!name)
+    {
+        return NULL;
+    }
+    subject_len = wolfSSL_X509_NAME_get_sz(name);
+
+    subject = gc_malloc(subject_len, FALSE, gc);
+    check_malloc_return(subject);
+
+    return wolfSSL_X509_NAME_oneline(name, subject, subject_len);
+}
+
+struct buffer x509_get_sha1_fingerprint(openvpn_x509_cert_t *cert,
+        struct gc_arena *gc)
+{
+    unsigned int hashSz = wc_HashGetDigestSize(WC_HASH_TYPE_SHA);
+    struct buffer hash = alloc_buf_gc(hashSz, gc);
+    check_malloc_return(BPTR(&hash));
+    if (wolfSSL_X509_digest(cert, wolfSSL_EVP_sha1(), BPTR(&hash),
+            &hashSz) != SSL_SUCCESS)
+    {
+        msg(M_FATAL, "wolfSSL_X509_digest for sha1 failed.");
+    }
+    return hash;
+}
+
+struct buffer x509_get_sha256_fingerprint(openvpn_x509_cert_t *cert,
+        struct gc_arena *gc)
+{
+    unsigned int hashSz = wc_HashGetDigestSize(WC_HASH_TYPE_SHA256);
+    struct buffer hash = alloc_buf_gc(hashSz, gc);
+    check_malloc_return(BPTR(&hash));
+    if (wolfSSL_X509_digest(cert, wolfSSL_EVP_sha256(), BPTR(&hash),
+            &hashSz) != SSL_SUCCESS)
+    {
+        msg(M_FATAL, "wolfSSL_X509_digest for sha256 failed.");
+    }
+    return hash;
+}
+
+#ifdef ENABLE_X509ALTUSERNAME
+static enum Extensions_Sum x509_username_field_ext_get_nid(const char *extname)
+{
+    if (!strcmp(extname, "subjectAltName"))
+    {
+        return ALT_NAMES_OID;
+    }
+    else if (!strcmp(extname, "issuerAltName"))
+    {
+        return ISSUE_ALT_NAMES_OID;
+    }
+    else
+    {
+        return 0;
+    }
+}
+bool x509_username_field_ext_supported(const char *extname)
+{
+    return x509_username_field_ext_get_nid(extname) != 0;
+}
+#endif
+
+result_t backend_x509_get_username(char *common_name, int cn_len,
+        char *x509_username_field, openvpn_x509_cert_t *peer_cert)
+{
+    result_t res = FAILURE;
+
+    char *subject = NULL;
+#ifdef ENABLE_X509ALTUSERNAME
+    WOLFSSL_STACK *ext = NULL;
+    bool found = false;
+    if (strncmp("ext:", x509_username_field, 4) == 0)
+    {
+        int i;
+        int nid;
+        char szOid[1024];
+        WOLFSSL_ASN1_OBJECT *name;
+
+        if (!(nid = x509_username_field_ext_get_nid(x509_username_field + 4)))
+        {
+            msg(D_TLS_ERRORS,
+                    "ERROR: --x509-username-field 'ext:%s' not supported",
+                    x509_username_field + 4);
+            goto failure;
+        }
+
+        if (!(ext = wolfSSL_X509_get_ext_d2i(peer_cert, nid, NULL, NULL)))
+        {
+            goto failure;
+        }
+
+        for (i = 0; i < wolfSSL_sk_GENERAL_NAME_num(ext); i++)
+        {
+            name = wolfSSL_sk_GENERAL_NAME_value(ext, i);
+            if (name->type == ASN_RFC822_TYPE)
+            { /* Check if email type */
+                if (wolfSSL_OBJ_obj2txt(szOid, sizeof(szOid), name, 0) > 0)
+                {
+                    strncpy(common_name, szOid, cn_len);
+                    found = true;
+                    break;
+                }
+                else if (name->obj)
+                {
+                    strncpy(common_name, (char*) name->obj, cn_len);
+                    found = true;
+                    break;
+                }
+            }
+        }
+
+        if (!found)
+        {
+            goto failure;
+        }
+    }
+    else
+#endif
+    {
+        char *c;
+        char *start_pos;
+        int field_len = strlen(x509_username_field);
+        int value_len;
+        WOLFSSL_X509_NAME* name = wolfSSL_X509_get_subject_name(peer_cert);
+        if (!name)
+        {
+            goto failure;
+        }
+        subject = wolfSSL_X509_NAME_oneline(name, NULL, 0);
+
+        for (c = subject; *c != '\0'; c++)
+        {
+            if (*c == '/'
+                    && strncmp(c + 1, x509_username_field, field_len) == 0)
+            {
+                c += field_len + 1; // increment to value of field
+                start_pos = c + 1;
+                while (*(++c) != '/' && *c != '\0')
+                    ; // inc to next slash or end of string
+                value_len = MIN(c - start_pos, cn_len - 1);
+                memcpy(common_name, start_pos, value_len);
+                common_name[value_len] = '\0';
+                break;
+            }
+        }
+    }
+
+    res = SUCCESS;
+    failure: if (subject)
+    {
+        free(subject);
+    }
+#ifdef ENABLE_X509ALTUSERNAME
+    if (ext)
+    {
+        wolfSSL_sk_ASN1_OBJECT_free(ext);
+    }
+#endif
+    return res;
+}
+
+char *backend_x509_get_serial(openvpn_x509_cert_t *cert, struct gc_arena *gc)
+{
+    uint8_t buf[EXTERNAL_SERIAL_SIZE];
+    int buf_len = EXTERNAL_SERIAL_SIZE, ret, radix_size;
+    mp_int big_num;
+    struct buffer dec_string;
+
+    /*
+     * The serial number buffer is in big endian.
+     */
+    if ((ret = wolfSSL_X509_get_serial_number(cert, buf, &buf_len))
+            != WOLFSSL_SUCCESS)
+    {
+        msg(M_FATAL, "wolfSSL_X509_get_serial_number failed with Errno: %d",
+                ret);
+    }
+
+    if (mp_init(&big_num) != MP_OKAY)
+    {
+        msg(M_FATAL, "mp_init failed");
+    }
+
+    if ((ret = mp_read_unsigned_bin(&big_num, buf, buf_len)) != MP_OKAY)
+    {
+        msg(M_FATAL, "mp_read_unsigned_bin failed with Errno: %d", ret);
+    }
+
+    if ((ret = mp_radix_size(&big_num, MP_RADIX_DEC, &radix_size)) != MP_OKAY)
+    {
+        msg(M_FATAL, "mp_radix_size failed with Errno: %d", ret);
+    }
+
+    dec_string = alloc_buf_gc(radix_size, gc);
+    check_malloc_return(BPTR(&dec_string));
+
+    if ((ret = mp_todecimal(&big_num, (char*) BPTR(&dec_string))) != MP_OKAY)
+    {
+        msg(M_FATAL, "mp_todecimal failed with Errno: %d", ret);
+    }
+
+    dec_string.len = radix_size;
+
+    return (char*) BPTR(&dec_string);
+}
+
+char *backend_x509_get_serial_hex(openvpn_x509_cert_t *cert,
+        struct gc_arena *gc)
+{
+    uint8_t buf[EXTERNAL_SERIAL_SIZE];
+    int buf_len = EXTERNAL_SERIAL_SIZE, ret;
+
+    /*
+     * The serial number buffer is in big endian.
+     */
+    if ((ret = wolfSSL_X509_get_serial_number(cert, buf, &buf_len))
+            != WOLFSSL_SUCCESS)
+    {
+        msg(M_FATAL, "wolfSSL_X509_get_serial_number failed with Errno: %d",
+                ret);
+    }
+
+    return format_hex_ex(buf, buf_len, 0, 1, ":", gc);
+}
+
+void x509_setenv(struct env_set *es, int cert_depth, openvpn_x509_cert_t *cert)
+{
+    char *subject;
+    char *c;
+    char *name_start_pos;
+    int name_len;
+    char *value_start_pos;
+    int value_len;
+    char* name_buf = NULL;
+    char* value_buf = NULL;
+    char *full_name_buf = NULL;
+    int full_name_len;
+    WOLFSSL_X509_NAME* name = wolfSSL_X509_get_subject_name(cert);
+    if (!name)
+    {
+        return;
+    }
+    subject = wolfSSL_X509_NAME_oneline(name, NULL, 0);
+
+    for (c = subject; *c != '\0';)
+    {
+        ASSERT(*c == '/'); // c should point to slash on each loop
+
+        name_start_pos = c + 1;
+        while (*(++c) != '=' && *c != '\0')
+            ; // increment to equals sign
+        name_len = c - name_start_pos;
+
+        value_start_pos = c + 1;
+        while (*(++c) != '/' && *c != '\0')
+            ; // increment to next slash
+        value_len = c - value_start_pos;
+
+        /*
+         * length of buffer is: length of name + null teminator +
+         *                      6 chars from naming convention +
+         *                      5 chars for depth number (should be enough)
+         */
+        full_name_len = name_len + 1 + 6 + 5;
+
+        name_buf = realloc(name_buf, name_len + 1);
+        check_malloc_return(name_buf);
+        full_name_buf = realloc(full_name_buf, full_name_len);
+        check_malloc_return(full_name_buf);
+        value_buf = realloc(value_buf, value_len + 1);
+        check_malloc_return(value_buf);
+
+        memcpy(name_buf, name_start_pos, name_len);
+        memcpy(value_buf, value_start_pos, value_len);
+        name_buf[name_len] = '\0';
+        value_buf[value_len] = '\0';
+
+        openvpn_snprintf(full_name_buf, full_name_len, "X509_%d_%s", cert_depth,
+                name_buf);
+
+        setenv_str_incr(es, full_name_buf, value_buf);
+    }
+
+    if (name_buf)
+    {
+        free(name_buf);
+    }
+    if (full_name_buf)
+    {
+        free(full_name_buf);
+    }
+    if (value_buf)
+    {
+        free(value_buf);
+    }
+    free(subject);
+}
+
+void x509_track_add(const struct x509_track **ll_head, const char *name,
+        int msglevel, struct gc_arena *gc)
+{
+    struct x509_track *xt;
+    ALLOC_OBJ_CLEAR_GC(xt, struct x509_track, gc);
+    if (*name == '+')
+    {
+        xt->flags |= XT_FULL_CHAIN;
+        ++name;
+    }
+    xt->name = name;
+    xt->nid = wolfSSL_OBJ_txt2nid(name);
+    if (xt->nid != NID_undef)
+    {
+        xt->next = *ll_head;
+        *ll_head = xt;
+    }
+    else
+    {
+        msg(msglevel, "x509_track: no such attribute '%s'", name);
+    }
+}
+
+/* worker method for setenv_x509_track */
+static void do_setenv_x509(struct env_set *es, const char *name, char *value,
+        int depth)
+{
+    char *name_expand;
+    size_t name_expand_size;
+
+    string_mod(value, CC_ANY, CC_CRLF, '?');
+    msg(D_X509_ATTR, "X509 ATTRIBUTE name='%s' value='%s' depth=%d", name,
+            value, depth);
+    name_expand_size = 64 + strlen(name);
+    name_expand = (char *) malloc(name_expand_size);
+    check_malloc_return(name_expand);
+    openvpn_snprintf(name_expand, name_expand_size, "X509_%d_%s", depth, name);
+    setenv_str(es, name_expand, value);
+    free(name_expand);
+}
+
+void x509_setenv_track(const struct x509_track *xt, struct env_set *es,
+        const int depth, openvpn_x509_cert_t *x509)
+{
+    struct gc_arena gc = gc_new();
+    struct buffer fp_buf;
+    char *fp_str = NULL;
+    int i;
+    WOLFSSL_X509_NAME *x509_name = wolfSSL_X509_get_subject_name(x509);
+    while (xt)
+    {
+        if (depth == 0 || (xt->flags & XT_FULL_CHAIN))
+        {
+            switch (xt->nid)
+            {
+            case NID_sha1:
+            case NID_sha256:
+                if (xt->nid == NID_sha1)
+                {
+                    fp_buf = x509_get_sha1_fingerprint(x509, &gc);
+                }
+                else
+                {
+                    fp_buf = x509_get_sha256_fingerprint(x509, &gc);
+                }
+                fp_str = format_hex_ex(BPTR(&fp_buf), BLEN(&fp_buf), 0,
+                        1 | FHE_CAPS, ":", &gc);
+                do_setenv_x509(es, xt->name, fp_str, depth);
+                break;
+            default:
+                i = wolfSSL_X509_NAME_get_index_by_NID(x509_name, xt->nid, -1);
+                if (i >= 0)
+                {
+                    WOLFSSL_X509_NAME_ENTRY *ent = wolfSSL_X509_NAME_get_entry(
+                            x509_name, i);
+                    if (ent)
+                    {
+                        WOLFSSL_ASN1_STRING *val =
+                                wolfSSL_X509_NAME_ENTRY_get_data(ent);
+                        do_setenv_x509(es, xt->name, val->data, depth);
+                    }
+                }
+                else
+                {
+                    WOLFSSL_STACK *ext = wolfSSL_X509_get_ext_d2i(x509, xt->nid,
+                    NULL, NULL);
+                    if (ext)
+                    {
+                        for (i = 0; i < wolfSSL_sk_GENERAL_NAME_num(ext); i++)
+                        {
+                            WOLFSSL_ASN1_OBJECT *oid =
+                                    wolfSSL_sk_GENERAL_NAME_value(ext, i);
+                            char szOid[1024];
+
+                            if (wolfSSL_OBJ_obj2txt(szOid, sizeof(szOid), oid,
+                                    0) > 0)
+                            {
+                                do_setenv_x509(es, xt->name, szOid, depth);
+                                break;
+                            }
+                            else if (wolfSSL_OBJ_obj2txt(szOid, sizeof(szOid),
+                                    oid, 1) > 0)
+                            {
+                                do_setenv_x509(es, xt->name, szOid, depth);
+                                break;
+                            }
+                        }
+                    }
+                    wolfSSL_sk_ASN1_OBJECT_free(ext);
+                }
+            }
+        }
+        xt = xt->next;
+    }
+    gc_free(&gc);
+}
+
+result_t x509_verify_ns_cert_type(openvpn_x509_cert_t *cert, const int usage)
+{
+    if (usage == NS_CERT_CHECK_NONE)
+    {
+        return SUCCESS;
+    }
+
+    msg(M_FATAL,
+            "wolfSSL does not grant access to the Netscape Cert Type extension.");
+    return FAILURE;
+}
+
+result_t x509_verify_cert_ku(openvpn_x509_cert_t *x509,
+        const unsigned * const expected_ku, int expected_len)
+{
+    unsigned int ku = wolfSSL_X509_get_keyUsage(x509);
+
+    if (ku == 0)
+    {
+        msg(D_TLS_ERRORS, "Certificate does not have key usage extension");
+        return FAILURE;
+    }
+
+    if (expected_ku[0] == OPENVPN_KU_REQUIRED)
+    {
+        /* Extension required, value checked by TLS library */
+        return SUCCESS;
+    }
+
+    /*
+     * Fixup if no LSB bits
+     */
+    if ((ku & 0xff) == 0)
+    {
+        ku >>= 8;
+    }
+
+    msg(D_HANDSHAKE, "Validating certificate key usage");
+    result_t found = FAILURE;
+    for (size_t i = 0; i < expected_len; i++)
+    {
+        if (expected_ku[i] != 0 && (ku & expected_ku[i]) == expected_ku[i])
+        {
+            found = SUCCESS;
+            break;
+        }
+    }
+
+    if (found != SUCCESS)
+    {
+        msg(D_TLS_ERRORS,
+                "ERROR: Certificate has key usage %04x, expected one of:", ku);
+        for (size_t i = 0; i < expected_len && expected_ku[i]; i++)
+        {
+            msg(D_TLS_ERRORS, " * %04x", expected_ku[i]);
+        }
+    }
+
+    return found;
+}
+
+static const char* oid_translate_num_to_str(const char* oid)
+{
+    static const struct oid_dict
+    {
+        char* num;
+        char* desc;
+    } oid_dict[] =
+    {
+    { "2.5.29.37.0", "Any Extended Key Usage" },
+    { "1.3.6.1.5.5.7.3.1", "TLS Web Server Authentication" },
+    { "1.3.6.1.5.5.7.3.2", "TLS Web Client Authentication" },
+    { "1.3.6.1.5.5.7.3.3", "Code Signing" },
+    { "1.3.6.1.5.5.7.3.4", "E-mail Protection" },
+    { "1.3.6.1.5.5.7.3.8", "Time Stamping" },
+    { "1.3.6.1.5.5.7.3.9", "OCSP Signing" },
+    { NULL, NULL } };
+    const struct oid_dict* idx;
+    for (idx = oid_dict; idx->num != NULL; idx++)
+    {
+        if (!strcmp(oid, idx->num))
+        {
+            return idx->desc;
+        }
+    }
+    return NULL;
+}
+
+result_t x509_verify_cert_eku(openvpn_x509_cert_t *x509,
+        const char * const expected_oid)
+{
+    WOLFSSL_STACK *eku = NULL;
+    result_t found = FAILURE;
+    const char* desc;
+
+    if ((eku = (WOLFSSL_STACK *) wolfSSL_X509_get_ext_d2i(x509,
+            EXT_KEY_USAGE_OID,
+            NULL, NULL)) == NULL)
+    {
+        msg(D_HANDSHAKE,
+                "Certificate does not have extended key usage extension");
+    }
+    else
+    {
+        int i;
+        msg(D_HANDSHAKE, "Validating certificate extended key usage");
+        for (i = 0; i < wolfSSL_sk_GENERAL_NAME_num(eku); i++)
+        {
+            WOLFSSL_ASN1_OBJECT *oid = wolfSSL_sk_GENERAL_NAME_value(eku, i);
+            char szOid[1024];
+
+            if (wolfSSL_OBJ_obj2txt(szOid, sizeof(szOid), oid, 0) > 0)
+            {
+                msg(D_HANDSHAKE, "++ Certificate has EKU (str) %s, expects %s",
+                        szOid, expected_oid);
+                if (!strcmp(expected_oid, szOid))
+                {
+                    found = SUCCESS;
+                    break;
+                }
+            }
+            if (wolfSSL_OBJ_obj2txt(szOid, sizeof(szOid), oid, 1) > 0)
+            {
+                msg(D_HANDSHAKE, "++ Certificate has EKU (oid) %s, expects %s",
+                        szOid, expected_oid);
+                if (!strcmp(expected_oid, szOid))
+                {
+                    found = SUCCESS;
+                    break;
+                }
+                if ((desc = oid_translate_num_to_str(szOid)) != NULL)
+                {
+                    msg(D_HANDSHAKE, "++ oid %s translated to %s", szOid, desc);
+                    if (!strcmp(expected_oid, desc))
+                    {
+                        found = SUCCESS;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    if (eku != NULL)
+    {
+        wolfSSL_sk_GENERAL_NAME_pop_free(eku, NULL);
+    }
+
+    return found;
+}
+
+result_t x509_write_pem(FILE *peercert_file, openvpn_x509_cert_t *peercert)
+{
+    if (wolfSSL_PEM_write_X509(peercert_file, peercert) < 0)
+    {
+        msg(M_NONFATAL, "Failed to write peer certificate in PEM format");
+        return FAILURE;
+    }
+    return SUCCESS;
+}
+
+bool tls_verify_crl_missing(const struct tls_options *opt)
+{
+    /*
+     * This is checked at load time.
+     */
+    return false;
+}
+
+#endif /* ENABLE_CRYPTO_WOLFSSL */
diff --git a/src/openvpn/ssl_verify_wolfssl.h b/src/openvpn/ssl_verify_wolfssl.h
new file mode 100644
index 00000000..9de4061f
--- /dev/null
+++ b/src/openvpn/ssl_verify_wolfssl.h
@@ -0,0 +1,76 @@ 
+/*
+ *  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) 2002-2019 OpenVPN Inc <sales@openvpn.net>
+ *  Copyright (C) 2010-2019 Fox Crypto B.V. <openvpn@fox-it.com>
+ *
+ *  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.
+ */
+
+/**
+ * @file Control Channel Verification Module wolfSSL backend
+ */
+#ifndef SRC_OPENVPN_SSL_VERIFY_WOLFSSL_H_
+#define SRC_OPENVPN_SSL_VERIFY_WOLFSSL_H_
+
+#include <wolfssl/options.h>
+#include <wolfssl/wolfcrypt/types.h>
+#include <wolfssl/ssl.h>
+#include <wolfssl/wolfcrypt/integer.h>
+#include <wolfssl/wolfcrypt/asn_public.h>
+#include "buffer.h"
+
+#ifndef __OPENVPN_X509_CERT_T_DECLARED
+#define __OPENVPN_X509_CERT_T_DECLARED
+typedef WOLFSSL_X509 openvpn_x509_cert_t;
+#endif
+
+#define NID_sha256 672
+#define NID_sha1   64
+
+/**
+ * Verify that the remote OpenVPN peer's certificate allows setting up a
+ * VPN tunnel.
+ * @ingroup control_tls
+ *
+ * This callback function is called every time a new TLS session is being
+ * setup to determine whether the remote OpenVPN peer's certificate is
+ * allowed to connect. It is called for once for every certificate in the chain.
+ * The callback functionality is configured in the \c init_ssl() function, which
+ * calls the OpenSSL library's \c SSL_CTX_set_verify() function with \c
+ * verify_callback() as its callback argument.
+ *
+ * It checks preverify_ok, and registers the certificate hash. If these steps
+ * succeed, it calls the \c verify_cert() function, which performs
+ * OpenVPN-specific verification.
+ *
+ * @param preverify_ok - Whether the remote OpenVPN peer's certificate
+ *                       past verification.  A value of 1 means it
+ *                       verified successfully, 0 means it failed.
+ * @param ctx          - The complete context used by the OpenSSL library
+ *                       to verify the certificate chain.
+ *
+ * @return The return value indicates whether the supplied certificate is
+ *     allowed to set up a VPN tunnel.  The following values can be
+ *     returned:
+ *      - \c 0: failure, this certificate is not allowed to connect.
+ *      - \c 1: success, this certificate is allowed to connect.
+ */
+int verify_callback(int preverify_ok, WOLFSSL_X509_STORE_CTX *ctx);
+
+#endif /* SRC_OPENVPN_SSL_VERIFY_WOLFSSL_H_ */
diff --git a/src/openvpn/ssl_wolfssl.c b/src/openvpn/ssl_wolfssl.c
new file mode 100644
index 00000000..271294cf
--- /dev/null
+++ b/src/openvpn/ssl_wolfssl.c
@@ -0,0 +1,1194 @@ 
+/*
+ *  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) 2002-2019 OpenVPN Inc <sales@openvpn.net>
+ *  Copyright (C) 2010-2019 Fox Crypto B.V. <openvpn@fox-it.com>
+ *
+ *  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.
+ */
+
+/**
+ * @file Control Channel wolfSSL Backend
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#include "syshead.h"
+
+#if defined(ENABLE_CRYPTO_WOLFSSL)
+
+#include "errlevel.h"
+#include "buffer.h"
+#include "misc.h"
+#include "manage.h"
+#include "memdbg.h"
+#include "ssl_backend.h"
+#include "ssl_common.h"
+#include "ssl_verify_wolfssl.h"
+#include "base64.h"
+
+/*
+ *
+ * Functions used in ssl.c which must be implemented by the backend SSL library
+ *
+ */
+
+void tls_init_lib(void)
+{
+    int ret;
+    if ((ret = wolfSSL_Init()) != SSL_SUCCESS)
+    {
+        msg(M_FATAL, "wolfSSL_Init failed with Errno: %d", ret);
+    }
+}
+
+void tls_free_lib(void)
+{
+    int ret;
+    if ((ret = wolfSSL_Cleanup()) != SSL_SUCCESS)
+    {
+        msg(M_FATAL, "wolfSSL_Cleanup failed with Errno: %d", ret);
+    }
+}
+
+void tls_clear_error(void)
+{
+    wolfSSL_ERR_clear_error();
+}
+
+int tls_version_max(void)
+{
+#ifdef WOLFSSL_TLS13
+    return TLS_VER_1_3;
+#endif
+    return TLS_VER_1_2;
+}
+
+void tls_ctx_server_new(struct tls_root_ctx *ctx)
+{
+    ASSERT(NULL != ctx);
+
+    ctx->ctx = wolfSSL_CTX_new(wolfSSLv23_server_method());
+    check_malloc_return(ctx->ctx);
+}
+
+void tls_ctx_client_new(struct tls_root_ctx *ctx)
+{
+    ASSERT(NULL != ctx);
+
+    ctx->ctx = wolfSSL_CTX_new(wolfSSLv23_client_method());
+    check_malloc_return(ctx->ctx);
+}
+
+void tls_ctx_free(struct tls_root_ctx *ctx)
+{
+    ASSERT(NULL != ctx);
+    if (NULL != ctx->ctx)
+    {
+        wolfSSL_CTX_free(ctx->ctx);
+        ctx->ctx = NULL;
+    }
+}
+
+bool tls_ctx_initialised(struct tls_root_ctx *ctx)
+{
+    ASSERT(NULL != ctx);
+    return NULL != ctx->ctx;
+}
+
+static void info_callback(const WOLFSSL* ssl, int type, int val)
+{
+    if (type & SSL_CB_LOOP)
+    {
+        dmsg(D_HANDSHAKE_VERBOSE, "SSL state (%s): %s",
+                type & SSL_ST_CONNECT ? "connect" :
+                type & SSL_ST_ACCEPT ? "accept" : "undefined",
+                wolfSSL_state_string_long(ssl));
+    }
+    else if (type & SSL_CB_ALERT)
+    {
+        dmsg(D_HANDSHAKE_VERBOSE, "SSL alert (%s): %s",
+                type & SSL_CB_READ ? "read" : "write",
+                wolfSSL_alert_type_string_long(val));
+    }
+}
+
+bool tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags)
+{
+    int ret = SSL_SUCCESS;
+    int verify_flags = WOLFSSL_VERIFY_PEER
+            | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+    ASSERT(NULL != ctx);
+
+    switch ((ssl_flags >> SSLF_TLS_VERSION_MIN_SHIFT)
+            & SSLF_TLS_VERSION_MIN_MASK)
+    {
+    case TLS_VER_1_3:
+        ret = wolfSSL_CTX_SetMinVersion(ctx->ctx, WOLFSSL_TLSV1_3);
+        break;
+    case TLS_VER_1_2:
+        ret = wolfSSL_CTX_SetMinVersion(ctx->ctx, WOLFSSL_TLSV1_2);
+        break;
+    case TLS_VER_1_1:
+        ret = wolfSSL_CTX_SetMinVersion(ctx->ctx, WOLFSSL_TLSV1_1);
+        break;
+    case TLS_VER_1_0:
+        ret = wolfSSL_CTX_SetMinVersion(ctx->ctx, WOLFSSL_TLSV1);
+        break;
+    case TLS_VER_UNSPEC:
+        break;
+    default:
+        msg(M_FATAL, "Unidentified minimum TLS version");
+    }
+
+    if (ret != SSL_SUCCESS)
+    {
+        msg(M_FATAL, "wolfSSL_CTX_SetMinVersion failed");
+    }
+
+    switch ((ssl_flags >> SSLF_TLS_VERSION_MAX_SHIFT)
+            & SSLF_TLS_VERSION_MAX_MASK)
+    {
+    case TLS_VER_1_0:
+        wolfSSL_CTX_set_options(ctx->ctx, SSL_OP_NO_TLSv1_1);
+        /* no break */
+    case TLS_VER_1_1:
+        wolfSSL_CTX_set_options(ctx->ctx, SSL_OP_NO_TLSv1_2);
+        /* no break */
+    case TLS_VER_1_2:
+        wolfSSL_CTX_set_options(ctx->ctx, SSL_OP_NO_TLSv1_3);
+        /* no break */
+    case TLS_VER_1_3:
+    case TLS_VER_UNSPEC:
+        break;
+    default:
+        msg(M_FATAL, "Unidentified maximum TLS version");
+    }
+
+    wolfSSL_CTX_set_session_cache_mode(ctx->ctx, WOLFSSL_SESS_CACHE_OFF);
+    wolfSSL_CTX_set_default_passwd_cb(ctx->ctx, pem_password_callback);
+    wolfSSL_CTX_set_info_callback(ctx->ctx, info_callback);
+
+    /* Require peer certificate verification */
+#if P2MP_SERVER
+    if (ssl_flags & SSLF_CLIENT_CERT_NOT_REQUIRED)
+    {
+        verify_flags = WOLFSSL_VERIFY_NONE;
+    }
+    else if (ssl_flags & SSLF_CLIENT_CERT_OPTIONAL)
+    {
+        verify_flags = WOLFSSL_VERIFY_PEER;
+    }
+#endif
+
+    wolfSSL_CTX_set_verify(ctx->ctx, verify_flags, &verify_callback);
+
+    return true;
+}
+
+void tls_ctx_restrict_ciphers(struct tls_root_ctx *ctx, const char *ciphers)
+{
+    if (ciphers == NULL)
+    {
+        return;
+    }
+
+    if (wolfSSL_CTX_set_cipher_list(ctx->ctx, ciphers) != SSL_SUCCESS)
+    {
+        msg(M_FATAL, "Failed to set ciphers: %s", ciphers);
+    }
+}
+
+void tls_ctx_restrict_ciphers_tls13(struct tls_root_ctx *ctx,
+        const char *ciphers)
+{
+    if (ciphers == NULL)
+    {
+        return;
+    }
+
+    if (wolfSSL_CTX_set_cipher_list(ctx->ctx, ciphers) != SSL_SUCCESS)
+    {
+        msg(M_FATAL, "Failed to set ciphers: %s", ciphers);
+    }
+}
+
+void tls_ctx_set_cert_profile(struct tls_root_ctx *ctx, const char *profile)
+{
+    if (profile)
+    {
+        msg(M_WARN, "WARNING: wolfSSL does not support --tls-cert-profile"
+                ", ignoring user-set profile: '%s'", profile);
+    }
+}
+
+void tls_ctx_check_cert_time(const struct tls_root_ctx *ctx)
+{
+    /*
+     * This is verified during loading of certificate.
+     */
+}
+
+void tls_ctx_load_dh_params(struct tls_root_ctx *ctx, const char *dh_file,
+        const char *dh_file_inline)
+{
+    int dh_len, ret;
+
+    ASSERT(ctx != NULL);
+
+    if (!strcmp(dh_file, INLINE_FILE_TAG) && dh_file_inline)
+    {
+        /* Parameters in memory */
+        if ((dh_len = strlen(dh_file_inline)) == 0)
+        {
+            msg(M_FATAL, "Empty DH parameters passed.");
+        }
+
+        if ((ret = wolfSSL_CTX_SetTmpDH_buffer(ctx->ctx,
+                (uint8_t*) dh_file_inline, dh_len,
+                SSL_FILETYPE_PEM)) != SSL_SUCCESS)
+        {
+            msg(M_FATAL, "wolfSSL_CTX_SetTmpDH_buffer failed with Errno: %d",
+                    ret);
+        }
+    }
+    else
+    {
+        /* Parameters in file */
+        if ((ret = wolfSSL_CTX_SetTmpDH_file(ctx->ctx, dh_file,
+        SSL_FILETYPE_PEM)) != SSL_SUCCESS)
+        {
+            msg(M_FATAL, "wolfSSL_CTX_SetTmpDH_file failed with Errno: %d",
+                    ret);
+        }
+    }
+}
+
+void tls_ctx_load_ecdh_params(struct tls_root_ctx *ctx, const char *curve_name)
+{
+    int nid;
+    WOLFSSL_EC_KEY* ecdh;
+
+    if (curve_name == NULL)
+    {
+        return;
+    }
+
+    msg(D_TLS_DEBUG, "Using user specified ECDH curve (%s)", curve_name);
+
+    if ((nid = wc_ecc_get_curve_id_from_name(curve_name)) < 0)
+    {
+        msg(M_FATAL, "Unknown curve name: %s", curve_name);
+    }
+
+    if (!(ecdh = wolfSSL_EC_KEY_new_by_curve_name(nid)))
+    {
+        msg(M_FATAL, "wolfSSL_EC_KEY_new_by_curve_name failed");
+    }
+
+    if (wolfSSL_SSL_CTX_set_tmp_ecdh(ctx->ctx, ecdh) != WOLFSSL_SUCCESS)
+    {
+        wolfSSL_EC_KEY_free(ecdh);
+        msg(M_FATAL, "wolfSSL_SSL_CTX_set_tmp_ecdh failed");
+    }
+
+    wolfSSL_EC_KEY_free(ecdh);
+}
+
+int tls_ctx_load_pkcs12(struct tls_root_ctx *ctx, const char *pkcs12_file,
+        const char *pkcs12_file_inline, bool load_ca_file)
+{
+    int err, i, ret = 1;
+    uint32_t pkcs12_len;
+    struct gc_arena gc = gc_new();
+    struct buffer buf;
+    WC_PKCS12* pkcs12 = wc_PKCS12_new();
+    WOLFSSL_EVP_PKEY* pkey;
+    WOLFSSL_X509* cert;
+    const uint8_t* cert_der;
+    int cert_der_len;
+    WOLFSSL_X509_STORE* store;
+    WOLF_STACK_OF(WOLFSSL_X509)* ca;
+    char password[256];
+
+    ASSERT(ctx != NULL);
+
+    if (!strcmp(pkcs12_file, INLINE_FILE_TAG) && pkcs12_file_inline)
+    {
+        /* PKCS12 in memory */
+        if ((pkcs12_len = strlen(pkcs12_file_inline)) == 0)
+        {
+            msg(M_FATAL, "Empty pkcs12 parameters passed.");
+        }
+
+        /* DER length will be less than PEM length */
+        buf = alloc_buf_gc(pkcs12_len, &gc);
+        if (!buf_valid(&buf))
+        {
+            msg(M_FATAL, "Error allocating %d bytes for pkcs12 buffer",
+                    pkcs12_len);
+        }
+
+        if ((err = Base64_Decode((uint8_t*) pkcs12_file_inline, pkcs12_len,
+                BPTR(&buf), &pkcs12_len)) != 0)
+        {
+            msg(M_FATAL, "Base64_Decode failed with Errno: %d", err);
+        }
+        buf.len = pkcs12_len;
+    }
+    else
+    {
+        /* PKCS12 in file */
+        buf = buffer_read_from_file(pkcs12_file, &gc);
+        if (!buf_valid(&buf))
+        {
+            msg(M_FATAL, "Read error on pkcs12 file ('%s')", pkcs12_file);
+        }
+    }
+
+    if ((err = wc_d2i_PKCS12(BPTR(&buf), BLEN(&buf), pkcs12)) != 0)
+    {
+        msg(M_FATAL, "wc_d2i_PKCS12 failed. Errno: %d", err);
+    }
+
+    if (wolfSSL_PKCS12_parse(pkcs12, "", &pkey, &cert, &ca) != WOLFSSL_SUCCESS)
+    {
+        pem_password_callback(password, sizeof(password) - 1, 0, NULL);
+        if (wolfSSL_PKCS12_parse(pkcs12, password, &pkey, &cert, &ca)
+                != WOLFSSL_SUCCESS)
+        {
+            msg(M_INFO,
+                    "wolfSSL_PKCS12_parse failed. wolfSSL only supports PKCS "
+                            "data encrypted using SHA1 with 128 bit RC4 and SHA1 with "
+                            "DES3-CBC. Please check that the certificate is using these "
+                            "encryption algorithms. When compiling a certificate with "
+                            "OpenSSL use the -descert option to use the appropriate "
+                            "algorithm.");
+            goto cleanup;
+        }
+    }
+
+    if (pkey)
+    {
+        if (wolfSSL_CTX_use_PrivateKey(ctx->ctx, pkey) != WOLFSSL_SUCCESS)
+        {
+            msg(M_FATAL, "wolfSSL_CTX_use_PrivateKey failed.");
+        }
+    }
+
+    if (cert)
+    {
+        if (!(cert_der = wolfSSL_X509_get_der(cert, &cert_der_len)))
+        {
+            msg(M_FATAL, "wolfSSL_X509_get_der failed.");
+        }
+        if ((err = wolfSSL_CTX_use_certificate_buffer(ctx->ctx, cert_der,
+                cert_der_len, WOLFSSL_FILETYPE_ASN1)) != SSL_SUCCESS)
+        {
+            msg(M_FATAL, "wolfSSL_CTX_use_certificate_buffer failed. Errno: %d",
+                    err);
+        }
+    }
+
+    ASSERT(store = wolfSSL_CTX_get_cert_store(ctx->ctx));
+
+    for (i = 0; i < wolfSSL_sk_GENERAL_NAME_num(ca); i++)
+    {
+        WOLFSSL_X509* x509 = wolfSSL_sk_X509_value(ca, i);
+        if (wolfSSL_X509_STORE_add_cert(store, x509) != WOLFSSL_SUCCESS)
+        {
+            msg(M_FATAL, "wolfSSL_X509_STORE_add_cert failed.");
+        }
+    }
+
+    ret = 0;
+
+    cleanup: if (pkey)
+    {
+        wolfSSL_EVP_PKEY_free(pkey);
+    }
+    if (cert)
+    {
+        wolfSSL_X509_free(cert);
+    }
+    if (ca)
+    {
+        wolfSSL_sk_X509_free(ca);
+    }
+    if (pkcs12)
+    {
+        wc_PKCS12_free(pkcs12);
+    }
+    gc_free(&gc);
+
+    return ret;
+}
+
+#ifdef ENABLE_CRYPTOAPI
+void
+tls_ctx_load_cryptoapi(struct tls_root_ctx *ctx, const char *cryptoapi_cert)
+{
+    msg(M_FATAL, "Windows CryptoAPI is not yet supported for wolfSSL.");
+}
+#endif /* ENABLE_CRYPTOAPI */
+
+void tls_ctx_load_cert_file(struct tls_root_ctx *ctx, const char *cert_file,
+        const char *cert_file_inline)
+{
+    int ret;
+    int cert_len;
+    ASSERT(ctx != NULL);
+
+    if (!strcmp(cert_file, INLINE_FILE_TAG) && cert_file_inline)
+    {
+        /* Certificate in memory */
+        if ((cert_len = strlen(cert_file_inline)) == 0)
+        {
+            msg(M_FATAL, "Empty certificate passed.");
+            return;
+        }
+        /*
+         * Load certificate.
+         */
+        if ((ret = wolfSSL_CTX_use_certificate_chain_buffer(ctx->ctx,
+                (uint8_t*) cert_file_inline, cert_len)) != SSL_SUCCESS)
+        {
+            msg(M_FATAL,
+                    "wolfSSL_CTX_use_certificate_buffer failed with Errno: %d",
+                    ret);
+            return;
+        }
+        /*
+         * Load any additional certificates.
+         */
+        if ((ret = wolfSSL_CTX_load_verify_buffer(ctx->ctx,
+                (uint8_t*) cert_file_inline, cert_len,
+                SSL_FILETYPE_PEM)) != SSL_SUCCESS)
+        {
+            msg(M_FATAL, "wolfSSL_CTX_load_verify_buffer failed with Errno: %d",
+                    ret);
+            return;
+        }
+    }
+    else
+    {
+        /* Certificate in file */
+        /*
+         * Load certificate.
+         */
+        if ((ret = wolfSSL_CTX_use_certificate_chain_file(ctx->ctx, cert_file))
+                != SSL_SUCCESS)
+        {
+            msg(M_FATAL,
+                    "wolfSSL_CTX_use_certificate_chain_file failed with Errno: %d",
+                    ret);
+            return;
+        }
+        /*
+         * Load any additional certificates.
+         */
+        if ((ret = wolfSSL_CTX_load_verify_locations(ctx->ctx, cert_file, NULL))
+                != SSL_SUCCESS)
+        {
+            msg(M_FATAL,
+                    "wolfSSL_CTX_load_verify_locations failed with Errno: %d",
+                    ret);
+            return;
+        }
+    }
+}
+
+int tls_ctx_load_priv_file(struct tls_root_ctx *ctx, const char *priv_key_file,
+        const char *priv_key_file_inline)
+{
+
+    int ret;
+    int key_len;
+    ASSERT(ctx != NULL);
+
+    if (!strcmp(priv_key_file, INLINE_FILE_TAG) && priv_key_file_inline)
+    {
+        /* Key in memory */
+        if ((key_len = strlen(priv_key_file_inline)) == 0)
+        {
+            msg(M_FATAL, "Empty certificate passed.");
+            return 1;
+        }
+        if ((ret = wolfSSL_CTX_use_PrivateKey_buffer(ctx->ctx,
+                (uint8_t*) priv_key_file_inline, key_len,
+                SSL_FILETYPE_PEM)) != SSL_SUCCESS)
+        {
+            msg(M_FATAL,
+                    "wolfSSL_CTX_use_PrivateKey_buffer failed with Errno: %d",
+                    ret);
+            return 1;
+        }
+    }
+    else
+    {
+        /* Key in file */
+        if ((ret = wolfSSL_CTX_use_PrivateKey_file(ctx->ctx, priv_key_file,
+        SSL_FILETYPE_PEM)) != SSL_SUCCESS)
+        {
+            msg(M_FATAL,
+                    "wolfSSL_CTX_use_PrivateKey_file failed with Errno: %d",
+                    ret);
+            return 1;
+        }
+    }
+    return 0;
+}
+
+#ifdef ENABLE_MANAGEMENT
+int tls_ctx_use_management_external_key(struct tls_root_ctx *ctx)
+{
+    msg(M_INFO, "%s: key already loaded", __func__);
+    return 1;
+}
+#endif /* ENABLE_MANAGEMENT */
+
+void tls_ctx_load_ca(struct tls_root_ctx *ctx, const char *ca_file,
+        const char *ca_file_inline, const char *ca_path, bool tls_server)
+{
+    int ca_len, ret;
+
+    ASSERT(ctx != NULL);
+
+    if (!strcmp(ca_file, INLINE_FILE_TAG) && ca_file_inline)
+    {
+        /* Certificate in memory */
+        if ((ca_len = strlen(ca_file_inline)) == 0)
+        {
+            msg(M_FATAL, "Empty certificate passed.");
+        }
+
+        if ((ret = wolfSSL_CTX_load_verify_buffer(ctx->ctx,
+                (uint8_t*) ca_file_inline, ca_len,
+                SSL_FILETYPE_PEM)) != SSL_SUCCESS)
+        {
+            msg(M_FATAL, "wolfSSL_CTX_load_verify_buffer failed with Errno: %d",
+                    ret);
+        }
+        if (ca_path)
+        {
+            if ((ret = wolfSSL_CTX_load_verify_locations(ctx->ctx, NULL,
+                    ca_path)) != SSL_SUCCESS)
+            {
+                msg(M_FATAL,
+                        "wolfSSL_CTX_load_verify_locations failed with Errno: %d",
+                        ret);
+            }
+        }
+    }
+    else
+    {
+        /* Certificate in file */
+        if ((ret = wolfSSL_CTX_load_verify_locations(ctx->ctx, ca_file, ca_path))
+                != SSL_SUCCESS)
+        {
+            msg(M_FATAL,
+                    "wolfSSL_CTX_load_verify_locations failed with Errno: %d",
+                    ret);
+        }
+    }
+}
+
+void tls_ctx_load_extra_certs(struct tls_root_ctx *ctx,
+        const char *extra_certs_file, const char *extra_certs_file_inline)
+{
+    tls_ctx_load_ca(ctx, extra_certs_file, extra_certs_file_inline, NULL,
+    false);
+}
+
+void backend_tls_ctx_reload_crl(struct tls_root_ctx *ssl_ctx,
+        const char *crl_file, const char *crl_inline)
+{
+    int ret, len;
+    if (!strcmp(crl_file, INLINE_FILE_TAG) && crl_inline)
+    {
+        /* CRL in memory */
+        if ((len = strlen(crl_inline)) == 0)
+        {
+            msg(M_FATAL, "Empty CRL passed.");
+        }
+        if ((ret = wolfSSL_CTX_LoadCRLBuffer(ssl_ctx->ctx,
+                (unsigned char*) crl_inline, len, SSL_FILETYPE_PEM))
+                != SSL_SUCCESS)
+        {
+            msg(M_FATAL, "wolfSSL_CTX_LoadCRLBuffer failed with Errno: %d",
+                    ret);
+        }
+    }
+    else
+    {
+        /* CRL in file */
+        if ((ret = wolfSSL_CTX_LoadCRL(ssl_ctx->ctx, crl_file, SSL_FILETYPE_PEM,
+                0)) != SSL_SUCCESS)
+        {
+            msg(M_FATAL, "wolfSSL_CTX_LoadCRL failed with Errno: %d", ret);
+        }
+    }
+}
+
+/* **************************************
+ *
+ * Key-state specific functions
+ *
+ * **************************************/
+
+/*
+ * SSL is handled by library (wolfSSL in this case) but data is dumped
+ * to buffers instead of being sent directly through TCP sockets. OpenVPN
+ * itself handles sending and receiving data.
+ */
+
+static int ssl_buff_read(WOLFSSL *ssl, char *buf, int sz, void *ctx)
+{
+    struct list_buffer_t* ssl_buf = (struct list_buffer_t*) ctx;
+    struct bucket_t* b;
+    uint32_t l, ret = 0, len = sz;
+
+    if (!ssl_buf->first || !ssl_buf->len)
+    {
+        return WOLFSSL_CBIO_ERR_WANT_READ;
+    }
+
+    while (len && ssl_buf->len)
+    {
+        l = MIN(len, ssl_buf->first->len);
+        memcpy(buf, ssl_buf->first->buf + ssl_buf->first->offset, l);
+        ssl_buf->first->offset += l;
+        ssl_buf->first->len -= l;
+        ssl_buf->len -= l;
+        len -= l;
+        buf += l;
+        ret += l;
+        if (!ssl_buf->first->len)
+        {
+            /* Bucket is wholly read */
+            if (ssl_buf->first != ssl_buf->last)
+            {
+                /* Free bucket and go to next one */
+                b = ssl_buf->first;
+                ssl_buf->first = ssl_buf->first->next;
+                free(b);
+            }
+            else
+            {
+                /*
+                 * Reset and keep one bucket so that we don't have to
+                 * malloc buckets for many small messages.
+                 */
+                ssl_buf->first->len = 0;
+                ssl_buf->first->offset = 0;
+            }
+        }
+    }
+
+    return ret;
+}
+
+static void allocate_new_bucket(struct bucket_t** last,
+        struct bucket_t** second_last, uint32_t* len, char** buf)
+{
+    uint32_t l;
+
+    *last = *second_last = (struct bucket_t*) malloc(sizeof(struct bucket_t));
+    check_malloc_return(*last);
+
+    l = MIN(*len, BUCKET_BUF_LEN);
+    (*last)->len = l;
+    (*last)->offset = 0;
+    (*last)->next = NULL;
+    memcpy((*last)->buf, *buf, l);
+
+    *len -= l;
+    *buf += l;
+}
+
+static int ssl_buff_write(WOLFSSL *ssl, char *buf, int sz, void *ctx)
+{
+    struct list_buffer_t* ssl_buf = (struct list_buffer_t*) ctx;
+    uint32_t l, len = sz;
+
+    if (len == 0)
+    {
+        return 0;
+    }
+
+    ssl_buf->len += len;
+
+    if (!ssl_buf->first)
+    {
+        /* First time ssl_buff_write so we need to allocate the first bucket. */
+
+        allocate_new_bucket(&ssl_buf->last, &ssl_buf->first, &len, &buf);
+    }
+
+    while (len)
+    {
+        l = MIN(len,
+                BUCKET_BUF_LEN - (ssl_buf->last->offset + ssl_buf->last->len));
+        if (l)
+        {
+            /* If there is any room in the last bucket then copy */
+            memcpy(
+                    ssl_buf->last->buf + ssl_buf->last->offset
+                            + ssl_buf->last->len, buf, l);
+            len -= l;
+            buf += l;
+            ssl_buf->last->len += l;
+        }
+
+        if (!len)
+        {
+            /* No more data to write */
+            break;
+        }
+
+        allocate_new_bucket(&ssl_buf->last, &ssl_buf->last->next, &len, &buf);
+    }
+
+    return sz;
+}
+
+void key_state_ssl_init(struct key_state_ssl *ks_ssl,
+        const struct tls_root_ctx *ssl_ctx, bool is_server,
+        struct tls_session *session)
+{
+    int err;
+
+    ASSERT(ssl_ctx != NULL);
+
+    if ((ks_ssl->ssl = wolfSSL_new(ssl_ctx->ctx)) == NULL)
+    {
+        msg(M_FATAL, "wolfSSL_new failed");
+    }
+
+    if ((ks_ssl->send_buf = (struct list_buffer_t*) calloc(
+            sizeof(struct list_buffer_t), 1)) == NULL)
+    {
+        wolfSSL_free(ks_ssl->ssl);
+        msg(M_FATAL, "Failed to allocate memory for send buffer.");
+    }
+
+    if ((ks_ssl->recv_buf = (struct list_buffer_t*) calloc(
+            sizeof(struct list_buffer_t), 1)) == NULL)
+    {
+        free(ks_ssl->send_buf);
+        wolfSSL_free(ks_ssl->ssl);
+        msg(M_FATAL, "Failed to allocate memory for receive buffer.");
+    }
+
+    /* Register functions handling queueing of data in buffers */
+    wolfSSL_SSLSetIORecv(ks_ssl->ssl, &ssl_buff_read);
+    wolfSSL_SSLSetIOSend(ks_ssl->ssl, &ssl_buff_write);
+
+    /* Register pointers to appropriate buffers */
+    wolfSSL_SetIOWriteCtx(ks_ssl->ssl, ks_ssl->send_buf);
+    wolfSSL_SetIOReadCtx(ks_ssl->ssl, ks_ssl->recv_buf);
+
+    if (is_server)
+    {
+        if ((err = wolfSSL_accept(ks_ssl->ssl)) != SSL_SUCCESS)
+        {
+            err = wolfSSL_get_error(ks_ssl->ssl, err);
+            switch (err)
+            {
+            case WOLFSSL_ERROR_WANT_WRITE:
+            case WOLFSSL_ERROR_WANT_READ:
+                break;
+            default:
+                msg(M_FATAL, "wolfSSL_accept failed");
+            }
+        }
+    }
+    else
+    {
+        if ((err = wolfSSL_connect(ks_ssl->ssl)) != SSL_SUCCESS)
+        {
+            err = wolfSSL_get_error(ks_ssl->ssl, err);
+            switch (err)
+            {
+            case WOLFSSL_ERROR_WANT_WRITE:
+            case WOLFSSL_ERROR_WANT_READ:
+                break;
+            default:
+                msg(M_FATAL, "wolfSSL_connect failed");
+            }
+        }
+    }
+
+    ks_ssl->session = session;
+    wolfSSL_SetCertCbCtx(ks_ssl->ssl, ks_ssl);
+}
+
+void key_state_ssl_free(struct key_state_ssl *ks_ssl)
+{
+    struct bucket_t* b;
+    struct bucket_t* c;
+    wolfSSL_free(ks_ssl->ssl);
+    if (ks_ssl->recv_buf)
+    {
+        b = ks_ssl->recv_buf->first;
+        while (b)
+        {
+            c = b->next;
+            free(b);
+            b = c;
+        }
+        free(ks_ssl->recv_buf);
+    }
+    if (ks_ssl->send_buf)
+    {
+        b = ks_ssl->send_buf->first;
+        while (b)
+        {
+            c = b->next;
+            free(b);
+            b = c;
+        }
+        free(ks_ssl->send_buf);
+    }
+    ks_ssl->ssl = NULL;
+    ks_ssl->recv_buf = NULL;
+    ks_ssl->send_buf = NULL;
+    ks_ssl->session = NULL;
+}
+
+void key_state_export_keying_material(struct key_state_ssl *ks_ssl,
+        struct tls_session *session)
+{
+    msg(M_FATAL, "%s not supported by wolfSSL", __func__);
+}
+
+int key_state_write_plaintext(struct key_state_ssl *ks_ssl, struct buffer *buf)
+{
+    int ret = 1;
+    perf_push(PERF_BIO_WRITE_PLAINTEXT);
+
+    ASSERT(ks_ssl != NULL);
+
+    switch (key_state_write_plaintext_const(ks_ssl, BPTR(buf), BLEN(buf)))
+    {
+    case 1:
+        ret = 1;
+        memset(BPTR(buf), 0, BLEN(buf)); /* erase data just written */
+        buf->len = 0;
+        break;
+    case 0:
+        ret = 0;
+        break;
+    case -1:
+        ret = -1;
+        break;
+    default:
+        msg(M_WARN, "Invalid error code from key_state_write_plaintext_const");
+        break;
+    }
+
+    perf_pop();
+    return ret;
+}
+
+int key_state_write_plaintext_const(struct key_state_ssl *ks_ssl,
+        const uint8_t *data, int len)
+{
+    int err = 0;
+    int ret = 1;
+    perf_push(PERF_BIO_WRITE_PLAINTEXT);
+
+    ASSERT(ks_ssl != NULL);
+
+    if (len > 0)
+    {
+        if ((err = wolfSSL_write(ks_ssl->ssl, data, len)) != len)
+        {
+            err = wolfSSL_get_error(ks_ssl->ssl, err);
+            switch (err)
+            {
+            case WOLFSSL_ERROR_WANT_WRITE:
+            case WOLFSSL_ERROR_WANT_READ:
+                ret = 0;
+                break;
+            default:
+                msg(M_WARN, "wolfSSL_write failed with Error: %s",
+                        wc_GetErrorString(err));
+                ret = -1;
+                break;
+            }
+        }
+    }
+
+    perf_pop();
+    return ret;
+}
+
+int key_state_read_ciphertext(struct key_state_ssl *ks_ssl, struct buffer *buf,
+        int maxlen)
+{
+    int ret = 1;
+    perf_push(PERF_BIO_READ_CIPHERTEXT);
+
+    if (BLEN(buf) != 0)
+    {
+        ret = 0;
+        goto cleanup;
+    }
+
+    ASSERT(ks_ssl != NULL);
+    buf->len = ssl_buff_read(ks_ssl->ssl, (char*) BPTR(buf), maxlen,
+            ks_ssl->send_buf);
+
+    ret = buf->len > 0 ? 1 : 0;
+
+    cleanup: perf_pop();
+    return ret;
+}
+
+int key_state_write_ciphertext(struct key_state_ssl *ks_ssl, struct buffer *buf)
+{
+    int err, ret = 1;
+    perf_push(PERF_BIO_WRITE_CIPHERTEXT);
+
+    ASSERT(ks_ssl != NULL);
+
+    if (BLEN(buf) > 0)
+    {
+        if ((err = (ssl_buff_write(ks_ssl->ssl, (char*) BPTR(buf), BLEN(buf),
+                ks_ssl->recv_buf))) != BLEN(buf))
+        {
+            ret = 0;
+            goto cleanup;
+        }
+        memset(BPTR(buf), 0, BLEN(buf)); /* erase data just written */
+        buf->len = 0;
+    }
+
+    cleanup: perf_pop();
+    return ret;
+}
+
+int key_state_read_plaintext(struct key_state_ssl *ks_ssl, struct buffer *buf,
+        int maxlen)
+{
+    int err, ret = 1;
+    perf_push(PERF_BIO_READ_PLAINTEXT);
+
+    ASSERT(ks_ssl != NULL);
+
+    if (BLEN(buf) != 0)
+    {
+        ret = 0;
+        goto cleanup;
+    }
+
+    if ((err = wolfSSL_read(ks_ssl->ssl, BPTR(buf), maxlen)) < 0)
+    {
+        err = wolfSSL_get_error(ks_ssl->ssl, err);
+        switch (err)
+        {
+        case WOLFSSL_ERROR_WANT_WRITE:
+        case WOLFSSL_ERROR_WANT_READ:
+            ret = 0;
+            goto cleanup;
+        default:
+            msg(M_WARN, "wolfSSL_read failed with Error: %s",
+                    wc_GetErrorString(err));
+            ret = -1;
+            goto cleanup;
+        }
+    }
+    buf->len = err;
+
+    cleanup: perf_pop();
+    return ret;
+}
+
+void print_details(struct key_state_ssl *ks_ssl, const char *prefix)
+{
+    WOLFSSL_X509 *cert = NULL;
+    const WOLFSSL_CIPHER *ciph;
+    WOLFSSL_EVP_PKEY *key = NULL;
+    char s1[256];
+    char s2[256];
+    char desc[256];
+    const WOLFSSL_EC_GROUP* group;
+
+    openvpn_snprintf(s2, sizeof(s2), "%s %s", prefix,
+            wolfSSL_get_version(ks_ssl->ssl));
+
+    ciph = wolfSSL_get_current_cipher(ks_ssl->ssl);
+    if (wolfSSL_CIPHER_description(ciph, desc, 256))
+    {
+        openvpn_snprintf(s1, sizeof(s1), "%s, cipher %s", s2, desc);
+    }
+
+    if ((cert = wolfSSL_get_peer_certificate(ks_ssl->ssl)) && (key =
+            wolfSSL_X509_get_pubkey(cert)))
+    {
+        memcpy(s2, s1, sizeof(s1));
+
+        switch (wolfSSL_X509_get_pubkey_type(cert))
+        {
+        case RSAk:
+            openvpn_snprintf(s1, sizeof(s1), "%s, %d bit RSA", s2,
+                    wolfSSL_EVP_PKEY_bits(key));
+            break;
+        default:
+            /*
+             * wolfSSL only supports RSA and ECC certificate public keys so if it isn't RSA then
+             * it must be ECC.
+             */
+            if ((group = wolfSSL_EC_KEY_get0_group(key->ecc))
+                    && (wc_ecc_is_valid_idx(group->curve_idx)))
+            {
+                openvpn_snprintf(s1, sizeof(s1), "%s, %d bit EC, curve: %s", s2,
+                        wolfSSL_EVP_PKEY_bits(key),
+                        wc_ecc_get_name(wc_ecc_get_curve_id(group->curve_idx)));
+            }
+            else
+            {
+                openvpn_snprintf(s1, sizeof(s1),
+                        "%s, %d bit EC, curve: Error getting curve name", s2,
+                        wolfSSL_EVP_PKEY_bits(key));
+            }
+            break;
+        }
+    }
+
+    msg(D_HANDSHAKE, "%s", s1);
+
+    if (key)
+    {
+        wolfSSL_EVP_PKEY_free(key);
+    }
+    if (cert)
+    {
+        wolfSSL_X509_free(cert);
+    }
+}
+
+void show_available_tls_ciphers_list(const char *cipher_list,
+        const char *tls_cert_profile,
+        bool tls13)
+{
+    int i;
+    char* cipher;
+    WOLFSSL *ssl;
+    WOLFSSL_CTX *ctx;
+
+#ifdef WOLFSSL_TLS13
+    if (tls13)
+    {
+        if ((ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())) == NULL)
+        {
+            wolfSSL_CTX_free(ctx);
+            msg(M_FATAL, "wolfSSL_CTX_new failed");
+        }
+    }
+    else
+#else
+        msg(M_FATAL, "wolfSSL library compiled without TLS 1.3 support.");
+#endif
+    {
+        if ((ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())) == NULL)
+        {
+            wolfSSL_CTX_free(ctx);
+            msg(M_FATAL, "wolfSSL_CTX_new failed");
+        }
+    }
+    if (cipher_list)
+    {
+        if (wolfSSL_CTX_set_cipher_list(ctx, cipher_list) != SSL_SUCCESS)
+        {
+            msg(M_FATAL, "Failed to set ciphers: %s", cipher_list);
+        }
+    }
+    if ((ssl = wolfSSL_new(ctx)) == NULL)
+    {
+        msg(M_FATAL, "wolfSSL_new failed");
+    }
+    for (i = 0; (cipher = wolfSSL_get_cipher_list_ex(ssl, i)); i++)
+    {
+        if (tls13 && !strncmp(cipher, "TLS13-", 6))
+        {
+            printf("%s\n", cipher);
+        }
+        else if (!tls13 && strncmp(cipher, "TLS13-", 6))
+        {
+            printf("%s\n", cipher);
+        }
+    }
+}
+
+void show_available_curves(void)
+{
+#ifdef HAVE_ECC
+    int i;
+    printf("Available Elliptic curves:\n");
+    for (i = 0; wc_ecc_is_valid_idx(i); i++)
+    {
+        printf("%s\n", wc_ecc_get_name(wc_ecc_get_curve_id(i)));
+    }
+#else
+    msg(M_FATAL, "wolfSSL library compiled without ECC support.");
+#endif
+}
+
+void get_highest_preference_tls_cipher(char *buf, int size)
+{
+    WOLFSSL *ssl;
+    WOLFSSL_CTX* ctx;
+    const char* cipher_name;
+
+    if ((ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())) == NULL)
+    {
+        wolfSSL_CTX_free(ctx);
+        msg(M_FATAL, "wolfSSL_CTX_new failed");
+    }
+
+    if ((ssl = wolfSSL_new(ctx)) == NULL)
+    {
+        msg(M_FATAL, "wolfSSL_new failed");
+    }
+
+    cipher_name = wolfSSL_get_cipher_name(ssl);
+    if (cipher_name)
+    {
+        strncpynt(buf, cipher_name, size);
+    }
+    else
+    {
+        msg(M_WARN, "wolfSSL_get_cipher_name failed");
+    }
+
+    wolfSSL_free(ssl);
+    wolfSSL_CTX_free(ctx);
+}
+
+const char * get_ssl_library_version(void)
+{
+    return wolfSSL_lib_version();
+}
+
+#endif /* ENABLE_CRYPTO_WOLFSSL */
diff --git a/src/openvpn/ssl_wolfssl.h b/src/openvpn/ssl_wolfssl.h
new file mode 100644
index 00000000..431dc87f
--- /dev/null
+++ b/src/openvpn/ssl_wolfssl.h
@@ -0,0 +1,93 @@ 
+/*
+ *  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) 2002-2019 OpenVPN Inc <sales@openvpn.net>
+ *  Copyright (C) 2010-2019 Fox Crypto B.V. <openvpn@fox-it.com>
+ *
+ *  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.
+ */
+
+/**
+ * @file Control Channel wolfSSL Backend
+ */
+
+#ifndef SSL_WOLFSSL_H_
+#define SSL_WOLFSSL_H_
+
+#include <wolfssl/options.h>
+#include <wolfssl/wolfcrypt/settings.h>
+#include <wolfssl/wolfcrypt/asn_public.h>
+#include <wolfssl/wolfcrypt/ecc.h>
+#include <wolfssl/wolfcrypt/error-crypt.h>
+#include <wolfssl/wolfcrypt/pkcs12.h>
+#include <wolfssl/openssl/ec.h>
+#include <wolfssl/ssl.h>
+
+#define TLS1_1_VERSION                  0x0302
+#define TLS1_2_VERSION                  0x0303
+#define TLS1_3_VERSION                  0x0304
+
+/* The list_buffer_t structure malloc's increments of BUCKET_BUF_LEN buckets */
+#define BUCKET_BUF_LEN (1024*5)
+
+/*
+ * The len and offset members refer to the length and offset within a bucket.
+ * Each bucket can hold up to BUCKET_BUF_LEN data.
+ */
+struct bucket_t
+{
+    uint32_t len;
+    uint32_t offset;
+    struct bucket_t* next;
+    uint8_t buf[BUCKET_BUF_LEN];
+};
+
+/*
+ * The buffer uses a list of buckets to hold data. This way the optimal amount
+ * of space is used (buckets are malloc'ed and free'd accordingly). The len
+ * member tracks the overall length of available data across all buckets.
+ * The granularity of BUCKET_BUF_LEN avoids malloc'ing too much memory or calling
+ * malloc too often.
+ */
+struct list_buffer_t
+{
+    uint32_t len;
+    struct bucket_t* first;
+    struct bucket_t* last;
+};
+
+/**
+ * Structure that wraps the TLS context. Contents differ depending on the
+ * SSL library used.
+ */
+struct tls_root_ctx
+{
+    WOLFSSL_CTX *ctx;
+    time_t crl_last_mtime;
+    off_t crl_last_size;
+};
+
+struct key_state_ssl
+{
+    WOLFSSL *ssl;
+    struct list_buffer_t *send_buf;
+    struct list_buffer_t *recv_buf;
+    struct tls_session *session;
+};
+
+#endif /* SSL_WOLFSSL_H_ */
diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am
index d015b293..542b38d2 100644
--- a/tests/unit_tests/openvpn/Makefile.am
+++ b/tests/unit_tests/openvpn/Makefile.am
@@ -44,6 +44,7 @@  crypto_testdriver_SOURCES = test_crypto.c mock_msg.c mock_msg.h \
 	$(openvpn_srcdir)/crypto.c \
 	$(openvpn_srcdir)/crypto_mbedtls.c \
 	$(openvpn_srcdir)/crypto_openssl.c \
+	$(openvpn_srcdir)/crypto_wolfssl.c \
 	$(openvpn_srcdir)/otime.c \
 	$(openvpn_srcdir)/packet_id.c \
 	$(openvpn_srcdir)/platform.c
@@ -72,6 +73,7 @@  tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c mock_msg.h \
 	$(openvpn_srcdir)/crypto.c \
 	$(openvpn_srcdir)/crypto_mbedtls.c \
 	$(openvpn_srcdir)/crypto_openssl.c \
+	$(openvpn_srcdir)/crypto_wolfssl.c \
 	$(openvpn_srcdir)/env_set.c \
 	$(openvpn_srcdir)/otime.c \
 	$(openvpn_srcdir)/packet_id.c \
@@ -90,6 +92,7 @@  networking_testdriver_SOURCES = test_networking.c mock_msg.c \
 	$(openvpn_srcdir)/crypto.c \
 	$(openvpn_srcdir)/crypto_mbedtls.c \
 	$(openvpn_srcdir)/crypto_openssl.c \
+	$(openvpn_srcdir)/crypto_wolfssl.c \
 	$(openvpn_srcdir)/otime.c \
 	$(openvpn_srcdir)/packet_id.c \
 	$(openvpn_srcdir)/platform.c
diff --git a/tests/unit_tests/openvpn/test_crypto.c b/tests/unit_tests/openvpn/test_crypto.c
index 7027d3da..5c5def6f 100644
--- a/tests/unit_tests/openvpn/test_crypto.c
+++ b/tests/unit_tests/openvpn/test_crypto.c
@@ -43,6 +43,13 @@ 
 
 static const char testtext[] = "Dummy text to test PEM encoding";
 
+static void
+show_available_ciphers_digests_test(void **state) {
+	show_available_ciphers();
+	show_available_digests();
+	show_available_engines();
+}
+
 static void
 crypto_pem_encode_decode_loopback(void **state) {
     struct gc_arena gc = gc_new();
@@ -68,10 +75,20 @@  crypto_pem_encode_decode_loopback(void **state) {
     gc_free(&gc);
 }
 
+static void
+rand_bytes_test(void **state) {
+	uint8_t input[10] = {0};
+	uint8_t output[10] = {0};
+	assert_true(rand_bytes(output, 10));
+	assert_memory_not_equal(input, output, 10);
+}
+
 int
 main(void) {
     const struct CMUnitTest tests[] = {
         cmocka_unit_test(crypto_pem_encode_decode_loopback),
+        cmocka_unit_test(show_available_ciphers_digests_test),
+        cmocka_unit_test(rand_bytes_test),
     };
 
 #if defined(ENABLE_CRYPTO_OPENSSL)