============

Make self-signed cert:
$ openssl req -x509 -newkey ec:<(openssl ecparam -name secp384r1) -keyout key.pem -out cert.pem -nodes -sha256 -days 3650 -subj '/CN=server'

Record our fingerprint in an environment variable for the client to use later:
$ server_fingerprint="$(openssl x509 -in cert.pem -noout -sha256 -fingerprint | sed 's/.*=//;s/\(.*\)/\L\1/')"

Start openvpn with tls verify script:
$ sudo openvpn --server 10.66.0.0 255.255.255.0 --dev tun --dh none --ca none --cert cert.pem --key key.pem --tls-verify $(readlink -f tls-verify.sh) --script-security 2


TLS Verify Script:
==================
#!/bin/sh
[ -n "$tls_digest_sha256_0" -a -e "/tmp/allowed-openvpn-fingerprints/$tls_digest_sha256_0" ]


Client side:
============

Make self-signed cert:
$ openssl req -x509 -newkey ec:<(openssl ecparam -name secp384r1) -keyout key.pem -out cert.pem -nodes -sha256 -days 3650 -subj '/CN=client'

"Tell" the server about our fingerprint:
$ mkdir -p /tmp/allowed-openvpn-fingerprints; touch "/tmp/allowed-openvpn-fingerprints/$(openssl x509 -in cert.pem -noout -sha256 -fingerprint | sed 's/.*=//;s/\(.*\)/\L\1/')"

Start openvpn with server fingerprint verification:
$ sudo openvpn --client --remote 127.0.0.1 --dev tun --ca none --cert cert.pem --key key.pem --verify-hash "$server_fingerprint" SHA256 --nobind


Crufty patch:
=============

diff -ru openvpn-2.4.5/src/openvpn/init.c openvpn-2.4.5-modified/src/openvpn/init.c
--- openvpn-2.4.5/src/openvpn/init.c	2018-03-01 08:22:19.000000000 +0100
+++ openvpn-2.4.5-modified/src/openvpn/init.c	2018-04-17 17:15:09.388690819 +0200
@@ -2749,6 +2749,7 @@
     to.remote_cert_eku = options->remote_cert_eku;
     to.verify_hash = options->verify_hash;
     to.verify_hash_algo = options->verify_hash_algo;
+    to.ca_file_none = options->ca_file_none;
 #ifdef ENABLE_X509ALTUSERNAME
     to.x509_username_field = (char *) options->x509_username_field;
 #else
diff -ru openvpn-2.4.5/src/openvpn/options.c openvpn-2.4.5-modified/src/openvpn/options.c
--- openvpn-2.4.5/src/openvpn/options.c	2018-03-01 08:22:19.000000000 +0100
+++ openvpn-2.4.5-modified/src/openvpn/options.c	2018-04-17 16:54:11.523414012 +0200
@@ -3290,7 +3290,10 @@
 #ifdef ENABLE_CRYPTO
     /* ** SSL/TLS/crypto related files ** */
     errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE, options->dh_file, R_OK, "--dh");
-    errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE, options->ca_file, R_OK, "--ca");
+    if (!options->ca_file_none)
+    {
+        errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE, options->ca_file, R_OK, "--ca");
+    }
     errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE, options->ca_path, R_OK, "--capath");
     errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE, options->cert_file, R_OK, "--cert");
     errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE, options->extra_certs_file, R_OK,
@@ -7700,6 +7703,10 @@
         {
             options->ca_file_inline = p[2];
         }
+        else if (streq(p[1], "none"))
+        {
+            options->ca_file_none = true;
+        }
     }
 #ifndef ENABLE_CRYPTO_MBEDTLS
     else if (streq(p[0], "capath") && p[1] && !p[2])
diff -ru openvpn-2.4.5/src/openvpn/options.h openvpn-2.4.5-modified/src/openvpn/options.h
--- openvpn-2.4.5/src/openvpn/options.h	2018-03-01 08:22:19.000000000 +0100
+++ openvpn-2.4.5-modified/src/openvpn/options.h	2018-04-17 16:51:00.962640290 +0200
@@ -495,6 +495,7 @@
     /* TLS (control channel) parms */
     bool tls_server;
     bool tls_client;
+    bool ca_file_none;
     const char *ca_file;
     const char *ca_path;
     const char *dh_file;
diff -ru openvpn-2.4.5/src/openvpn/ssl.c openvpn-2.4.5-modified/src/openvpn/ssl.c
--- openvpn-2.4.5/src/openvpn/ssl.c	2018-03-01 08:22:19.000000000 +0100
+++ openvpn-2.4.5-modified/src/openvpn/ssl.c	2018-04-17 17:14:00.563153748 +0200
@@ -695,7 +695,7 @@
         }
     }

-    if (options->ca_file || options->ca_path)
+    if ((!options->ca_file_none && options->ca_file) || options->ca_path)
     {
         tls_ctx_load_ca(new_ctx, options->ca_file, options->ca_file_inline,
                         options->ca_path, options->tls_server);
diff -ru openvpn-2.4.5/src/openvpn/ssl_common.h openvpn-2.4.5-modified/src/openvpn/ssl_common.h
--- openvpn-2.4.5/src/openvpn/ssl_common.h	2018-03-01 08:22:19.000000000 +0100
+++ openvpn-2.4.5-modified/src/openvpn/ssl_common.h	2018-04-17 17:14:41.447285597 +0200
@@ -272,6 +272,7 @@
     uint8_t *verify_hash;
     hash_algo_type verify_hash_algo;
     char *x509_username_field;
+    bool ca_file_none;

     /* allow openvpn config info to be
      * passed over control channel */
diff -ru openvpn-2.4.5/src/openvpn/ssl_verify.c openvpn-2.4.5-modified/src/openvpn/ssl_verify.c
--- openvpn-2.4.5/src/openvpn/ssl_verify.c	2018-03-01 08:22:19.000000000 +0100
+++ openvpn-2.4.5-modified/src/openvpn/ssl_verify.c	2018-04-17 17:17:55.230139543 +0200
@@ -719,7 +719,7 @@
     }

     /* verify level 1 cert, i.e. the CA that signed our leaf cert */
-    if (cert_depth == 1 && opt->verify_hash)
+    if ((opt->ca_file_none || cert_depth == 1) && opt->verify_hash)
     {
         struct buffer ca_hash = {0};

diff -ru openvpn-2.4.5/src/openvpn/ssl_verify_openssl.c openvpn-2.4.5-modified/src/openvpn/ssl_verify_openssl.c
--- openvpn-2.4.5/src/openvpn/ssl_verify_openssl.c	2018-03-01 08:22:19.000000000 +0100
+++ openvpn-2.4.5-modified/src/openvpn/ssl_verify_openssl.c	2018-04-17 17:17:30.001681856 +0200
@@ -66,7 +66,7 @@
     cert_hash_remember(session, X509_STORE_CTX_get_error_depth(ctx), &cert_hash);

     /* did peer present cert which was signed by our root cert? */
-    if (!preverify_ok)
+    if (!preverify_ok && !session->opt->ca_file_none)
     {
         /* get the X509 name */
         char *subject = x509_get_subject(current_cert, &gc);
