diff --git a/src/openssl.c b/src/openssl.c index e3caa70..1b8904c 100644 --- a/src/openssl.c +++ b/src/openssl.c @@ -39,7 +39,7 @@ as that of the covered work. */ #include #include -#include +#include #include #include @@ -480,9 +480,11 @@ bool ssl_check_certificate (int fd, const char *host) { X509 *cert; + GENERAL_NAMES *subjectAltNames; char common_name[256]; long vresult; bool success = true; + bool dNSName_checked = false; /* If the user has specified --no-check-cert, we still want to warn him about problems with the server's certificate. */ @@ -550,9 +552,11 @@ ssl_check_certificate (int fd, const char *host) /* Check that HOST matches the common name in the certificate. #### The following remains to be done: - - It should use dNSName/ipAddress subjectAltName extensions if - available; according to rfc2818: "If a subjectAltName extension - of type dNSName is present, that MUST be used as the identity." + - It should use ipAddress subjectAltName extension if available and + numerical hostname given in URL; accoroding to rfc2818: "In some + cases, the URI is specified as an IP address rather than a hostname. + In this case, the iPAddress subjectAltName must be present + in the certificate and must exactly match the IP in the URI." - When matching against common names, it should loop over all common names and choose the most specific one, i.e. the last @@ -561,15 +565,60 @@ ssl_check_certificate (int fd, const char *host) - Ensure that ASN1 strings from the certificate are encoded as UTF-8 which can be meaningfully compared to HOST. */ - common_name[0] = '\0'; - X509_NAME_get_text_by_NID (X509_get_subject_name (cert), - NID_commonName, common_name, sizeof (common_name)); - if (!pattern_match (common_name, host)) + subjectAltNames = X509_get_ext_d2i (cert, NID_subject_alt_name, NULL, NULL); + + if (subjectAltNames) { - logprintf (LOG_NOTQUIET, _("\ -%s: certificate common name `%s' doesn't match requested host name `%s'.\n"), - severity, escnonprint (common_name), escnonprint (host)); - success = false; + /* Test subject alternative names */ + int numaltnames = sk_GENERAL_NAME_num (subjectAltNames); + int i; + for (i=0; i < numaltnames; i++) + { + const GENERAL_NAME *name = + sk_GENERAL_NAME_value (subjectAltNames, i); + if (name && (name->type == GEN_DNS)) + { + dNSName_checked = true; + /* dNSName should be IA5String (i.e. ASCII), however who + * does trust CA? Convert it into UTF-8 for sure. */ + unsigned char *name_in_utf8 = NULL; + if (0 <= ASN1_STRING_to_UTF8 (&name_in_utf8, name->d.dNSName)) + { + if (pattern_match ((char *)name_in_utf8, host)) + { + OPENSSL_free (name_in_utf8); + break; + } + OPENSSL_free (name_in_utf8); + } + } + } + sk_GENERAL_NAME_free (subjectAltNames); + + if (dNSName_checked == true && i >= numaltnames) + { + logprintf (LOG_NOTQUIET, + _("%s: none certificate subject alternative name of type " + "dDNSName matches\n\trequested host name `%s'.\n"), + severity, escnonprint (host)); + success = false; + } + } + + if (dNSName_checked == false) + { + /* Test commomName */ + common_name[0] = '\0'; + X509_NAME_get_text_by_NID (X509_get_subject_name (cert), + NID_commonName, common_name, + sizeof (common_name)); + if (!pattern_match (common_name, host)) + { + logprintf (LOG_NOTQUIET, _("\ + %s: certificate common name `%s' doesn't match requested host name `%s'.\n"), + severity, escnonprint (common_name), escnonprint (host)); + success = false; + } } if (success) @@ -586,3 +635,7 @@ To connect to %s insecurely, use `--no-check-certificate'.\n"), /* Allow --no-check-cert to disable certificate checking. */ return opt.check_cert ? success : true; } + +/* + * vim: tabstop=2 shiftwidth=2 softtabstop=2 + */