| /* |
| * Copyright (C) Tildeslash Ltd. All rights reserved. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Affero General Public License version 3. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU Affero General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * |
| * In addition, as a special exception, the copyright holders give |
| * permission to link the code of portions of this program with the |
| * OpenSSL library under certain conditions as described in each |
| * individual source file, and distribute linked combinations |
| * including the two. |
| * |
| * You must obey the GNU Affero General Public License in all respects |
| * for all of the code used other than OpenSSL. |
| */ |
| |
| |
| #include "config.h" |
| |
| |
| #ifdef HAVE_OPENSSL |
| |
| |
| #ifdef HAVE_STDIO_H |
| #include <stdio.h> |
| #endif |
| |
| #ifdef HAVE_MEMORY_H |
| #include <memory.h> |
| #endif |
| |
| #if TIME_WITH_SYS_TIME |
| # include <sys/time.h> |
| # include <time.h> |
| #else |
| # if HAVE_SYS_TIME_H |
| # include <sys/time.h> |
| # else |
| # include <time.h> |
| # endif |
| #endif |
| |
| #ifdef HAVE_SYS_TYPES_H |
| #include <sys/types.h> |
| #endif |
| |
| #ifdef HAVE_SYS_SOCKET_H |
| #include <sys/socket.h> |
| #endif |
| |
| #ifdef HAVE_NETINET_IN_H |
| #include <netinet/in.h> |
| #endif |
| |
| #ifdef HAVE_ARPA_INET_H |
| #include <arpa/inet.h> |
| #endif |
| |
| #ifdef HAVE_NETDB_H |
| #include <netdb.h> |
| #endif |
| |
| #ifdef HAVE_PTHREAD_H |
| #include <pthread.h> |
| #endif |
| |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| |
| #ifdef HAVE_ERRNO_H |
| #include <errno.h> |
| #endif |
| |
| #ifdef HAVE_STRING_H |
| #include <string.h> |
| #endif |
| |
| |
| #include <openssl/crypto.h> |
| #include <openssl/x509.h> |
| #include <openssl/x509_vfy.h> |
| #include <openssl/pem.h> |
| #include <openssl/ssl.h> |
| #include <openssl/err.h> |
| #include <openssl/rand.h> |
| #include <openssl/bio.h> |
| |
| #include "monit.h" |
| #include "net.h" |
| #include "ssl.h" |
| |
| |
| /* -------------------------------------------------------------- Prototypes */ |
| |
| |
| #define SSLERROR ERR_error_string(ERR_get_error(),NULL) |
| |
| static int unsigned long ssl_thread_id(); |
| static void ssl_mutex_lock(int, int n, const char *, int ); |
| static int verify_init(ssl_server_connection *); |
| static int verify_callback(int, X509_STORE_CTX *); |
| static int check_preverify(X509_STORE_CTX *); |
| static void cleanup_ssl_socket(ssl_connection *); |
| static void cleanup_ssl_server_socket(ssl_server_connection *); |
| static int handle_error(int, ssl_connection *); |
| static int update_ssl_cert_data(ssl_connection *); |
| static ssl_server_connection *new_ssl_server_connection(char *, char *); |
| static int start_ssl(); |
| |
| static int ssl_initialized = FALSE; |
| static pthread_mutex_t ssl_mutex = PTHREAD_MUTEX_INITIALIZER; |
| static pthread_mutex_t *ssl_mutex_table; |
| |
| |
| /* ------------------------------------------------------------- Definitions */ |
| |
| |
| /** |
| * Number of random bytes to obtain |
| */ |
| #define RANDOM_BYTES 1024 |
| |
| /** |
| * The PRIMARY random device selected for seeding the PRNG. We use a |
| * non-blocking pseudo random device, to generate pseudo entropy. |
| */ |
| #define URANDOM_DEVICE "/dev/urandom" |
| |
| /** |
| * If a non-blocking device is not found on the system a blocking |
| * entropy producer is tried instead. |
| */ |
| #define RANDOM_DEVICE "/dev/random" |
| |
| |
| /* The list of all ciphers suites in order of strength except those containing |
| anonymous DH ciphers, low bit-size ciphers, export-crippled ciphers or the |
| MD5 hash algorithm */ |
| #define CIPHER_LIST "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH" |
| |
| |
| /** |
| * SSL Socket methods. |
| * |
| * @file |
| */ |
| |
| |
| /* ------------------------------------------------------------------ Public */ |
| |
| |
| /** |
| * Embeds a socket in a ssl connection. |
| * @param socket the socket to be used. |
| * @return The ssl connection or NULL if an error occured. |
| */ |
| int embed_ssl_socket(ssl_connection *ssl, int socket) { |
| int ssl_error; |
| time_t ssl_time; |
| |
| if (!ssl) |
| return FALSE; |
| |
| if (!ssl_initialized) |
| start_ssl(); |
| |
| if (socket >= 0) { |
| ssl->socket = socket; |
| } else { |
| LogError("%s: Socket error!\n", prog); |
| goto sslerror; |
| } |
| |
| if ((ssl->handler = SSL_new (ssl->ctx)) == NULL) { |
| LogError("%s: Cannot initialize the SSL handler -- %s\n", prog, SSLERROR); |
| goto sslerror; |
| } |
| |
| if (SSL_CTX_set_cipher_list(ssl->ctx, CIPHER_LIST) != 1) { |
| LogError("%s: Error setting cipher list '" CIPHER_LIST "' (no valid ciphers)", prog); |
| goto sslerror; |
| } |
| |
| set_noblock(ssl->socket); |
| |
| if ((ssl->socket_bio = BIO_new_socket(ssl->socket, BIO_NOCLOSE)) == NULL) { |
| LogError("%s: Cannot generate IO buffer -- %s\n", prog, SSLERROR); |
| goto sslerror; |
| } |
| |
| SSL_set_bio(ssl->handler, ssl->socket_bio, ssl->socket_bio); |
| ssl_time = time(NULL); |
| |
| while ((ssl_error = SSL_connect (ssl->handler)) < 0) { |
| if ((time(NULL) - ssl_time) > SSL_TIMEOUT) { |
| LogError("%s: SSL service timeout!\n", prog); |
| goto sslerror; |
| } |
| |
| if (!handle_error(ssl_error, ssl)) |
| goto sslerror; |
| |
| if (!BIO_should_retry(ssl->socket_bio)) |
| goto sslerror; |
| } |
| |
| ssl->cipher = (char *) SSL_get_cipher(ssl->handler); |
| |
| if (! update_ssl_cert_data(ssl)) { |
| LogError("%s: Cannot get the SSL server certificate!\n", prog); |
| goto sslerror; |
| } |
| |
| return TRUE; |
| |
| sslerror: |
| cleanup_ssl_socket(ssl); |
| return FALSE; |
| } |
| |
| |
| /** |
| * Compare certificate with given md5 sum |
| * @param ssl reference to ssl connection |
| * @param md5sum string of the md5sum to test against |
| * @return TRUE, if sums do not match FALSE |
| */ |
| int check_ssl_md5sum(ssl_connection *ssl, char *md5sum) { |
| unsigned int i = 0; |
| |
| ASSERT(md5sum); |
| |
| while ((i < ssl->cert_md5_len) && (md5sum[2*i] != '\0') && (md5sum[2*i+1] != '\0')) { |
| unsigned char c = (md5sum[2*i] > 57 ? md5sum[2*i] - 87 : md5sum[2*i] - 48) * 0x10+ (md5sum[2*i+1] > 57 ? md5sum[2*i+1] - 87 : md5sum[2*i+1] - 48); |
| if (c != ssl->cert_md5[i]) |
| return FALSE; |
| i++; |
| } |
| return TRUE; |
| } |
| |
| |
| /** |
| * Closes a ssl connection (ssl socket + net socket) |
| * @param ssl ssl connection |
| * @return TRUE, or FALSE if an error has occured. |
| */ |
| int close_ssl_socket(ssl_connection *ssl) { |
| int rv; |
| |
| if (!ssl) |
| return FALSE; |
| |
| if (! (rv = SSL_shutdown(ssl->handler))) { |
| shutdown(ssl->socket, 1); |
| rv = SSL_shutdown(ssl->handler); |
| } |
| |
| close_socket(ssl->socket); |
| cleanup_ssl_socket(ssl); |
| |
| return (rv > 0) ? TRUE : FALSE; |
| } |
| |
| |
| /** |
| * Garbage collection for non-reusable parts a ssl connection |
| * @param ssl ssl connection |
| */ |
| void delete_ssl_socket(ssl_connection *ssl) { |
| if (!ssl) |
| return; |
| |
| cleanup_ssl_socket(ssl); |
| |
| if (ssl->ctx && !ssl->accepted) |
| SSL_CTX_free(ssl->ctx); |
| |
| ssl->ctx = NULL; |
| |
| FREE(ssl); |
| } |
| |
| |
| /** |
| * Initializes a ssl connection for server use. |
| * @param pemfilename Filename for the key/cert file |
| * @return An ssl connection, or NULL if an error occured. |
| */ |
| ssl_server_connection *init_ssl_server(char *pemfile, char *clientpemfile) { |
| SSL_METHOD *server_method = NULL; |
| ssl_server_connection *ssl_server; |
| |
| ASSERT(pemfile); |
| |
| if (!ssl_initialized) |
| start_ssl(); |
| |
| ssl_server = new_ssl_server_connection(pemfile, clientpemfile); |
| #ifdef OPENSSL_FIPS |
| if (FIPS_mode()) |
| server_method = TLSv1_server_method(); |
| else |
| #endif |
| server_method = SSLv23_server_method(); |
| if (!(ssl_server->method = server_method)) { |
| LogError("%s: Cannot initialize the SSL method -- %s\n", prog, SSLERROR); |
| goto sslerror; |
| } |
| |
| if (!(ssl_server->ctx = SSL_CTX_new(ssl_server->method))) { |
| LogError("%s: Cannot initialize SSL server certificate handler -- %s\n", prog, SSLERROR); |
| goto sslerror; |
| } |
| |
| if (SSL_CTX_use_certificate_chain_file(ssl_server->ctx, pemfile) != 1) { |
| LogError("%s: Cannot initialize SSL server certificate -- %s\n", prog, SSLERROR); |
| goto sslerror; |
| } |
| |
| if (SSL_CTX_use_PrivateKey_file(ssl_server->ctx, pemfile, SSL_FILETYPE_PEM) != 1) { |
| LogError("%s: Cannot initialize SSL server private key -- %s\n", prog, SSLERROR); |
| goto sslerror; |
| } |
| |
| if (SSL_CTX_check_private_key(ssl_server->ctx) != 1) { |
| LogError("%s: The private key doesn't match the certificate public key -- %s\n", prog, SSLERROR); |
| goto sslerror; |
| } |
| |
| /* Disable session cache */ |
| SSL_CTX_set_session_cache_mode(ssl_server->ctx, SSL_SESS_CACHE_OFF); |
| |
| /* |
| * We need this to force transmission of client certs |
| */ |
| if (!verify_init(ssl_server)) { |
| LogError("%s: Verification engine was not properly initialized -- %s\n", prog, SSLERROR); |
| goto sslerror; |
| } |
| |
| if (ssl_server->clientpemfile) { |
| STACK_OF(X509_NAME) *stack = SSL_CTX_get_client_CA_list(ssl_server->ctx); |
| LogInfo("%s: Found %d client certificates\n", prog, sk_X509_NAME_num(stack)); |
| } |
| |
| return ssl_server; |
| |
| sslerror: |
| delete_ssl_server_socket(ssl_server); |
| return NULL; |
| } |
| |
| |
| /** |
| * Deletes a SSL server connection. |
| * @param ssl_server data for ssl server connection |
| */ |
| void delete_ssl_server_socket(ssl_server_connection *ssl_server) { |
| if (!ssl_server) |
| return; |
| |
| cleanup_ssl_server_socket(ssl_server); |
| |
| if (ssl_server->ctx) |
| SSL_CTX_free(ssl_server->ctx); |
| |
| FREE(ssl_server); |
| } |
| |
| |
| /** |
| * Inserts an SSL connection in the connection list of a server. |
| * @param ssl_server data for ssl server connection |
| * @return new SSL connection for the connection, or NULL if failed |
| */ |
| ssl_connection *insert_accepted_ssl_socket(ssl_server_connection *ssl_server) { |
| ssl_connection *ssl; |
| |
| ASSERT(ssl_server); |
| |
| if (!ssl_initialized) |
| start_ssl(); |
| |
| NEW(ssl); |
| ssl->method = NULL; |
| ssl->handler = NULL; |
| ssl->cert = NULL; |
| ssl->cipher = NULL; |
| ssl->socket = 0; |
| ssl->next = NULL; |
| ssl->accepted = FALSE; |
| ssl->cert_md5= NULL; |
| ssl->cert_md5_len = 0; |
| ssl->clientpemfile = NULL; |
| |
| if (ssl_server->clientpemfile != NULL) |
| ssl->clientpemfile = Str_dup(ssl_server->clientpemfile); |
| |
| LOCK(ssl_mutex); |
| |
| ssl->prev = NULL; |
| ssl->next = ssl_server->ssl_conn_list; |
| |
| if ( ssl->next != NULL ) |
| ssl->next->prev = ssl; |
| |
| END_LOCK; |
| |
| ssl_server->ssl_conn_list = ssl; |
| ssl->ctx = ssl_server->ctx; |
| ssl->accepted = TRUE; |
| |
| return ssl; |
| } |
| |
| |
| /** |
| * Closes an accepted SSL server connection and deletes it form the |
| * connection list. |
| * @param ssl_server data for ssl server connection |
| * @param ssl data the connection to be deleted |
| */ |
| void close_accepted_ssl_socket(ssl_server_connection *ssl_server, ssl_connection *ssl) { |
| if (!ssl || !ssl_server) |
| return; |
| |
| close_socket(ssl->socket); |
| |
| LOCK(ssl_mutex); |
| |
| if (ssl->prev == NULL) |
| ssl_server->ssl_conn_list = ssl->next; |
| else |
| ssl->prev->next = ssl->next; |
| |
| END_LOCK; |
| |
| delete_ssl_socket(ssl); |
| } |
| |
| |
| /** |
| * Embeds an accepted server socket in an existing ssl connection. |
| * @param ssl ssl connection |
| * @param socket the socket to be used. |
| * @return TRUE, or FALSE if an error has occured. |
| */ |
| int embed_accepted_ssl_socket(ssl_connection *ssl, int socket) { |
| int ssl_error; |
| time_t ssl_time; |
| |
| ASSERT(ssl); |
| |
| ssl->socket = socket; |
| |
| if (!ssl_initialized) |
| start_ssl(); |
| |
| if (!(ssl->handler = SSL_new(ssl->ctx))) { |
| LogError("%s: Cannot initialize the SSL handler -- %s\n", prog, SSLERROR); |
| return FALSE; |
| } |
| |
| if (socket < 0) { |
| LogError("%s: Socket error!\n", prog); |
| return FALSE; |
| } |
| |
| set_noblock(ssl->socket); |
| |
| if (!(ssl->socket_bio = BIO_new_socket(ssl->socket, BIO_NOCLOSE))) { |
| LogError("%s: Cannot generate IO buffer -- %s\n", prog, SSLERROR); |
| return FALSE; |
| } |
| |
| SSL_set_bio(ssl->handler, ssl->socket_bio, ssl->socket_bio); |
| |
| ssl_time = time(NULL); |
| |
| while ((ssl_error = SSL_accept(ssl->handler)) < 0) { |
| |
| if ((time(NULL) - ssl_time) > SSL_TIMEOUT) { |
| LogError("%s: SSL service timeout!\n", prog); |
| return FALSE; |
| } |
| |
| if (!handle_error(ssl_error, ssl)) |
| return FALSE; |
| |
| if (!BIO_should_retry(ssl->socket_bio)) |
| return FALSE; |
| |
| } |
| |
| ssl->cipher = (char *)SSL_get_cipher(ssl->handler); |
| |
| if (!update_ssl_cert_data(ssl) && ssl->clientpemfile) { |
| LogError("%s: The client did not supply a required client certificate!\n", |
| prog); |
| return FALSE; |
| } |
| |
| if (SSL_get_verify_result(ssl->handler) > 0) { |
| LogError("%s: Verification of the certificate has failed!\n", prog); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| |
| /** |
| * Send data package though the ssl connection |
| * @param ssl ssl connection |
| * @param buffer array containg the data |
| * @param len size of the data container |
| * @param timeout Seconds to wait for data to be written |
| * @return number of bytes transmitted, -1 in case of an error |
| */ |
| int send_ssl_socket(ssl_connection *ssl, void *buffer, size_t len, int timeout) { |
| int n = 0; |
| |
| ASSERT(ssl); |
| |
| do { |
| n = SSL_write(ssl->handler, buffer, (int)len); |
| } while (n <= 0 && BIO_should_retry(ssl->socket_bio) && can_write(ssl->socket, timeout)); |
| |
| return (n > 0) ? n : -1; |
| } |
| |
| |
| /** |
| * Receive data package though the ssl connection |
| * @param ssl ssl connection |
| * @param buffer array to hold the data |
| * @param len size of the data container |
| * @param timeout Seconds to wait for data to be available |
| * @return number of bytes transmitted, -1 in case of an error |
| */ |
| int recv_ssl_socket(ssl_connection *ssl, void *buffer, int len, int timeout) { |
| int n = 0; |
| |
| ASSERT(ssl); |
| |
| do { |
| n = SSL_read(ssl->handler, buffer, len); |
| } while (n < 0 && BIO_should_retry(ssl->socket_bio) && can_read(ssl->socket, timeout)); |
| |
| return (n >= 0) ? n : -1; |
| } |
| |
| |
| /** |
| * Stop SSL support library |
| * @return TRUE, or FALSE if an error has occured. |
| */ |
| void stop_ssl() { |
| if (ssl_initialized) { |
| int i; |
| ssl_initialized = FALSE; |
| ERR_free_strings(); |
| CRYPTO_set_id_callback(NULL); |
| CRYPTO_set_locking_callback(NULL); |
| for (i = 0; i < CRYPTO_num_locks(); i++) |
| assert(pthread_mutex_destroy(&ssl_mutex_table[i]) == 0); |
| FREE(ssl_mutex_table); |
| RAND_cleanup(); |
| } |
| } |
| |
| |
| /** |
| * Generate a new ssl connection |
| * @return ssl connection container |
| */ |
| ssl_connection *new_ssl_connection(char *clientpemfile, int sslversion) { |
| ssl_connection *ssl; |
| |
| if (!ssl_initialized) |
| start_ssl(); |
| |
| NEW(ssl); |
| ssl->socket_bio = NULL; |
| ssl->handler = NULL; |
| ssl->cert = NULL; |
| ssl->cipher = NULL; |
| ssl->socket = 0; |
| ssl->next = NULL; |
| ssl->accepted = FALSE; |
| ssl->cert_md5 = NULL; |
| ssl->cert_md5_len = 0; |
| ssl->clientpemfile = clientpemfile ? Str_dup(clientpemfile) : NULL; |
| |
| switch (sslversion) { |
| |
| case SSL_VERSION_AUTO: |
| #ifdef OPENSSL_FIPS |
| if (FIPS_mode()) { |
| ssl->method = TLSv1_client_method(); |
| } else |
| #endif |
| ssl->method = SSLv23_client_method(); |
| break; |
| |
| case SSL_VERSION_SSLV2: |
| #ifdef OPENSSL_NO_SSL2 |
| LogError("SSLv2 is not allowed - use either SSLv3 or TLSv1"); |
| goto sslerror; |
| #else |
| #ifdef OPENSSL_FIPS |
| if (FIPS_mode()) { |
| LogError("SSLv2 is not allowed in FIPS mode - use TLSv1"); |
| goto sslerror; |
| } else |
| #endif |
| ssl->method = SSLv2_client_method(); |
| #endif |
| break; |
| |
| case SSL_VERSION_SSLV3: |
| #ifdef OPENSSL_FIPS |
| if (FIPS_mode()) { |
| LogError("SSLv3 is not allowed in FIPS mode - use TLSv1"); |
| goto sslerror; |
| } else |
| #endif |
| ssl->method = SSLv3_client_method(); |
| break; |
| |
| case SSL_VERSION_TLS: |
| /* fall through */ |
| default: |
| ssl->method = TLSv1_client_method(); |
| break; |
| |
| } |
| |
| if (!ssl->method) { |
| LogError("%s: Cannot initialize SSL method -- %s\n", prog, SSLERROR); |
| goto sslerror; |
| } |
| |
| if (!(ssl->ctx = SSL_CTX_new(ssl->method))) { |
| LogError("%s: Cannot initialize SSL server certificate handler -- %s\n", prog, SSLERROR); |
| goto sslerror; |
| } |
| |
| if (ssl->clientpemfile) { |
| |
| if (SSL_CTX_use_certificate_chain_file(ssl->ctx, ssl->clientpemfile) <= 0) { |
| LogError("%s: Cannot initialize SSL server certificate -- %s\n", prog, SSLERROR); |
| goto sslerror; |
| } |
| |
| if (SSL_CTX_use_PrivateKey_file(ssl->ctx, ssl->clientpemfile, SSL_FILETYPE_PEM) <= 0) { |
| LogError("%s: Cannot initialize SSL server private key -- %s\n", prog, SSLERROR); |
| goto sslerror; |
| } |
| |
| if (!SSL_CTX_check_private_key(ssl->ctx)) { |
| LogError("%s: Private key does not match the certificate public key -- %s\n", prog, SSLERROR); |
| goto sslerror; |
| } |
| |
| } |
| |
| return ssl; |
| |
| sslerror: |
| delete_ssl_socket(ssl); |
| return NULL; |
| } |
| |
| |
| /* ----------------------------------------------------------------- Private */ |
| |
| |
| /** |
| * Init verification of transmitted client certs |
| */ |
| static int verify_init(ssl_server_connection *ssl_server) { |
| struct stat stat_buf; |
| |
| if (!ssl_server->clientpemfile) { |
| SSL_CTX_set_verify(ssl_server->ctx, SSL_VERIFY_NONE, NULL); |
| return TRUE; |
| } |
| |
| if (stat(ssl_server->clientpemfile, &stat_buf) == -1) { |
| LogError("%s: Cannot stat the SSL pem path '%s' -- %s\n", prog, Run.httpsslclientpem, STRERROR); |
| return FALSE; |
| } |
| |
| if (S_ISDIR(stat_buf.st_mode)) { |
| |
| if (!SSL_CTX_load_verify_locations(ssl_server->ctx, NULL , ssl_server->clientpemfile)) { |
| LogError("%s: Error setting verify directory to %s -- %s\n", prog, Run.httpsslclientpem, SSLERROR); |
| return FALSE; |
| } |
| |
| LogInfo("%s: Loaded SSL client pem directory '%s'\n", prog, ssl_server->clientpemfile); |
| |
| /* Monit's server cert for cli support */ |
| |
| if (!SSL_CTX_load_verify_locations(ssl_server->ctx, ssl_server->pemfile, NULL)) { |
| LogError("%s: Error loading verify certificates from %s -- %s\n", prog, ssl_server->pemfile, SSLERROR); |
| return FALSE; |
| } |
| |
| LogInfo("%s: Loaded monit's SSL pem server file '%s'\n", prog, ssl_server->pemfile); |
| |
| } else if (S_ISREG(stat_buf.st_mode)) { |
| |
| if (!SSL_CTX_load_verify_locations(ssl_server->ctx, ssl_server->clientpemfile, NULL)) { |
| LogError("%s: Error loading verify certificates from %s -- %s\n", prog, Run.httpsslclientpem, SSLERROR); |
| return FALSE; |
| } |
| |
| LogInfo("%s: Loaded SSL pem client file '%s'\n", prog, ssl_server->clientpemfile); |
| |
| /* Monits server cert for cli support ! */ |
| |
| if (!SSL_CTX_load_verify_locations(ssl_server->ctx, ssl_server->pemfile, NULL)) { |
| LogError("%s: Error loading verify certificates from %s -- %s\n", prog, ssl_server->pemfile, SSLERROR); |
| return FALSE; |
| } |
| |
| LogInfo("%s: Loaded monit's SSL pem server file '%s'\n", prog, ssl_server->pemfile); |
| |
| SSL_CTX_set_client_CA_list(ssl_server->ctx, SSL_load_client_CA_file(ssl_server->clientpemfile)); |
| |
| } else { |
| LogError("%s: SSL client pem path is no file or directory %s\n", prog, ssl_server->clientpemfile); |
| return FALSE; |
| } |
| |
| SSL_CTX_set_verify(ssl_server->ctx, SSL_VERIFY_PEER, verify_callback); |
| |
| return TRUE; |
| } |
| |
| |
| /** |
| * Check the transmitted client certs and a compare with client cert database |
| */ |
| static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { |
| char subject[STRLEN]; |
| X509_OBJECT found_cert; |
| |
| X509_NAME_oneline(X509_get_subject_name(ctx->current_cert), subject, STRLEN-1); |
| |
| if (!preverify_ok && !check_preverify(ctx)) |
| return 0; |
| |
| if (ctx->error_depth == 0 && X509_STORE_get_by_subject(ctx, X509_LU_X509, X509_get_subject_name(ctx->current_cert), &found_cert) != 1) { |
| LogError("%s: SSL connection rejected. No matching certificate found -- %s\n", prog, SSLERROR); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| |
| /** |
| * Analyse errors found before actual verification |
| * @return TRUE if successful |
| */ |
| static int check_preverify(X509_STORE_CTX *ctx) { |
| if ((ctx->error != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) && (ctx->error != X509_V_ERR_INVALID_PURPOSE)) { |
| /* Remote site specified a certificate, but it's not correct */ |
| LogError("%s: SSL connection rejected because certificate verification has failed -- error %i\n", prog, ctx->error); |
| /* Reject connection */ |
| return FALSE; |
| } |
| |
| if (Run.allowselfcert && (ctx->error == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT)) { |
| /* Let's accept self signed certs for the moment! */ |
| LogInfo("%s: SSL connection accepted with self signed certificate!\n", prog); |
| ctx->error = 0; |
| return TRUE; |
| } |
| |
| /* Reject connection */ |
| LogError("%s: SSL connection rejected because certificate verification has failed -- error %i!\n", prog, ctx->error); |
| return FALSE; |
| } |
| |
| |
| /** |
| * Helper function for the SSL threadding support |
| * @return current thread number |
| */ |
| static int unsigned long ssl_thread_id() { |
| return ((unsigned long) pthread_self()); |
| } |
| |
| |
| /** |
| * Helper function for the SSL threadding support |
| */ |
| static void ssl_mutex_lock(int mode, int n, const char *file, int line) { |
| if (mode & CRYPTO_LOCK) |
| assert(pthread_mutex_lock( & ssl_mutex_table[n]) == 0); |
| else |
| assert(pthread_mutex_unlock( & ssl_mutex_table[n]) == 0); |
| } |
| |
| |
| /** |
| * Handle errors during read, write, connect and accept |
| * @return TRUE if non fatal, FALSE if non fatal and retry |
| */ |
| static int handle_error(int code, ssl_connection *ssl) { |
| int ssl_error = SSL_get_error(ssl->handler, code); |
| |
| switch (ssl_error) { |
| |
| case SSL_ERROR_WANT_READ: |
| if (can_read(ssl->socket, SSL_TIMEOUT)) |
| return TRUE; |
| LogError("%s: Openssl read timeout error!\n", prog); |
| break; |
| |
| case SSL_ERROR_WANT_WRITE: |
| if (can_read(ssl->socket, SSL_TIMEOUT)) |
| return TRUE; |
| LogError("%s: Openssl write timeout error!\n", prog); |
| break; |
| |
| case SSL_ERROR_SYSCALL: |
| LogError("%s: Openssl syscall error: %s!\n", prog, STRERROR); |
| break; |
| |
| case SSL_ERROR_SSL: |
| LogError("%s: Openssl engine error: %s\n", prog, SSLERROR); |
| break; |
| |
| default: |
| LogError("%s: Openssl error!\n", prog); |
| break; |
| |
| } |
| |
| return FALSE; |
| } |
| |
| |
| /** |
| * Garbage collection for non reusable parts of the ssl connection |
| * @param ssl ssl connection |
| */ |
| static void cleanup_ssl_socket(ssl_connection *ssl) { |
| if (!ssl) |
| return; |
| |
| if (ssl->cert) { |
| X509_free(ssl->cert); |
| ssl->cert = NULL; |
| } |
| |
| if (ssl->handler) { |
| SSL_free(ssl->handler); |
| ssl->handler = NULL; |
| } |
| |
| if (ssl->socket_bio) { |
| /* no BIO_free(ssl->socket_bio); necessary, because BIO is freed by ssl->handler */ |
| ssl->socket_bio = NULL; |
| } |
| |
| FREE(ssl->cert_issuer); |
| FREE(ssl->cert_subject); |
| FREE(ssl->cert_md5); |
| FREE(ssl->clientpemfile); |
| } |
| |
| |
| /** |
| * Garbage collection for a SSL server connection. |
| * @param ssl_server data for ssl server connection |
| */ |
| static void cleanup_ssl_server_socket(ssl_server_connection *ssl_server) { |
| if (!ssl_server) |
| return; |
| |
| FREE(ssl_server->pemfile); |
| FREE(ssl_server->clientpemfile); |
| |
| while (ssl_server->ssl_conn_list) { |
| ssl_connection *ssl = ssl_server->ssl_conn_list; |
| ssl_server->ssl_conn_list = ssl_server->ssl_conn_list->next; |
| close_accepted_ssl_socket(ssl_server, ssl); |
| } |
| } |
| |
| |
| /** |
| * Updates some data in the ssl connection |
| * @param ssl reference to ssl connection |
| * @return TRUE, if not successful FALSE |
| */ |
| static int update_ssl_cert_data(ssl_connection *ssl) { |
| unsigned char md5[EVP_MAX_MD_SIZE]; |
| |
| ASSERT(ssl); |
| |
| if (!(ssl->cert = SSL_get_peer_certificate(ssl->handler))) |
| return FALSE; |
| |
| #ifdef OPENSSL_FIPS |
| if (!FIPS_mode()) { |
| /* In FIPS-140 mode, MD5 is unavailable. */ |
| #endif |
| ssl->cert_issuer = X509_NAME_oneline (X509_get_issuer_name(ssl->cert), 0, 0); |
| ssl->cert_subject = X509_NAME_oneline (X509_get_subject_name(ssl->cert), 0, 0); |
| X509_digest(ssl->cert, EVP_md5(), md5, &ssl->cert_md5_len); |
| ssl->cert_md5= (unsigned char *)Str_dup((char *)md5); |
| #ifdef OPENSSL_FIPS |
| } |
| #endif |
| return TRUE; |
| } |
| |
| |
| /** |
| * Generate a new ssl server connection |
| * @return ssl server connection container |
| */ |
| static ssl_server_connection *new_ssl_server_connection(char * pemfile, char * clientpemfile) { |
| ssl_server_connection *ssl_server; |
| |
| ASSERT(pemfile); |
| |
| NEW(ssl_server); |
| ssl_server->ctx = NULL; |
| ssl_server->method = NULL; |
| ssl_server->server_socket = 0; |
| ssl_server->ssl_conn_list = NULL; |
| ssl_server->pemfile = Str_dup(pemfile); |
| ssl_server->clientpemfile = clientpemfile ? Str_dup(clientpemfile) : NULL; |
| |
| return ssl_server; |
| } |
| |
| #ifdef OPENSSL_FIPS |
| /** |
| * Enable FIPS mode, if it isn't enabled yet. |
| */ |
| void enable_fips_mode() { |
| if (!FIPS_mode()) { |
| ASSERT(FIPS_mode_set(1)); |
| LogInfo("FIPS-140 mode is enabled\n"); |
| } |
| } |
| #endif |
| |
| /** |
| * Start SSL support library. It has to be run before the SSL support |
| * can be used. |
| * @return TRUE, or FALSE if an error has occured. |
| */ |
| static int start_ssl() { |
| if (! ssl_initialized) { |
| int i; |
| int locks = CRYPTO_num_locks(); |
| |
| #ifdef OPENSSL_FIPS |
| if (Run.fipsEnabled) |
| enable_fips_mode(); |
| #endif |
| |
| ssl_initialized = TRUE; |
| ERR_load_crypto_strings(); |
| ssl_mutex_table = CALLOC(locks, sizeof(pthread_mutex_t)); |
| for (i = 0; i < locks; i++) |
| pthread_mutex_init(&ssl_mutex_table[i], NULL); |
| CRYPTO_set_id_callback(ssl_thread_id); |
| CRYPTO_set_locking_callback(ssl_mutex_lock); |
| SSL_library_init(); |
| if (file_exist(URANDOM_DEVICE)) { |
| return(RAND_load_file(URANDOM_DEVICE, RANDOM_BYTES)==RANDOM_BYTES); |
| } else if (file_exist(RANDOM_DEVICE)) { |
| DEBUG("Gathering entropy from the random device\n"); |
| return(RAND_load_file(RANDOM_DEVICE, RANDOM_BYTES)==RANDOM_BYTES); |
| } |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| |
| #endif |
| |
| |