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 <steffan@karger.me>
Acked-by: Gert Doering <gert@greenie.muc.de>
Message-Id: <
1489612820-15284-1-git-send-email-steffan@karger.me>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg14265.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>
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
.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
.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
}
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])
{
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
/** 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
*/
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;
}
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;
}