Skip to content

Commit 9c591a4

Browse files
committed
Support client-side hostname checks with leading .
1 parent fb01b33 commit 9c591a4

File tree

4 files changed

+230
-4
lines changed

4 files changed

+230
-4
lines changed

crypto/test/x509_util.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99

1010
#include <openssl/x509.h>
1111

12-
int Verify(X509 *leaf, const std::vector<X509 *> &roots,
13-
const std::vector<X509 *> &intermediates,
14-
const std::vector<X509_CRL *> &crls, unsigned long flags = 0,
15-
std::function<void(X509_STORE_CTX *)> configure_callback = nullptr);
12+
int Verify(
13+
X509 *leaf, const std::vector<X509 *> &roots,
14+
const std::vector<X509 *> &intermediates,
15+
const std::vector<X509_CRL *> &crls, unsigned long flags = 0,
16+
std::function<void(X509_STORE_CTX *)> configure_callback = nullptr);
1617

1718
// CRLsToStack converts a vector of |X509_CRL*| to an OpenSSL
1819
// STACK_OF(X509_CRL), bumping the reference counts for each CRL in question.

crypto/x509/internal.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,11 @@ OPENSSL_EXPORT int validate_cidr_mask(CBS *cidr_mask);
575575

576576
OPENSSL_EXPORT int cn2dnsid(ASN1_STRING *cn, unsigned char **dnsid, size_t *idlen);
577577

578+
// Match reference identifiers starting with "." to any sub-domain.
579+
// This is a non-public flag, turned on implicitly when the subject
580+
// reference identity is a DNS name.
581+
#define _X509_CHECK_FLAG_DOT_SUBDOMAINS 0x8000
582+
578583
#if defined(__cplusplus)
579584
} // extern C
580585
#endif

crypto/x509/v3_utl.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,10 +686,40 @@ typedef int (*equal_fn)(const unsigned char *pattern, size_t pattern_len,
686686
const unsigned char *subject, size_t subject_len,
687687
unsigned int flags);
688688

689+
// Skip pattern prefix to match "wildcard" subject
690+
static void skip_prefix(const unsigned char **p, size_t *plen,
691+
size_t subject_len, unsigned int flags) {
692+
const unsigned char *pattern = *p;
693+
size_t pattern_len = *plen;
694+
695+
// If subject starts with a leading '.' followed by more octets, and
696+
// pattern is longer, compare just an equal-length suffix with the
697+
// full subject (starting at the '.'), provided the prefix contains
698+
// no NULs.
699+
if ((flags & _X509_CHECK_FLAG_DOT_SUBDOMAINS) == 0) {
700+
return;
701+
}
702+
703+
while (pattern_len > subject_len && *pattern) {
704+
if ((flags & X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS) && *pattern == '.') {
705+
break;
706+
}
707+
++pattern;
708+
--pattern_len;
709+
}
710+
711+
// Skip if entire prefix acceptable
712+
if (pattern_len == subject_len) {
713+
*p = pattern;
714+
*plen = pattern_len;
715+
}
716+
}
717+
689718
// Compare while ASCII ignoring case.
690719
static int equal_nocase(const unsigned char *pattern, size_t pattern_len,
691720
const unsigned char *subject, size_t subject_len,
692721
unsigned int flags) {
722+
skip_prefix(&pattern, &pattern_len, subject_len, flags);
693723
if (pattern_len != subject_len) {
694724
return 0;
695725
}
@@ -719,6 +749,7 @@ static int equal_nocase(const unsigned char *pattern, size_t pattern_len,
719749
static int equal_case(const unsigned char *pattern, size_t pattern_len,
720750
const unsigned char *subject, size_t subject_len,
721751
unsigned int flags) {
752+
skip_prefix(&pattern, &pattern_len, subject_len, flags);
722753
if (pattern_len != subject_len) {
723754
return 0;
724755
}
@@ -932,12 +963,20 @@ static int do_x509_check(const X509 *x, const char *chk, size_t chklen,
932963
int alt_type;
933964
int rv = 0;
934965
equal_fn equal;
966+
967+
// This flag is internal-only and is never expected to be set by caller.
968+
flags &= ~_X509_CHECK_FLAG_DOT_SUBDOMAINS;
969+
935970
if (check_type == GEN_EMAIL) {
936971
cnid = NID_pkcs9_emailAddress;
937972
alt_type = V_ASN1_IA5STRING;
938973
equal = equal_email;
939974
} else if (check_type == GEN_DNS) {
940975
cnid = NID_commonName;
976+
// Implicit client-side DNS sub-domain pattern
977+
if (chklen > 1 && chk[0] == '.') {
978+
flags |= _X509_CHECK_FLAG_DOT_SUBDOMAINS;
979+
}
941980
alt_type = V_ASN1_IA5STRING;
942981
if (flags & X509_CHECK_FLAG_NO_WILDCARDS) {
943982
equal = equal_nocase;

crypto/x509/x509_compat_test.cc

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1869,6 +1869,69 @@ Lw6dQq2WGG6gApL/Cc0QonvzksvY5Ewf2qIpu2Si
18691869
-----END CERTIFICATE-----
18701870
)";
18711871

1872+
/*
1873+
Certificate:
1874+
Data:
1875+
Version: 3 (0x2)
1876+
Serial Number:
1877+
13:c1:63:e7:03:6f:a3:26:ac:31:71:a9:fe:ad:a6:34:20:94:bf:3a
1878+
Signature Algorithm: ecdsa-with-SHA256
1879+
Issuer: C=US, ST=Washington, O=AWS Libcrypto, OU=Good CA, CN=Root CA 1
1880+
Validity
1881+
Not Before: Jan 1 00:00:00 2015 GMT
1882+
Not After : Jan 1 00:00:00 2100 GMT
1883+
Subject: C=US, ST=Washington, O=AWS Libcrypto, OU=Good Endpoint, SN=Wildcard Testing, CN=*.sub2.example.com
1884+
Subject Public Key Info:
1885+
Public Key Algorithm: id-ecPublicKey
1886+
Public-Key: (256 bit)
1887+
pub:
1888+
04:b2:b7:bd:35:f2:eb:da:86:d5:dc:40:44:c7:23:
1889+
14:f9:d0:a5:40:17:30:85:b6:c6:11:38:c2:db:2c:
1890+
c5:bc:0c:19:11:d8:68:61:d6:a3:92:6b:8a:18:52:
1891+
2c:dc:86:a7:ad:29:ad:91:ac:7e:df:87:24:3b:f3:
1892+
b4:71:2b:4e:58
1893+
ASN1 OID: prime256v1
1894+
NIST CURVE: P-256
1895+
X509v3 extensions:
1896+
X509v3 Key Usage: critical
1897+
Digital Signature, Key Encipherment
1898+
X509v3 Basic Constraints: critical
1899+
CA:FALSE
1900+
X509v3 Extended Key Usage:
1901+
TLS Web Server Authentication, TLS Web Client Authentication
1902+
X509v3 Subject Alternative Name:
1903+
DNS:*.sub1.example.com, DNS:*.example.org, DNS:host.example.com
1904+
X509v3 Subject Key Identifier:
1905+
C8:78:64:E9:F7:9C:0F:56:E2:1D:CE:EE:ED:24:E0:9F:1D:4B:A3:BF
1906+
X509v3 Authority Key Identifier:
1907+
19:19:E1:8C:09:E2:5D:5C:16:04:E1:9C:74:66:19:FD:B8:52:5B:DF
1908+
Signature Algorithm: ecdsa-with-SHA256
1909+
Signature Value:
1910+
30:45:02:20:13:bc:6c:9c:3b:8e:c7:95:e7:9f:31:08:dd:7f:
1911+
6c:ea:97:4e:29:01:72:b5:9c:45:f1:29:bc:d7:ce:39:5a:21:
1912+
02:21:00:f5:77:2c:9d:23:6d:71:69:4d:93:eb:7e:fd:a5:17:
1913+
24:37:ee:97:01:4f:1c:54:09:cd:3d:87:e9:1d:da:5f:7e
1914+
*/
1915+
static char kFoo[] = R"(
1916+
-----BEGIN CERTIFICATE-----
1917+
MIICsDCCAlagAwIBAgIUE8Fj5wNvoyasMXGp/q2mNCCUvzowCgYIKoZIzj0EAwIw
1918+
YDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xFjAUBgNVBAoMDUFX
1919+
UyBMaWJjcnlwdG8xEDAOBgNVBAsMB0dvb2QgQ0ExEjAQBgNVBAMMCVJvb3QgQ0Eg
1920+
MTAgFw0xNTAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowgYoxCzAJBgNVBAYT
1921+
AlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRYwFAYDVQQKDA1BV1MgTGliY3J5cHRv
1922+
MRYwFAYDVQQLDA1Hb29kIEVuZHBvaW50MRkwFwYDVQQEDBBXaWxkY2FyZCBUZXN0
1923+
aW5nMRswGQYDVQQDDBIqLnN1YjIuZXhhbXBsZS5jb20wWTATBgcqhkjOPQIBBggq
1924+
hkjOPQMBBwNCAASyt7018uvahtXcQETHIxT50KVAFzCFtsYROMLbLMW8DBkR2Ghh
1925+
1qOSa4oYUizchqetKa2RrH7fhyQ787RxK05Yo4HAMIG9MA4GA1UdDwEB/wQEAwIF
1926+
oDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjA+
1927+
BgNVHREENzA1ghIqLnN1YjEuZXhhbXBsZS5jb22CDSouZXhhbXBsZS5vcmeCEGhv
1928+
c3QuZXhhbXBsZS5jb20wHQYDVR0OBBYEFMh4ZOn3nA9W4h3O7u0k4J8dS6O/MB8G
1929+
A1UdIwQYMBaAFBkZ4YwJ4l1cFgThnHRmGf24UlvfMAoGCCqGSM49BAMCA0gAMEUC
1930+
IBO8bJw7jseV558xCN1/bOqXTikBcrWcRfEpvNfOOVohAiEA9XcsnSNtcWlNk+t+
1931+
/aUXJDfulwFPHFQJzT2H6R3aX34=
1932+
-----END CERTIFICATE-----
1933+
)";
1934+
18721935
// EE certificate should not verify if signed by invalid root CA
18731936
TEST(X509CompatTest, CertificatesFromTrustStoreValidated) {
18741937
bssl::UniquePtr<X509> root = CertFromPEM(kRootBadBasicConstraints);
@@ -2367,3 +2430,121 @@ TEST(X509CompatTest, CommonNameToDNS) {
23672430
}
23682431
}
23692432

2433+
2434+
TEST(X509CompatTest, WildcardNameBehaviors) {
2435+
bssl::UniquePtr<X509> root = CertFromPEM(kValidRootCA1);
2436+
ASSERT_TRUE(root);
2437+
bssl::UniquePtr<X509> leaf = CertFromPEM(kFoo);
2438+
ASSERT_TRUE(leaf);
2439+
2440+
EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH,
2441+
Verify(
2442+
leaf.get(), /*roots=*/{root.get()},
2443+
/*intermediates=*/{},
2444+
/*crls=*/{}, /*flags=*/0,
2445+
[](X509_STORE_CTX *ctx) {
2446+
X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx);
2447+
char host[] = "example.com";
2448+
X509_VERIFY_PARAM_set1_host(param, host, sizeof(host) - 1);
2449+
}));
2450+
EXPECT_EQ(X509_V_OK,
2451+
Verify(leaf.get(), /*roots=*/{root.get()},
2452+
/*intermediates=*/{},
2453+
/*crls=*/{}, /*flags=*/0, [](X509_STORE_CTX *ctx) {
2454+
X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx);
2455+
char host[] = "host.example.com";
2456+
X509_VERIFY_PARAM_set1_host(param, host, sizeof(host) - 1);
2457+
}));
2458+
EXPECT_EQ(X509_V_OK,
2459+
Verify(leaf.get(), /*roots=*/{root.get()},
2460+
/*intermediates=*/{},
2461+
/*crls=*/{}, /*flags=*/0, [](X509_STORE_CTX *ctx) {
2462+
X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx);
2463+
char host[] = "foo.sub1.example.com";
2464+
X509_VERIFY_PARAM_set1_host(param, host, sizeof(host) - 1);
2465+
}));
2466+
// *.sub2.example.com is in the commonName so not checked by default if DNS
2467+
// SAN is present
2468+
EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH,
2469+
Verify(leaf.get(), /*roots=*/{root.get()},
2470+
/*intermediates=*/{},
2471+
/*crls=*/{}, /*flags=*/0, [](X509_STORE_CTX *ctx) {
2472+
X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx);
2473+
char host[] = "bar.sub2.example.com";
2474+
X509_VERIFY_PARAM_set1_host(param, host, sizeof(host) - 1);
2475+
}));
2476+
EXPECT_EQ(X509_V_OK,
2477+
Verify(leaf.get(), /*roots=*/{root.get()},
2478+
/*intermediates=*/{},
2479+
/*crls=*/{}, /*flags=*/0, [](X509_STORE_CTX *ctx) {
2480+
X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx);
2481+
X509_VERIFY_PARAM_set_hostflags(
2482+
param, X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT);
2483+
char host[] = "bar.sub2.example.com";
2484+
X509_VERIFY_PARAM_set1_host(param, host, sizeof(host) - 1);
2485+
}));
2486+
EXPECT_EQ(X509_V_OK,
2487+
Verify(leaf.get(), /*roots=*/{root.get()},
2488+
/*intermediates=*/{},
2489+
/*crls=*/{}, /*flags=*/0, [](X509_STORE_CTX *ctx) {
2490+
X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx);
2491+
char host[] = "baz.example.org";
2492+
X509_VERIFY_PARAM_set1_host(param, host, sizeof(host) - 1);
2493+
}));
2494+
2495+
// client-side host name verification behavior with leading '.'
2496+
EXPECT_EQ(X509_V_OK,
2497+
Verify(leaf.get(), /*roots=*/{root.get()},
2498+
/*intermediates=*/{},
2499+
/*crls=*/{}, /*flags=*/0, [](X509_STORE_CTX *ctx) {
2500+
X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx);
2501+
char host[] = ".example.com";
2502+
X509_VERIFY_PARAM_set1_host(param, host, sizeof(host) - 1);
2503+
}));
2504+
EXPECT_EQ(X509_V_OK,
2505+
Verify(leaf.get(), /*roots=*/{root.get()},
2506+
/*intermediates=*/{},
2507+
/*crls=*/{}, /*flags=*/0, [](X509_STORE_CTX *ctx) {
2508+
X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx);
2509+
char host[] = ".sub1.example.com";
2510+
X509_VERIFY_PARAM_set1_host(param, host, sizeof(host) - 1);
2511+
}));
2512+
EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH,
2513+
Verify(leaf.get(), /*roots=*/{root.get()},
2514+
/*intermediates=*/{},
2515+
/*crls=*/{}, /*flags=*/0, [](X509_STORE_CTX *ctx) {
2516+
X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx);
2517+
char host[] = ".sub2.example.com";
2518+
X509_VERIFY_PARAM_set1_host(param, host, sizeof(host) - 1);
2519+
}));
2520+
EXPECT_EQ(X509_V_OK,
2521+
Verify(leaf.get(), /*roots=*/{root.get()},
2522+
/*intermediates=*/{},
2523+
/*crls=*/{}, /*flags=*/0, [](X509_STORE_CTX *ctx) {
2524+
X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx);
2525+
X509_VERIFY_PARAM_set_hostflags(
2526+
param, X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT);
2527+
char host[] = ".sub2.example.com";
2528+
X509_VERIFY_PARAM_set1_host(param, host, sizeof(host) - 1);
2529+
}));
2530+
EXPECT_EQ(X509_V_OK,
2531+
Verify(leaf.get(), /*roots=*/{root.get()},
2532+
/*intermediates=*/{},
2533+
/*crls=*/{}, /*flags=*/0, [](X509_STORE_CTX *ctx) {
2534+
X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx);
2535+
X509_VERIFY_PARAM_set_hostflags(
2536+
param, X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT);
2537+
char host[] = ".example.org";
2538+
X509_VERIFY_PARAM_set1_host(param, host, sizeof(host) - 1);
2539+
}));
2540+
EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH,
2541+
Verify(leaf.get(), /*roots=*/{root.get()},
2542+
/*intermediates=*/{},
2543+
/*crls=*/{}, /*flags=*/0, [](X509_STORE_CTX *ctx) {
2544+
X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx);
2545+
X509_VERIFY_PARAM_set_hostflags(
2546+
param, X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT);
2547+
char host[] = ".baz.example.org";
2548+
X509_VERIFY_PARAM_set1_host(param, host, sizeof(host) - 1);
2549+
}));
2550+
}

0 commit comments

Comments
 (0)