SSL_CTX *SSLSocket::sslCreateCtx(bool client, bool verify, const char *CAfile, const char *CRTfile, const char *KEYfile, void *passwd) { SSL_CTX *sctx = SSL_CTX_new(client ? SSLv23_client_method() : SSLv23_server_method()); if (sctx == NULL) { throw SSLSocketException ( "Could not create SSL context." ); } else { // Generate a new DH key during each handshake SSL_CTX_set_options(sctx, SSL_OP_SINGLE_DH_USE); // The verification contextof certificates is activated if (verify) SSL_CTX_set_verify(sctx, SSL_VERIFY_PEER, NULL); // Sets the password of the private key SSL_CTX_set_default_passwd_cb_userdata(sctx, passwd); if (!client) { int s_server_session_id_context = 1; SSL_CTX_set_session_id_context(sctx, (const unsigned char*) &s_server_session_id_context, sizeof(s_server_session_id_context)); } if (SSL_CTX_load_verify_locations(sctx, CAfile, NULL) == 0) { throw CertificateException ( "CA file could not be loaded." ); } if (SSL_CTX_use_certificate_file(sctx, CRTfile, SSL_FILETYPE_PEM) == 0) { ERR_print_errors_fp(stderr); throw CertificateException ( "CRT file could not be loaded." ); } if (SSL_CTX_use_PrivateKey_file(sctx, KEYfile, SSL_FILETYPE_PEM) == 0) { throw CertificateException ( "KEY file could not be loaded." ); } } return sctx; }
int SSLSocket::checkSSL(int ret) { if (!ssl) { return -1; } if (ret <= 0) { /* inspired by boost.asio (asio/ssl/detail/impl/engine.ipp, function engine::perform) and the SSL_get_error doc at <https://www.openssl.org/docs/ssl/SSL_get_error.html>. */ auto err = SSL_get_error(ssl, ret); switch (err) { case SSL_ERROR_NONE: // Fallthrough - YaSSL doesn't for example return an openssl compatible error on recv fail case SSL_ERROR_WANT_READ: // Fallthrough case SSL_ERROR_WANT_WRITE: return -1; case SSL_ERROR_ZERO_RETURN: throw SocketException(STRING(CONNECTION_CLOSED)); case SSL_ERROR_SYSCALL: { auto sys_err = ERR_get_error(); if (sys_err == 0) { if (ret == 0) { dcdebug("TLS error: call ret = %d, SSL_get_error = %d, ERR_get_error = %lu\n", ret, err, sys_err); throw SSLSocketException(STRING(CONNECTION_CLOSED)); } sys_err = getLastError(); } throw SSLSocketException(sys_err); } default: //display the cert errors as first choice, if the error is not the certs display the error from the ssl. auto sys_err = ERR_get_error(); string _error; int v_err = SSL_get_verify_result(ssl); if (v_err == X509_V_ERR_APPLICATION_VERIFICATION) { _error = "Keyprint mismatch"; } else if (v_err != X509_V_OK) { _error = X509_verify_cert_error_string(v_err); } else { _error = ERR_error_string(sys_err, NULL); } //dcdebug("TLS error: call ret = %d, SSL_get_error = %d, ERR_get_error = " U64_FMT ",ERROR string: %s \n", ret, err, sys_err, ERR_error_string(sys_err, NULL)); throw SSLSocketException(STRING(TLS_ERROR) + (_error.empty() ? "" : + ": " + _error)); } } return ret; }
int SSLSocket::checkSSL(int ret) { if(!ssl) { return -1; } if(ret <= 0) { int err = SSL_get_error(ssl, ret); switch(err) { case SSL_ERROR_NONE: // Fallthrough - YaSSL doesn't for example return an openssl compatible error on recv fail case SSL_ERROR_WANT_READ: // Fallthrough case SSL_ERROR_WANT_WRITE: return -1; case SSL_ERROR_ZERO_RETURN: throw SocketException("Connection closed"); default: { ssl.reset(); // @todo replace 80 with MAX_ERROR_SZ or whatever's appropriate for yaSSL in some nice way... int error = ERR_get_error(); throw SSLSocketException(str(boost::format("SSL Error %1%: %2%") % err % (error == 0 ? CSTRING(CONNECTION_CLOSED) : ERR_reason_error_string(error)))); } } } return ret; }
// Establish a connection using an SSL layer int SSLSocket::sslConnect(const char *addr, uint16_t port, int timeout) { conn.sslHandle = NULL; conn.socket = tcpConnect(addr, port, timeout); // Create an SSL struct for the connection conn.sslHandle = SSL_new(ctx); if (conn.sslHandle == NULL) { close(); throw SSLSocketException ( "Could not create SSL object." ); } // Connect the SSL struct to our connection if (!SSL_set_fd(conn.sslHandle, conn.socket)) { close(); throw SSLSocketException ( "Could not connect the SSL object to the socket." ); } // Initiate SSL handshake if (SSL_connect(conn.sslHandle) != 1) { close(); throw SSLSocketException ( "Error during SSL handshake." ); } return conn.socket; }
int SSLSocket::checkSSL(int ret) { if(!ssl) { return -1; } if(ret <= 0) { /* inspired by boost.asio (asio/ssl/detail/impl/engine.ipp, function engine::perform) and the SSL_get_error doc at <https://www.openssl.org/docs/ssl/SSL_get_error.html>. */ auto err = SSL_get_error(ssl, ret); switch(err) { case SSL_ERROR_NONE: // Fallthrough - YaSSL doesn't for example return an openssl compatible error on recv fail case SSL_ERROR_WANT_READ: // Fallthrough case SSL_ERROR_WANT_WRITE: return -1; case SSL_ERROR_ZERO_RETURN: throw SocketException(STRING(CONNECTION_CLOSED)); case SSL_ERROR_SYSCALL: { auto sys_err = ERR_get_error(); if(sys_err == 0) { if(ret == 0) { dcdebug("TLS error: call ret = %d, SSL_get_error = %d, ERR_get_error = %d\n", ret, err, sys_err); throw SSLSocketException(STRING(CONNECTION_CLOSED)); } sys_err = getLastError(); } throw SSLSocketException(sys_err); } default: /* don't bother getting error messages from the codes because 1) there is some additional management necessary (eg SSL_load_error_strings) and 2) openssl error codes aren't shown to the end user; they only hit standard output in debug builds. */ dcdebug("TLS error: call ret = %d, SSL_get_error = %d, ERR_get_error = %d\n", ret, err, ERR_get_error()); throw SSLSocketException(STRING(TLS_ERROR)); } } return ret; }
bool SSLSocket::waitWant(int ret, uint64_t millis) { #ifdef HEADER_OPENSSLV_H int err = SSL_get_error(ssl, ret); switch(err) { case SSL_ERROR_WANT_READ: return wait(millis, Socket::WAIT_READ) == WAIT_READ; case SSL_ERROR_WANT_WRITE: return wait(millis, Socket::WAIT_WRITE) == WAIT_WRITE; #else int err = ssl->last_error; switch(err) { case GNUTLS_E_INTERRUPTED: case GNUTLS_E_AGAIN: { int waitFor = wait(millis, Socket::WAIT_READ | Socket::WAIT_WRITE); return (waitFor & Socket::WAIT_READ) || (waitFor & Socket::WAIT_WRITE); } #endif // Check if this is a fatal error... default: checkSSL(ret); } dcdebug("SSL: Unexpected fallthrough"); // There was no error? return true; } int SSLSocket::read(void* aBuffer, int aBufLen) throw(SocketException) { if(!ssl) { return -1; } int len = checkSSL(SSL_read(ssl, aBuffer, aBufLen)); if(len > 0) { stats.totalDown += len; //dcdebug("In(s): %.*s\n", len, (char*)aBuffer); } return len; } int SSLSocket::write(const void* aBuffer, int aLen) throw(SocketException) { if(!ssl) { return -1; } int ret = checkSSL(SSL_write(ssl, aBuffer, aLen)); if(ret > 0) { stats.totalUp += ret; //dcdebug("Out(s): %.*s\n", ret, (char*)aBuffer); } return ret; } int SSLSocket::checkSSL(int ret) throw(SocketException) { if(!ssl) { return -1; } if(ret <= 0) { int err = SSL_get_error(ssl, ret); switch(err) { case SSL_ERROR_NONE: // Fallthrough - YaSSL doesn't for example return an openssl compatible error on recv fail case SSL_ERROR_WANT_READ: // Fallthrough case SSL_ERROR_WANT_WRITE: return -1; case SSL_ERROR_ZERO_RETURN: #ifndef HEADER_OPENSSLV_H if(ssl->last_error == GNUTLS_E_INTERRUPTED || ssl->last_error == GNUTLS_E_AGAIN) return -1; #endif throw SocketException(STRING(CONNECTION_CLOSED)); default: { ssl.reset(); // @todo replace 80 with MAX_ERROR_SZ or whatever's appropriate for yaSSL in some nice way... char errbuf[80]; /* TODO: better message for SSL_ERROR_SYSCALL * If the error queue is empty (i.e. ERR_get_error() returns 0), ret can be used to find out more about the error: * If ret == 0, an EOF was observed that violates the protocol. If ret == -1, the underlying BIO reported an I/O error * (for socket I/O on Unix systems, consult errno for details). */ int error = ERR_get_error(); sprintf(errbuf, "%s %d: %s", CSTRING(SSL_ERROR), err, (error == 0) ? CSTRING(CONNECTION_CLOSED) : ERR_reason_error_string(error)); throw SSLSocketException(errbuf); } } } return ret; } int SSLSocket::wait(uint64_t millis, int waitFor) throw(SocketException) { #ifdef HEADER_OPENSSLV_H if(ssl && (waitFor & Socket::WAIT_READ)) { /** @todo Take writing into account as well if reading is possible? */ char c; if(SSL_peek(ssl, &c, 1) > 0) return WAIT_READ; } #endif return Socket::wait(millis, waitFor); } bool SSLSocket::isTrusted() throw() { if(!ssl) { return false; } #ifdef HEADER_OPENSSLV_H if(SSL_get_verify_result(ssl) != X509_V_OK) { return false; } #else if(gnutls_certificate_verify_peers(((SSL*)ssl)->gnutls_state) != 0) { return false; } #endif X509* cert = SSL_get_peer_certificate(ssl); if(!cert) { return false; } X509_free(cert); return true; } std::string SSLSocket::getCipherName() throw() { if(!ssl) return Util::emptyString; return SSL_get_cipher_name(ssl); } std::string SSLSocket::getDigest() const throw() { #ifdef HEADER_OPENSSLV_H if(!ssl) return Util::emptyString; X509* x509 = SSL_get_peer_certificate(ssl); if(!x509) return Util::emptyString; return ssl::X509_digest(x509, EVP_sha1()); #else return Util::emptyString; #endif } void SSLSocket::shutdown() throw() { if(ssl) SSL_shutdown(ssl); } void SSLSocket::close() throw() { if(ssl) { ssl.reset(); } Socket::shutdown(); Socket::close(); } } // namespace dcpp
int SSLSocket::read(void *buf, int num) const { int status = SSL_read(conn.sslHandle, buf, num); if (status < 0) throw SSLSocketException ( "Could not read from socket." ); return status; }
int SSLSocket::write(const void *buf, int num) const { int status = SSL_write(conn.sslHandle, buf, num); if (status <= 0) throw SSLSocketException( "Could not write byte" ); return status; }