aboutsummaryrefslogtreecommitdiffstats
path: root/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp
blob: eda1b4b6879ad273abc9a4f482b637bfb1582c2f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/test_kit.h>
#include <vespa/vespalib/net/tls/tls_context.h>
#include <vespa/vespalib/net/tls/transport_security_options.h>
#include <vespa/vespalib/net/tls/crypto_codec.h>
#include <iostream>
#include <stdlib.h>

using namespace vespalib;
using namespace vespalib::net::tls;

/*
 * Generated with the following commands:
 *
 * openssl ecparam -name prime256v1 -genkey -out ca.key
 *
 * openssl req -new -x509 -nodes -key ca.key \
 *    -sha256 -out ca.pem \
 *    -subj '/C=US/L=LooneyVille/O=ACME/OU=ACME test CA/CN=acme.example.com' \
 *    -days 10000
 *
 * openssl ecparam -name prime256v1 -genkey -out host.key
 *
 * openssl req -new -key host.key -out host.csr \
 *    -subj '/C=US/L=LooneyVille/O=Wile. E. Coyote, Ltd./CN=wile.example.com' \
 *    -sha256
 *
 * openssl x509 -req -in host.csr \
 *   -CA ca.pem \
 *   -CAkey ca.key \
 *   -CAcreateserial \
 *   -out host.pem \
 *   -days 10000 \
 *   -sha256
 *
 * TODO generate keypairs and certs at test-time to avoid any hard-coding
 * There certs are valid until 2046, so that buys us some time..!
 */

// ca.pem
constexpr const char* ca_pem = R"(-----BEGIN CERTIFICATE-----
MIIBuDCCAV4CCQDpVjQIixTxvDAKBggqhkjOPQQDAjBkMQswCQYDVQQGEwJVUzEU
MBIGA1UEBwwLTG9vbmV5VmlsbGUxDTALBgNVBAoMBEFDTUUxFTATBgNVBAsMDEFD
TUUgdGVzdCBDQTEZMBcGA1UEAwwQYWNtZS5leGFtcGxlLmNvbTAeFw0xODA4MzEx
MDU3NDVaFw00NjAxMTYxMDU3NDVaMGQxCzAJBgNVBAYTAlVTMRQwEgYDVQQHDAtM
b29uZXlWaWxsZTENMAsGA1UECgwEQUNNRTEVMBMGA1UECwwMQUNNRSB0ZXN0IENB
MRkwFwYDVQQDDBBhY21lLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D
AQcDQgAE1L7IzCN5pbyVnBATIHieuxq+hf9kWyn5yfjkXMhD52T5ITz1huq4nbiN
YtRoRP7XmipI60R/uiCHzERcsVz4rDAKBggqhkjOPQQDAgNIADBFAiEA6wmZDBca
y0aJ6ABtjbjx/vlmVDxdkaSZSgO8h2CkvIECIFktCkbZhDFfSvbqUScPOGuwkdGQ
L/EW2Bxp+1BPcYoZ
-----END CERTIFICATE-----)";

// host.pem
constexpr const char* cert_pem = R"(-----BEGIN CERTIFICATE-----
MIIBsTCCAVgCCQD6GfDh0ltpsjAKBggqhkjOPQQDAjBkMQswCQYDVQQGEwJVUzEU
MBIGA1UEBwwLTG9vbmV5VmlsbGUxDTALBgNVBAoMBEFDTUUxFTATBgNVBAsMDEFD
TUUgdGVzdCBDQTEZMBcGA1UEAwwQYWNtZS5leGFtcGxlLmNvbTAeFw0xODA4MzEx
MDU3NDVaFw00NjAxMTYxMDU3NDVaMF4xCzAJBgNVBAYTAlVTMRQwEgYDVQQHDAtM
b29uZXlWaWxsZTEeMBwGA1UECgwVV2lsZS4gRS4gQ295b3RlLCBMdGQuMRkwFwYD
VQQDDBB3aWxlLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
e+Y4hxt66em0STviGUj6ZDbxzoLoubXWRml8JDFrEc2S2433KWw2npxYSKVCyo3a
/Vo33V8/H0WgOXioKEZJxDAKBggqhkjOPQQDAgNHADBEAiAN+87hQuGv3z0Ja2BV
b8PHq2vp3BJHjeMuxWu4BFPn0QIgYlvIHikspgGatXRNMZ1gPC0oCccsJFcie+Cw
zL06UPI=
-----END CERTIFICATE-----)";

// host.key
constexpr const char* key_pem = R"(-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEID6di2PFYn8hPrxPbkFDGkSqF+K8L520In7nx3g0jwzOoAoGCCqGSM49
AwEHoUQDQgAEe+Y4hxt66em0STviGUj6ZDbxzoLoubXWRml8JDFrEc2S2433KWw2
npxYSKVCyo3a/Vo33V8/H0WgOXioKEZJxA==
-----END EC PRIVATE KEY-----)";

const char* decode_state_to_str(DecodeResult::State state) noexcept {
    switch (state) {
        case DecodeResult::State::Failed: return "Broken";
        case DecodeResult::State::OK:     return "OK";
        case DecodeResult::State::NeedsMorePeerData: return "NeedsMorePeerData";
        default:
            abort();
    }
}

const char* hs_state_to_str(HandshakeResult::State state) noexcept {
    switch (state) {
    case HandshakeResult::State::Failed: return "Broken";
    case HandshakeResult::State::Done:   return "Completed";
    case HandshakeResult::State::NeedsMorePeerData: return "NeedsMorePeerData";
    default:
        abort();
    }
}

void log_handshake_result(const char* mode, const HandshakeResult& res) {
    fprintf(stderr, "(handshake) %s consumed %zu peer bytes, wrote %zu peer bytes. State: %s\n",
            mode, res.bytes_consumed, res.bytes_produced,
            hs_state_to_str(res.state));
}

void log_encode_result(const char* mode, const EncodeResult& res) {
    fprintf(stderr, "(encode) %s read %zu plaintext, wrote %zu cipher. State: %s\n",
            mode, res.bytes_consumed, res.bytes_produced,
            res.failed ? "Broken! D:" : "OK");
}

void log_decode_result(const char* mode, const DecodeResult& res) {
    fprintf(stderr, "(decode) %s read %zu cipher, wrote %zu plaintext. State: %s\n",
            mode, res.bytes_consumed, res.bytes_produced,
            decode_state_to_str(res.state));
}

bool complete_handshake(CryptoCodec& client, CryptoCodec& server) {
    std::string client_to_server_buf;
    std::string server_to_client_buf;

    HandshakeResult cli_res, serv_res;
    while (!(cli_res.done() && serv_res.done())) {
        client_to_server_buf.resize(client.min_encode_buffer_size());
        server_to_client_buf.resize(client.min_encode_buffer_size());

        cli_res = client.handshake(server_to_client_buf.data(), serv_res.bytes_produced,
                                   client_to_server_buf.data(), client_to_server_buf.size());
        log_handshake_result("client", cli_res);
        server_to_client_buf.erase(server_to_client_buf.begin(), server_to_client_buf.begin() + cli_res.bytes_consumed);

        serv_res = server.handshake(client_to_server_buf.data(), cli_res.bytes_produced,
                                    server_to_client_buf.data(), server_to_client_buf.size());
        log_handshake_result("server", serv_res);
        client_to_server_buf.erase(client_to_server_buf.begin(), client_to_server_buf.begin() + serv_res.bytes_consumed);

        if (cli_res.failed() || serv_res.failed()) {
            return false;
        }
    }
    return true;
}

TEST("client and server can complete handshake") {
    // TODO move to fixture
    auto tls_opts = TransportSecurityOptions(ca_pem, cert_pem, key_pem);
    auto tls_ctx = TlsContext::create_default_context(tls_opts);
    auto client = CryptoCodec::create_default_codec(*tls_ctx, CryptoCodec::Mode::Client);
    auto server = CryptoCodec::create_default_codec(*tls_ctx, CryptoCodec::Mode::Server);

    EXPECT_TRUE(complete_handshake(*client, *server));
}

TEST("client can send single data frame to server after handshake") {
    // TODO move to fixture
    auto tls_opts = TransportSecurityOptions(ca_pem, cert_pem, key_pem);
    auto tls_ctx = TlsContext::create_default_context(tls_opts);
    auto client = CryptoCodec::create_default_codec(*tls_ctx, CryptoCodec::Mode::Client);
    auto server = CryptoCodec::create_default_codec(*tls_ctx, CryptoCodec::Mode::Server);

    ASSERT_TRUE(complete_handshake(*client, *server));

    std::string client_to_server_buf;
    std::string server_to_client_buf;

    client_to_server_buf.resize(client->min_encode_buffer_size());
    server_to_client_buf.resize(client->min_encode_buffer_size());

    std::string client_plaintext = "Hellooo world! :D";
    auto cli_res = client->encode(client_plaintext.data(), client_plaintext.size(),
                                  client_to_server_buf.data(), client_to_server_buf.size());
    log_encode_result("client", cli_res);

    std::string server_plaintext_out;
    server_plaintext_out.resize(client->min_encode_buffer_size());
    auto serv_res = server->decode(client_to_server_buf.data(), cli_res.bytes_produced,
                                   server_plaintext_out.data(), server_plaintext_out.size());
    log_decode_result("server", serv_res);

    ASSERT_FALSE(cli_res.failed);
    ASSERT_FALSE(serv_res.failed());

    ASSERT_TRUE(serv_res.state == DecodeResult::State::OK);
    std::string data_received(server_plaintext_out.data(), serv_res.bytes_produced);
    EXPECT_EQUAL(client_plaintext, data_received);
}

/*
 * TODO tests:
 *  - full duplex read/write
 *  - read and write of > frame size data
 *  - handshakes with multi frame writes
 *  - completed handshake with pipelined data frame
 *  - short ciphertext reads on decode
 *  - short plaintext writes on decode (.. if we even want to support this..)
 *  - short ciphertext write on encode
 *  - peer certificate validation on server
 *  - peer certificate validation on client
 *  - detection of peer shutdown session
 */

TEST_MAIN() { TEST_RUN_ALL(); }