Skip to content
Snippets Groups Projects
Select Git revision
  • 567403145f8497161237144f18b26d3cde50f790
  • master default protected
2 results

mapping.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    tls_session.h 9.39 KiB
    /*
     *  Copyright (C) 2016-2017 Savoir-faire Linux Inc.
     *
     *  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"
    #include "noncopyable.h"
    
    #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>
    #include <atomic>
    #include <iterator>
    #include <array>
    #include <stdexcept>
    #include <bitset>
    
    namespace ring {
    class IceTransport;
    class IceSocket;
    } // namespace ring
    
    namespace dht { namespace crypto {
    struct Certificate;
    struct PrivateKey;
    }} // namespace dht::crypto
    
    namespace ring { namespace tls {
    
    static constexpr uint8_t MTUS_TO_TEST = 3; //number of mtus to test in path mtu discovery.
    static constexpr int DTLS_MTU {1232}; // (1280 from IPv6 minimum MTU - 40 IPv6 header - 8 UDP header)
    static constexpr uint16_t MIN_MTU {512};
    
    enum class TlsSessionState {
        SETUP,
        COOKIE, // server only
        HANDSHAKE,
        MTU_DISCOVERY,
        ESTABLISHED,
        SHUTDOWN
    };
    
    class DhParams {
    public:
        DhParams() = default;
        DhParams(DhParams&&) = default;
        DhParams(const DhParams& other) {
            *this = other;
        }
    
        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} {}
    
        /** Deserialize DER or PEM encoded DH-params */
        DhParams(const std::vector<uint8_t>& data);
    
        gnutls_dh_params_t get() {
            return params_.get();
        }
        gnutls_dh_params_t get() const {
            return params_.get();
        }
    
        explicit inline operator bool() const {
            return bool(params_);
        }
    
        /** Serialize data in PEM format */
        std::vector<uint8_t> serialize() const;
    
        static DhParams generate();
    
    private:
        std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)*> params_ {nullptr, gnutls_dh_params_deinit};
    };
    
    struct TlsParams {
        // User CA list for session credentials
        std::string ca_list;
    
        std::shared_ptr<dht::crypto::Certificate> peer_ca;
    
        // 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;
        };
    
        TlsSession(const std::shared_ptr<IceTransport>& ice, int ice_comp_id, const TlsParams& params,
                   const TlsSessionCallbacks& cbs, bool anonymous=true);
        ~TlsSession();
    
        // Returns the TLS session type ('server' or 'client')
        const char* typeName() const;
    
        bool isServer() const { return isServer_; }
    
        // Request TLS thread to stop and quit. IO are not possible after that.
        void shutdown();
    
        // 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_; }
    
        // 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).
        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);
    
        uint16_t getMtu();
    
    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_;
        const bool anonymous_;
    
        // State machine
        TlsSessionState handleStateSetup(TlsSessionState state);
        TlsSessionState handleStateCookie(TlsSessionState state);
        TlsSessionState handleStateHandshake(TlsSessionState state);
        TlsSessionState handleStateMtuDiscovery(TlsSessionState state);
        TlsSessionState handleStateEstablished(TlsSessionState state);
        TlsSessionState handleStateShutdown(TlsSessionState state);
        std::map<TlsSessionState, StateHandler> fsmHandlers_ {};
        std::atomic<TlsSessionState> state_ {TlsSessionState::SETUP};
        std::atomic<unsigned int> maxPayload_;
    
        // IO GnuTLS <-> ICE
        std::mutex txMutex_ {};
        std::mutex rxMutex_ {};
        std::condition_variable rxCv_ {};
        std::list<std::vector<uint8_t>> rxQueue_ {};
    
        std::mutex reorderBufMutex_;
        bool flushProcessing_ {false}; ///< protect against recursive call to flushRxQueue
        std::vector<uint8_t> rawPktBuf_; ///< gnutls incoming packet buffer
        uint64_t baseSeq_ {0}; ///< sequence number of first application data packet received
        uint64_t lastRxSeq_ {0}; ///< last received and valid packet sequence number
        uint64_t gapOffset_ {0}; ///< offset of first byte not received yet
        clock::time_point lastReadTime_;
        std::map<uint64_t, std::vector<uint8_t>> reorderBuffer_ {};
    
        ssize_t send_(const uint8_t* tx_data, std::size_t tx_size);
        ssize_t sendRaw(const void*, size_t);
        ssize_t sendRawVec(const giovec_t*, int);
        ssize_t recvRaw(void*, size_t);
        int waitForRawData(unsigned);
    
        bool initFromRecordState(int offset=0);
        void handleDataPacket(std::vector<uint8_t>&&, uint64_t);
        void flushRxQueue();
    
        // 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};
        void dump_io_stats() const;
    
        // GnuTLS backend and connection state
        class TlsCertificateCredendials;
        class TlsAnonymousClientCredendials;
        class TlsAnonymousServerCredendials;
        std::unique_ptr<TlsAnonymousClientCredendials> cacred_; // ctor init.
        std::unique_ptr<TlsAnonymousServerCredendials> sacred_; // ctor init.
        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();
        void initAnonymous();
        void initCredentials();
        bool commonSessionInit();
    
        // FSM thread (TLS states)
        ThreadLoop thread_; // ctor init.
        bool setup();
        void process();
        void cleanup();
    
        // Path mtu discovery
        std::array<uint16_t, MTUS_TO_TEST>::const_iterator mtuProbe_;
        unsigned hbPingRecved_ {0};
        bool pmtudOver_ {false};
        uint8_t transportOverhead_;
        void pathMtuHeartbeat();
    };
    
    }} // namespace ring::tls