From 422196759f93df249db38619f136cabd5dcf42cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Verschelde?= Date: Wed, 12 Oct 2016 23:06:17 +0200 Subject: openssl: Move to a module and split thirdparty lib Same rationale as the previous commits. --- modules/openssl/stream_peer_openssl.cpp | 646 ++++++++++++++++++++++++++++++++ 1 file changed, 646 insertions(+) create mode 100644 modules/openssl/stream_peer_openssl.cpp (limited to 'modules/openssl/stream_peer_openssl.cpp') diff --git a/modules/openssl/stream_peer_openssl.cpp b/modules/openssl/stream_peer_openssl.cpp new file mode 100644 index 0000000000..b9bec4ca0b --- /dev/null +++ b/modules/openssl/stream_peer_openssl.cpp @@ -0,0 +1,646 @@ +/*************************************************************************/ +/* stream_peer_openssl.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "stream_peer_openssl.h" +//hostname matching code from curl + + +//#include // To prevent crashing (see the OpenSSL FAQ) + +bool StreamPeerOpenSSL::_match_host_name(const char *name, const char *hostname) { + + return Tool_Curl_cert_hostcheck(name,hostname)==CURL_HOST_MATCH; +// print_line("MATCH: "+String(name)+" vs "+String(hostname)); +// return true; +} + +Error StreamPeerOpenSSL::_match_common_name(const char *hostname, const X509 *server_cert) { + + int common_name_loc = -1; + X509_NAME_ENTRY *common_name_entry = NULL; + ASN1_STRING *common_name_asn1 = NULL; + char *common_name_str = NULL; + + // Find the position of the CN field in the Subject field of the certificate + common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *) server_cert), NID_commonName, -1); + + ERR_FAIL_COND_V(common_name_loc < 0, ERR_INVALID_PARAMETER ); + + // Extract the CN field + common_name_entry = X509_NAME_get_entry(X509_get_subject_name((X509 *) server_cert), common_name_loc); + + ERR_FAIL_COND_V(common_name_entry == NULL, ERR_INVALID_PARAMETER ); + + // Convert the CN field to a C string + common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry); + + ERR_FAIL_COND_V(common_name_asn1 == NULL, ERR_INVALID_PARAMETER ); + + common_name_str = (char *) ASN1_STRING_data(common_name_asn1); + + // Make sure there isn't an embedded NUL character in the CN + bool malformed_certificate = (size_t)ASN1_STRING_length(common_name_asn1) != strlen(common_name_str); + + ERR_FAIL_COND_V(malformed_certificate, ERR_INVALID_PARAMETER ); + + + // Compare expected hostname with the CN + + + return _match_host_name(common_name_str,hostname)?OK:FAILED; + +} + + +/** +* Tries to find a match for hostname in the certificate's Subject Alternative Name extension. +* +*/ + +Error StreamPeerOpenSSL::_match_subject_alternative_name(const char *hostname, const X509 *server_cert) { + + Error result = FAILED; + int i; + int san_names_nb = -1; + STACK_OF(GENERAL_NAME) *san_names = NULL; + + // Try to extract the names within the SAN extension from the certificate + san_names = (STACK_OF(GENERAL_NAME) *)X509_get_ext_d2i((X509 *) server_cert, NID_subject_alt_name, NULL, NULL); + if (san_names == NULL) { + return ERR_FILE_NOT_FOUND; + } + san_names_nb = sk_GENERAL_NAME_num(san_names); + + // Check each name within the extension + for (i=0; itype == GEN_DNS) { + // Current name is a DNS name, let's check it + char *dns_name = (char *) ASN1_STRING_data(current_name->d.dNSName); + + // Make sure there isn't an embedded NUL character in the DNS name + if ((size_t)ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) { + result = ERR_INVALID_PARAMETER; + break; + } + else { // Compare expected hostname with the DNS name + if (_match_host_name(dns_name, hostname)) { + result = OK; + break; + } + } + } + } + sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free); + + return result; +} + +/* See http://archives.seul.org/libevent/users/Jan-2013/msg00039.html */ +int StreamPeerOpenSSL::_cert_verify_callback(X509_STORE_CTX *x509_ctx, void *arg) { + + /* This is the function that OpenSSL would call if we hadn't called + * SSL_CTX_set_cert_verify_callback(). Therefore, we are "wrapping" + * the default functionality, rather than replacing it. */ + + bool base_cert_valid = X509_verify_cert(x509_ctx); + if (!base_cert_valid) { + print_line("Cause: "+String(X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509_ctx)))); + ERR_print_errors_fp(stdout); + } + X509 *server_cert = X509_STORE_CTX_get_current_cert(x509_ctx); + + ERR_FAIL_COND_V(!server_cert,0); + + char cert_str[256]; + X509_NAME_oneline(X509_get_subject_name (server_cert), + cert_str, sizeof (cert_str)); + + print_line("CERT STR: "+String(cert_str)); + print_line("VALID: "+itos(base_cert_valid)); + + if (!base_cert_valid) + return 0; + + StreamPeerOpenSSL *ssl = (StreamPeerOpenSSL *)arg; + + if (ssl->validate_hostname) { + + Error err = _match_subject_alternative_name(ssl->hostname.utf8().get_data(),server_cert); + + if (err==ERR_FILE_NOT_FOUND) { + + err = _match_common_name(ssl->hostname.utf8().get_data(),server_cert); + } + + if (err!=OK) { + + ssl->status=STATUS_ERROR_HOSTNAME_MISMATCH; + return 0; + } + } + + return 1; + +} + + + +int StreamPeerOpenSSL::_bio_create( BIO *b ) { + b->init = 1; + b->num = 0; + b->ptr = NULL; + b->flags = 0; + return 1; +} + +int StreamPeerOpenSSL::_bio_destroy( BIO *b ) +{ + if ( b == NULL ) + return 0; + + b->ptr = NULL; /* sb_tls_remove() will free it */ + b->init = 0; + b->flags = 0; + return 1; +} + +int StreamPeerOpenSSL::_bio_read( BIO *b, char *buf, int len ) { + + + if ( buf == NULL || len <= 0 ) return 0; + + StreamPeerOpenSSL *sp = (StreamPeerOpenSSL *)b->ptr; + + ERR_FAIL_COND_V( sp == NULL, 0); + + BIO_clear_retry_flags( b ); + if (sp->use_blocking) { + + Error err = sp->base->get_data((uint8_t*)buf,len); + if (err!=OK) { + return -1; + } + + return len; + } else { + + int got; + Error err = sp->base->get_partial_data((uint8_t*)buf,len,got); + if (err!=OK) { + return -1; + } + if (got==0) { + BIO_set_retry_read( b ); + } + return got; + } + + //unreachable + return 0; +} + +int StreamPeerOpenSSL::_bio_write( BIO *b, const char *buf, int len ) { + + if ( buf == NULL || len <= 0 ) return 0; + + StreamPeerOpenSSL *sp = (StreamPeerOpenSSL *)b->ptr; + + ERR_FAIL_COND_V( sp == NULL, 0); + + BIO_clear_retry_flags( b ); + if (sp->use_blocking) { + + Error err = sp->base->put_data((const uint8_t*)buf,len); + if (err!=OK) { + return -1; + } + + return len; + } else { + + int sent; + Error err = sp->base->put_partial_data((const uint8_t*)buf,len,sent); + if (err!=OK) { + return -1; + } + if (sent==0) { + BIO_set_retry_write( b ); + } + return sent; + + } + + //unreachable + return 0; +} + +long StreamPeerOpenSSL::_bio_ctrl( BIO *b, int cmd, long num, void *ptr ) +{ + if ( cmd == BIO_CTRL_FLUSH ) { + /* The OpenSSL library needs this */ + return 1; + } + return 0; +} + +int StreamPeerOpenSSL::_bio_gets( BIO *b, char *buf, int len ) +{ + return -1; +} + +int StreamPeerOpenSSL::_bio_puts( BIO *b, const char *str ) +{ + return _bio_write( b, str, strlen( str ) ); +} + +BIO_METHOD StreamPeerOpenSSL::_bio_method = { + /* it's a source/sink BIO */ + ( 100 | 0x400 ), + "streampeer glue", + _bio_write, + _bio_read, + _bio_puts, + _bio_gets, + _bio_ctrl, + _bio_create, + _bio_destroy +}; + +Error StreamPeerOpenSSL::connect(Ref p_base, bool p_validate_certs, const String& p_for_hostname) { + + if (connected) + disconnect(); + + + hostname=p_for_hostname; + status=STATUS_DISCONNECTED; + + // Set up a SSL_CTX object, which will tell our BIO object how to do its work + ctx = SSL_CTX_new(SSLv23_client_method()); + base=p_base; + validate_certs=p_validate_certs; + validate_hostname=p_for_hostname!=""; + + + + + if (p_validate_certs) { + + + if (certs.size()) { + //yay for undocumented OpenSSL functions + + X509_STORE *store = SSL_CTX_get_cert_store(ctx); + for(int i=0;iptr = this; + SSL_set_bio( ssl, bio, bio ); + + if (p_for_hostname!=String()) { + SSL_set_tlsext_host_name(ssl,p_for_hostname.utf8().get_data()); + } + + use_blocking=true; // let handshake use blocking + // Set the SSL to automatically retry on failure. + SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); + + // Same as before, try to connect. + int result = SSL_connect( ssl ); + + print_line("CONNECTION RESULT: "+itos(result)); + if (result<1) { + ERR_print_errors_fp(stdout); + _print_error(result); + } + + X509 * peer = SSL_get_peer_certificate(ssl); + + if (peer) { + bool cert_ok = SSL_get_verify_result(ssl) == X509_V_OK; + print_line("cert_ok: "+itos(cert_ok)); + + } else if (validate_certs){ + status=STATUS_ERROR_NO_CERTIFICATE; + } + + connected=true; + status=STATUS_CONNECTED; + + return OK; +} + +Error StreamPeerOpenSSL::accept(Ref p_base) { + + + return ERR_UNAVAILABLE; +} + +void StreamPeerOpenSSL::_print_error(int err) { + + err = SSL_get_error(ssl,err); + switch(err) { + case SSL_ERROR_NONE: ERR_PRINT("NO ERROR: The TLS/SSL I/O operation completed"); break; + case SSL_ERROR_ZERO_RETURN: ERR_PRINT("The TLS/SSL connection has been closed."); + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + ERR_PRINT("The operation did not complete."); break; + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + ERR_PRINT("The connect/accept operation did not complete"); break; + case SSL_ERROR_WANT_X509_LOOKUP: + ERR_PRINT("The operation did not complete because an application callback set by SSL_CTX_set_client_cert_cb() has asked to be called again."); break; + case SSL_ERROR_SYSCALL: + ERR_PRINT("Some I/O error occurred. The OpenSSL error queue may contain more information on the error."); break; + case SSL_ERROR_SSL: + ERR_PRINT("A failure in the SSL library occurred, usually a protocol error."); break; + + } +} + +Error StreamPeerOpenSSL::put_data(const uint8_t* p_data,int p_bytes) { + + ERR_FAIL_COND_V(!connected,ERR_UNCONFIGURED); + + while(p_bytes>0) { + int ret = SSL_write(ssl,p_data,p_bytes); + if (ret<=0) { + _print_error(ret); + disconnect(); + return ERR_CONNECTION_ERROR; + } + p_data+=ret; + p_bytes-=ret; + } + + return OK; + +} + +Error StreamPeerOpenSSL::put_partial_data(const uint8_t* p_data,int p_bytes, int &r_sent){ + + ERR_FAIL_COND_V(!connected,ERR_UNCONFIGURED); + if (p_bytes==0) + return OK; + + Error err = put_data(p_data,p_bytes); + if (err!=OK) + return err; + + r_sent=p_bytes; + return OK; + +} + +Error StreamPeerOpenSSL::get_data(uint8_t* p_buffer, int p_bytes){ + + ERR_FAIL_COND_V(!connected,ERR_UNCONFIGURED); + + while(p_bytes>0) { + + int ret = SSL_read(ssl,p_buffer,p_bytes); + if (ret<=0) { + _print_error(ret); + disconnect(); + return ERR_CONNECTION_ERROR; + } + p_buffer+=ret; + p_bytes-=ret; + } + + return OK; +} + +Error StreamPeerOpenSSL::get_partial_data(uint8_t* p_buffer, int p_bytes,int &r_received){ + + ERR_FAIL_COND_V(!connected,ERR_UNCONFIGURED); + if (p_bytes==0) { + r_received=0; + return OK; + } + + Error err = get_data(p_buffer,p_bytes); + if (err!=OK) + return err; + r_received=p_bytes; + return OK; +} + +int StreamPeerOpenSSL::get_available_bytes() const { + + ERR_FAIL_COND_V(!connected,0); + + return SSL_pending(ssl); + +} +StreamPeerOpenSSL::StreamPeerOpenSSL() { + + ctx=NULL; + ssl=NULL; + bio=NULL; + connected=false; + use_blocking=true; //might be improved int the future, but for now it always blocks + max_cert_chain_depth=9; + flags=0; +} + +void StreamPeerOpenSSL::disconnect() { + + if (!connected) + return; + SSL_shutdown( ssl ); + SSL_free( ssl ); + SSL_CTX_free(ctx); + base=Ref(); + connected=false; + validate_certs=false; + validate_hostname=false; + status=STATUS_DISCONNECTED; + + +} + +StreamPeerOpenSSL::Status StreamPeerOpenSSL::get_status() const { + + return status; +} + + +StreamPeerOpenSSL::~StreamPeerOpenSSL() { + disconnect(); +} + +StreamPeerSSL* StreamPeerOpenSSL::_create_func() { + + return memnew( StreamPeerOpenSSL ); +} + + +Vector StreamPeerOpenSSL::certs; + + +void StreamPeerOpenSSL::_load_certs(const ByteArray& p_array) { + + ByteArray::Read r = p_array.read(); + BIO* mem = BIO_new(BIO_s_mem()); + BIO_puts(mem,(const char*)r.ptr()); + while(true) { + X509*cert = PEM_read_bio_X509(mem, NULL, 0, NULL); + if (!cert) + break; + certs.push_back(cert); + } + BIO_free(mem); +} + +void StreamPeerOpenSSL::initialize_ssl() { + + available=true; + + load_certs_func=_load_certs; + + _create=_create_func; + CRYPTO_malloc_init(); // Initialize malloc, free, etc for OpenSSL's use + SSL_library_init(); // Initialize OpenSSL's SSL libraries + SSL_load_error_strings(); // Load SSL error strings + ERR_load_BIO_strings(); // Load BIO error strings + OpenSSL_add_all_algorithms(); // Load all available encryption algorithms + String certs_path =GLOBAL_DEF("ssl/certificates",""); + Globals::get_singleton()->set_custom_property_info("ssl/certificates",PropertyInfo(Variant::STRING,"ssl/certificates",PROPERTY_HINT_FILE,"*.crt")); + if (certs_path!="") { + + + + FileAccess *f=FileAccess::open(certs_path,FileAccess::READ); + if (f) { + ByteArray arr; + int flen = f->get_len(); + arr.resize(flen+1); + { + ByteArray::Write w = arr.write(); + f->get_buffer(w.ptr(),flen); + w[flen]=0; //end f string + } + + memdelete(f); + + _load_certs(arr); + print_line("Loaded certs from '"+certs_path+"': "+itos(certs.size())); + } + } + String config_path =GLOBAL_DEF("ssl/config",""); + Globals::get_singleton()->set_custom_property_info("ssl/config",PropertyInfo(Variant::STRING,"ssl/config",PROPERTY_HINT_FILE,"*.cnf")); + if (config_path!="") { + + Vector data = FileAccess::get_file_as_array(config_path); + if (data.size()) { + data.push_back(0); + BIO* mem = BIO_new(BIO_s_mem()); + BIO_puts(mem,(const char*) data.ptr()); + + while(true) { + X509*cert = PEM_read_bio_X509(mem, NULL, 0, NULL); + if (!cert) + break; + certs.push_back(cert); + } + BIO_free(mem); + } + print_line("Loaded certs from '"+certs_path+"': "+itos(certs.size())); + + } + +} + +void StreamPeerOpenSSL::finalize_ssl(){ + + for(int i=0;i