]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/ssl/bio.h
Source Format Enforcement (#532)
[thirdparty/squid.git] / src / ssl / bio.h
index b3bf4781e8300b497451e1d07de379837c04524c..22335b8a3bbe4159f624b3374f9512082866c6cf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
@@ -9,7 +9,14 @@
 #ifndef SQUID_SSL_BIO_H
 #define SQUID_SSL_BIO_H
 
-#include "SBuf.h"
+#if USE_OPENSSL
+
+#include "compat/openssl.h"
+#include "FadingCounter.h"
+#include "fd.h"
+#include "MemBuf.h"
+#include "security/Handshake.h"
+#include "ssl/support.h"
 
 #include <iosfwd>
 #include <list>
 #include <string>
 #include <type_traits>
 
-// TODO: Move BinaryTokenizer to its own set of files outside of Ssl namespace.
-
-/// Safely extracts byte-oriented (i.e., non-textual) fields from raw input.
-/// Supports commit points for atomic incremental parsing of multi-part fields.
-/// Throws InsufficientInput when more input is needed to parse the next field.
-/// Throws on errors.
-class BinaryTokenizer
-{
-public:
-    class InsufficientInput {}; // thrown when a method runs out of data
-    typedef uint64_t size_type; // enough for the largest supported offset
-
-    BinaryTokenizer();
-    explicit BinaryTokenizer(const SBuf &data);
-
-    /// restart parsing from the very beginning
-    /// this method is for using one BinaryTokenizer to parse independent inputs
-    void reset(const SBuf &data);
-
-    /// change input without changing parsing state
-    /// this method avoids append overheads during incremental parsing
-    void reinput(const SBuf &data) { data_ = data; }
-
-    /// make progress: future parsing failures will not rollback beyond this point
-    void commit();
-
-    /// resume [incremental] parsing from the last commit point
-    void rollback();
-
-    /// no more bytes to parse or skip
-    bool atEnd() const;
-
-    /// parse a single-byte unsigned integer
-    uint8_t uint8(const char *description);
-
-    // parse a two-byte unsigned integer
-    uint16_t uint16(const char *description);
-
-    // parse a three-byte unsigned integer (returned as uint32_t)
-    uint32_t uint24(const char *description);
-
-    // parse a four-byte unsigned integer
-    uint32_t uint32(const char *description);
-
-    /// parse size consecutive bytes as an opaque blob
-    SBuf area(uint64_t size, const char *description);
-
-    /// ignore the next size bytes
-    void skip(uint64_t size, const char *description);
-
-    /// yet unparsed bytes
-    SBuf leftovers() const { return data_.substr(parsed_); }
-
-    const char *context; ///< simplifies debugging
-
-protected:
-    uint32_t octet();
-    void want(uint64_t size, const char *description) const;
-    void got(uint32_t value, uint64_t size, const char *description) const;
-    void got(const SBuf &value, uint64_t size, const char *description) const;
-    void skipped(uint64_t size, const char *description) const;
-
-private:
-    SBuf data_;
-    uint64_t parsed_; ///< number of data bytes parsed or skipped
-    uint64_t syncPoint_; ///< where to re-start the next parsing attempt
-};
-
-
 namespace Ssl
 {
 
-// The Transport Layer Security (TLS) Protocol, Version 1.2
-
-// TODO: Consider removing this namespace. The idea was to encapsulate various
-// RFC 5246 types defined using the naming scheme from the RFC rather than
-// following Squid naming conventions. However, using these names in other code
-// may make that code inconsistent. Besides, we are running into some C++ naming
-// limits.
-namespace Rfc5246
-{
-
-/// Helper class to debug parsing of various TLS structures
-class FieldGroup
-{
-public:
-    FieldGroup(BinaryTokenizer &tk, const char *description); ///< starts parsing
-
-    void commit(BinaryTokenizer &tk); ///< commits successful parsing results
-};
-
-/// TLS Record Layer's content types from RFC 5246 Section 6.2.1
-enum ContentType {
-    ctChangeCipherSpec = 20,
-    ctAlert = 21,
-    ctHandshake = 22,
-    ctApplicationData = 23
-};
-
-/// TLS Record Layer's protocol version from RFC 5246 Section 6.2.1
-struct ProtocolVersion
-{
-    explicit ProtocolVersion(BinaryTokenizer &tk);
-
-    // the "v" prefix works around environments that #define major and minor
-    uint8_t vMajor;
-    uint8_t vMinor;
-};
-
-/// TLS Record Layer's frame from RFC 5246 Section 6.2.1.
-struct TLSPlaintext: public FieldGroup
-{
-    explicit TLSPlaintext(BinaryTokenizer &tk);
-
-    uint8_t type; ///< Rfc5246::ContentType
-    ProtocolVersion version;
-    uint16_t length;
-    SBuf fragment; ///< exactly length bytes
-};
-
-/// TLS Handshake protocol's handshake types from RFC 5246 Section 7.4
-enum HandshakeType {
-    hskServerHello = 2,
-    hskCertificate = 11,
-    hskServerHelloDone = 14
-};
-
-/// TLS Handshake Protocol frame from RFC 5246 Section 7.4.
-struct Handshake: public FieldGroup
-{
-    explicit Handshake(BinaryTokenizer &tk);
-
-    uint32_t msg_type: 8; ///< HandshakeType
-    uint32_t length: 24;
-    SBuf body; ///< Handshake Protocol message, exactly length bytes
-};
-
-/// TLS Alert protocol frame from RFC 5246 Section 7.2.
-struct Alert: public FieldGroup
-{
-    explicit Alert(BinaryTokenizer &tk);
-    uint8_t level; ///< warning or fatal
-    uint8_t description; ///< close_notify, unexpected_message, etc.
-};
-
-/// Like a Pascal "length-first" string but with a 3-byte length field.
-/// Used for (undocumented in RRC 5246?) Certificate and ASN1.Cert encodings.
-struct P24String: public FieldGroup
-{
-    explicit P24String(BinaryTokenizer &tk, const char *description);
-
-    uint32_t length;  // bytes in body (stored using 3 bytes, not 4!)
-    SBuf body; ///< exactly length bytes
-};
-
-} // namespace Rfc5246
-
-
-/// Incremental SSL Handshake parser.
-class HandshakeParser {
-public:
-    /// The parsing states
-    typedef enum {atHelloNone = 0, atHelloStarted, atHelloReceived, atCertificatesReceived, atHelloDoneReceived, atNstReceived, atCcsReceived, atFinishReceived} ParserState;
-
-    HandshakeParser(): state(atHelloNone), ressumingSession(false), parseDone(false), parseError(false), currentContentType(0), unParsedContent(0), parsingPos(0), currentMsg(0), currentMsgSize(0), certificatesMsgPos(0), certificatesMsgSize(0) {}
-
-    /// Parses the initial sequence of raw bytes sent by the SSL server.
-    /// Returns true upon successful completion (HelloDone or Finished received).
-    /// Otherwise, returns false (and sets parseError to true on errors).
-    bool parseServerHello(const SBuf &data);
-
-    Ssl::X509_STACK_Pointer serverCertificates; ///< parsed certificates chain
-
-    ParserState state; ///< current parsing state.
-
-    bool ressumingSession; ///< True if this is a resumming session
-
-    bool parseDone; ///< The parser finishes its job
-    bool parseError; ///< Set to tru by parse on parse error.
-
-private:
-    unsigned int currentContentType; ///< The current SSL record content type
-    size_t unParsedContent; ///< The size of current SSL record, which is not parsed yet
-    size_t parsingPos; ///< The parsing position from the beginning of parsed data
-    size_t currentMsg; ///< The current handshake message possition from the beginning of parsed data
-    size_t currentMsgSize; ///< The current handshake message size.
-
-    size_t certificatesMsgPos; ///< The possition of certificates message from the beggining of parsed data
-    size_t certificatesMsgSize; ///< The size of certificates message
-
-private:
-    void parseServerHelloTry();
-
-    void parseRecord();
-    void parseMessages();
-
-    void parseChangeCipherCpecMessage();
-    void parseAlertMessage();
-    void parseHandshakeMessage();
-    void parseApplicationDataMessage();
-    void skipMessage(const char *msgType);
-
-    void parseServerCertificates(const SBuf &raw);
-    static X509 *ParseCertificate(const SBuf &raw);
-
-    /// concatenated TLSPlaintext.fragments of TLSPlaintext.type
-    SBuf fragments;
-
-    BinaryTokenizer tkRecords; // TLS record layer (parsing uninterpreted data)
-    BinaryTokenizer tkMessages; // TLS message layer (parsing fragments)
-};
-
 /// BIO source and sink node, handling socket I/O and monitoring SSL state
 class Bio
 {
 public:
-    enum Type {
-        BIO_TO_CLIENT = 6000,
-        BIO_TO_SERVER
-    };
-
-    /// Class to store SSL connection features
-    class sslFeatures
-    {
-    public:
-        sslFeatures();
-        bool get(const SSL *ssl); ///< Retrieves the features from SSL object
-        /// Retrieves features from raw SSL Hello message.
-        /// \param record  whether to store Message to the helloMessage member
-        bool get(const MemBuf &, bool record = true);
-        /// Parses a v3 ClientHello message
-        bool parseV3Hello(const unsigned char *hello, size_t helloSize);
-        /// Parses a v23 ClientHello message
-        bool parseV23Hello(const unsigned char *hello, size_t helloSize);
-        /// Parses a v3 ServerHello message.
-        bool parseV3ServerHello(const unsigned char *hello, size_t helloSize);
-        /// Prints to os stream a human readable form of sslFeatures object
-        std::ostream & print(std::ostream &os) const;
-        /// Converts to the internal squid SSL version form the sslVersion
-        int toSquidSSLVersion() const;
-        /// Configure the SSL object with the SSL features of the sslFeatures object
-        void applyToSSL(SSL *ssl, Ssl::BumpMode bumpMode) const;
-        /// Parses an SSL Message header. It returns the ssl Message size.
-        /// \retval >0 if the hello size is retrieved
-        /// \retval 0 if the contents of the buffer are not enough
-        /// \retval <0 if the contents of buf are not SSLv3 or TLS hello message
-        int parseMsgHead(const MemBuf &);
-    public:
-        int sslVersion; ///< The requested/used SSL version
-        int compressMethod; ///< The requested/used compressed  method
-        int helloMsgSize; ///< the hello message size
-        mutable SBuf serverName; ///< The SNI hostname, if any
-        std::string clientRequestedCiphers; ///< The client requested ciphers
-        bool unknownCiphers; ///< True if one or more ciphers are unknown
-        std::string ecPointFormatList;///< tlsExtension ecPointFormatList
-        std::string ellipticCurves; ///< tlsExtension ellipticCurveList
-        std::string opaquePrf; ///< tlsExtension opaquePrf
-        bool doHeartBeats;
-        bool tlsTicketsExtension; ///< whether TLS tickets extension is enabled
-        bool hasTlsTicket; ///< whether a TLS ticket is included
-        bool tlsStatusRequest; ///< whether the TLS status request extension is set
-        SBuf tlsAppLayerProtoNeg; ///< The value of the TLS application layer protocol extension if it is enabled
-        /// The client random number
-        unsigned char client_random[SSL3_RANDOM_SIZE];
-        SBuf sessionId;
-        std::list<int> extensions;
-        SBuf helloMessage;
-        bool initialized_;
-    };
     explicit Bio(const int anFd);
     virtual ~Bio();
 
@@ -309,20 +54,14 @@ public:
 
     /// Creates a low-level BIO table, creates a high-level Ssl::Bio object
     /// for a given socket, and then links the two together via BIO_C_SET_FD.
-    static BIO *Create(const int fd, Type type);
+    static BIO *Create(const int fd, Security::Io::Type type);
     /// Tells ssl connection to use BIO and monitor state via stateChanged()
     static void Link(SSL *ssl, BIO *bio);
 
-    /// Prepare the rbuf buffer to accept hello data
-    void prepReadBuf();
-
-    /// Reads data from socket and record them to a buffer
-    int readAndBuffer(BIO *table, const char *description);
-
-    const MemBuf &rBufData() {return rbuf;}
+    const SBuf &rBufData() {return rbuf;} ///< The buffered input data
 protected:
     const int fd_; ///< the SSL socket we are reading and writing
-    MemBuf rbuf;  ///< Used to buffer input data.
+    SBuf rbuf;  ///< Used to buffer input data.
 };
 
 /// BIO node to handle socket IO for squid client side
@@ -331,9 +70,7 @@ protected:
 class ClientBio: public Bio
 {
 public:
-    /// The ssl hello message read states
-    typedef enum {atHelloNone = 0, atHelloStarted, atHelloReceived} HelloReadState;
-    explicit ClientBio(const int anFd): Bio(anFd), holdRead_(false), holdWrite_(false), helloState(atHelloNone), helloSize(0), wrongProtocol(false) {}
+    explicit ClientBio(const int anFd);
 
     /// The ClientBio version of the Ssl::Bio::stateChanged method
     /// When the client hello message retrieved, fill the
@@ -345,24 +82,27 @@ public:
     /// If the holdRead flag is true then it does not write any data
     /// to socket and sets the "read retry" flag of the BIO to true
     virtual int read(char *buf, int size, BIO *table);
-    /// Return true if the client hello message received and analized
-    bool gotHello() { return (helloState == atHelloReceived); }
-    /// Return the SSL features requested by SSL client
-    const Bio::sslFeatures &getFeatures() const {return features;}
     /// Prevents or allow writting on socket.
     void hold(bool h) {holdRead_ = holdWrite_ = h;}
-    /// True if client does not looks like an SSL client
-    bool noSslClient() {return wrongProtocol;}
+
+    /// Sets the buffered input data (Bio::rbuf).
+    /// Used to pass payload data (normally client HELLO data) retrieved
+    /// by the caller.
+    void setReadBufData(SBuf &data) {rbuf = data;}
 private:
-    /// True if the SSL state corresponds to a hello message
-    bool isClientHello(int state);
-    /// The futures retrieved from client SSL hello message
-    Bio::sslFeatures features;
+    /// approximate size of a time window for computing client-initiated renegotiation rate (in seconds)
+    static const time_t RenegotiationsWindow = 10;
+
+    /// the maximum tolerated number of client-initiated renegotiations in RenegotiationsWindow
+    static const int RenegotiationsLimit = 5;
+
     bool holdRead_; ///< The read hold state of the bio.
     bool holdWrite_;  ///< The write hold state of the bio.
-    HelloReadState helloState; ///< The SSL hello read state
     int helloSize; ///< The SSL hello message sent by client size
-    bool wrongProtocol; ///< true if client SSL hello parsing failed
+    FadingCounter renegotiations; ///< client requested renegotiations limit control
+
+    /// why we should terminate the connection during next TLS operation (or nil)
+    const char *abortReason;
 };
 
 /// BIO node to handle socket IO for squid server side
@@ -383,7 +123,8 @@ private:
 class ServerBio: public Bio
 {
 public:
-    explicit ServerBio(const int anFd): Bio(anFd), helloMsgSize(0), helloBuild(false), allowSplice(false), allowBump(false), holdWrite_(false), holdRead_(true), bumpMode_(bumpNone), rbufConsumePos(0) {}
+    explicit ServerBio(const int anFd);
+
     /// The ServerBio version of the Ssl::Bio::stateChanged method
     virtual void stateChanged(const SSL *ssl, int where, int ret);
     /// The ServerBio version of the Ssl::Bio::write method
@@ -398,14 +139,10 @@ public:
     /// Flushes any buffered data
     virtual void flush(BIO *table);
     /// Sets the random number to use in client SSL HELLO message
-    void setClientFeatures(const sslFeatures &features);
+    void setClientFeatures(Security::TlsDetails::Pointer const &details, SBuf const &hello);
 
     bool resumingSession();
 
-    /// Reads Server hello message+certificates+ServerHelloDone message sent
-    /// by server and buffer it to rbuf member
-    int readAndBufferServerHelloMsg(BIO *table, const char *description);
-
     /// The write hold state
     bool holdWrite() const {return holdWrite_;}
     /// Enables or disables the write hold state
@@ -414,6 +151,8 @@ public:
     bool holdRead() const {return holdRead_;}
     /// Enables or disables the read hold state
     void holdRead(bool h) {holdRead_ = h;}
+    /// Enables or disables the input data recording, for internal analysis.
+    void recordInput(bool r) {record_ = r;}
     /// Whether we can splice or not the SSL stream
     bool canSplice() {return allowSplice;}
     /// Whether we can bump or not the SSL stream
@@ -422,16 +161,28 @@ public:
     void mode(Ssl::BumpMode m) {bumpMode_ = m;}
     Ssl::BumpMode bumpMode() {return bumpMode_;} ///< return the bumping mode
 
-    /// Return true if the Server hello message received
-    bool gotHello() const { return (parser_.parseDone && !parser_.parseError); }
+    /// \retval true if the Server hello message received
+    bool gotHello() const { return (parsedHandshake && !parseError); }
 
     /// Return true if the Server Hello parsing failed
-    bool gotHelloFailed() const { return (parser_.parseDone && parser_.parseError); }
+    bool gotHelloFailed() const { return (parsedHandshake && parseError); }
+
+    /// \return the server certificates list if received and parsed correctly
+    const Security::CertList &serverCertificatesIfAny() { return parser_.serverCertificates; }
 
-    const Ssl::X509_STACK_Pointer &serverCertificates() { return parser_.serverCertificates; } /* XXX: may be nil */
+    /// \return the TLS Details advertised by TLS server.
+    const Security::TlsDetails::Pointer &receivedHelloDetails() const {return parser_.details;}
 
 private:
-    sslFeatures clientFeatures; ///< SSL client features extracted from ClientHello message or SSL object
+    int readAndGive(char *buf, const int size, BIO *table);
+    int readAndParse(char *buf, const int size, BIO *table);
+    int readAndBuffer(BIO *table);
+    int giveBuffered(char *buf, const int size);
+
+    /// SSL client features extracted from ClientHello message or SSL object
+    Security::TlsDetails::Pointer clientTlsDetails;
+    /// TLS client hello message, used to adapt our tls Hello message to the server
+    SBuf clientSentHello;
     SBuf helloMsg; ///< Used to buffer output data.
     mb_size_t  helloMsgSize;
     bool helloBuild; ///< True if the client hello message sent to the server
@@ -439,20 +190,21 @@ private:
     bool allowBump;  ///< True if the SSL stream can be bumped
     bool holdWrite_;  ///< The write hold state of the bio.
     bool holdRead_;  ///< The read hold state of the bio.
+    bool record_; ///< If true the input data recorded to rbuf for internal use
+    bool parsedHandshake; ///< whether we are done parsing TLS Hello
+    bool parseError; ///< error while parsing server hello message
     Ssl::BumpMode bumpMode_;
 
-    ///< The size of data stored in rbuf which passed to the openSSL
+    /// The size of data stored in rbuf which passed to the openSSL
     size_t rbufConsumePos;
-    HandshakeParser parser_; ///< The SSL messages parser.
+    Security::HandshakeParser parser_; ///< The TLS/SSL messages parser.
 };
 
-inline
-std::ostream &operator <<(std::ostream &os, Ssl::Bio::sslFeatures const &f)
-{
-    return f.print(os);
-}
-
 } // namespace Ssl
 
+void
+applyTlsDetailsToSSL(SSL *ssl, Security::TlsDetails::Pointer const &details, Ssl::BumpMode bumpMode);
+
+#endif /* USE_OPENSSL */
 #endif /* SQUID_SSL_BIO_H */