We long recommended users to use --ns-cert-type to distinguish between
client and server certificates, but that extension is long deprecated and
now can even no longer be accurately checked in OpenSSL 1.1+.  We support
a more modern alternative, --remote-cert-tls (which expands to
--remote-cert-ku + --remote-cert-eku), but are overly strict in checking
the keyUsage.  This patch makes our implementation less picky, so that
correct-but-slightly-weird certicates will not immediately be rejected.

We currently allow users to specify a list of allowed keyUsage values, and
require that the remote certificate matches one of these values exactly.
This is for more strict than keyUsage usually requires; which is that a
certificate is okay to use if it can *at least* be used for our intended
purpose.  This patch changes the behaviour to match that, by using the
library-provided mbedtls_x509_crt_check_key_usage() function in mbed TLS
builds, and performing the 'at least bits xyz' check for OpenSSL builds
(OpenSSL unfortunately does not expose a similar function).

Furthermore, this patch adds better error messages when the checking fails;
it now explains that is expects to match either of the supplied values,
and only does so if the check actually failed.

This patch also changes --remote-cert-tls to still require a specific EKU,
but only *some* keyUsage value.  Both our supported crypto libraries will
check the keyUsage value for correctness during the handshake, but only if
it is present.  So this still enforces a correct keyUsage, but is a bit
less picky about certificates that do not exactly match expectations.

This patch should be applied together with the 'deprecate --ns-cert-type'
patch I sent earlier.

Signed-off-by: Steffan Karger <stef...@karger.me>
---
 Changes.rst                      |  7 ++++
 doc/openvpn.8                    | 33 ++++++++++--------
 src/openvpn/options.c            | 15 +++++----
 src/openvpn/ssl_verify.h         |  3 ++
 src/openvpn/ssl_verify_mbedtls.c | 42 ++++++++++++++---------
 src/openvpn/ssl_verify_openssl.c | 72 +++++++++++++++++++++-------------------
 6 files changed, 102 insertions(+), 70 deletions(-)

diff --git a/Changes.rst b/Changes.rst
index 0af29e3..2a94990 100644
--- a/Changes.rst
+++ b/Changes.rst
@@ -306,6 +306,13 @@ Maintainer-visible changes
 
 Version 2.4.1
 =============
+ - ``--remote-cert-ku`` now only requires the certificate to have at least the
+   bits set of one of the values in the supplied list, instead of requiring an
+   exact match to one of the values in the list.
+ - ``--remote-cert-tls`` now only requires that a keyUsage is present in the
+   certificate, and leaves the verification of the value up to the crypto
+   library, which has more information (i.e. the key exchange method in use)
+   to verify that the keyUsage is correct.
  - ``--ns-cert-type`` is deprecated.  Use ``--remote-cert-tls`` instead.
    The nsCertType x509 extension is very old, and barely used.
    ``--remote-cert-tls`` uses the far more common keyUsage and extendedKeyUsage
diff --git a/doc/openvpn.8 b/doc/openvpn.8
index f6822ec..89229fb 100644
--- a/doc/openvpn.8
+++ b/doc/openvpn.8
@@ -5344,15 +5344,25 @@ or
 .B \-\-tls\-verify.
 .\"*********************************************************
 .TP
-.B \-\-remote\-cert\-ku v...
+.B \-\-remote\-cert\-ku [v...]
 Require that peer certificate was signed with an explicit
 .B key usage.
 
-This is a useful security option for clients, to ensure that
-the host they connect to is a designated server.
+If present in the certificate, the keyUsage value is validated by the TLS
+library during the TLS handshake.  Specifying this option without arguments
+requires this extension to be present (so the TLS library will verify it).
 
-The key usage should be encoded in hex, more than one key
-usage can be specified.
+If the list
+.B v...
+is also supplied, the keyUsage field must have
+.B at least
+the same bits set as the bits in
+.B one of
+the values supplied in the list
+.B v...
+
+The key usage values in the list must be encoded in hex, e.g.
+"\-\-remote\-cert\-ku a0"
 .\"*********************************************************
 .TP
 .B \-\-remote\-cert\-eku oid
@@ -5373,24 +5383,21 @@ and
 .B extended key usage
 based on RFC3280 TLS rules.
 
-This is a useful security option for clients, to ensure that
-the host they connect to is a designated server.
+This is a useful security option for clients, to ensure that the host they
+connect to is a designated server.  Or the other way around; for a server to
+verify that only hosts with a client certificate can connect.
 
 The
 .B \-\-remote\-cert\-tls client
 option is equivalent to
 .B
-\-\-remote\-cert\-ku 80 08 88 \-\-remote\-cert\-eku "TLS Web Client 
Authentication"
-
-The key usage is digitalSignature and/or keyAgreement.
+\-\-remote\-cert\-ku \-\-remote\-cert\-eku "TLS Web Client Authentication"
 
 The
 .B \-\-remote\-cert\-tls server
 option is equivalent to
 .B
-\-\-remote\-cert\-ku a0 88 \-\-remote\-cert\-eku "TLS Web Server 
Authentication"
-
-The key usage is digitalSignature and ( keyEncipherment or keyAgreement ).
+\-\-remote\-cert\-ku \-\-remote\-cert\-eku "TLS Web Server Authentication"
 
 This is an important security precaution to protect against
 a man-in-the-middle attack where an authorized client
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index 1243db0..d0d39f6 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -7950,14 +7950,18 @@ add_option(struct options *options,
     }
     else if (streq(p[0], "remote-cert-ku"))
     {
-        int j;
-
         VERIFY_PERMISSION(OPT_P_GENERAL);
 
+        size_t j;
         for (j = 1; j < MAX_PARMS && p[j] != NULL; ++j)
         {
             sscanf(p[j], "%x", &(options->remote_cert_ku[j-1]));
         }
+        if (j == 1)
+        {
+            /* No specific KU required, but require KU to be present */
+            options->remote_cert_ku[0] = OPENVPN_KU_REQUIRED;
+        }
     }
     else if (streq(p[0], "remote-cert-eku") && p[1] && !p[2])
     {
@@ -7970,15 +7974,12 @@ add_option(struct options *options,
 
         if (streq(p[1], "server"))
         {
-            options->remote_cert_ku[0] = 0xa0;
-            options->remote_cert_ku[1] = 0x88;
+            options->remote_cert_ku[0] = OPENVPN_KU_REQUIRED;
             options->remote_cert_eku = "TLS Web Server Authentication";
         }
         else if (streq(p[1], "client"))
         {
-            options->remote_cert_ku[0] = 0x80;
-            options->remote_cert_ku[1] = 0x08;
-            options->remote_cert_ku[2] = 0x88;
+            options->remote_cert_ku[0] = OPENVPN_KU_REQUIRED;
             options->remote_cert_eku = "TLS Web Client Authentication";
         }
         else
diff --git a/src/openvpn/ssl_verify.h b/src/openvpn/ssl_verify.h
index ffab218..22a6190 100644
--- a/src/openvpn/ssl_verify.h
+++ b/src/openvpn/ssl_verify.h
@@ -218,6 +218,9 @@ struct x509_track
 /** Do not perform Netscape certificate type verification */
 #define NS_CERT_CHECK_CLIENT (1<<1)
 
+/** Require keyUsage to be present in cert (0xFFFF is an invalid KU value) */
+#define OPENVPN_KU_REQUIRED (0xFFFF)
+
 /*
  * TODO: document
  */
diff --git a/src/openvpn/ssl_verify_mbedtls.c b/src/openvpn/ssl_verify_mbedtls.c
index 4068dd3..c32e481 100644
--- a/src/openvpn/ssl_verify_mbedtls.c
+++ b/src/openvpn/ssl_verify_mbedtls.c
@@ -437,32 +437,42 @@ result_t
 x509_verify_cert_ku(mbedtls_x509_crt *cert, const unsigned *const expected_ku,
                     int expected_len)
 {
-    result_t fFound = FAILURE;
+    msg(D_HANDSHAKE, "Validating certificate key usage");
 
     if (!(cert->ext_types & MBEDTLS_X509_EXT_KEY_USAGE))
     {
-        msg(D_HANDSHAKE, "Certificate does not have key usage extension");
+        msg(D_TLS_ERRORS,
+            "ERROR: Certificate does not have key usage extension");
+        return FAILURE;
     }
-    else
+
+    if (expected_ku[0] == OPENVPN_KU_REQUIRED)
     {
-        int i;
-        unsigned nku = cert->key_usage;
+        /* Extension required, value checked by TLS library */
+        return SUCCESS;
+    }
 
-        msg(D_HANDSHAKE, "Validating certificate key usage");
-        for (i = 0; SUCCESS != fFound && i<expected_len; i++)
+    result_t fFound = FAILURE;
+    for (size_t i = 0; SUCCESS != fFound && i<expected_len; i++)
+    {
+        if (expected_ku[i] != 0
+            && 0 == mbedtls_x509_crt_check_key_usage(cert, expected_ku[i]))
         {
-            if (expected_ku[i] != 0)
-            {
-                msg(D_HANDSHAKE, "++ Certificate has key usage  %04x, expects "
-                    "%04x", nku, expected_ku[i]);
+            fFound = SUCCESS;
+        }
+    }
 
-                if (nku == expected_ku[i])
-                {
-                    fFound = SUCCESS;
-                }
-            }
+    if (fFound != SUCCESS)
+    {
+        msg(D_TLS_ERRORS,
+            "ERROR: Certificate has key usage %04x, expected one of:",
+            cert->key_usage);
+        for (size_t i = 0; i < expected_len && expected_ku[i]; i++)
+        {
+            msg(D_TLS_ERRORS, " * %04x", expected_ku[i]);
         }
     }
+
     return fFound;
 }
 
diff --git a/src/openvpn/ssl_verify_openssl.c b/src/openvpn/ssl_verify_openssl.c
index 5bdd1e3..d598240 100644
--- a/src/openvpn/ssl_verify_openssl.c
+++ b/src/openvpn/ssl_verify_openssl.c
@@ -600,55 +600,59 @@ result_t
 x509_verify_cert_ku(X509 *x509, const unsigned *const expected_ku,
                     int expected_len)
 {
-    ASN1_BIT_STRING *ku = NULL;
-    result_t fFound = FAILURE;
+    ASN1_BIT_STRING *ku = X509_get_ext_d2i(x509, NID_key_usage, NULL, NULL);
 
-    if ((ku = (ASN1_BIT_STRING *) X509_get_ext_d2i(x509, NID_key_usage, NULL,
-                                                   NULL)) == NULL)
+    if (ku == NULL)
     {
-        msg(D_HANDSHAKE, "Certificate does not have key usage extension");
+        msg(D_TLS_ERRORS, "Certificate does not have key usage extension");
+        return FAILURE;
     }
-    else
+
+    if (expected_ku[0] == OPENVPN_KU_REQUIRED)
     {
-        unsigned nku = 0;
-        int i;
-        for (i = 0; i < 8; i++)
-        {
-            if (ASN1_BIT_STRING_get_bit(ku, i))
-            {
-                nku |= 1 << (7 - i);
-            }
-        }
+        /* Extension required, value checked by TLS library */
+        return SUCCESS;
+    }
 
-        /*
-         * Fixup if no LSB bits
-         */
-        if ((nku & 0xff) == 0)
+    unsigned nku = 0;
+    for (size_t i = 0; i < 8; i++)
+    {
+        if (ASN1_BIT_STRING_get_bit(ku, i))
         {
-            nku >>= 8;
+            nku |= 1 << (7 - i);
         }
+    }
 
-        msg(D_HANDSHAKE, "Validating certificate key usage");
-        for (i = 0; fFound != SUCCESS && i < expected_len; i++)
-        {
-            if (expected_ku[i] != 0)
-            {
-                msg(D_HANDSHAKE, "++ Certificate has key usage  %04x, expects "
-                    "%04x", nku, expected_ku[i]);
+    /*
+     * Fixup if no LSB bits
+     */
+    if ((nku & 0xff) == 0)
+    {
+        nku >>= 8;
+    }
 
-                if (nku == expected_ku[i])
-                {
-                    fFound = SUCCESS;
-                }
-            }
+    msg(D_HANDSHAKE, "Validating certificate key usage");
+    result_t fFound = FAILURE;
+    for (size_t i = 0; fFound != SUCCESS && i < expected_len; i++)
+    {
+        if (expected_ku[i] != 0 && (nku & expected_ku[i]) == expected_ku[i])
+        {
+            fFound = SUCCESS;
         }
     }
 
-    if (ku != NULL)
+    if (fFound != SUCCESS)
     {
-        ASN1_BIT_STRING_free(ku);
+        msg(D_TLS_ERRORS,
+            "ERROR: Certificate has key usage %04x, expected one of:", nku);
+        for (size_t i = 0; i < expected_len && expected_ku[i]; i++)
+        {
+            msg(D_TLS_ERRORS, " * %04x", expected_ku[i]);
+        }
     }
 
+    ASN1_BIT_STRING_free(ku);
+
     return fFound;
 }
 
-- 
2.7.4


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Openvpn-devel mailing list
Openvpn-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to