1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2017 Couchbase, Inc.
4 *
5 *   Licensed under the Apache License, Version 2.0 (the "License");
6 *   you may not use this file except in compliance with the License.
7 *   You may obtain a copy of the License at
8 *
9 *       http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *   Unless required by applicable law or agreed to in writing, software
12 *   distributed under the License is distributed on an "AS IS" BASIS,
13 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *   See the License for the specific language governing permissions and
15 *   limitations under the License.
16 */
17#include "ssl_context.h"
18
19#include "memcached.h"
20#include "runtime.h"
21#include "settings.h"
22
23#include <platform/socket.h>
24#include <platform/strerror.h>
25#include <utilities/logtags.h>
26
27SslContext::~SslContext() {
28    if (enabled) {
29        disable();
30    }
31}
32
33int SslContext::accept() {
34    return SSL_accept(client);
35}
36
37int SslContext::getError(int errormask) const {
38    return SSL_get_error(client, errormask);
39}
40
41int SslContext::read(void* buf, int num) {
42    return SSL_read(client, buf, num);
43}
44
45int SslContext::write(const void* buf, int num) {
46    return SSL_write(client, buf, num);
47}
48
49bool SslContext::havePendingInputData() {
50    if (isEnabled()) {
51        // Move any data in the memory buffer over to the ssl pipe
52        drainInputSocketBuf();
53        return SSL_pending(client) > 0;
54    }
55    return false;
56}
57
58bool SslContext::enable(const std::string& cert, const std::string& pkey) {
59    ctx = SSL_CTX_new(SSLv23_server_method());
60    SSL_CTX_set_mode(ctx,
61                     SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER |
62                             SSL_MODE_ENABLE_PARTIAL_WRITE);
63    set_ssl_ctx_protocol_mask(ctx);
64
65    /* @todo don't read files, but use in-memory-copies */
66    if (!SSL_CTX_use_certificate_chain_file(ctx, cert.c_str()) ||
67        !SSL_CTX_use_PrivateKey_file(ctx, pkey.c_str(), SSL_FILETYPE_PEM)) {
68        LOG_WARNING("Failed to use SSL cert {} and pkey {}",
69                    cb::logtags::tagUserData(cert),
70                    cb::logtags::tagUserData(pkey));
71        return false;
72    }
73
74    set_ssl_ctx_cipher_list(ctx);
75    int ssl_flags = 0;
76    switch (settings.getClientCertMode()) {
77    case cb::x509::Mode::Mandatory:
78        ssl_flags |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
79    // FALLTHROUGH
80    case cb::x509::Mode::Enabled: {
81        ssl_flags |= SSL_VERIFY_PEER;
82        STACK_OF(X509_NAME)* certNames = SSL_load_client_CA_file(cert.c_str());
83        if (certNames == NULL) {
84            LOG_WARNING("Failed to read SSL cert {}",
85                        cb::logtags::tagUserData(cert));
86            return false;
87        }
88        SSL_CTX_set_client_CA_list(ctx, certNames);
89        SSL_CTX_load_verify_locations(ctx, cert.c_str(), nullptr);
90        SSL_CTX_set_verify(ctx, ssl_flags, nullptr);
91        break;
92    }
93    case cb::x509::Mode::Disabled:
94        break;
95    }
96
97    enabled = true;
98    error = false;
99    client = NULL;
100
101    try {
102        inputPipe.ensureCapacity(settings.getBioDrainBufferSize());
103        outputPipe.ensureCapacity(settings.getBioDrainBufferSize());
104    } catch (std::bad_alloc) {
105        return false;
106    }
107
108    BIO_new_bio_pair(&application,
109                     settings.getBioDrainBufferSize(),
110                     &network,
111                     settings.getBioDrainBufferSize());
112
113    client = SSL_new(ctx);
114    SSL_set_bio(client, application, application);
115
116    return true;
117}
118
119std::pair<cb::x509::Status, std::string> SslContext::getCertUserName() {
120    cb::openssl::unique_x509_ptr cert(SSL_get_peer_certificate(client));
121    return settings.lookupUser(cert.get());
122}
123
124void SslContext::disable() {
125    if (network != nullptr) {
126        BIO_free_all(network);
127    }
128    if (client != nullptr) {
129        SSL_free(client);
130    }
131    error = false;
132    if (ctx != nullptr) {
133        SSL_CTX_free(ctx);
134    }
135    enabled = false;
136}
137
138bool SslContext::drainInputSocketBuf() {
139    if (!inputPipe.empty()) {
140        auto* bio = network;
141        auto n =
142                inputPipe.consume([bio](cb::const_byte_buffer data) -> ssize_t {
143                    return BIO_write(
144                            bio, data.data(), gsl::narrow<int>(data.size()));
145                });
146
147        if (n > 0) {
148            // We did move some data
149            return true;
150        }
151    }
152
153    return false;
154}
155
156void SslContext::drainBioRecvPipe(SOCKET sfd) {
157    bool stop;
158
159    do {
160        // Try to move data from our internal buffer to the SSL pipe
161        stop = !drainInputSocketBuf();
162
163        // If there is room in the input pipe (the internal buffer for read)
164        // try to read out as much as possible from the socket
165        if (!inputPipe.full()) {
166            auto n = inputPipe.produce([sfd](cb::byte_buffer data) -> ssize_t {
167                return cb::net::recv(sfd,
168                                     reinterpret_cast<char*>(data.data()),
169                                     data.size(),
170                                     0);
171            });
172            if (n > 0) {
173                totalRecv += n;
174                // We did receive some data... move it into the BIO
175                stop = false;
176            } else {
177                if (n == 0) {
178                    error = true; /* read end shutdown */
179                } else {
180                    if (!cb::net::is_blocking(cb::net::get_socket_error())) {
181                        error = true;
182                    }
183                }
184            }
185        }
186
187        // As long as we moved some data (from the socket to our internal buffer
188        // or from our internal buffer to the BIO) we should keep on moving
189        // data.
190    } while (!stop);
191
192    // At this time there is:
193    //   * either no more data to receive (everything is moved into the
194    //     BIO object
195    //   * The BIO object is full and the input pipe is:
196    //       * full - there may be more data on the network
197    //       * not full - there is no more data to read from the network
198}
199
200void SslContext::drainBioSendPipe(SOCKET sfd) {
201    bool stop;
202
203    do {
204        stop = true;
205        // Try to move data from our internal buffer to the socket
206        if (!outputPipe.empty()) {
207            auto n = outputPipe.consume(
208                    [sfd](cb::const_byte_buffer data) -> ssize_t {
209                        return cb::net::send(
210                                sfd,
211                                reinterpret_cast<const char*>(data.data()),
212                                data.size(),
213                                0);
214                    });
215
216            if (n > 0) {
217                totalSend += n;
218                // We did move some data
219                stop = false;
220            } else {
221                if (n == -1) {
222                    auto err = cb::net::get_socket_error();
223                    if (!cb::net::is_blocking(err)) {
224                        LOG_WARNING(
225                                "Failed to write, and not due to blocking: {}",
226                                cb_strerror(err));
227                        error = true;
228                    }
229                }
230                return;
231            }
232        }
233
234        if (!outputPipe.full()) {
235            auto* bio = network;
236            auto n = outputPipe.produce([bio](cb::byte_buffer data) -> ssize_t {
237                return BIO_read(
238                        bio, data.data(), gsl::narrow<int>(data.size()));
239            });
240
241            if (n > 0) {
242                // We did move data
243                stop = false;
244            }
245        }
246        // As long as we moved some data (from the internal buffer to the
247        // socket or from the BIO to our internal buffer) we should keep on
248        // moving data.
249    } while (!stop);
250
251    // At this time there is:
252    //   * There is no more data to send
253    //   * The socket buffer is full
254}
255
256void SslContext::dumpCipherList(uint32_t id) const {
257    unique_cJSON_ptr array(cJSON_CreateArray());
258
259    int ii = 0;
260    const char* cipher;
261    while ((cipher = SSL_get_cipher_list(client, ii++)) != nullptr) {
262        cJSON_AddItemToArray(array.get(), cJSON_CreateString(cipher));
263    }
264
265    LOG_DEBUG("{}: Using SSL ciphers: {}", id, to_string(array, false));
266}
267
268cJSON* SslContext::toJSON() const {
269    cJSON* obj = cJSON_CreateObject();
270    cJSON_AddBoolToObject(obj, "enabled", enabled);
271    if (enabled) {
272        cJSON_AddBoolToObject(obj, "connected", connected);
273        cJSON_AddBoolToObject(obj, "error", error);
274        cJSON_AddNumberToObject(obj, "total_recv", totalRecv);
275        cJSON_AddNumberToObject(obj, "total_send", totalSend);
276    }
277
278    return obj;
279}
280