1
0
mirror of https://github.com/stefanocasazza/ULib.git synced 2025-09-28 19:05:55 +08:00
ULib/include/ulib/ssl/net/sslsocket.h
stefanocasazza 2d1d846a7f MACOS fix
2016-02-29 18:00:26 +01:00

347 lines
13 KiB
C++

// ============================================================================
//
// = LIBRARY
// ULib - c++ library
//
// = FILENAME
// sslsocket.h
//
// = AUTHOR
// Stefano Casazza
//
// ============================================================================
#ifndef ULIB_SSLSOCKET_H
#define ULIB_SSLSOCKET_H 1
#include <ulib/net/socket.h>
#ifndef USE_LIBSSL
#define USE_LIBSSL
#endif
#include <ulib/utility/services.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#if !defined(OPENSSL_NO_OCSP) && defined(SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB)
# include <openssl/ocsp.h>
# ifndef U_OCSP_MAX_RESPONSE_SIZE
# define U_OCSP_MAX_RESPONSE_SIZE 10240
# endif
#endif
/**
* --------------------------------------------------------------------------------------------------------------------
* This class implements TCP/IP sockets with the Secure Sockets Layer (SSL v2/v3) and
* Transport Layer Security (TLS v1) protocols. The OpenSSL library is used in this implementation,
* see the OpenSSL homepage for more information.
* --------------------------------------------------------------------------------------------------------------------
* Quando un'applicazione (client) chiede di aprire una connessione SSL verso un server si attiva il servizio
* che utilizza i protocolli di handshake e di scambio delle chiavi di cifratura. Lo scopo di questa fase è quello
* di utilizzare un sistema crittografico asimmetrico per svolgere due funzioni:
*
* 1) Effettuare l'autenticazione reciproca del cliente e del server.
* 2) Scambiare fra cliente e server le chiavi di cifratura da utilizzare nel corso della connessione.
* Le chiavi sono due per il cliente e due per il server. Una di esse serve per la cifratura dei dati
* e l'altra per il codice di autenticazione (MAC).
*
* Al termine di questa fase inizia la vera e propria connessione durante la quale il messaggio (ovvero i dati
* inviati da cliente a server e viceversa) viene trattato come segue:
*
* a) Il messaggio viene suddiviso in blocchi di lunghezza prefissata.
* b) Ciascun blocco viene compresso con un algoritmo di compressione.
* c) A ciascun blocco viene aggiunto il codice MAC che serve a garantire l'autenticità dei dati.
* d) Il blocco complessivo (dati compressi+MAC) viene cifrato utilizzando un algoritmo simmetrico e l'opportuna chiave.
* e) In testa al blocco cifrato viene posto lo "header" SSL.
* f) Il blocco viene inviato al corrispondente utilizzando il protocollo TCP/IP.
*
* Il corrispondente alla ricezione del blocco dovrà efettuare le operazioni contrarie, ovvero:
*
* a) Eliminare lo header SSL
* b) Decifrare il messaggio utilizzando la chiave opportuna
* c) Verificare che il codice MAC sia quello del corrispondente
* d) Eliminare il codice MAC
* e) Decomprimere il blocco risultante
* f) Riunire i frammenti del messaggio
* ---------------------------------------------------------------------------------------------------------------------
*/
#define SSL_VERIFY_PEER_STRICT (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
typedef int (*PEM_PASSWD_CB)(char* buf, int size, int rwflag, void* password); // Password callback
class UHTTP;
class UTCPSocket;
class UHttpPlugIn;
class UClient_Base;
class UServer_Base;
class UClientImage_Base;
template <class T> class UClient;
template <class T> class UServer;
template <class T> class UClientImage;
class U_EXPORT USSLSocket : public USocket {
public:
// see: https://wiki.mozilla.org/Security/Server_Side_TLS
enum CipherSuiteModel {
Intermediate = 0x000,
Modern = 0x001,
Old = 0x002
};
// COSTRUTTORI
USSLSocket(bool bSocketIsIPv6 = false, SSL_CTX* ctx = 0, bool server = false);
~USSLSocket();
// VARIE
bool secureConnection();
bool acceptSSL(USSLSocket* pcConnection);
const char* getProtocolList() { return (ciphersuite_model == 1 ? "TLSv1.2,TLSv1.1" :
ciphersuite_model == 2 ? "TLSv1.2,TLSv1.1,TLSv1.0,SSLv3" : "TLSv1.2,TLSv1.1,TLSv1.0"); }
const char* getConfigurationModel() { return (ciphersuite_model == 1 ? "Modern" :
ciphersuite_model == 2 ? "Old" : "Intermediate"); }
static long getOptions(const UVector<UString>& vec);
/**
* Load Diffie-Hellman parameters from file. These are used to generate a DH key exchange.
* See man SSL_CTX_set_tmp_dh_callback(3) and www.skip-vpn.org/spec/numbers.html for more information.
* Should be called before accept() or connect() if used. Returns true on success
*/
bool useDHFile(const char* dh_file = 0);
/**
* Load a certificate. A socket used on the server side needs to have a certificate (but a temporary RSA session
* certificate may be created if you don't load one yourself). The client side can also load certificates but it
* is not required. The files should be in ASCII PEM format and the certificate and the private key can either be
* in the same file or two separate files. OpenSSL's standard password prompt will be used if the private key uses
* a password. You should load the certificate before calling accept() or connect(). Should the peer certificate be
* verified ? The arguments specify the locations of trusted CA certificates used in the verification. Either CAfile
* or CApath can be set to NULL. See man SSL_CTX_load_verify_locations(3) for format information. Should be called
* before accept() or connect() if used and the verification result is then available by calling getVerifyResult()
* on the connected socket (the new socket from accept() on the server side, the same socket on the client side).
* Returns true on success
*/
bool setContext(const char* dh_file, const char* cert_file,
const char* private_key_file, const char* passwd,
const char* CAfile, const char* CApath,
int mode = SSL_VERIFY_PEER_STRICT | SSL_VERIFY_CLIENT_ONCE);
/**
* For successful verification, the peer certificate must be signed with the CA certificate directly or indirectly
* (a proper certificate chain exists). The certificate chain length from the CA certificate to the peer certificate
* can be set in the verify_depth field of the SSL_CTX and SSL structures. (The value in SSL is inherited from SSL_CTX
* when you create an SSL structure using the SSL_new() API). Setting verify_depth to 1 means that the peer certificate
* must be directly signed by the CA certificate.
*/
void setVerifyDepth(int depth = 1)
{
U_TRACE(1, "USSLSocket::setVerifyDepth(%d)", depth)
U_INTERNAL_ASSERT_POINTER(ctx)
U_SYSCALL_VOID(SSL_CTX_set_verify_depth, "%p,%d", ctx, depth);
}
/**
* Verify callback
*/
void setVerifyCallback(verify_cb func, int mode = SSL_VERIFY_PEER_STRICT | SSL_VERIFY_CLIENT_ONCE)
{
U_TRACE(1, "USSLSocket::setVerifyCallback(%p,%d)", func, mode)
U_INTERNAL_ASSERT_POINTER(func)
U_SYSCALL_VOID(SSL_CTX_set_verify, "%p,%d,%p", ctx, mode, func);
}
/**
* Gets the peer certificate verification result. Should be called after connect() or accept() where the verification is
* done. On the server side this should be done on the new object returned by accept() and not on the listener object! If
* you don't get X509_V_OK and don't trust the peer you should disconnect. If you trust the peer (or perhaps ask the user
* if he/she does) but didn't get X509_V_OK you might consider adding this certificate to the trusted CA certificates loaded
* by setContext(), but don't add invalid certificates...
*/
long getVerifyResult()
{
U_TRACE_NO_PARAM(1, "USSLSocket::getVerifyResult()")
U_INTERNAL_ASSERT_POINTER(ssl)
long result = U_SYSCALL(SSL_get_verify_result, "%p", ssl);
# ifdef DEBUG
UServices::setVerifyStatus(result);
U_INTERNAL_DUMP("verify_status = %.*S", u_buffer_len, u_buffer)
u_buffer_len = 0;
# endif
U_RETURN(result);
}
/**
* Get peer certificate. Should be called after connect() or accept() when using verification
* NB: OpenSSL already tested the cert validity during SSL handshake and returns a X509 ptr just if the certificate is valid...
*/
X509* getPeerCertificate()
{
U_TRACE_NO_PARAM(1, "USSLSocket::getPeerCertificate()")
X509* peer = (X509*) (ssl ? U_SYSCALL(SSL_get_peer_certificate, "%p", ssl) : 0);
U_RETURN_POINTER(peer, X509);
}
/**
* Server side RE-NEGOTIATE asking for client cert
*/
bool askForClientCertificate();
/**
* Returns the number of bytes which are available inside ssl for immediate read
*/
uint32_t pending() const
{
U_TRACE_NO_PARAM(0, "USSLSocket::pending()")
if (USocket::isSSLActive())
{
U_INTERNAL_DUMP("this = %p ssl = %p", this, ssl)
U_INTERNAL_ASSERT_POINTER(ssl)
// NB: data are received in blocks from the peer. Therefore data can be buffered
// inside ssl and are ready for immediate retrieval with SSL_read()...
uint32_t result = U_SYSCALL(SSL_pending, "%p", ssl);
U_RETURN(result);
}
U_RETURN(0);
}
// VIRTUAL METHOD
virtual int send(const char* pData, uint32_t iDataLen) U_DECL_FINAL;
virtual int recv( void* pBuffer, uint32_t iBufferLen) U_DECL_FINAL;
/**
* This method is called to connect the socket to a server SSL that is specified
* by the provided host name and port number. We call the SSL_connect() function to perform the connection
*/
virtual bool connectServer(const UString& server, unsigned int iServPort, int timeoutMS = 0) U_DECL_FINAL;
/**
* OCSP stapling is a way for a SSL server to obtain OCSP responses for his own certificate, and provide them to the client,
* under the assumption that the client may need them. This makes the whole process more efficient: the client does not have
* to open extra connections to get the OCSP responses itself, and the same OCSP response can be sent by the server to all
* clients within a given time frame. One way to see it is that the SSL server acts as a Web proxy for the purpose of
* downloading OCSP responses.
*/
#if !defined(OPENSSL_NO_OCSP) && defined(SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB)
typedef struct stapling {
void* data;
char* path;
long valid;
X509* cert;
X509* issuer;
UString* url;
EVP_PKEY* pkey;
int len, verify;
OCSP_CERTID* id;
OCSP_REQUEST* req;
UClient<UTCPSocket>* client;
} stapling;
static stapling staple;
static bool doStapling();
static void cleanupStapling();
static bool setDataForStapling();
static void certificate_status_callback(SSL* _ssl, void* data);
#endif
#if defined(U_STDCPP_ENABLE) && defined(DEBUG)
const char* dump(bool reset) const;
#endif
protected:
SSL* ssl;
SSL_CTX* ctx;
int ret, renegotiations, ciphersuite_model;
void closesocket();
static SSL_CTX* cctx; // client
static SSL_CTX* sctx; // server
static int session_cache_index;
static void setStatus(SSL* _ssl, int _ret, bool _flag);
static void info_callback(const SSL* ssl, int where, int ret);
void setStatus(bool _flag) const { return setStatus(ssl, ret, _flag); }
static SSL_CTX* getClientContext() { return getContext(0, false, 0); }
static SSL_CTX* getServerContext() { return getContext(0, true, 0); }
static SSL_CTX* getContext(SSL_METHOD* method, bool server, long options);
/**
* A TLS extension called "Server Name Indication" (SNI) allows name-based HTTPS virtual hosting.
* This is especially common when serving HTTPS requests with a wildcard certificate (*.domain.tld)
*/
#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_set_tlsext_host_name)
static int callback_ServerNameIndication(SSL* _ssl, int* alert, void* data);
#endif
private:
static int nextProto(SSL* ssl, const unsigned char** data, unsigned int* len, void* arg) U_NO_EXPORT;
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
static int selectProto(SSL* ssl, const unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen, void* arg) U_NO_EXPORT;
#endif
#ifdef U_COMPILER_DELETE_MEMBERS
USSLSocket(const USSLSocket&) = delete;
USSLSocket& operator=(const USSLSocket&) = delete;
#else
USSLSocket(const USSLSocket&) : USocket(false) {}
USSLSocket& operator=(const USSLSocket&) { return *this; }
#endif
friend class UHTTP;
friend class USocket;
friend class UHttpPlugIn;
friend class UClient_Base;
friend class UServer_Base;
friend class UClientImage_Base;
template <class T> friend class UClient;
template <class T> friend class UServer;
template <class T> friend class UClientImage;
};
#endif