tls_session.h 8.67 KB
Newer Older
1
/*
2
 *  Copyright (C) 2016-2017 Savoir-faire Linux Inc.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 *
 *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
 *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 */

#pragma once

#include "threadloop.h"
25
#include "noncopyable.h"
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

#include <gnutls/gnutls.h>
#include <gnutls/dtls.h>
#include <gnutls/abstract.h>

#include <string>
#include <list>
#include <functional>
#include <memory>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <future>
#include <utility>
#include <vector>
#include <map>
42
#include <atomic>
43 44
#include <iterator>
#include <array>
45
#include <stdexcept>
46 47 48 49 50 51 52

namespace ring {
class IceTransport;
class IceSocket;
} // namespace ring

namespace dht { namespace crypto {
53 54
struct Certificate;
struct PrivateKey;
55 56 57 58
}} // namespace dht::crypto

namespace ring { namespace tls {

59
static constexpr uint8_t MTUS_TO_TEST = 4; //number of mtus to test in path mtu discovery.
60 61
static constexpr int DTLS_MTU {1232}; // (1280 from IPv6 minimum MTU - 40 IPv6 header - 8 UDP header)
static constexpr uint16_t MIN_MTU {512};
62

63 64 65 66
enum class TlsSessionState {
    SETUP,
    COOKIE, // server only
    HANDSHAKE,
67
    MTU_DISCOVERY,
68 69 70 71
    ESTABLISHED,
    SHUTDOWN
};

Adrien Béraud's avatar
Adrien Béraud committed
72 73 74 75
class DhParams {
public:
    DhParams() = default;
    DhParams(DhParams&&) = default;
76 77
    DhParams(const DhParams& other) {
        *this = other;
78 79
    }

80 81 82 83 84 85 86 87
    DhParams& operator=(DhParams&& other) = default;
    DhParams& operator=(const DhParams& other);

    /// \brief Construct by taking ownership of given gnutls DH params
    ///
    /// User should not call gnutls_dh_params_deinit on given \a raw_params.
    /// The object is stolen and its live is manager by our object.
    explicit DhParams(gnutls_dh_params_t p) : params_ {p, gnutls_dh_params_deinit} {}
Adrien Béraud's avatar
Adrien Béraud committed
88 89 90 91 92 93 94

    /** Deserialize DER or PEM encoded DH-params */
    DhParams(const std::vector<uint8_t>& data);

    gnutls_dh_params_t get() {
        return params_.get();
    }
95
    gnutls_dh_params_t get() const {
Adrien Béraud's avatar
Adrien Béraud committed
96 97 98
        return params_.get();
    }

99 100 101 102
    explicit inline operator bool() const {
        return bool(params_);
    }

Adrien Béraud's avatar
Adrien Béraud committed
103 104
    /** Serialize data in PEM format */
    std::vector<uint8_t> serialize() const;
105

Adrien Béraud's avatar
Adrien Béraud committed
106 107 108
    static DhParams generate();

private:
109
    std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)*> params_ {nullptr, gnutls_dh_params_deinit};
Adrien Béraud's avatar
Adrien Béraud committed
110 111 112
};

struct TlsParams {
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
    // User CA list for session credentials
    std::string ca_list;

    // User identity for credential
    std::shared_ptr<dht::crypto::Certificate> cert;
    std::shared_ptr<dht::crypto::PrivateKey> cert_key;

    // Diffie-Hellman computed by gnutls_dh_params_init/gnutls_dh_params_generateX
    std::shared_future<DhParams> dh_params;

    // DTLS timeout
    std::chrono::steady_clock::duration timeout;

    // Callback for certificate checkings
    std::function<int(unsigned status,
                      const gnutls_datum_t* cert_list,
                      unsigned cert_list_size)> cert_check;
};

/**
 * TlsSession
 *
 * Manages a DTLS connection over an ICE transport.
 * This implementation uses a Threadloop to manage IO from ICE and TLS states,
 * so IO are asynchronous.
 */
class TlsSession {
public:
    using OnStateChangeFunc = std::function<void(TlsSessionState)>;
    using OnRxDataFunc = std::function<void(std::vector<uint8_t>&&)>;
    using OnCertificatesUpdate = std::function<void(const gnutls_datum_t*, const gnutls_datum_t*, unsigned int)>;
    using VerifyCertificate = std::function<int(gnutls_session_t)>;
    using TxDataCompleteFunc = std::function<void(std::size_t bytes_sent)>;

    // ===> WARNINGS <===
    // Following callbacks are called into the FSM thread context
    // Do not call blocking routines inside them.
    using TlsSessionCallbacks = struct {
        OnStateChangeFunc onStateChange;
        OnRxDataFunc onRxData;
        OnCertificatesUpdate onCertificatesUpdate;
        VerifyCertificate verifyCertificate;
    };

157
    TlsSession(const std::shared_ptr<IceTransport>& ice, int ice_comp_id, const TlsParams& params,
158
               const TlsSessionCallbacks& cbs, bool anonymous=true);
159 160 161 162 163
    ~TlsSession();

    // Returns the TLS session type ('server' or 'client')
    const char* typeName() const;

164 165
    bool isServer() const { return isServer_; }

166 167 168
    // Request TLS thread to stop and quit. IO are not possible after that.
    void shutdown();

169 170 171 172
    // Return maximum application payload size in bytes
    // Returned value must be checked and considered valid only if not 0 (session is initialized)
    unsigned int getMaxPayload() const { return maxPayload_; }

173 174 175 176 177 178 179
    // Can be called by onStateChange callback when state == ESTABLISHED
    // to obtain the used cypher suite id.
    // Return the name of current cipher.
    const char* getCurrentCipherSuiteId(std::array<uint8_t, 2>& cs_id) const;

    // Asynchronous sending operation. on_send_complete will be called with a positive number
    // for number of bytes sent, or negative for errors, or 0 in case of shutdown (end of session).
180 181 182 183 184 185 186
    int async_send(const void* data, std::size_t size, TxDataCompleteFunc on_send_complete);
    int async_send(std::vector<uint8_t>&& data, TxDataCompleteFunc on_send_complete);

    // Synchronous sending operation. Return negative number (gnutls error) or a positive number
    // for bytes sent.
    ssize_t send(const void* data, std::size_t size);
    ssize_t send(const std::vector<uint8_t>& data);
187

188 189
    uint16_t getMtu();

190 191 192 193 194 195 196 197 198
private:
    using clock = std::chrono::steady_clock;
    using StateHandler = std::function<TlsSessionState(TlsSessionState state)>;

    // Constants (ctor init.)
    const std::unique_ptr<IceSocket> socket_;
    const bool isServer_;
    const TlsParams params_;
    const TlsSessionCallbacks callbacks_;
199
    const bool anonymous_;
200 201 202 203 204

    // State machine
    TlsSessionState handleStateSetup(TlsSessionState state);
    TlsSessionState handleStateCookie(TlsSessionState state);
    TlsSessionState handleStateHandshake(TlsSessionState state);
205
    TlsSessionState handleStateMtuDiscovery(TlsSessionState state);
206 207 208 209
    TlsSessionState handleStateEstablished(TlsSessionState state);
    TlsSessionState handleStateShutdown(TlsSessionState state);
    std::map<TlsSessionState, StateHandler> fsmHandlers_ {};
    std::atomic<TlsSessionState> state_ {TlsSessionState::SETUP};
210
    std::atomic<unsigned int> maxPayload_ {0};
211 212

    // IO GnuTLS <-> ICE
213 214 215
    std::mutex txMutex_ {};
    std::mutex rxMutex_ {};
    std::condition_variable rxCv_ {};
216 217
    std::list<std::vector<uint8_t>> rxQueue_ {};

218
    ssize_t send_(const uint8_t* tx_data, std::size_t tx_size);
219 220 221 222 223
    ssize_t sendRaw(const void*, size_t);
    ssize_t sendRawVec(const giovec_t*, int);
    ssize_t recvRaw(void*, size_t);
    int waitForRawData(unsigned);

224 225 226 227 228 229
    // Statistics
    std::atomic<std::size_t> stRxRawPacketCnt_ {0};
    std::atomic<std::size_t> stRxRawBytesCnt_ {0};
    std::atomic<std::size_t> stRxRawPacketDropCnt_ {0};
    std::atomic<std::size_t> stTxRawPacketCnt_ {0};
    std::atomic<std::size_t> stTxRawBytesCnt_ {0};
230 231 232 233
    void dump_io_stats() const;

    // GnuTLS backend and connection state
    class TlsCertificateCredendials;
234 235 236 237
    class TlsAnonymousClientCredendials;
    class TlsAnonymousServerCredendials;
    std::unique_ptr<TlsAnonymousClientCredendials> cacred_; // ctor init.
    std::unique_ptr<TlsAnonymousServerCredendials> sacred_; // ctor init.
238 239 240 241 242 243 244 245
    std::unique_ptr<TlsCertificateCredendials> xcred_; // ctor init.
    gnutls_session_t session_ {nullptr};
    gnutls_datum_t cookie_key_ {nullptr, 0};
    gnutls_dtls_prestate_st prestate_ {};
    ssize_t cookie_count_ {0};

    TlsSessionState setupClient();
    TlsSessionState setupServer();
246
    void initAnonymous();
247 248 249 250 251 252 253 254
    void initCredentials();
    bool commonSessionInit();

    // FSM thread (TLS states)
    ThreadLoop thread_; // ctor init.
    bool setup();
    void process();
    void cleanup();
255 256 257 258 259 260 261

    // Path mtu discovery
    std::array<uint16_t, MTUS_TO_TEST>::const_iterator mtuProbe_;
    unsigned hbPingRecved_ {0};
    bool pmtudOver_ {false};
    uint8_t transportOverhead_;
    void pathMtuHeartbeat();
262 263 264
};

}} // namespace ring::tls