/* * IXSocketOpenSSL.cpp * Author: Benjamin Sergeant * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. * * Adapted from Satori SDK OpenSSL code. */ #include "IXSocketOpenSSL.h" #include "IXSocketConnect.h" #include #include #include #include #define socketerrno errno namespace ix { std::atomic SocketOpenSSL::_openSSLInitializationSuccessful(false); std::once_flag SocketOpenSSL::_openSSLInitFlag; SocketOpenSSL::SocketOpenSSL(int fd) : Socket(fd), _ssl_connection(nullptr), _ssl_context(nullptr) { std::call_once(_openSSLInitFlag, &SocketOpenSSL::openSSLInitialize, this); } SocketOpenSSL::~SocketOpenSSL() { SocketOpenSSL::close(); } void SocketOpenSSL::openSSLInitialize() { #if OPENSSL_VERSION_NUMBER >= 0x10100000L if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, nullptr)) return; #else (void) OPENSSL_config(nullptr); #endif (void) OpenSSL_add_ssl_algorithms(); (void) SSL_load_error_strings(); _openSSLInitializationSuccessful = true; } std::string SocketOpenSSL::getSSLError(int ret) { unsigned long e; int err = SSL_get_error(_ssl_connection, ret); if (err == SSL_ERROR_WANT_CONNECT || err == SSL_ERROR_WANT_ACCEPT) { return "OpenSSL failed - connection failure"; } else if (err == SSL_ERROR_WANT_X509_LOOKUP) { return "OpenSSL failed - x509 error"; } else if (err == SSL_ERROR_SYSCALL) { e = ERR_get_error(); if (e > 0) { std::string errMsg("OpenSSL failed - "); errMsg += ERR_error_string(e, nullptr); return errMsg; } else if (e == 0 && ret == 0) { return "OpenSSL failed - received early EOF"; } else { return "OpenSSL failed - underlying BIO reported an I/O error"; } } else if (err == SSL_ERROR_SSL) { e = ERR_get_error(); std::string errMsg("OpenSSL failed - "); errMsg += ERR_error_string(e, nullptr); return errMsg; } else if (err == SSL_ERROR_NONE) { return "OpenSSL failed - err none"; } else if (err == SSL_ERROR_ZERO_RETURN) { return "OpenSSL failed - err zero return"; } else { return "OpenSSL failed - unknown error"; } } SSL_CTX* SocketOpenSSL::openSSLCreateContext(std::string& errMsg) { const SSL_METHOD* method = SSLv23_client_method(); if (method == nullptr) { errMsg = "SSLv23_client_method failure"; return nullptr; } _ssl_method = method; SSL_CTX* ctx = SSL_CTX_new(_ssl_method); if (ctx) { // To skip verification, pass in SSL_VERIFY_NONE SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, [](int preverify, X509_STORE_CTX*) -> int { return preverify; }); SSL_CTX_set_verify_depth(ctx, 4); SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); } return ctx; } /** * Check whether a hostname matches a pattern */ bool SocketOpenSSL::checkHost(const std::string& host, const char *pattern) { return fnmatch(pattern, host.c_str(), 0) != FNM_NOMATCH; } bool SocketOpenSSL::openSSLCheckServerCert(SSL *ssl, const std::string& hostname, std::string& errMsg) { X509 *server_cert = SSL_get_peer_certificate(ssl); if (server_cert == nullptr) { errMsg = "OpenSSL failed - peer didn't present a X509 certificate."; return false; } #if OPENSSL_VERSION_NUMBER < 0x10100000L // Check server name bool hostname_verifies_ok = false; STACK_OF(GENERAL_NAME) *san_names = (STACK_OF(GENERAL_NAME)*) X509_get_ext_d2i((X509 *)server_cert, NID_subject_alt_name, NULL, NULL); if (san_names) { for (int i=0; itype == GEN_DNS) { char *name = (char *)ASN1_STRING_data(sk_name->d.dNSName); if ((size_t)ASN1_STRING_length(sk_name->d.dNSName) == strlen(name) && checkHost(hostname, name)) { hostname_verifies_ok = true; break; } } } } sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free); if (!hostname_verifies_ok) { int cn_pos = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *)server_cert), NID_commonName, -1); if (cn_pos) { X509_NAME_ENTRY *cn_entry = X509_NAME_get_entry( X509_get_subject_name((X509 *)server_cert), cn_pos); if (cn_entry) { ASN1_STRING *cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry); char *cn = (char *)ASN1_STRING_data(cn_asn1); if ((size_t)ASN1_STRING_length(cn_asn1) == strlen(cn) && checkHost(hostname, cn)) { hostname_verifies_ok = true; } } } } if (!hostname_verifies_ok) { errMsg = "OpenSSL failed - certificate was issued for a different domain."; return false; } #endif X509_free(server_cert); return true; } bool SocketOpenSSL::openSSLHandshake(const std::string& host, std::string& errMsg) { while (true) { if (_ssl_connection == nullptr || _ssl_context == nullptr) { return false; } ERR_clear_error(); int connect_result = SSL_connect(_ssl_connection); if (connect_result == 1) { return openSSLCheckServerCert(_ssl_connection, host, errMsg); } int reason = SSL_get_error(_ssl_connection, connect_result); bool rc = false; if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE) { rc = true; } else { errMsg = getSSLError(connect_result); rc = false; } if (!rc) { return false; } } } bool SocketOpenSSL::connect(const std::string& host, int port, std::string& errMsg, const CancellationRequest& isCancellationRequested) { bool handshakeSuccessful = false; { std::lock_guard lock(_mutex); if (!_openSSLInitializationSuccessful) { errMsg = "OPENSSL_init_ssl failure"; return false; } _sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested); if (_sockfd == -1) return false; _ssl_context = openSSLCreateContext(errMsg); if (_ssl_context == nullptr) { return false; } ERR_clear_error(); int cert_load_result = SSL_CTX_set_default_verify_paths(_ssl_context); if (cert_load_result == 0) { unsigned long ssl_err = ERR_get_error(); errMsg = "OpenSSL failed - SSL_CTX_default_verify_paths loading failed: "; errMsg += ERR_error_string(ssl_err, nullptr); } _ssl_connection = SSL_new(_ssl_context); if (_ssl_connection == nullptr) { errMsg = "OpenSSL failed to connect"; SSL_CTX_free(_ssl_context); _ssl_context = nullptr; return false; } SSL_set_fd(_ssl_connection, _sockfd); // SNI support SSL_set_tlsext_host_name(_ssl_connection, host.c_str()); #if OPENSSL_VERSION_NUMBER >= 0x10002000L // Support for server name verification // (The docs say that this should work from 1.0.2, and is the default from // 1.1.0, but it does not. To be on the safe side, the manual test below is // enabled for all versions prior to 1.1.0.) X509_VERIFY_PARAM *param = SSL_get0_param(_ssl_connection); X509_VERIFY_PARAM_set1_host(param, host.c_str(), 0); #endif handshakeSuccessful = openSSLHandshake(host, errMsg); } if (!handshakeSuccessful) { close(); return false; } return true; } void SocketOpenSSL::close() { std::lock_guard lock(_mutex); if (_ssl_connection != nullptr) { SSL_free(_ssl_connection); _ssl_connection = nullptr; } if (_ssl_context != nullptr) { SSL_CTX_free(_ssl_context); _ssl_context = nullptr; } Socket::close(); } ssize_t SocketOpenSSL::send(char* buf, size_t nbyte) { ssize_t sent = 0; while (nbyte > 0) { std::lock_guard lock(_mutex); if (_ssl_connection == nullptr || _ssl_context == nullptr) { return 0; } ERR_clear_error(); ssize_t write_result = SSL_write(_ssl_connection, buf + sent, (int) nbyte); int reason = SSL_get_error(_ssl_connection, (int) write_result); if (reason == SSL_ERROR_NONE) { nbyte -= write_result; sent += write_result; } else if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE) { errno = EWOULDBLOCK; return -1; } else { return -1; } } return sent; } ssize_t SocketOpenSSL::send(const std::string& buffer) { return send((char*)&buffer[0], buffer.size()); } ssize_t SocketOpenSSL::recv(void* buf, size_t nbyte) { while (true) { std::lock_guard lock(_mutex); if (_ssl_connection == nullptr || _ssl_context == nullptr) { return 0; } ERR_clear_error(); ssize_t read_result = SSL_read(_ssl_connection, buf, (int) nbyte); if (read_result > 0) { return read_result; } int reason = SSL_get_error(_ssl_connection, (int) read_result); if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE) { errno = EWOULDBLOCK; } return -1; } } }