Skip to content
This repository was archived by the owner on Apr 10, 2025. It is now read-only.

Commit 4af5e65

Browse files
committed
Check that certificates are valid for the domain we're trying to contact.
This depends on x509_check_host, which was added in openssl 1.0.2. If we want to support older versions of openssl we need to parse certs ourself, like svn does, which is a bunch of moderately tricky security sensitive code that I'd really rather avoid. We normally build agaist boringssl, which has this function, but we also prepare a tarball build that intendes to link against system openssl. So anyone using that build process will need to upgrade to 1.0.2.
1 parent 5ac28b3 commit 4af5e65

File tree

4 files changed

+77
-21
lines changed

4 files changed

+77
-21
lines changed

‎pagespeed/system/serf_url_async_fetcher.cc‎

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ apr_status_t serf_ssl_set_certificates_directory(serf_ssl_context_t *ssl_ctx,
8282
const char* path);
8383
apr_status_t serf_ssl_set_certificates_file(serf_ssl_context_t *ssl_ctx,
8484
const char* file);
85+
int serf_ssl_check_host(const serf_ssl_certificate_t *cert,
86+
const char* hostname);
87+
8588
} // extern "C"
8689

8790
namespace net_instaweb {
@@ -237,18 +240,19 @@ int64 SerfFetch::TimeDuration() const {
237240

238241
#if SERF_HTTPS_FETCHING
239242
// static
240-
apr_status_t SerfFetch::SSLCertError(void *data, int failures,
243+
apr_status_t SerfFetch::SSLCertValidate(void *data, int failures,
241244
const serf_ssl_certificate_t *cert) {
242-
return static_cast<SerfFetch*>(data)->HandleSSLCertErrors(failures, 0);
245+
return static_cast<SerfFetch*>(data)->HandleSSLCertValidation(
246+
failures, 0, cert);
243247
}
244248

245249
// static
246-
apr_status_t SerfFetch::SSLCertChainError(
250+
apr_status_t SerfFetch::SSLCertChainValidate(
247251
void *data, int failures, int error_depth,
248252
const serf_ssl_certificate_t * const *certs,
249253
apr_size_t certs_count) {
250-
return static_cast<SerfFetch*>(data)->HandleSSLCertErrors(failures,
251-
error_depth);
254+
return static_cast<SerfFetch*>(data)->HandleSSLCertValidation(
255+
failures, error_depth, NULL);
252256
}
253257
#endif
254258

@@ -293,12 +297,11 @@ apr_status_t SerfFetch::ConnectionSetup(
293297
}
294298
}
295299

296-
serf_ssl_server_cert_callback_set(fetch->ssl_context_, SSLCertError,
297-
fetch);
300+
serf_ssl_server_cert_callback_set(
301+
fetch->ssl_context_, SSLCertValidate, fetch);
298302

299-
serf_ssl_server_cert_chain_callback_set(fetch->ssl_context_,
300-
SSLCertError, SSLCertChainError,
301-
fetch);
303+
serf_ssl_server_cert_chain_callback_set(
304+
fetch->ssl_context_, SSLCertValidate, SSLCertChainValidate, fetch);
302305

303306
status = serf_ssl_set_hostname(fetch->ssl_context_, fetch->sni_host_);
304307
if (status != APR_SUCCESS) {
@@ -382,14 +385,15 @@ bool SerfFetch::IsStatusOk(apr_status_t status) {
382385
}
383386

384387
#if SERF_HTTPS_FETCHING
385-
apr_status_t SerfFetch::HandleSSLCertErrors(int errors, int failure_depth) {
388+
apr_status_t SerfFetch::HandleSSLCertValidation(
389+
int errors, int failure_depth, const serf_ssl_certificate_t *cert) {
386390
// TODO(jmarantz): is there value in logging the errors and failure_depth
387391
// formals here?
388392

389-
// Note that HandleSSLCertErrors can be called multiple times for
390-
// a single request. As far as I can tell, there is value in
391-
// recording only one of these. For now, I have set up the logic
392-
// so only the last error will be printed lazilly, in ReadHeaders.
393+
// Note that HandleSSLCertValidation can be called multiple times for a single
394+
// request. As far as I can tell, there is value in recording only one of
395+
// these. For now, I have set up the logic so only the last error will be
396+
// printed lazilly, in ReadHeaders.
393397
if (((errors & SERF_SSL_CERT_SELF_SIGNED) != 0) &&
394398
!fetcher_->allow_self_signed()) {
395399
ssl_error_message_ = "SSL certificate is self-signed";
@@ -406,9 +410,35 @@ apr_status_t SerfFetch::HandleSSLCertErrors(int errors, int failure_depth) {
406410
ssl_error_message_ = "SSL certificate has an unknown error";
407411
}
408412

413+
if (ssl_error_message_ == NULL && async_fetch_ != NULL) {
414+
if (// If cert is null that means we're being called via SSLCertChainError.
415+
// We only need to check the host name matches when being called via
416+
// SSLCertError, in which case cert won't be null.
417+
cert != NULL &&
418+
// No point in checking the host if we're allowing self-signed or a made
419+
// up CA, since people can forge whatever they want and often don't
420+
// bother to make the name match.
421+
!fetcher_->allow_self_signed() &&
422+
!fetcher_->allow_unknown_certificate_authority()) {
423+
424+
DCHECK(serf_ssl_cert_depth(cert) == 0) <<
425+
"Serf should be filtering out intermediate certs before hitting us.";
426+
427+
// You would think we could do whatever serf_get.c does, but it turns
428+
// out that does no checking at all. There's x509_check_host, added in
429+
// 1.0.2, but when svn uses serf it rolls its own check because it wants
430+
// to support older versions. We generally build with boringssl, which
431+
// forked from 1.0.2 and has always had this function, but when we build
432+
// with openssl we now require 1.0.2.
433+
if (serf_ssl_check_host(cert, sni_host_) != 1) {
434+
ssl_error_message_ = "Failed to match host.";
435+
}
436+
}
437+
}
438+
409439
// Immediately call the fetch callback on a cert error. Note that
410-
// HandleSSLCertErrors is called multiple times when there is an error,
411-
// so check async_fetch before CallCallback.
440+
// HandleSSLCertValidation is called multiple times when there is an error, so
441+
// check async_fetch before CallCallback.
412442
if ((ssl_error_message_ != NULL) && (async_fetch_ != NULL)) {
413443
fetcher_->cert_errors_->Add(1);
414444
CallCallback(false); // sets async_fetch_ to null.

‎pagespeed/system/serf_url_async_fetcher.h‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -330,10 +330,10 @@ class SerfFetch : public PoolElement<SerfFetch> {
330330
// Note this must be ifdef'd because calling serf_bucket_ssl_decrypt_create
331331
// requires ssl_buckets.c in the link. ssl_buckets.c requires openssl.
332332
#if SERF_HTTPS_FETCHING
333-
static apr_status_t SSLCertError(void *data, int failures,
333+
static apr_status_t SSLCertValidate(void *data, int failures,
334334
const serf_ssl_certificate_t *cert);
335335

336-
static apr_status_t SSLCertChainError(
336+
static apr_status_t SSLCertChainValidate(
337337
void *data, int failures, int error_depth,
338338
const serf_ssl_certificate_t * const *certs,
339339
apr_size_t certs_count);
@@ -363,9 +363,13 @@ class SerfFetch : public PoolElement<SerfFetch> {
363363
// non-null for errors as a signal to ReadHeaders that we should not let
364364
// any output thorugh.
365365
//
366-
// Interpretation of two of the error conditions is configuraable:
366+
// Interpretation of two of the error conditions is configurable:
367367
// 'allow_unknown_certificate_authority' and 'allow_self_signed'.
368-
apr_status_t HandleSSLCertErrors(int errors, int failure_depth);
368+
//
369+
// If there is a cert that should be checked for a hostname match, that should
370+
// go in cert. Otherwise cert should be null.
371+
apr_status_t HandleSSLCertValidation(
372+
int errors, int failure_depth, const serf_ssl_certificate_t *cert);
369373
#endif
370374

371375
apr_status_t HandleResponse(serf_bucket_t* response);

‎pagespeed/system/serf_url_async_fetcher_test.cc‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,13 @@ TEST_F(SerfUrlAsyncFetcherTest, TestHttpsWithExplicitHost) {
652652
ExpectHttpsSucceeds(index);
653653
}
654654

655+
TEST_F(SerfUrlAsyncFetcherTest, TestHttpsFailsWithIncorrectHost) {
656+
serf_url_async_fetcher_->SetHttpsOptions("enable");
657+
int index = AddTestUrl("https://www.google.com", "");
658+
request_headers(index)->Add(HttpAttributes::kHost, "www.example.com");
659+
TestHttpsFails(index, index);
660+
}
661+
655662
TEST_F(SerfUrlAsyncFetcherTest, TestHttpsWithExplicitHostPort) {
656663
// Similar to above, but just throw in an explicit port number;
657664
// if it doesn't get properly dropped from the SNI Apache will

‎third_party/serf/instaweb_ssl_buckets.c‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,6 +1680,21 @@ int serf_ssl_cert_depth(const serf_ssl_certificate_t *cert)
16801680
return cert->depth;
16811681
}
16821682

1683+
/*
1684+
* PageSpeed addition to allow verification of certificate hostnames.
1685+
*
1686+
* Hostname must be null-terminated.
1687+
* For return values, see X509_check_host.
1688+
*/
1689+
int serf_ssl_check_host(const serf_ssl_certificate_t *cert,
1690+
const char* hostname)
1691+
{
1692+
return X509_check_host(cert->ssl_cert,
1693+
hostname,
1694+
strlen(hostname),
1695+
0 /* we don't need to set any flags */,
1696+
NULL /* we don't need the SAN or CN extracted*/);
1697+
}
16831698

16841699
apr_hash_t *serf_ssl_cert_issuer(
16851700
const serf_ssl_certificate_t *cert,

0 commit comments

Comments
 (0)