From patchwork Mon Feb 10 17:59:32 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [Openvpn-devel] openssl: alternative names support for --verify-x509-name CN checks X-Patchwork-Submitter: Kristof Provost via Openvpn-devel X-Patchwork-Id: 987 Message-Id: To: "openvpn-devel@lists.sourceforge.net" Date: Mon, 10 Feb 2020 17:59:32 +0000 From: Mateusz Markowicz via Openvpn-devel List-Id: when using "--verify-x509-name [hostname] name" hostname will now be accepted also when matched against one of the X509v3 Subject Alternative Name IP or DNS entries (instead of just Subject's CN). see also: https://github.com/OpenVPN/openvpn/pull/136/ Signed-off-by: Mateusz Markowicz --- src/openvpn/options.c | 4 +++ src/openvpn/ssl_verify.c | 18 +++++++++++--- src/openvpn/ssl_verify.h | 1 + src/openvpn/ssl_verify_backend.h | 7 ++++++ src/openvpn/ssl_verify_mbedtls.c | 11 +++++++++ src/openvpn/ssl_verify_openssl.c | 42 ++++++++++++++++++++++++++++++++ 6 files changed, 80 insertions(+), 3 deletions(-) -- 2.20.1 diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 173a1eea..438dfff0 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -8144,6 +8144,10 @@ add_option(struct options *options, { type = VERIFY_X509_SUBJECT_RDN_PREFIX; } + else if (streq(p[2], "subject-alt-name")) + { + type = VERIFY_X509_SAN; + } else { msg(msglevel, "unknown X.509 name type: %s", p[2]); diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c index 65188d23..6480b5eb 100644 --- a/src/openvpn/ssl_verify.c +++ b/src/openvpn/ssl_verify.c @@ -390,15 +390,27 @@ verify_peer_cert(const struct tls_options *opt, openvpn_x509_cert_t *peer_cert, /* verify X509 name or username against --verify-x509-[user]name */ if (opt->verify_x509_type != VERIFY_X509_NONE) { - if ( (opt->verify_x509_type == VERIFY_X509_SUBJECT_DN + bool match; + if (opt->verify_x509_type == VERIFY_X509_SAN) + { + bool have_alt_names; + match = x509v3_is_host_in_alternative_names(peer_cert, opt->verify_x509_name, &have_alt_names) + || (!have_alt_names && strcmp(opt->verify_x509_name, common_name) == 0); + } + else + { + match = (opt->verify_x509_type == VERIFY_X509_SUBJECT_DN && strcmp(opt->verify_x509_name, subject) == 0) || (opt->verify_x509_type == VERIFY_X509_SUBJECT_RDN && strcmp(opt->verify_x509_name, common_name) == 0) || (opt->verify_x509_type == VERIFY_X509_SUBJECT_RDN_PREFIX && strncmp(opt->verify_x509_name, common_name, - strlen(opt->verify_x509_name)) == 0) ) + strlen(opt->verify_x509_name)) == 0); + } + + if (match) { - msg(D_HANDSHAKE, "VERIFY X509NAME OK: %s", subject); + msg(D_HANDSHAKE, "VERIFY X509NAME OK: %s", opt->verify_x509_name); } else { diff --git a/src/openvpn/ssl_verify.h b/src/openvpn/ssl_verify.h index c54b89a6..1295e76b 100644 --- a/src/openvpn/ssl_verify.h +++ b/src/openvpn/ssl_verify.h @@ -64,6 +64,7 @@ struct cert_hash_set { #define VERIFY_X509_SUBJECT_DN 1 #define VERIFY_X509_SUBJECT_RDN 2 #define VERIFY_X509_SUBJECT_RDN_PREFIX 3 +#define VERIFY_X509_SAN 4 #define TLS_AUTHENTICATION_SUCCEEDED 0 #define TLS_AUTHENTICATION_FAILED 1 diff --git a/src/openvpn/ssl_verify_backend.h b/src/openvpn/ssl_verify_backend.h index d6b31bfa..927a5a29 100644 --- a/src/openvpn/ssl_verify_backend.h +++ b/src/openvpn/ssl_verify_backend.h @@ -268,4 +268,11 @@ result_t x509_write_pem(FILE *peercert_file, openvpn_x509_cert_t *peercert); */ bool tls_verify_crl_missing(const struct tls_options *opt); +/** + * Return true iff {host} was found in {cert} Subject Alternative Names DNS or IP entries. + * If {has_alt_names} != NULL it'll return true iff Subject Alternative Names were defined + * for {cert}. + */ +bool x509v3_is_host_in_alternative_names(openvpn_x509_cert_t *cert, const char *host, bool *has_alt_names); + #endif /* SSL_VERIFY_BACKEND_H_ */ diff --git a/src/openvpn/ssl_verify_mbedtls.c b/src/openvpn/ssl_verify_mbedtls.c index fd31bbbd..2f2e04be 100644 --- a/src/openvpn/ssl_verify_mbedtls.c +++ b/src/openvpn/ssl_verify_mbedtls.c @@ -245,6 +245,17 @@ x509_get_subject(mbedtls_x509_crt *cert, struct gc_arena *gc) return subject; } +bool +x509v3_is_host_in_alternative_names(mbedtls_x509_crt *cert, const char *host, bool *has_alt_names) +{ + msg(M_WARN, "Missing support for subject alternative names in mbedtls."); + if (has_alt_names != NULL) + { + *has_alt_names = false; + } + return false; +} + static void do_setenv_x509(struct env_set *es, const char *name, char *value, int depth) { diff --git a/src/openvpn/ssl_verify_openssl.c b/src/openvpn/ssl_verify_openssl.c index ff14db23..bb639abc 100644 --- a/src/openvpn/ssl_verify_openssl.c +++ b/src/openvpn/ssl_verify_openssl.c @@ -364,6 +364,48 @@ err: return subject; } +bool +x509v3_is_host_in_alternative_names(X509 *cert, const char *host, bool *has_alt_names) +{ + GENERAL_NAMES* altnames = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (has_alt_names != NULL) + { + *has_alt_names = altnames != NULL; + } + if (altnames == NULL) + { + return false; + } + + int n = sk_GENERAL_NAME_num(altnames); + for (int i = 0; i < n; i++) + { + GENERAL_NAME* altname = sk_GENERAL_NAME_value(altnames, i); + ASN1_STRING *altname_asn1 = NULL; + if (altname->type == GEN_DNS) + { + altname_asn1 = altname->d.dNSName; + } + else if (altname->type == GEN_IPADD) + { + altname_asn1 = altname->d.iPAddress; + } + + if (altname_asn1 != NULL) + { + char* altname_cstr = NULL; + if (ASN1_STRING_to_UTF8((unsigned char **)&altname_cstr, altname_asn1) >= 0) { + bool match = strcmp(host, altname_cstr) == 0; + OPENSSL_free(altname_cstr); + if (match) + { + return true; + } + } + } + } + return false; +} /* * x509-track implementation -- save X509 fields to environment,