From: Viktor Dukhovni Date: Wed, 21 May 2014 09:57:44 +0000 (+0100) Subject: Fixes to host checking. X-Git-Tag: master-post-reformat~794 X-Git-Url: https://git.openssl.org/?a=commitdiff_plain;h=397a8e747dc3f964196caed5ca4e08d4b598362a;p=openssl.git Fixes to host checking. Fixes to host checking wild card support and add support for setting host checking flags when verifying a certificate chain. --- diff --git a/crypto/x509/vpm_int.h b/crypto/x509/vpm_int.h index af998212a1..d18a4d48e9 100644 --- a/crypto/x509/vpm_int.h +++ b/crypto/x509/vpm_int.h @@ -62,6 +62,7 @@ struct X509_VERIFY_PARAM_ID_st { unsigned char *host; /* If not NULL hostname to match */ size_t hostlen; + unsigned int hostflags; /* Flags to control matching features */ unsigned char *email; /* If not NULL email address to match */ size_t emaillen; unsigned char *ip; /* If not NULL IP address to match */ diff --git a/crypto/x509/x509_vfy.c b/crypto/x509/x509_vfy.c index 32b07a0306..350c3475fe 100644 --- a/crypto/x509/x509_vfy.c +++ b/crypto/x509/x509_vfy.c @@ -744,7 +744,8 @@ static int check_id(X509_STORE_CTX *ctx) X509_VERIFY_PARAM *vpm = ctx->param; X509_VERIFY_PARAM_ID *id = vpm->id; X509 *x = ctx->cert; - if (id->host && !X509_check_host(x, id->host, id->hostlen, 0)) + if (id->host && !X509_check_host(x, id->host, id->hostlen, + id->hostflags)) { if (!check_id_error(ctx, X509_V_ERR_HOSTNAME_MISMATCH)) return 0; diff --git a/crypto/x509/x509_vfy.h b/crypto/x509/x509_vfy.h index 24115ccfa2..4b61d28d74 100644 --- a/crypto/x509/x509_vfy.h +++ b/crypto/x509/x509_vfy.h @@ -560,6 +560,8 @@ int X509_VERIFY_PARAM_set1_policies(X509_VERIFY_PARAM *param, int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM *param, const unsigned char *name, size_t namelen); +void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *param, + unsigned int flags); int X509_VERIFY_PARAM_set1_email(X509_VERIFY_PARAM *param, const unsigned char *email, size_t emaillen); int X509_VERIFY_PARAM_set1_ip(X509_VERIFY_PARAM *param, diff --git a/crypto/x509/x509_vpm.c b/crypto/x509/x509_vpm.c index 14336a7ef7..9b22093277 100644 --- a/crypto/x509/x509_vpm.c +++ b/crypto/x509/x509_vpm.c @@ -239,6 +239,7 @@ int X509_VERIFY_PARAM_inherit(X509_VERIFY_PARAM *dest, { if (!X509_VERIFY_PARAM_set1_host(dest, id->host, id->hostlen)) return 0; + dest->id->hostflags = id->hostflags; } if (test_x509_verify_param_copy_id(email, NULL)) @@ -402,6 +403,12 @@ int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM *param, name, namelen); } +void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *param, + unsigned int flags) + { + param->id->hostflags = flags; + } + int X509_VERIFY_PARAM_set1_email(X509_VERIFY_PARAM *param, const unsigned char *email, size_t emaillen) { @@ -437,7 +444,7 @@ const char *X509_VERIFY_PARAM_get0_name(const X509_VERIFY_PARAM *param) return param->name; } -static X509_VERIFY_PARAM_ID _empty_id = {NULL, 0, NULL, 0, NULL, 0}; +static X509_VERIFY_PARAM_ID _empty_id = {NULL, 0, 0U, NULL, 0, NULL, 0}; #define vpm_empty_id (X509_VERIFY_PARAM_ID *)&_empty_id diff --git a/crypto/x509v3/v3_utl.c b/crypto/x509v3/v3_utl.c index de43c2fb3b..7a4bd45960 100644 --- a/crypto/x509v3/v3_utl.c +++ b/crypto/x509v3/v3_utl.c @@ -569,11 +569,13 @@ void X509_email_free(STACK_OF(OPENSSL_STRING) *sk) } typedef int (*equal_fn)(const unsigned char *pattern, size_t pattern_len, - const unsigned char *subject, size_t subject_len); + const unsigned char *subject, size_t subject_len, + unsigned int flags); /* Compare while ASCII ignoring case. */ static int equal_nocase(const unsigned char *pattern, size_t pattern_len, - const unsigned char *subject, size_t subject_len) + const unsigned char *subject, size_t subject_len, + unsigned int unused_flags) { if (pattern_len != subject_len) return 0; @@ -602,7 +604,8 @@ static int equal_nocase(const unsigned char *pattern, size_t pattern_len, /* Compare using memcmp. */ static int equal_case(const unsigned char *pattern, size_t pattern_len, - const unsigned char *subject, size_t subject_len) + const unsigned char *subject, size_t subject_len, + unsigned int unused_flags) { /* The pattern must not contain NUL characters. */ if (memchr(pattern, '\0', pattern_len) != NULL) @@ -615,7 +618,8 @@ static int equal_case(const unsigned char *pattern, size_t pattern_len, /* RFC 5280, section 7.5, requires that only the domain is compared in a case-insensitive manner. */ static int equal_email(const unsigned char *a, size_t a_len, - const unsigned char *b, size_t b_len) + const unsigned char *b, size_t b_len, + unsigned int unused_flags) { size_t i = a_len; if (a_len != b_len) @@ -629,103 +633,177 @@ static int equal_email(const unsigned char *a, size_t a_len, if (a[i] == '@' || b[i] == '@') { if (!equal_nocase(a + i, a_len - i, - b + i, a_len - i)) + b + i, a_len - i, 0)) return 0; break; } } if (i == 0) i = a_len; - return equal_case(a, i, b, i); + return equal_case(a, i, b, i, 0); } /* Compare the prefix and suffix with the subject, and check that the characters in-between are valid. */ static int wildcard_match(const unsigned char *prefix, size_t prefix_len, const unsigned char *suffix, size_t suffix_len, - const unsigned char *subject, size_t subject_len) + const unsigned char *subject, size_t subject_len, + unsigned int flags) { const unsigned char *wildcard_start; const unsigned char *wildcard_end; const unsigned char *p; + int allow_multi = 0; + int allow_idna = 0; + if (subject_len < prefix_len + suffix_len) return 0; - if (!equal_nocase(prefix, prefix_len, subject, prefix_len)) + if (!equal_nocase(prefix, prefix_len, subject, prefix_len, flags)) return 0; wildcard_start = subject + prefix_len; wildcard_end = subject + (subject_len - suffix_len); - if (!equal_nocase(wildcard_end, suffix_len, suffix, suffix_len)) + if (!equal_nocase(wildcard_end, suffix_len, suffix, suffix_len, flags)) return 0; - /* The wildcard must match at least one character. */ - if (wildcard_start == wildcard_end) + /* + * If the wildcard makes up the entire first label, it must match at + * least one character. + */ + if (prefix_len == 0 && *suffix == '.') + { + if (wildcard_start == wildcard_end) + return 0; + allow_idna = 1; + if (flags & X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS) + allow_multi = 1; + } + /* IDNA labels cannot match partial wildcards */ + if (!allow_idna && + subject_len >= 4 && strncasecmp((char *)subject, "xn--", 4) == 0) return 0; - /* Check that the part matched by the wildcard contains only - permitted characters and only matches a single label. */ + /* The wildcard may match a literal '*' */ + if (wildcard_end == wildcard_start + 1 && *wildcard_start == '*') + return 1; + /* + * Check that the part matched by the wildcard contains only + * permitted characters and only matches a single label unless + * allow_multi is set. + */ for (p = wildcard_start; p != wildcard_end; ++p) if (!(('0' <= *p && *p <= '9') || ('A' <= *p && *p <= 'Z') || ('a' <= *p && *p <= 'z') || - *p == '-')) + *p == '-' || (allow_multi && *p == '.'))) return 0; return 1; } -/* Checks if the memory region consistens of [0-9A-Za-z.-]. */ -static int valid_domain_characters(const unsigned char *p, size_t len) - { - while (len) - { - if (!(('0' <= *p && *p <= '9') || - ('A' <= *p && *p <= 'Z') || - ('a' <= *p && *p <= 'z') || - *p == '-' || *p == '.')) - return 0; - ++p; - --len; - } - return 1; - } +#define LABEL_START (1 << 0) +#define LABEL_END (1 << 1) +#define LABEL_HYPHEN (1 << 2) +#define LABEL_IDNA (1 << 3) -/* Find the '*' in a wildcard pattern. If no such character is found - or the pattern is otherwise invalid, returns NULL. */ -static const unsigned char *wildcard_find_star(const unsigned char *pattern, - size_t pattern_len) +static const unsigned char *valid_star(const unsigned char *p, size_t len, + unsigned int flags) { - const unsigned char *star = memchr(pattern, '*', pattern_len); - size_t dot_count = 0; - const unsigned char *suffix_start; - size_t suffix_length; - if (star == NULL) - return NULL; - suffix_start = star + 1; - suffix_length = (pattern + pattern_len) - (star + 1); - if (!(valid_domain_characters(pattern, star - pattern) && - valid_domain_characters(suffix_start, suffix_length))) - return NULL; - /* Check that the suffix matches at least two labels. */ - while (suffix_length) + const unsigned char *star = 0; + size_t i; + int state = LABEL_START; + int dots = 0; + for (i = 0; i < len; ++i) { - if (*suffix_start == '.') - ++dot_count; - ++suffix_start; - --suffix_length; + /* + * Locate first and only legal wildcard, either at the start + * or end of a non-IDNA first and not final label. + */ + if (p[i] == '*') + { + int atstart = (state & LABEL_START); + int atend = (i == len - 1 || p[i+i] == '.'); + /* + * At most one wildcard per pattern. + * No wildcards in IDNA labels. + * No wildcards after the first label. + */ + if (star != NULL || (state & LABEL_IDNA) != 0 || dots) + return NULL; + /* Only full-label '*.example.com' wildcards? */ + if ((flags & X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS) + && (!atstart || !atend)) + return NULL; + /* No 'foo*bar' wildcards */ + if (!atstart && !atend) + return NULL; + star = &p[i]; + state &= ~LABEL_START; + } + else if ((state & LABEL_START) != 0) + { + /* + * At the start of a label, skip any "xn--" and + * remain in the LABEL_START state, but set the + * IDNA label state + */ + if ((state & LABEL_IDNA) == 0 && len - i >= 4 + && strncasecmp((char *)&p[i], "xn--", 4) == 0) + { + i += 3; + state |= LABEL_IDNA; + continue; + } + /* Labels must start with a letter or digit */ + state &= ~LABEL_START; + if (('a' <= p[i] && p[i] <= 'z') + || ('A' <= p[i] && p[i] <= 'Z') + || ('0' <= p[i] && p[i] <= '9')) + continue; + return NULL; + } + else if (('a' <= p[i] && p[i] <= 'z') + || ('A' <= p[i] && p[i] <= 'Z') + || ('0' <= p[i] && p[i] <= '9')) + { + state &= LABEL_IDNA; + continue; + } + else if (p[i] == '.') + { + if (state & (LABEL_HYPHEN | LABEL_START)) + return NULL; + state = LABEL_START; + ++dots; + } + else if (p[i] == '-') + { + if (state & LABEL_HYPHEN) + return NULL; + state |= LABEL_HYPHEN; + } + else + return NULL; } - if (dot_count < 2) + + /* + * The final label must not end in a hyphen or ".", and + * there must be at least two dots after the star. + */ + if ((state & (LABEL_START | LABEL_HYPHEN)) != 0 + || dots < 2) return NULL; return star; } /* Compare using wildcards. */ static int equal_wildcard(const unsigned char *pattern, size_t pattern_len, - const unsigned char *subject, size_t subject_len) + const unsigned char *subject, size_t subject_len, + unsigned int flags) { - const unsigned char *star = wildcard_find_star(pattern, pattern_len); + const unsigned char *star = valid_star(pattern, pattern_len, flags); if (star == NULL) return equal_nocase(pattern, pattern_len, - subject, subject_len); + subject, subject_len, flags); return wildcard_match(pattern, star - pattern, star + 1, (pattern + pattern_len) - star - 1, - subject, subject_len); + subject, subject_len, flags); } /* Compare an ASN1_STRING to a supplied string. If they match @@ -734,6 +812,7 @@ static int equal_wildcard(const unsigned char *pattern, size_t pattern_len, */ static int do_check_string(ASN1_STRING *a, int cmp_type, equal_fn equal, + unsigned int flags, const unsigned char *b, size_t blen) { if (!a->data || !a->length) @@ -743,7 +822,7 @@ static int do_check_string(ASN1_STRING *a, int cmp_type, equal_fn equal, if (cmp_type != a->type) return 0; if (cmp_type == V_ASN1_IA5STRING) - return equal(a->data, a->length, b, blen); + return equal(a->data, a->length, b, blen, flags); if (a->length == (int)blen && !memcmp(a->data, b, blen)) return 1; else @@ -756,7 +835,7 @@ static int do_check_string(ASN1_STRING *a, int cmp_type, equal_fn equal, astrlen = ASN1_STRING_to_UTF8(&astr, a); if (astrlen < 0) return -1; - rv = equal(astr, astrlen, b, blen); + rv = equal(astr, astrlen, b, blen, flags); OPENSSL_free(astr); return rv; } @@ -770,6 +849,7 @@ static int do_x509_check(X509 *x, const unsigned char *chk, size_t chklen, int i; int cnid; int alt_type; + int san_present = 0; equal_fn equal; if (check_type == GEN_EMAIL) { @@ -805,15 +885,17 @@ static int do_x509_check(X509 *x, const unsigned char *chk, size_t chklen, GENERAL_NAME *gen; ASN1_STRING *cstr; gen = sk_GENERAL_NAME_value(gens, i); - if(gen->type != check_type) + if (gen->type != check_type) continue; + san_present = 1; if (check_type == GEN_EMAIL) cstr = gen->d.rfc822Name; else if (check_type == GEN_DNS) cstr = gen->d.dNSName; else cstr = gen->d.iPAddress; - if (do_check_string(cstr, alt_type, equal, chk, chklen)) + if (do_check_string(cstr, alt_type, equal, flags, + chk, chklen)) { rv = 1; break; @@ -822,7 +904,9 @@ static int do_x509_check(X509 *x, const unsigned char *chk, size_t chklen, GENERAL_NAMES_free(gens); if (rv) return 1; - if (!(flags & X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT) || !cnid) + if (!cnid + || (san_present + && !(flags & X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT))) return 0; } i = -1; @@ -833,7 +917,7 @@ static int do_x509_check(X509 *x, const unsigned char *chk, size_t chklen, ASN1_STRING *str; ne = X509_NAME_get_entry(name, i); str = X509_NAME_ENTRY_get_data(ne); - if (do_check_string(str, -1, equal, chk, chklen)) + if (do_check_string(str, -1, equal, flags, chk, chklen)) return 1; } return 0; diff --git a/crypto/x509v3/v3nametest.c b/crypto/x509v3/v3nametest.c index 77d86795c1..4cd6f36888 100644 --- a/crypto/x509v3/v3nametest.c +++ b/crypto/x509v3/v3nametest.c @@ -7,11 +7,10 @@ static const char *const names[] = { "a", "b", ".", "*", "@", ".a", "a.", ".b", "b.", ".*", "*.", "*@", "@*", "a@", "@a", "b@", "..", - "@@", "**", - "*.com", "*com", "*.*.com", "*com", "com*", "*example.com", - "*@example.com", "test@*.example.com", - "example.com", "www.example.com", "test.www.example.com", - "*.example.com", "*.www.example.com", "test.*.example.com", "www.*.com", + "@@", "**", "*.com", "*com", "*.*.com", "*com", "com*", "*example.com", + "*@example.com", "test@*.example.com", "example.com", "www.example.com", + "test.www.example.com", "*.example.com", "*.www.example.com", + "test.*.example.com", "www.*.com", "example.net", "xn--rger-koa.example.com", "a.example.com", "b.example.com", "postmaster@example.com", "Postmaster@example.com", @@ -21,28 +20,20 @@ static const char *const names[] = static const char *const exceptions[] = { - "set CN: host: [*.example.com] does not match [*.example.com]", "set CN: host: [*.example.com] matches [a.example.com]", "set CN: host: [*.example.com] matches [b.example.com]", "set CN: host: [*.example.com] matches [www.example.com]", "set CN: host: [*.example.com] matches [xn--rger-koa.example.com]", - "set CN: host: [test.*.example.com] does not match [test.*.example.com]", - "set CN: host: [test.*.example.com] matches [test.www.example.com]", - "set CN: host: [*.www.example.com] does not match [*.www.example.com]", "set CN: host: [*.www.example.com] matches [test.www.example.com]", "set emailAddress: email: [postmaster@example.com] does not match [Postmaster@example.com]", "set emailAddress: email: [postmaster@EXAMPLE.COM] does not match [Postmaster@example.com]", "set emailAddress: email: [Postmaster@example.com] does not match [postmaster@example.com]", "set emailAddress: email: [Postmaster@example.com] does not match [postmaster@EXAMPLE.COM]", "set dnsName: host: [*.example.com] matches [www.example.com]", - "set dnsName: host: [*.example.com] does not match [*.example.com]", "set dnsName: host: [*.example.com] matches [a.example.com]", "set dnsName: host: [*.example.com] matches [b.example.com]", "set dnsName: host: [*.example.com] matches [xn--rger-koa.example.com]", "set dnsName: host: [*.www.example.com] matches [test.www.example.com]", - "set dnsName: host: [*.www.example.com] does not match [*.www.example.com]", - "set dnsName: host: [test.*.example.com] matches [test.www.example.com]", - "set dnsName: host: [test.*.example.com] does not match [test.*.example.com]", "set rfc822Name: email: [postmaster@example.com] does not match [Postmaster@example.com]", "set rfc822Name: email: [Postmaster@example.com] does not match [postmaster@example.com]", "set rfc822Name: email: [Postmaster@example.com] does not match [postmaster@EXAMPLE.COM]", diff --git a/crypto/x509v3/x509v3.h b/crypto/x509v3/x509v3.h index 272374e5a2..406300f296 100644 --- a/crypto/x509v3/x509v3.h +++ b/crypto/x509v3/x509v3.h @@ -704,8 +704,12 @@ STACK_OF(OPENSSL_STRING) *X509_get1_ocsp(X509 *x); /* Always check subject name for host match even if subject alt names present */ #define X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT 0x1 -/* Disable wild-card matching for dnsName fields and common name. */ +/* Disable wildcard matching for dnsName fields and common name. */ #define X509_CHECK_FLAG_NO_WILDCARDS 0x2 +/* Wildcards must not match a partial label. */ +#define X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS 0x4 +/* Allow (non-partial) wildcards to match multiple labels. */ +#define X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS 0x8 int X509_check_host(X509 *x, const unsigned char *chk, size_t chklen, unsigned int flags); diff --git a/doc/crypto/X509_VERIFY_PARAM_set_flags.pod b/doc/crypto/X509_VERIFY_PARAM_set_flags.pod index 46cac2bea2..1059d4ff84 100644 --- a/doc/crypto/X509_VERIFY_PARAM_set_flags.pod +++ b/doc/crypto/X509_VERIFY_PARAM_set_flags.pod @@ -26,6 +26,17 @@ X509_VERIFY_PARAM_set_flags, X509_VERIFY_PARAM_clear_flags, X509_VERIFY_PARAM_ge void X509_VERIFY_PARAM_set_depth(X509_VERIFY_PARAM *param, int depth); int X509_VERIFY_PARAM_get_depth(const X509_VERIFY_PARAM *param); + int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM *param, + const unsigned char *name, size_t namelen); + void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *param, + unsigned int flags); + int X509_VERIFY_PARAM_set1_email(X509_VERIFY_PARAM *param, + const unsigned char *email, size_t emaillen); + int X509_VERIFY_PARAM_set1_ip(X509_VERIFY_PARAM *param, + const unsigned char *ip, size_t iplen); + int X509_VERIFY_PARAM_set1_ip_asc(X509_VERIFY_PARAM *param, + const char *ipasc); + =head1 DESCRIPTION These functions manipulate the B structure associated with @@ -61,12 +72,43 @@ X509_VERIFY_PARAM_set_depth() sets the maximum verification depth to B. That is the maximum number of untrusted CA certificates that can appear in a chain. +X509_VERIFY_PARAM_set1_host() sets the expected DNS hostname to B. If +B is NUL-terminated, B may be zero, otherwise B must +be set to the length of B. When a hostname is specified, certificate +verification automatically invokes L with flags equal to +the B argument given to B (default +zero). Applications are strongly advised to use this interface in preference +to explicitly calling L, hostname checks are +out of scope with the DANE-EE(3) certificate usage, and the internal +check will be suppressed as appropriate when DANE support is added +to OpenSSL. + +X509_VERIFY_PARAM_set1_email() sets the expected RFC822 email address to +B. If B may be zero, otherwise +B must be set to the length of B. When an email address +is specified, certificate verification automatically invokes +L. + +X509_VERIFY_PARAM_set1_ip() sets the expected IP address to B. +The B argument is in binary format, in network byte-order and +B must be set to 4 for IPv4 and 16 for IPv6. When an IP +address is specified, certificate verification automatically invokes +L. + +X509_VERIFY_PARAM_set1_ip_asc() sets the expected IP address to +B. The B argument is a NUL-terminal ASCII string: +dotted decimal quad for IPv4 and colon-separated hexadecimal for +IPv6. The condensed "::" notation is supported for IPv6 addresses. + =head1 RETURN VALUES -X509_VERIFY_PARAM_set_flags(), X509_VERIFY_PARAM_clear_flags(), +X509_VERIFY_PARAM_set_flags(), X509_VERIFY_PARAM_clear_flags(), X509_VERIFY_PARAM_set_purpose(), X509_VERIFY_PARAM_set_trust(), -X509_VERIFY_PARAM_add0_policy() and X509_VERIFY_PARAM_set1_policies() return 1 -for success and 0 for failure. +X509_VERIFY_PARAM_add0_policy() X509_VERIFY_PARAM_set1_policies(), +X509_VERIFY_PARAM_set1_host(), X509_VERIFY_PARAM_set_hostflags(), +X509_VERIFY_PARAM_set1_email(), X509_VERIFY_PARAM_set1_ip() and +X509_VERIFY_PARAM_set1_ip_asc() return 1 for success and 0 for +failure. X509_VERIFY_PARAM_get_flags() returns the current verification flags. diff --git a/doc/crypto/X509_check_host.pod b/doc/crypto/X509_check_host.pod index 5ac2137a50..64a84d2ab5 100644 --- a/doc/crypto/X509_check_host.pod +++ b/doc/crypto/X509_check_host.pod @@ -47,17 +47,38 @@ X509_check_ip_asc() is similar, except that the NUL-terminated string B
is first converted to the internal representation. The B argument is usually 0. It can be the bitwise OR of the -flags B, -B. +flags: + +=over 4 + +=item B, + +=item B, + +=item B, + +=item B. + +=back The B flag causes the function -to check the subject DN even if the certificate contains a subject -alternative name extension is present; the default is to ignore the -subject DN in preference of the extension. +to consider the subject DN even if the certificate contains at least +one subject alternative name of the right type (DNS name or email +address as appropriate); the default is to ignore the subject DN +when at least one corresponding subject alternative names is present. -If present, B disables wildcard +If set, B disables wildcard expansion; this only applies to B. +If set, B suppresses support +for "*" as wildcard pattern in labels that have a prefix or suffix, +such as: "www*" or "*www"; this only aplies to B. + +If set, B, allows a "*" +that constitutes the complete label of a DNS name (e.g. +"*.example.com") to match more than one label in B; +this only applies to B. + =head1 RETURN VALUES The functions return 1 for a successful match, 0 for a failed match