| // Copyright 2021 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "pw_tls_client/test/test_server.h" |
| |
| #include <span> |
| #include <string> |
| |
| #include "gtest/gtest.h" |
| |
| // The following header contains a set of test certificates and keys. |
| // It is generated by |
| // third_party/boringssl/py/boringssl/generate_test_data.py. |
| #include "test_certs_and_keys.h" |
| |
| #define ASSERT_OK(expr) ASSERT_EQ(pw::OkStatus(), expr) |
| |
| namespace pw::tls_client::test { |
| namespace { |
| |
| int TestClientBioRead(BIO* bio, char* out, int outl) { |
| auto read_writer = static_cast<stream::ReaderWriter*>(bio->ptr); |
| auto res = read_writer->Read(out, outl); |
| if (!res.ok()) { |
| return -1; |
| } |
| if (res.value().empty()) { |
| BIO_set_retry_read(bio); |
| return -1; |
| } |
| return res.value().size(); |
| } |
| |
| int TestClientBioWrite(BIO* bio, const char* in, int inl) { |
| auto read_writer = static_cast<stream::ReaderWriter*>(bio->ptr); |
| auto res = read_writer->Write(in, inl); |
| if (!res.ok()) { |
| return -1; |
| } |
| return inl; |
| } |
| |
| int TestClientBioNew(BIO* bio) { |
| bio->init = 1; |
| return 1; |
| } |
| |
| long TestClientBioCtrl(BIO*, int, long, void*) { return 1; } |
| |
| int TestClientBioFree(BIO*) { return 1; } |
| |
| const BIO_METHOD bio_method = { |
| BIO_TYPE_MEM, |
| "bio test server test", |
| TestClientBioWrite, |
| TestClientBioRead, |
| nullptr, |
| nullptr, |
| TestClientBioCtrl, |
| TestClientBioNew, |
| TestClientBioFree, |
| nullptr, |
| }; |
| |
| // Server needs to send certificate. Thus the send buffer needs to be bigger. |
| std::array<std::byte, 4096> server_send_buffer; |
| std::array<std::byte, 512> server_receive_buffer; |
| |
| // Create a raw BoringSSL client and load test trust anchors. |
| void CreateSSLClient(bssl::UniquePtr<SSL_CTX>* ctx, |
| bssl::UniquePtr<SSL>* client, |
| stream::ReaderWriter* read_writer) { |
| *ctx = bssl::UniquePtr<SSL_CTX>(SSL_CTX_new(TLS_method())); |
| ASSERT_NE(*ctx, nullptr); |
| *client = bssl::UniquePtr<SSL>(SSL_new(ctx->get())); |
| ASSERT_NE(*client, nullptr); |
| BIO* bio = BIO_new(&bio_method); |
| ASSERT_NE(bio, nullptr); |
| |
| // Load trust anchors to client |
| auto store = SSL_CTX_get_cert_store(ctx->get()); |
| X509_VERIFY_PARAM_clear_flags(store->param, X509_V_FLAG_USE_CHECK_TIME); |
| const pw::ConstByteSpan kTrustAnchors[] = {kRootACert, kRootBCert}; |
| for (auto cert : kTrustAnchors) { |
| auto res = ParseDerCertificate(cert); |
| ASSERT_OK(res.status()); |
| ASSERT_EQ(X509_STORE_add_cert(store, res.value()), 1); |
| X509_free(res.value()); |
| } |
| bio->ptr = read_writer; |
| SSL_set_bio(client->get(), bio, bio); |
| } |
| |
| } // namespace |
| |
| TEST(InMemoryTestServer, NormalConnectionSucceed) { |
| InMemoryTestServer server(server_receive_buffer, server_send_buffer); |
| const ConstByteSpan kIntermediates[] = {kSubCACert}; |
| ASSERT_OK(server.Initialize(kServerKey, kServerCert, kIntermediates)); |
| |
| // Create a raw BoringSSL client |
| bssl::UniquePtr<SSL_CTX> client_ctx; |
| bssl::UniquePtr<SSL> ssl_client; |
| CreateSSLClient(&client_ctx, &ssl_client, &server); |
| |
| // Handshake should be OK |
| ASSERT_EQ(SSL_connect(ssl_client.get()), 1); |
| ASSERT_TRUE(server.SessionEstablished()); |
| |
| // Client should pass certificate verification. |
| ASSERT_EQ(SSL_get_verify_result(ssl_client.get()), 0); |
| |
| // Send some data to server |
| const char send_expected[] = "hello"; |
| int send_len = |
| SSL_write(ssl_client.get(), send_expected, sizeof(send_expected)); |
| ASSERT_EQ(static_cast<size_t>(send_len), sizeof(send_expected)); |
| |
| char receive_actual[sizeof(send_expected) + 1] = {0}; |
| int read_ret = |
| SSL_read(ssl_client.get(), receive_actual, sizeof(receive_actual)); |
| ASSERT_EQ(static_cast<size_t>(read_ret), sizeof(send_expected)); |
| ASSERT_STREQ(send_expected, receive_actual); |
| |
| // Shutdown |
| EXPECT_FALSE(server.ClientShutdownReceived()); |
| ASSERT_NE(SSL_shutdown(ssl_client.get()), -1); |
| ASSERT_TRUE(server.ClientShutdownReceived()); |
| } |
| |
| TEST(InMemoryTestServer, BufferTooSmallErrorsOut) { |
| std::array<std::byte, 1> insufficient_buffer; |
| InMemoryTestServer server(server_receive_buffer, insufficient_buffer); |
| const ConstByteSpan kIntermediates[] = {kSubCACert}; |
| ASSERT_OK(server.Initialize(kServerKey, kServerCert, kIntermediates)); |
| |
| // Create a raw BoringSSL client |
| bssl::UniquePtr<SSL_CTX> client_ctx; |
| bssl::UniquePtr<SSL> ssl_client; |
| CreateSSLClient(&client_ctx, &ssl_client, &server); |
| |
| // Handshake should fail as server shouldn't have enough buffer |
| ASSERT_NE(SSL_connect(ssl_client.get()), 1); |
| ASSERT_EQ(server.GetLastBioStatus(), Status::ResourceExhausted()); |
| } |
| |
| } // namespace pw::tls_client::test |