Fix SSL_load_client_CA_file when given an empty file. https://boringssl-review.googlesource.com/c/boringssl/+/53007 inadvertently changed the semantics of SSL_load_client_CA_file slightly. The original implementation, by delaying allocating ret, would fail rather than return an empty list. Fix this and add a test. We don't have much support for testing filesystem-related things yet, so I've just used /dev/null and gated it to Linux + macOS for now. If we need it later, we can add temporary file support to the test_support library. Change-Id: If77dd493a433819a65378d76cf400cce48c0abaa Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/53785 Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: Adam Langley <agl@google.com> Auto-Submit: David Benjamin <davidben@google.com>
diff --git a/ssl/ssl_file.cc b/ssl/ssl_file.cc index 1625200..d867cf1 100644 --- a/ssl/ssl_file.cc +++ b/ssl/ssl_file.cc
@@ -128,25 +128,8 @@ return X509_NAME_cmp(*a, *b); } -STACK_OF(X509_NAME) *SSL_load_client_CA_file(const char *file) { - bssl::UniquePtr<STACK_OF(X509_NAME)> ret(sk_X509_NAME_new_null()); - if (ret == nullptr || - !SSL_add_file_cert_subjects_to_stack(ret.get(), file)) { - return nullptr; - } - return ret.release(); -} - -int SSL_add_file_cert_subjects_to_stack(STACK_OF(X509_NAME) *out, - const char *file) { - bssl::UniquePtr<BIO> in(BIO_new_file(file, "r")); - if (in == nullptr) { - return 0; - } - return SSL_add_bio_cert_subjects_to_stack(out, in.get()); -} - -int SSL_add_bio_cert_subjects_to_stack(STACK_OF(X509_NAME) *out, BIO *bio) { +static int add_bio_cert_subjects_to_stack(STACK_OF(X509_NAME) *out, BIO *bio, + bool allow_empty) { // This function historically sorted |out| after every addition and skipped // duplicates. This implementation preserves that behavior, but only sorts at // the end, to avoid a quadratic running time. Existing duplicates in |out| @@ -165,15 +148,20 @@ RestoreCmpFunc restore = {out, sk_X509_NAME_set_cmp_func(out, xname_cmp)}; sk_X509_NAME_sort(out); + bool first = true; for (;;) { bssl::UniquePtr<X509> x509( PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)); if (x509 == nullptr) { + if (first && !allow_empty) { + return 0; + } // TODO(davidben): This ignores PEM syntax errors. It should only succeed // on |PEM_R_NO_START_LINE|. ERR_clear_error(); break; } + first = false; X509_NAME *subject = X509_get_subject_name(x509.get()); // Skip if already present in |out|. Duplicates in |to_append| will be @@ -211,6 +199,33 @@ return 1; } +STACK_OF(X509_NAME) *SSL_load_client_CA_file(const char *file) { + bssl::UniquePtr<BIO> in(BIO_new_file(file, "r")); + if (in == nullptr) { + return nullptr; + } + bssl::UniquePtr<STACK_OF(X509_NAME)> ret(sk_X509_NAME_new_null()); + if (ret == nullptr || // + !add_bio_cert_subjects_to_stack(ret.get(), in.get(), + /*allow_empty=*/false)) { + return nullptr; + } + return ret.release(); +} + +int SSL_add_file_cert_subjects_to_stack(STACK_OF(X509_NAME) *out, + const char *file) { + bssl::UniquePtr<BIO> in(BIO_new_file(file, "r")); + if (in == nullptr) { + return 0; + } + return SSL_add_bio_cert_subjects_to_stack(out, in.get()); +} + +int SSL_add_bio_cert_subjects_to_stack(STACK_OF(X509_NAME) *out, BIO *bio) { + return add_bio_cert_subjects_to_stack(out, bio, /*allow_empty=*/true); +} + int SSL_use_certificate_file(SSL *ssl, const char *file, int type) { int reason_code; BIO *in;
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc index 1ea0893..295b6ef 100644 --- a/ssl/ssl_test.cc +++ b/ssl/ssl_test.cc
@@ -8332,6 +8332,15 @@ } } +#if defined(OPENSSL_LINUX) || defined(OPENSSL_APPLE) +TEST(SSLTest, EmptyClientCAList) { + // Use /dev/null on POSIX systems as an empty file. + bssl::UniquePtr<STACK_OF(X509_NAME)> names( + SSL_load_client_CA_file("/dev/null")); + EXPECT_FALSE(names); +} +#endif // OPENSSL_LINUX || OPENSSL_APPLE + TEST(SSLTest, EmptyWriteBlockedOnHandshakeData) { bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method())); bssl::UniquePtr<SSL_CTX> server_ctx =