diff --git a/openssl/openssl_hostname_validation.c b/openssl/openssl_hostname_validation.c index 2bd5ee0..38321ae 100644 --- a/openssl/openssl_hostname_validation.c +++ b/openssl/openssl_hostname_validation.c @@ -20,6 +20,62 @@ #define HOSTNAME_MAX_SIZE 255 +static int lowercase(int ch) { + if ('A' <= ch && ch <= 'Z') + return ch - 'A' + 'a'; + return ch; +} + +static int memeq_ncase(const char *x, const char *y, size_t l) { + if (l == 0) + return 1; + do { + if (lowercase(*x++) != lowercase(*y++)) + return 0; + } while (--l != 0); + return 1; +} + +static int has_nul(const char *s, size_t l) { + if (l == 0) + return 0; + do { + if (*s++ == '\0') + return 1; + } while (--l != 0); + return 0; +} + +static HostnameValidationResult validate_name(const char *hostname, ASN1_STRING *certname_asn1) { + char *certname_s = (char *) ASN1_STRING_data(certname_asn1); + int certname_len = ASN1_STRING_length(certname_asn1), hostname_len = strlen(hostname); + + // Make sure there isn't an embedded NUL character in the DNS name + if (has_nul(certname_s, certname_len)) { + return MalformedCertificate; + } + // remove last '.' from hostname + if (hostname_len != 0 && hostname[hostname_len - 1] == '.') + --hostname_len; + // skip the first segment if wildcard + if (certname_len > 2 && certname_s[0] == '*' && certname_s[1] == '.') { + if (hostname_len != 0) { + do { + --hostname_len; + if (*hostname++ == '.') + break; + } while (hostname_len != 0); + } + certname_s += 2; + certname_len -= 2; + } + // Compare expected hostname with the DNS name + if (certname_len != hostname_len) { + return MatchNotFound; + } + return memeq_ncase(hostname, certname_s, hostname_len) ? MatchFound : MatchNotFound; +} + /** * Tries to find a match for hostname in the certificate's Common Name field. * @@ -32,7 +88,6 @@ static HostnameValidationResult matches_common_name(const char *hostname, const int common_name_loc = -1; X509_NAME_ENTRY *common_name_entry = NULL; ASN1_STRING *common_name_asn1 = NULL; - char *common_name_str = NULL; // Find the position of the CN field in the Subject field of the certificate common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *) server_cert), NID_commonName, -1); @@ -45,26 +100,13 @@ static HostnameValidationResult matches_common_name(const char *hostname, const if (common_name_entry == NULL) { return Error; } - - // Convert the CN field to a C string common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry); if (common_name_asn1 == NULL) { return Error; - } - common_name_str = (char *) ASN1_STRING_data(common_name_asn1); - - // Make sure there isn't an embedded NUL character in the CN - if (ASN1_STRING_length(common_name_asn1) != strlen(common_name_str)) { - return MalformedCertificate; } - // Compare expected hostname with the CN - if (strcasecmp(hostname, common_name_str) == 0) { - return MatchFound; - } - else { - return MatchNotFound; - } + // validate the names + return validate_name(hostname, common_name_asn1); } @@ -95,19 +137,10 @@ static HostnameValidationResult matches_subject_alternative_name(const char *hos if (current_name->type == GEN_DNS) { // Current name is a DNS name, let's check it - char *dns_name = (char *) ASN1_STRING_data(current_name->d.dNSName); - - // Make sure there isn't an embedded NUL character in the DNS name - if (ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) { - result = MalformedCertificate; + result = validate_name(hostname, current_name->d.dNSName); + if (result != MatchNotFound) { break; } - else { // Compare expected hostname with the DNS name - if (strcasecmp(hostname, dns_name) == 0) { - result = MatchFound; - break; - } - } } } sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);