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 =