提交 397a8e74 编写于 作者: V Viktor Dukhovni 提交者: Dr. Stephen Henson

Fixes to host checking.

Fixes to host checking wild card support and add support for
setting host checking flags when verifying a certificate
chain.
上级 558c94ef
......@@ -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 */
......
......@@ -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;
......
......@@ -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,
......
......@@ -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
......
......@@ -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;
......
......@@ -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]",
......
......@@ -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);
......
......@@ -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<X509_VERIFY_PARAM> structure associated with
......@@ -61,12 +72,43 @@ X509_VERIFY_PARAM_set_depth() sets the maximum verification depth to B<depth>.
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<name>. If
B<name> is NUL-terminated, B<namelen> may be zero, otherwise B<namelen> must
be set to the length of B<name>. When a hostname is specified, certificate
verification automatically invokes L<X509_check_host(3)> with flags equal to
the B<flags> argument given to B<X509_VERIFY_PARAM_set_hostflags()> (default
zero). Applications are strongly advised to use this interface in preference
to explicitly calling L<X509_check_host(3)>, 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<email>. If B<email is NUL-terminated, B<emaillen> may be zero, otherwise
B<emaillen> must be set to the length of B<email>. When an email address
is specified, certificate verification automatically invokes
L<X509_check_email(3)>.
X509_VERIFY_PARAM_set1_ip() sets the expected IP address to B<ip>.
The B<ip> argument is in binary format, in network byte-order and
B<iplen> must be set to 4 for IPv4 and 16 for IPv6. When an IP
address is specified, certificate verification automatically invokes
L<X509_check_ip(3)>.
X509_VERIFY_PARAM_set1_ip_asc() sets the expected IP address to
B<ipasc>. The B<ipasc> 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.
......
......@@ -47,17 +47,38 @@ X509_check_ip_asc() is similar, except that the NUL-terminated
string B<address> is first converted to the internal representation.
The B<flags> argument is usually 0. It can be the bitwise OR of the
flags B<X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT>,
B<X509_CHECK_FLAG_NO_WILDCARDS>.
flags:
=over 4
=item B<X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT>,
=item B<X509_CHECK_FLAG_NO_WILDCARDS>,
=item B<X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS>,
=item B<X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS>.
=back
The B<X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT> 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<X509_CHECK_FLAG_NO_WILDCARDS> disables wildcard
If set, B<X509_CHECK_FLAG_NO_WILDCARDS> disables wildcard
expansion; this only applies to B<X509_check_host>.
If set, B<X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS> suppresses support
for "*" as wildcard pattern in labels that have a prefix or suffix,
such as: "www*" or "*www"; this only aplies to B<X509_check_host>.
If set, B<X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS>, allows a "*"
that constitutes the complete label of a DNS name (e.g.
"*.example.com") to match more than one label in B<name>;
this only applies to B<X509_check_host>.
=head1 RETURN VALUES
The functions return 1 for a successful match, 0 for a failed match
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册