]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#4274] Checkpoint: added TLS request UTs
authorFrancis Dupont <fdupont@isc.org>
Mon, 26 Jan 2026 10:12:54 +0000 (11:12 +0100)
committerFrancis Dupont <fdupont@isc.org>
Mon, 9 Feb 2026 21:05:47 +0000 (22:05 +0100)
src/hooks/dhcp/radius/tests/meson.build
src/hooks/dhcp/radius/tests/status_unittests.cc
src/hooks/dhcp/radius/tests/tls_request_unittests.cc [new file with mode: 0644]
src/hooks/dhcp/radius/tests/tls_status_unittests.cc

index c333258ae6fff4afe4d962a07102470a2bd60cc3..c6d29eca28ef7a31203c3d0eb7d378e139e41284 100644 (file)
@@ -27,6 +27,7 @@ dhcp_radius_tests = executable(
     'status_unittests.cc',
     'sync_request_unittests.cc',
     'tcp_exchange_unittests.cc',
+    'tls_request_unittests.cc',
     'tls_status_unittests.cc',
     'utils_unittests.cc',
     cpp_args: [
index 898274724b2eb044ba2342ec0b08278cbb72b3ad..b148c5479e672e15038fcc903e5467f8b1165210 100644 (file)
@@ -5,6 +5,7 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 #include <config.h>
+
 #include <cryptolink/crypto_hash.h>
 #include <asiolink/asio_wrapper.h>
 #include <asiolink/interval_timer.h>
diff --git a/src/hooks/dhcp/radius/tests/tls_request_unittests.cc b/src/hooks/dhcp/radius/tests/tls_request_unittests.cc
new file mode 100644 (file)
index 0000000..b30652b
--- /dev/null
@@ -0,0 +1,1193 @@
+// Copyright (C) 2020-2025 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @file This file contains tests which exercise the load and unload
+/// functions in the Radius hooks library. In order to test the load
+/// function, one must be able to pass it hook library parameters. The
+/// the only way to populate these parameters is by actually loading the
+/// library via HooksManager::loadLibraries().
+
+#include <config.h>
+
+#include <cryptolink/crypto_hash.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/tls_socket.h>
+#include <asiolink/testutils/test_tls.h>
+#include <database/database_connection.h>
+#include <dhcpsrv/host_data_source_factory.h>
+#include <dhcpsrv/host_mgr.h>
+#include <radius.h>
+#include <radius_request.h>
+#include <radius_tls.h>
+#include <attribute_test.h>
+#include <gtest/gtest.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <atomic>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::radius;
+using namespace isc::util;
+namespace ph = std::placeholders;
+namespace ba = boost::asio::ip;
+
+namespace {
+
+/// Fake server.
+const char SERVER_ADDRESS[] = "::1";
+const unsigned short SERVER_PORT = 12083;
+
+/// Configs.
+const char* CONFIGS[] = {
+    // CONFIGURATION 0
+"{\n"
+"    \"access\": {\n"
+"        \"enabled\": true\n"
+"    },\n"
+"    \"accounting\": {\n"
+"        \"enabled\": true\n"
+"    },\n"
+"    \"common-tls\": {\n"
+"        \"servers\": [ {\n"
+"            \"name\": \"::1\",\n"
+"            \"port\": 12083,\n"
+"            \"trust-anchor\": \"" TEST_CA_DIR "\",\n"
+"            \"cert-file\": \"" TEST_CA_DIR "/kea-client.crt\",\n"
+"            \"key-file\": \"" TEST_CA_DIR "/kea-client.key\"\n"
+"        } ]\n"
+"    },\n"
+"    \"protocol\": \"TLS\",\n"
+"    \"retries\": 0,\n"
+"    \"timeout\": 1 }\n"
+};
+
+/// @brief Class for callbacks.
+class Callback {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param error_code Reference to the error code.
+    /// @param size Reference to the returned size.
+    /// @param flag Reference to called flag.
+    Callback(boost::system::error_code& error_code,
+             size_t& size, atomic<bool>& flag)
+        : error_code_(error_code), size_(size), called_(flag) { }
+
+    /// @brief Callback.
+    ///
+    /// @param ec Error code.
+    void operator()(boost::system::error_code ec) {
+        error_code_ = ec;
+        size_ = 0;
+        called_ = true;
+    }
+
+    /// @brief Callback.
+    ///
+    /// @param ec Error code.
+    /// @param size Size.
+    void operator()(boost::system::error_code ec, size_t size) {
+        error_code_ = ec;
+        size_ = size;
+        called_ = true;
+    }
+
+    /// @brief Error code.
+    boost::system::error_code& error_code_;
+
+    /// @brief Size.
+    size_t& size_;
+
+    /// @brief Called flag.
+    atomic<bool>& called_;
+};
+
+/// @brief Test fixture for testing synchronous communication for
+/// the radius library.
+class TlsRequestTest : public radius::test::AttributeTest {
+public:
+
+    /// @brief Constructor.
+    TlsRequestTest() :
+        radius::test::AttributeTest(),
+        impl_(RadiusImpl::instance()), service_(new IOService()),
+        server_ep_(ba::tcp::v6(), SERVER_PORT), server_ctx_(),
+        acceptor_(service_->getInternalIOService(), server_ep_),
+        server_(), accept_error_code_(), accepted_(false),
+        handshake_error_code_(), handshake_(false),
+        receive_buffer_(4096), receive_error_code_(), received_(false),
+        send_buffer_(), send_error_code_(), sent_(false),
+        timer_(service_), timeout_(false), which_(""),
+        subnet_id_(SUBNET_ID_UNUSED), send_attributes_(),
+        result_(OK_RC), received_attributes_(),
+        handler_auth_(), handler_acct_(), finished_(false) {
+
+        acceptor_.set_option(ba::tcp::acceptor::reuse_address(true));
+        asiolink::test::configServer(server_ctx_);
+        server_.reset(new TlsStreamImpl(service_->getInternalIOService(),
+                                        server_ctx_->getContext()));
+        HostMgr::create();
+        auto factory = [] (const isc::db::DatabaseConnection::ParameterMap&) {
+            return (HostDataSourcePtr());
+        };
+        HostDataSourceFactory::registerFactory("cache", factory);
+
+        impl_.reset();
+        impl_.setIOService(service_);
+        impl_.setIOContext(service_);
+        impl_.tcp_client_.reset(new isc::tcp::TcpClient(service_, false, 0));
+    }
+
+    /// @brief Destructor
+    virtual ~TlsRequestTest() {
+        timer_stop();
+        server_close();
+
+        // As a best practice, call any remaining handlers.
+        service_->stopAndPoll();
+        HostDataSourceFactory::deregisterFactory("cache");
+    }
+
+    // Accept callback.
+    void acceptCallback(const boost::system::error_code& ec) {
+        accepted_ = true;
+        accept_error_code_ = ec;
+    }
+
+    // Handshake callback.
+    void handshakeCallback(const boost::system::error_code& ec) {
+        handshake_ = true;
+        handshake_error_code_ =  ec;
+    }
+
+    /// @brief Poll the I/O service.
+    /// From boost asio documentation: check if a handler is ready
+    /// and when there is one execute it.
+    void poll();
+
+    /// @brief Fake server accept new connection on the socket.
+    void server_accept();
+
+    /// @brief Fake server handshake.
+    void server_handshake();
+
+    /// @brief Close fake server socket.
+    void server_close();
+
+    /// @brief Timeout callback.
+    void timeout();
+
+    /// @brief Start timer.
+    ///
+    /// @param deadline Maximum time the test can run in milliseconds.
+    void timer_start(long deadline);
+
+    /// @brief Stop timer.
+    void timer_stop();
+
+    /// @brief Done authentication handler.
+    ///
+    /// @param handler Handler to the RadiusAsyncAuth.
+    void doneAuth(int result, AttributesPtr received);
+
+    /// @brief Done accounting handler.
+    ///
+    /// @param handler Handler to the RadiusAsyncAcct.
+    void doneAcct(int result);
+
+    /// @brief Run a request handler i.e. the client part.
+    ///
+    /// The server side is simulated by the individual test code.
+    void run();
+
+    /// @brief Radius implementation.
+    RadiusImpl& impl_;
+
+    /// @brief IO service.
+    IOServicePtr service_;
+
+    /// @brief Fake server endpoint.
+    ba::tcp::endpoint server_ep_;
+
+    /// @brief Fake server TLS context.
+    TlsContextPtr server_ctx_;
+
+    /// @brief Fake server acceptor.
+    ba::tcp::acceptor acceptor_;
+
+    /// @brief Fake server socket.
+    boost::scoped_ptr<TlsStreamImpl> server_;
+
+     /// @brief Accept error code.
+    boost::system::error_code accept_error_code_;
+
+    /// @brief Accepted flag.
+    atomic<bool> accepted_;
+
+    /// @brief Handshake error code.
+    boost::system::error_code handshake_error_code_;
+
+    /// @brief Handshake flag.
+    atomic<bool> handshake_;
+
+    /// @brief Receive buffer.
+    vector<uint8_t> receive_buffer_;
+
+    /// @brief Receive error code.
+    boost::system::error_code receive_error_code_;
+
+    /// @brief Received flag.
+    atomic<bool> received_;
+
+    /// @brief Send buffer.
+    vector<uint8_t> send_buffer_;
+
+    /// @brief Send error code.
+    boost::system::error_code send_error_code_;
+
+    /// @brief Sent flag.
+    atomic<bool> sent_;
+
+    /// @brief Timer for timeout.
+    IntervalTimer timer_;
+
+    /// @brief Timeout flag.
+    atomic<bool> timeout_;
+
+    /// @brief Fake server type: Authentication ("access") or
+    /// Accounting ("accounting").
+    string which_;
+
+    /// @brief Subnet ID (aka client/NAS port).
+    uint32_t subnet_id_;
+
+    /// @brief Attributes to send.
+    AttributesPtr send_attributes_;
+
+    /// @brief Request result.
+    int result_;
+
+    /// @brief Received attributes.
+    AttributesPtr received_attributes_;
+
+    /// @brief Handler for authentication.
+    RadiusAsyncAuthPtr handler_auth_;
+
+    /// @brief Handler for accounting..
+    RadiusAsyncAcctPtr handler_acct_;
+
+    /// @brief Finished flag.
+    atomic<bool> finished_;
+};
+
+void
+TlsRequestTest::poll() {
+    service_->poll();
+    EXPECT_NO_THROW(IfaceMgr::instance().receive4(0, 1000));
+}
+
+void
+TlsRequestTest::server_accept() {
+    // Accept new connection on the fake server socket.
+    acceptor_.async_accept(server_->lowest_layer(),
+                           std::bind(&TlsRequestTest::acceptCallback,
+                                     this, ph::_1));
+}
+
+void
+TlsRequestTest::server_handshake() {
+    server_->async_handshake(roleToImpl(TlsRole::SERVER),
+                             std::bind(&TlsRequestTest::handshakeCallback,
+                                       this, ph::_1));
+}
+
+void
+TlsRequestTest::server_close() {
+    if (server_) {
+        EXPECT_NO_THROW(server_->lowest_layer().close());
+    }
+    server_.reset();
+    acceptor_.close();
+}
+
+void
+TlsRequestTest::timeout() {
+    timeout_ = true;
+    FAIL() << "timeout";
+}
+
+void
+TlsRequestTest::timer_start(long deadline) {
+    timer_.setup(std::bind(&TlsRequestTest::timeout, this),
+                 deadline, IntervalTimer::ONE_SHOT);
+}
+
+void
+TlsRequestTest::timer_stop() {
+    timer_.cancel();
+}
+
+void
+TlsRequestTest::doneAuth(int result, AttributesPtr received) {
+    result_ = result;
+    received_attributes_ = received;
+    finished_ = true;
+}
+
+void
+TlsRequestTest::doneAcct(int result) {
+    result_ = result;
+    finished_ = true;
+}
+
+void
+TlsRequestTest::run() {
+    if (which_ == "access") {
+        handler_auth_.reset(new RadiusAsyncAuth(subnet_id_,
+                                send_attributes_,
+                                std::bind(&TlsRequestTest::doneAuth,
+                                          this, ph::_1, ph::_2)));
+        handler_auth_->start();
+    } else if (which_ == "accounting") {
+        handler_acct_.reset(new RadiusAsyncAcct(subnet_id_,
+                                send_attributes_,
+                                std::bind(&TlsRequestTest::doneAcct,
+                                          this, ph::_1)));
+        handler_acct_->start();
+    } else {
+        FAIL() << "which is not 'access' nor 'accounting'";
+    }
+}
+
+/// Verify what happens when there is no listening server at all.
+TEST_F(TlsRequestTest, noServer) {
+    // Use CONFIGS[0].
+    ElementPtr json;
+    ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0]));
+    ASSERT_NO_THROW(impl_.init(json));
+
+    // Build request parameters.
+    which_ = "access";
+    subnet_id_ = 1;
+    if (!send_attributes_) {
+        send_attributes_.reset(new Attributes());
+    }
+    send_attributes_->add(Attribute::fromString(PW_USER_NAME, "user"));
+
+    // Launch request handler i.e. the client code to test.
+    run();
+
+    // Start timer for 1.5s timeout.
+    timer_start(1500);
+
+    // Busy loop.
+    while (!finished_ && !timeout_) {
+        poll();
+    }
+
+    EXPECT_TRUE(finished_);
+    EXPECT_FALSE(timeout_);
+
+    // Done.
+    timer_stop();
+    service_->stopWork();
+
+    // Check result.
+    EXPECT_FALSE(received_attributes_);
+    EXPECT_EQ(TIMEOUT_RC, result_);
+}
+
+/// Verify what happens when no response is sent.
+TEST_F(TlsRequestTest, noResponse) {
+    // Use CONFIGS[0].
+    ElementPtr json;
+    ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0]));
+    ASSERT_NO_THROW(impl_.init(json));
+
+    // Build request parameters.
+    which_ = "accounting";
+    subnet_id_ = 1;
+    if (!send_attributes_) {
+        send_attributes_.reset(new Attributes());
+    }
+    send_attributes_->add(Attribute::fromString(PW_USER_NAME, "user"));
+
+    // Start timer for 1.5s timeout.
+    timer_start(1500);
+
+    // Accept server socket.
+    server_accept();
+
+    // Launch request handler i.e. the client code to test.
+    run();
+
+    // Wait for the connection to be established.
+    while (!accepted_) {
+        ASSERT_FALSE(timeout_);
+        poll();
+    }
+    ASSERT_FALSE(accept_error_code_);
+
+    // Perform handshake.
+    server_handshake();
+    while (!handshake_) {
+        ASSERT_FALSE(timeout_);
+        poll();
+    }
+    ASSERT_FALSE(handshake_error_code_);
+
+    // Push a receiver on the socket.
+    size_t size = 0;
+    Callback receive_callback(receive_error_code_, size, received_);
+    server_->async_read_some(
+        boost::asio::buffer(&receive_buffer_[0], receive_buffer_.size()),
+        receive_callback);
+
+    // Busy loop.
+    while ((!received_ || !finished_) && !timeout_) {
+        poll();
+    }
+
+    EXPECT_TRUE(finished_);
+    EXPECT_TRUE(received_);
+    EXPECT_FALSE(timeout_);
+
+    // Done.
+    timer_stop();
+    service_->stopWork();
+
+    // Check result.
+    EXPECT_EQ(TIMEOUT_RC, result_);
+
+    // Check received request.
+    receive_buffer_.resize(size);
+    ASSERT_LE(20, size);
+    EXPECT_EQ(PW_ACCOUNTING_REQUEST, receive_buffer_[0]);
+    uint16_t length = (receive_buffer_[2] << 8) | receive_buffer_[3];
+    ASSERT_LE(length, size);
+    EXPECT_GE(4096, length);
+
+    // Check attributes.
+    EXPECT_EQ(56, size);
+    uint8_t expected[] = {
+        0x01, // User-Name
+        0x06, // length
+        0x75, 0x73, 0x65, 0x72, // "user"
+        0x05, // NAS-Port
+        0x06, // length
+        0x00, 0x00, 0x00, 0x01, // 1
+        0x29, // Acct-Delay-Time
+        0x06, // length
+        0x00, 0x00, 0x00, 0x00, // 0
+        0x5f, // NAS-IPv6-Address
+        0x12, // length
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x01 // ::1
+    };
+    size_t expected_len = size - AUTH_HDR_LEN;
+    if (expected_len > sizeof(expected)) {
+        expected_len = sizeof(expected);
+    }
+    EXPECT_EQ(0, memcmp(expected,
+                        &receive_buffer_[AUTH_HDR_LEN],
+                        expected_len));
+
+    // Check authenticator.
+    vector<unsigned char> auth_input(size + 6);
+    memmove(&auth_input[0], &receive_buffer_[0], size);
+    memset(&auth_input[4], 0, AUTH_VECTOR_LEN);
+    auth_input[size] = 'r';
+    auth_input[size + 1] = 'a';
+    auth_input[size + 2] = 'd';
+    auth_input[size + 3] = 's';
+    auth_input[size + 4] = 'e';
+    auth_input[size + 5] = 'c';
+    OutputBuffer auth_output(AUTH_VECTOR_LEN);
+    digest(&auth_input[0], size + 6, isc::cryptolink::MD5,
+           auth_output, AUTH_VECTOR_LEN);
+    EXPECT_EQ(0, memcmp(auth_output.getData(),
+                        &receive_buffer_[4],
+                        AUTH_VECTOR_LEN));
+}
+
+/// Verify what happens with Access-Accept response.
+TEST_F(TlsRequestTest, accept) {
+    // Use CONFIGS[0].
+    ElementPtr json;
+    ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0]));
+    ASSERT_NO_THROW(impl_.init(json));
+
+    // Build request parameters.
+    which_ = "access";
+    subnet_id_ = 1;
+    if (!send_attributes_) {
+        send_attributes_.reset(new Attributes());
+    }
+    send_attributes_->add(Attribute::fromString(PW_USER_NAME, "user"));
+
+    // Start timer for 1.5s timeout.
+    timer_start(1500);
+
+    // Accept server socket.
+    server_accept();
+
+    // Launch request handler i.e. the client code to test.
+    run();
+
+    // Wait for the connection to be established.
+    while (!accepted_) {
+        ASSERT_FALSE(timeout_);
+        poll();
+    }
+    ASSERT_FALSE(accept_error_code_);
+
+    // Perform handshake.
+    server_handshake();
+    while (!handshake_) {
+        ASSERT_FALSE(timeout_);
+        poll();
+    }
+    ASSERT_FALSE(handshake_error_code_);
+
+    // Push a receiver on the socket.
+    size_t size = 0;
+    Callback receive_callback(receive_error_code_, size, received_);
+    server_->async_read_some(
+        boost::asio::buffer(&receive_buffer_[0], receive_buffer_.size()),
+        receive_callback);
+
+    // Busy loop.
+    while (!received_ && !timeout_) {
+        poll();
+    }
+
+    ASSERT_TRUE(received_);
+    ASSERT_FALSE(timeout_);
+
+    // Sanity checks on the request.
+    receive_buffer_.resize(size);
+    ASSERT_LE(26, size);
+
+    // Build the response.
+    size = AUTH_HDR_LEN + 2 + 4;          // header + User-Name attribute.
+    send_buffer_.resize(size);
+    send_buffer_[0] = PW_ACCESS_ACCEPT;   // Access-Accept.
+    send_buffer_[1] = receive_buffer_[1]; // Copy id.
+    send_buffer_[2] = size >> 8;          // Length
+    send_buffer_[3] = size & 0xff;
+    // Copy the authenticator.
+    memmove(&send_buffer_[4], &receive_buffer_[4], AUTH_VECTOR_LEN);
+    // Copy the User-Name attribute.
+    memmove(&send_buffer_[AUTH_HDR_LEN], &receive_buffer_[AUTH_HDR_LEN], 6);
+
+    // Compute the authenticator.
+    vector<unsigned char> auth_input(size + 6);
+    memmove(&auth_input[0], &send_buffer_[0], size);
+    auth_input[size] = 'r';
+    auth_input[size + 1] = 'a';
+    auth_input[size + 2] = 'd';
+    auth_input[size + 3] = 's';
+    auth_input[size + 4] = 'e';
+    auth_input[size + 5] = 'c';
+    OutputBuffer auth_output(AUTH_VECTOR_LEN);
+    digest(&auth_input[0], size + 6, isc::cryptolink::MD5,
+           auth_output, AUTH_VECTOR_LEN);
+    memmove(&send_buffer_[4], auth_output.getData(), AUTH_VECTOR_LEN);
+
+    // Push a sender on the socket.
+    size_t sent_size = 0;
+    Callback send_callback(send_error_code_, sent_size, sent_);
+    boost::asio::async_write(*server_,
+                             boost::asio::buffer(&send_buffer_[0], size),
+                             send_callback);
+
+    // Second busy loop.
+    while ((!sent_ || !finished_) && !timeout_) {
+        poll();
+    }
+
+    EXPECT_TRUE(finished_);
+    EXPECT_TRUE(sent_);
+    EXPECT_FALSE(timeout_);
+    EXPECT_EQ(size, sent_size);
+
+    // Done.
+    timer_stop();
+    service_->stopWork();
+
+    // Check result.
+    EXPECT_EQ(OK_RC, result_);
+    ASSERT_TRUE(received_attributes_);
+    ASSERT_EQ(1, received_attributes_->size());
+    ASSERT_EQ(1, received_attributes_->count(1));
+    const ConstAttributePtr& attr = received_attributes_->get(1);
+    ASSERT_TRUE(attr);
+    EXPECT_EQ("User-Name='user'", attr->toText());
+}
+
+/// Verify what happens with Accounting-Response response.
+TEST_F(TlsRequestTest, response) {
+    // Use CONFIGS[0].
+    ElementPtr json;
+    ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0]));
+    ASSERT_NO_THROW(impl_.init(json));
+
+    // Build request parameters.
+    which_ = "accounting";
+    subnet_id_ = 1;
+    if (!send_attributes_) {
+        send_attributes_.reset(new Attributes());
+    }
+    send_attributes_->add(Attribute::fromString(PW_USER_NAME, "user"));
+
+    // Start timer for 1.5s timeout.
+    timer_start(1500);
+
+    // Accept server socket.
+    server_accept();
+
+    // Launch request handler i.e. the client code to test.
+    run();
+
+    // Wait for the connection to be established.
+    while (!accepted_) {
+        ASSERT_FALSE(timeout_);
+        poll();
+    }
+    ASSERT_FALSE(accept_error_code_);
+
+    // Perform handshake.
+    server_handshake();
+    while (!handshake_) {
+        ASSERT_FALSE(timeout_);
+        poll();
+    }
+    ASSERT_FALSE(handshake_error_code_);
+
+    // Push a receiver on the socket.
+    size_t size = 0;
+    Callback receive_callback(receive_error_code_, size, received_);
+    server_->async_read_some(
+        boost::asio::buffer(&receive_buffer_[0], receive_buffer_.size()),
+        receive_callback);
+
+    // Busy loop.
+    while (!received_ && !timeout_) {
+        poll();
+    }
+
+    ASSERT_TRUE(received_);
+    ASSERT_FALSE(timeout_);
+
+    // Sanity checks on the request.
+    receive_buffer_.resize(size);
+    ASSERT_LE(20, size);
+
+    // Build the response.
+    size = AUTH_HDR_LEN;                       // header (no attributes).
+    send_buffer_.resize(size);
+    send_buffer_[0] = PW_ACCOUNTING_RESPONSE;  // Access-Accept.
+    send_buffer_[1] = receive_buffer_[1];      // Copy id.
+    send_buffer_[2] = size >> 8;               // Length
+    send_buffer_[3] = size & 0xff;
+    // Copy the authenticator.
+    memmove(&send_buffer_[4], &receive_buffer_[4], AUTH_VECTOR_LEN);
+
+    // Compute the authenticator.
+    vector<unsigned char> auth_input(size + 6);
+    memmove(&auth_input[0], &send_buffer_[0], size);
+    auth_input[size] = 'r';
+    auth_input[size + 1] = 'a';
+    auth_input[size + 2] = 'd';
+    auth_input[size + 3] = 's';
+    auth_input[size + 4] = 'e';
+    auth_input[size + 5] = 'c';
+    OutputBuffer auth_output(AUTH_VECTOR_LEN);
+    digest(&auth_input[0], size + 6, isc::cryptolink::MD5,
+           auth_output, AUTH_VECTOR_LEN);
+    memmove(&send_buffer_[4], auth_output.getData(), AUTH_VECTOR_LEN);
+
+    // Push a sender on the socket.
+    size_t sent_size = 0;
+    Callback send_callback(send_error_code_, sent_size, sent_);
+    boost::asio::async_write(*server_,
+                             boost::asio::buffer(&send_buffer_[0], size),
+                             send_callback);
+
+    // Second busy loop.
+    while ((!sent_ || !finished_) && !timeout_) {
+        poll();
+    }
+
+    EXPECT_TRUE(finished_);
+    EXPECT_TRUE(sent_);
+    EXPECT_FALSE(timeout_);
+    EXPECT_EQ(size, sent_size);
+
+    // Done.
+    timer_stop();
+    service_->stopWork();
+
+    // Check result.
+    EXPECT_EQ(OK_RC, result_);
+}
+
+/// Verify what happens with bad Access-Accept response.
+TEST_F(TlsRequestTest, badAccept) {
+    // Use CONFIGS[0].
+    ElementPtr json;
+    ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0]));
+    ASSERT_NO_THROW(impl_.init(json));
+
+    // Build request parameters.
+    which_ = "access";
+    subnet_id_ = 1;
+    if (!send_attributes_) {
+        send_attributes_.reset(new Attributes());
+    }
+    send_attributes_->add(Attribute::fromString(PW_USER_NAME, "user"));
+
+    // Start timer for 1.5s timeout.
+    timer_start(1500);
+
+    // Accept server socket.
+    server_accept();
+
+    // Launch request handler i.e. the client code to test.
+    run();
+
+    // Wait for the connection to be established.
+    while (!accepted_) {
+        ASSERT_FALSE(timeout_);
+        poll();
+    }
+    ASSERT_FALSE(accept_error_code_);
+
+    // Perform handshake.
+    server_handshake();
+    while (!handshake_) {
+        ASSERT_FALSE(timeout_);
+        poll();
+    }
+    ASSERT_FALSE(handshake_error_code_);
+
+    // Push a receiver on the socket.
+    size_t size = 0;
+    Callback receive_callback(receive_error_code_, size, received_);
+    server_->async_read_some(
+        boost::asio::buffer(&receive_buffer_[0], receive_buffer_.size()),
+        receive_callback);
+
+    // Busy loop.
+    while (!received_ && !timeout_) {
+        poll();
+    }
+
+    ASSERT_TRUE(received_);
+    ASSERT_FALSE(timeout_);
+
+    // Sanity checks on the request.
+    receive_buffer_.resize(size);
+    ASSERT_LE(26, size);
+
+    // Build the response.
+    size = AUTH_HDR_LEN + 2 + 4;          // header + User-Name attribute.
+    send_buffer_.resize(size);
+    send_buffer_[0] = PW_ACCESS_ACCEPT;   // Access-Accept.
+    // There are a lot of ways to get an error including this one.
+    send_buffer_[1] = receive_buffer_[1] ^ 0x12;
+    send_buffer_[2] = size >> 8;          // Length
+    send_buffer_[3] = size & 0xff;
+    // Copy the authenticator.
+    memmove(&send_buffer_[4], &receive_buffer_[4], AUTH_VECTOR_LEN);
+    // Copy the User-Name attribute.
+    memmove(&send_buffer_[AUTH_HDR_LEN], &receive_buffer_[AUTH_HDR_LEN], 6);
+
+    // Compute the authenticator.
+    vector<unsigned char> auth_input(size + 6);
+    memmove(&auth_input[0], &send_buffer_[0], size);
+    auth_input[size] = 'r';
+    auth_input[size + 1] = 'a';
+    auth_input[size + 2] = 'd';
+    auth_input[size + 3] = 's';
+    auth_input[size + 4] = 'e';
+    auth_input[size + 5] = 'c';
+    OutputBuffer auth_output(AUTH_VECTOR_LEN);
+    digest(&auth_input[0], size + 6, isc::cryptolink::MD5,
+           auth_output, AUTH_VECTOR_LEN);
+    memmove(&send_buffer_[4], auth_output.getData(), AUTH_VECTOR_LEN);
+
+    // Push a sender on the socket.
+    size_t sent_size = 0;
+    Callback send_callback(send_error_code_, sent_size, sent_);
+    boost::asio::async_write(*server_,
+                             boost::asio::buffer(&send_buffer_[0], size),
+                             send_callback);
+
+    // Second busy loop.
+    while ((!sent_ || !finished_) && !timeout_) {
+        poll();
+    }
+
+    EXPECT_TRUE(finished_);
+    EXPECT_TRUE(sent_);
+    EXPECT_FALSE(timeout_);
+    EXPECT_EQ(size, sent_size);
+
+    // Done.
+    timer_stop();
+    service_->stopWork();
+
+    // Check result.
+    EXPECT_EQ(BADRESP_RC, result_);
+    ASSERT_TRUE(received_attributes_);
+    ASSERT_EQ(1, received_attributes_->size());
+    ASSERT_EQ(1, received_attributes_->count(1));
+    const ConstAttributePtr& attr = received_attributes_->get(1);
+    ASSERT_TRUE(attr);
+    EXPECT_EQ("User-Name='user'", attr->toText());
+}
+
+/// Verify what happens with bad Accounting-Response response.
+TEST_F(TlsRequestTest, badResponse) {
+    // Use CONFIGS[0].
+    ElementPtr json;
+    ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0]));
+    ASSERT_NO_THROW(impl_.init(json));
+
+    // Build request parameters.
+    which_ = "accounting";
+    subnet_id_ = 1;
+    if (!send_attributes_) {
+        send_attributes_.reset(new Attributes());
+    }
+    send_attributes_->add(Attribute::fromString(PW_USER_NAME, "user"));
+
+    // Start timer for 1.5s timeout.
+    timer_start(1500);
+
+    // Accept server socket.
+    server_accept();
+
+    // Launch request handler i.e. the client code to test.
+    run();
+
+    // Wait for the connection to be established.
+    while (!accepted_) {
+        ASSERT_FALSE(timeout_);
+        poll();
+    }
+    ASSERT_FALSE(accept_error_code_);
+
+    // Perform handshake.
+    server_handshake();
+    while (!handshake_) {
+        ASSERT_FALSE(timeout_);
+        poll();
+    }
+    ASSERT_FALSE(handshake_error_code_);
+
+    // Push a receiver on the socket.
+    size_t size = 0;
+    Callback receive_callback(receive_error_code_, size, received_);
+    server_->async_read_some(
+        boost::asio::buffer(&receive_buffer_[0], receive_buffer_.size()),
+        receive_callback);
+
+    // Busy loop.
+    while (!received_ && !timeout_) {
+        poll();
+    }
+
+    ASSERT_TRUE(received_);
+    ASSERT_FALSE(timeout_);
+
+    // Sanity checks on the request.
+    receive_buffer_.resize(size);
+    ASSERT_LE(20, size);
+
+    // Build the response.
+    size = AUTH_HDR_LEN;                       // header (no attributes).
+    send_buffer_.resize(size);
+    send_buffer_[0] = PW_ACCOUNTING_RESPONSE;  // Access-Accept.
+    // There are a lot of ways to get an error including this one.
+    send_buffer_[1] = receive_buffer_[1] ^ 21;
+    send_buffer_[2] = size >> 8;               // Length
+    send_buffer_[3] = size & 0xff;
+    // Copy the authenticator.
+    memmove(&send_buffer_[4], &receive_buffer_[4], AUTH_VECTOR_LEN);
+
+    // Compute the authenticator.
+    vector<unsigned char> auth_input(size + 6);
+    memmove(&auth_input[0], &send_buffer_[0], size);
+    auth_input[size] = 'r';
+    auth_input[size + 1] = 'a';
+    auth_input[size + 2] = 'd';
+    auth_input[size + 3] = 's';
+    auth_input[size + 4] = 'e';
+    auth_input[size + 5] = 'c';
+    OutputBuffer auth_output(AUTH_VECTOR_LEN);
+    digest(&auth_input[0], size + 6, isc::cryptolink::MD5,
+           auth_output, AUTH_VECTOR_LEN);
+    memmove(&send_buffer_[4], auth_output.getData(), AUTH_VECTOR_LEN);
+
+    // Push a sender on the socket.
+    size_t sent_size = 0;
+    Callback send_callback(send_error_code_, sent_size, sent_);
+    boost::asio::async_write(*server_,
+                             boost::asio::buffer(&send_buffer_[0], size),
+                             send_callback);
+
+    // Second busy loop.
+    while ((!sent_ || !finished_) && !timeout_) {
+        poll();
+    }
+
+    EXPECT_TRUE(finished_);
+    EXPECT_TRUE(sent_);
+    EXPECT_FALSE(timeout_);
+    EXPECT_EQ(size, sent_size);
+
+    // Done.
+    timer_stop();
+    service_->stopWork();
+
+    // Check result.
+    EXPECT_EQ(BADRESP_RC, result_);
+}
+
+/// Verify what happens with bad (too short) response.
+TEST_F(TlsRequestTest, tooShort) {
+    // Use CONFIGS[0].
+    ElementPtr json;
+    ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0]));
+    ASSERT_NO_THROW(impl_.init(json));
+
+    // Build request parameters.
+    which_ = "accounting";
+    subnet_id_ = 1;
+    if (!send_attributes_) {
+        send_attributes_.reset(new Attributes());
+    }
+    send_attributes_->add(Attribute::fromString(PW_USER_NAME, "user"));
+
+    // Start timer for 1.5s timeout.
+    timer_start(1500);
+
+    // Accept server socket.
+    server_accept();
+
+    // Launch request handler i.e. the client code to test.
+    run();
+
+    // Wait for the connection to be established.
+    while (!accepted_) {
+        ASSERT_FALSE(timeout_);
+        poll();
+    }
+    ASSERT_FALSE(accept_error_code_);
+
+    // Perform handshake.
+    server_handshake();
+    while (!handshake_) {
+        ASSERT_FALSE(timeout_);
+        poll();
+    }
+    ASSERT_FALSE(handshake_error_code_);
+
+    // Push a receiver on the socket.
+    size_t size = 0;
+    Callback receive_callback(receive_error_code_, size, received_);
+    server_->async_read_some(
+        boost::asio::buffer(&receive_buffer_[0], receive_buffer_.size()),
+        receive_callback);
+
+    // Busy loop.
+    while (!received_ && !timeout_) {
+        poll();
+    }
+
+    ASSERT_TRUE(received_);
+    ASSERT_FALSE(timeout_);
+
+    // Sanity checks on the request.
+    receive_buffer_.resize(size);
+    ASSERT_LE(20, size);
+
+    // Build the response.
+    size = AUTH_HDR_LEN;                       // header (no attributes).
+    send_buffer_.resize(size);
+    send_buffer_[0] = PW_ACCOUNTING_RESPONSE;  // Access-Accept.
+    // There are a lot of ways to get an error including this one.
+    send_buffer_[1] = receive_buffer_[1];      // Copy id.
+    send_buffer_[2] = size >> 8;               // Length
+    send_buffer_[3] = size & 0xff;
+    // Copy the authenticator.
+    memmove(&send_buffer_[4], &receive_buffer_[4], AUTH_VECTOR_LEN);
+
+    // Compute the authenticator.
+    vector<unsigned char> auth_input(size + 6);
+    memmove(&auth_input[0], &send_buffer_[0], size);
+    auth_input[size] = 'r';
+    auth_input[size + 1] = 'a';
+    auth_input[size + 2] = 'd';
+    auth_input[size + 3] = 's';
+    auth_input[size + 4] = 'e';
+    auth_input[size + 5] = 'c';
+    OutputBuffer auth_output(AUTH_VECTOR_LEN);
+    digest(&auth_input[0], size + 6, isc::cryptolink::MD5,
+           auth_output, AUTH_VECTOR_LEN);
+    memmove(&send_buffer_[4], auth_output.getData(), AUTH_VECTOR_LEN);
+
+    // Push a sender on the socket.
+    size_t sent_size = 0;
+    Callback send_callback(send_error_code_, sent_size, sent_);
+    // Truncate the buffer.
+    boost::asio::async_write(*server_,
+                             boost::asio::buffer(&send_buffer_[0], size - 2),
+                             send_callback);
+
+    // Second busy loop.
+    while ((!sent_ || !finished_) && !timeout_) {
+        poll();
+    }
+
+    EXPECT_TRUE(finished_);
+    EXPECT_TRUE(sent_);
+    EXPECT_FALSE(timeout_);
+    EXPECT_EQ(size - 2, sent_size);
+
+    // Done.
+    timer_stop();
+    service_->stopWork();
+
+    // Check result.
+    EXPECT_EQ(TIMEOUT_RC, result_);
+}
+
+/// Verify what happens with Access-Reject response.
+TEST_F(TlsRequestTest, reject) {
+    // Use CONFIGS[0].
+    ElementPtr json;
+    ASSERT_NO_THROW(json = Element::fromJSON(CONFIGS[0]));
+    ASSERT_NO_THROW(impl_.init(json));
+
+    // Build request parameters.
+    which_ = "access";
+    subnet_id_ = 1;
+    if (!send_attributes_) {
+        send_attributes_.reset(new Attributes());
+    }
+    send_attributes_->add(Attribute::fromString(PW_USER_NAME, "user"));
+
+    // Start timer for 1.5s timeout.
+    timer_start(1500);
+
+    // Accept server socket.
+    server_accept();
+
+    // Launch request handler i.e. the client code to test.
+    run();
+
+    // Wait for the connection to be established.
+    while (!accepted_) {
+        ASSERT_FALSE(timeout_);
+        poll();
+    }
+    ASSERT_FALSE(accept_error_code_);
+
+    // Perform handshake.
+    server_handshake();
+    while (!handshake_) {
+        ASSERT_FALSE(timeout_);
+        poll();
+    }
+    ASSERT_FALSE(handshake_error_code_);
+
+    // Push a receiver on the socket.
+    size_t size = 0;
+    Callback receive_callback(receive_error_code_, size, received_);
+    server_->async_read_some(
+        boost::asio::buffer(&receive_buffer_[0], receive_buffer_.size()),
+        receive_callback);
+
+    // Busy loop.
+    while (!received_ && !timeout_) {
+        poll();
+    }
+
+    ASSERT_TRUE(received_);
+    ASSERT_FALSE(timeout_);
+
+    // Sanity checks on the request.
+    receive_buffer_.resize(size);
+    ASSERT_LE(26, size);
+
+    // Build the response.
+    size = AUTH_HDR_LEN + 2 + 4;          // header + User-Name attribute.
+    send_buffer_.resize(size);
+    send_buffer_[0] = PW_ACCESS_REJECT;   // Access-Reject.
+    send_buffer_[1] = receive_buffer_[1]; // Copy id.
+    send_buffer_[2] = size >> 8;          // Length
+    send_buffer_[3] = size & 0xff;
+    // Copy the authenticator.
+    memmove(&send_buffer_[4], &receive_buffer_[4], AUTH_VECTOR_LEN);
+    // Copy the User-Name attribute.
+    memmove(&send_buffer_[AUTH_HDR_LEN], &receive_buffer_[AUTH_HDR_LEN], 6);
+
+    // Compute the authenticator.
+    vector<unsigned char> auth_input(size + 6);
+    memmove(&auth_input[0], &send_buffer_[0], size);
+    auth_input[size] = 'r';
+    auth_input[size + 1] = 'a';
+    auth_input[size + 2] = 'd';
+    auth_input[size + 3] = 's';
+    auth_input[size + 4] = 'e';
+    auth_input[size + 5] = 'c';
+    OutputBuffer auth_output(AUTH_VECTOR_LEN);
+    digest(&auth_input[0], size + 6, isc::cryptolink::MD5,
+           auth_output, AUTH_VECTOR_LEN);
+    memmove(&send_buffer_[4], auth_output.getData(), AUTH_VECTOR_LEN);
+
+    // Push a sender on the socket.
+    size_t sent_size = 0;
+    Callback send_callback(send_error_code_, sent_size, sent_);
+    boost::asio::async_write(*server_,
+                             boost::asio::buffer(&send_buffer_[0], size),
+                             send_callback);
+
+    // Second busy loop.
+    while ((!sent_ || !finished_) && !timeout_) {
+        poll();
+    }
+
+    EXPECT_TRUE(finished_);
+    EXPECT_TRUE(sent_);
+    EXPECT_FALSE(timeout_);
+    EXPECT_EQ(size, sent_size);
+
+    // Done.
+    timer_stop();
+    service_->stopWork();
+
+    // Check result.
+    EXPECT_EQ(REJECT_RC, result_);
+    ASSERT_TRUE(received_attributes_);
+    ASSERT_EQ(1, received_attributes_->size());
+    ASSERT_EQ(1, received_attributes_->count(1));
+    const ConstAttributePtr& attr = received_attributes_->get(1);
+    ASSERT_TRUE(attr);
+    EXPECT_EQ("User-Name='user'", attr->toText());
+}
+
+} // end of anonymous namespace
index 1ac5a636a1abca8e9b87ae88be516cb1335826f1..59c56e0e7809425d50c8dc395c27b436a8c831bc 100644 (file)
@@ -5,6 +5,7 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 #include <config.h>
+
 #include <cryptolink/crypto_hash.h>
 #include <asiolink/asio_wrapper.h>
 #include <asiolink/interval_timer.h>
@@ -109,9 +110,6 @@ public:
 class TlsStatusTest : public radius::test::AttributeTest {
 public:
 
-    /// @brief Thread pointer type.
-    typedef boost::shared_ptr<thread> ThreadPtr;
-
     /// @brief Constructor.
     TlsStatusTest() :
         radius::test::AttributeTest(),
@@ -123,7 +121,7 @@ public:
         receive_buffer_(4096), receive_error_code_(), received_(false),
         send_buffer_(), send_error_code_(), sent_(false),
         timer_(service_), timeout_(false),
-        send_attributes_(), result_(OK_RC), thread_(),
+       send_attributes_(), result_(OK_RC),
         handler_(), finished_(false) {
 
         acceptor_.set_option(ba::tcp::acceptor::reuse_address(true));
@@ -146,10 +144,6 @@ public:
     virtual ~TlsStatusTest() {
         timer_stop();
         server_close();
-        if (thread_) {
-            thread_->join();
-            thread_.reset();
-        }
 
         // As a best practice, call any remaining handlers.
         service_->stopAndPoll();
@@ -264,9 +258,6 @@ public:
     /// @brief Request result.
     int result_;
 
-    /// @brief Thread.
-    ThreadPtr thread_;
-
     /// @brief Handler.
     RadiusTlsStatusPtr handler_;