You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
993 lines
24 KiB
C
993 lines
24 KiB
C
/*
|
|
* Part of Very Secure FTPd
|
|
* Licence: GPL v2. Note that this code interfaces with with the OpenSSL
|
|
* libraries, so please read LICENSE where I give explicit permission to link
|
|
* against the OpenSSL libraries.
|
|
* Author: Chris Evans
|
|
* ssl.c
|
|
*
|
|
* Routines to handle a SSL/TLS-based implementation of RFC 2228, i.e.
|
|
* encryption.
|
|
*/
|
|
|
|
#include "ssl.h"
|
|
#include "session.h"
|
|
#include "ftpcodes.h"
|
|
#include "ftpcmdio.h"
|
|
#include "defs.h"
|
|
#include "str.h"
|
|
#include "sysutil.h"
|
|
#include "tunables.h"
|
|
#include "utility.h"
|
|
#include "builddefs.h"
|
|
#include "logging.h"
|
|
|
|
#ifdef VSF_BUILD_SSL
|
|
|
|
#include <openssl/ssl.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/bio.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
|
|
static char* get_ssl_error();
|
|
static SSL* get_ssl(struct vsf_session* p_sess, int fd);
|
|
static int ssl_session_init(struct vsf_session* p_sess);
|
|
static void setup_bio_callbacks();
|
|
static long bio_callback(
|
|
BIO* p_bio, int oper, const char* p_arg, int argi, long argl, long retval);
|
|
static int ssl_verify_callback(int verify_ok, X509_STORE_CTX* p_ctx);
|
|
static int ssl_alpn_callback(SSL* p_ssl,
|
|
const unsigned char** p_out,
|
|
unsigned char* outlen,
|
|
const unsigned char* p_in,
|
|
unsigned int inlen,
|
|
void* p_arg);
|
|
static long ssl_sni_callback(SSL* p_ssl, int* p_al, void* p_arg);
|
|
static int ssl_cert_digest(
|
|
SSL* p_ssl, struct vsf_session* p_sess, struct mystr* p_str);
|
|
static void maybe_log_shutdown_state(struct vsf_session* p_sess);
|
|
static void maybe_log_ssl_error_state(struct vsf_session* p_sess, int ret);
|
|
static int ssl_read_common(struct vsf_session* p_sess,
|
|
SSL* p_ssl,
|
|
char* p_buf,
|
|
unsigned int len,
|
|
int (*p_ssl_func)(SSL*, void*, int));
|
|
|
|
static int ssl_inited;
|
|
static struct mystr debug_str;
|
|
|
|
void
|
|
ssl_init(struct vsf_session* p_sess)
|
|
{
|
|
if (!ssl_inited)
|
|
{
|
|
SSL_CTX* p_ctx;
|
|
long options;
|
|
int verify_option = 0;
|
|
SSL_library_init();
|
|
p_ctx = SSL_CTX_new(SSLv23_server_method());
|
|
if (p_ctx == NULL)
|
|
{
|
|
die("SSL: could not allocate SSL context");
|
|
}
|
|
options = SSL_OP_ALL;
|
|
if (!tunable_sslv2)
|
|
{
|
|
options |= SSL_OP_NO_SSLv2;
|
|
}
|
|
if (!tunable_sslv3)
|
|
{
|
|
options |= SSL_OP_NO_SSLv3;
|
|
}
|
|
if (!tunable_tlsv1)
|
|
{
|
|
options |= SSL_OP_NO_TLSv1;
|
|
}
|
|
if (!tunable_tlsv1_1)
|
|
{
|
|
options |= SSL_OP_NO_TLSv1_1;
|
|
}
|
|
if (!tunable_tlsv1_2)
|
|
{
|
|
options |= SSL_OP_NO_TLSv1_2;
|
|
}
|
|
if (!tunable_tlsv1_3)
|
|
{
|
|
options |= SSL_OP_NO_TLSv1_3;
|
|
}
|
|
SSL_CTX_set_options(p_ctx, options);
|
|
if (tunable_rsa_cert_file)
|
|
{
|
|
const char* p_key = tunable_rsa_private_key_file;
|
|
if (!p_key)
|
|
{
|
|
p_key = tunable_rsa_cert_file;
|
|
}
|
|
if (SSL_CTX_use_certificate_chain_file(p_ctx, tunable_rsa_cert_file) != 1)
|
|
{
|
|
die("SSL: cannot load RSA certificate");
|
|
}
|
|
if (SSL_CTX_use_PrivateKey_file(p_ctx, p_key, X509_FILETYPE_PEM) != 1)
|
|
{
|
|
die("SSL: cannot load RSA private key");
|
|
}
|
|
}
|
|
if (tunable_dsa_cert_file)
|
|
{
|
|
const char* p_key = tunable_dsa_private_key_file;
|
|
if (!p_key)
|
|
{
|
|
p_key = tunable_dsa_cert_file;
|
|
}
|
|
if (SSL_CTX_use_certificate_chain_file(p_ctx, tunable_dsa_cert_file) != 1)
|
|
{
|
|
die("SSL: cannot load DSA certificate");
|
|
}
|
|
if (SSL_CTX_use_PrivateKey_file(p_ctx, p_key, X509_FILETYPE_PEM) != 1)
|
|
{
|
|
die("SSL: cannot load DSA private key");
|
|
}
|
|
}
|
|
if (tunable_ssl_ciphers &&
|
|
SSL_CTX_set_cipher_list(p_ctx, tunable_ssl_ciphers) != 1)
|
|
{
|
|
die("SSL: could not set cipher list");
|
|
}
|
|
if (RAND_status() != 1)
|
|
{
|
|
die("SSL: RNG is not seeded");
|
|
}
|
|
{
|
|
EC_KEY* key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
|
|
if (key == NULL)
|
|
{
|
|
die("SSL: failed to get curve p256");
|
|
}
|
|
SSL_CTX_set_tmp_ecdh(p_ctx, key);
|
|
EC_KEY_free(key);
|
|
}
|
|
if (tunable_ssl_request_cert)
|
|
{
|
|
verify_option |= SSL_VERIFY_PEER;
|
|
}
|
|
if (tunable_require_cert)
|
|
{
|
|
verify_option |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
|
|
}
|
|
if (verify_option)
|
|
{
|
|
SSL_CTX_set_verify(p_ctx, verify_option, ssl_verify_callback);
|
|
if (tunable_ca_certs_file)
|
|
{
|
|
STACK_OF(X509_NAME)* p_names;
|
|
if (!SSL_CTX_load_verify_locations(p_ctx, tunable_ca_certs_file, NULL))
|
|
{
|
|
die("SSL: could not load verify file");
|
|
}
|
|
p_names = SSL_load_client_CA_file(tunable_ca_certs_file);
|
|
if (!p_names)
|
|
{
|
|
die("SSL: could not load client certs file");
|
|
}
|
|
SSL_CTX_set_client_CA_list(p_ctx, p_names);
|
|
}
|
|
}
|
|
{
|
|
static const char* p_ctx_id = "vsftpd";
|
|
SSL_CTX_set_session_id_context(p_ctx, (void*) p_ctx_id,
|
|
vsf_sysutil_strlen(p_ctx_id));
|
|
}
|
|
if (tunable_require_ssl_reuse)
|
|
{
|
|
/* Ensure cached session doesn't expire */
|
|
SSL_CTX_set_timeout(p_ctx, INT_MAX);
|
|
}
|
|
/* Set up ALPN to check for FTP protocol intention of client. */
|
|
SSL_CTX_set_alpn_select_cb(p_ctx, ssl_alpn_callback, p_sess);
|
|
/* Set up SNI callback for an optional hostname check. */
|
|
SSL_CTX_set_tlsext_servername_callback(p_ctx, ssl_sni_callback);
|
|
SSL_CTX_set_tlsext_servername_arg(p_ctx, p_sess);
|
|
|
|
p_sess->p_ssl_ctx = p_ctx;
|
|
ssl_inited = 1;
|
|
}
|
|
}
|
|
|
|
void
|
|
ssl_control_handshake(struct vsf_session* p_sess)
|
|
{
|
|
if (!ssl_session_init(p_sess))
|
|
{
|
|
struct mystr err_str = INIT_MYSTR;
|
|
str_alloc_text(&err_str, "Negotiation failed: ");
|
|
/* Technically, we shouldn't leak such detailed error messages. */
|
|
str_append_text(&err_str, get_ssl_error());
|
|
vsf_cmdio_write_str(p_sess, FTP_TLS_FAIL, &err_str);
|
|
vsf_sysutil_exit(1);
|
|
}
|
|
p_sess->control_use_ssl = 1;
|
|
}
|
|
|
|
void
|
|
handle_auth(struct vsf_session* p_sess)
|
|
{
|
|
str_upper(&p_sess->ftp_arg_str);
|
|
if (str_equal_text(&p_sess->ftp_arg_str, "TLS") ||
|
|
str_equal_text(&p_sess->ftp_arg_str, "TLS-C") ||
|
|
str_equal_text(&p_sess->ftp_arg_str, "SSL") ||
|
|
str_equal_text(&p_sess->ftp_arg_str, "TLS-P"))
|
|
{
|
|
vsf_cmdio_write(p_sess, FTP_AUTHOK, "Proceed with negotiation.");
|
|
ssl_control_handshake(p_sess);
|
|
if (str_equal_text(&p_sess->ftp_arg_str, "SSL") ||
|
|
str_equal_text(&p_sess->ftp_arg_str, "TLS-P"))
|
|
{
|
|
p_sess->data_use_ssl = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vsf_cmdio_write(p_sess, FTP_BADAUTH, "Unknown AUTH type.");
|
|
}
|
|
}
|
|
|
|
void
|
|
handle_pbsz(struct vsf_session* p_sess)
|
|
{
|
|
if (!p_sess->control_use_ssl)
|
|
{
|
|
vsf_cmdio_write(p_sess, FTP_BADPBSZ, "PBSZ needs a secure connection.");
|
|
}
|
|
else
|
|
{
|
|
vsf_cmdio_write(p_sess, FTP_PBSZOK, "PBSZ set to 0.");
|
|
}
|
|
}
|
|
|
|
void
|
|
handle_prot(struct vsf_session* p_sess)
|
|
{
|
|
str_upper(&p_sess->ftp_arg_str);
|
|
if (!p_sess->control_use_ssl)
|
|
{
|
|
vsf_cmdio_write(p_sess, FTP_BADPROT, "PROT needs a secure connection.");
|
|
}
|
|
else if (str_equal_text(&p_sess->ftp_arg_str, "C"))
|
|
{
|
|
p_sess->data_use_ssl = 0;
|
|
vsf_cmdio_write(p_sess, FTP_PROTOK, "PROT now Clear.");
|
|
}
|
|
else if (str_equal_text(&p_sess->ftp_arg_str, "P"))
|
|
{
|
|
p_sess->data_use_ssl = 1;
|
|
vsf_cmdio_write(p_sess, FTP_PROTOK, "PROT now Private.");
|
|
}
|
|
else if (str_equal_text(&p_sess->ftp_arg_str, "S") ||
|
|
str_equal_text(&p_sess->ftp_arg_str, "E"))
|
|
{
|
|
vsf_cmdio_write(p_sess, FTP_NOHANDLEPROT, "PROT not supported.");
|
|
}
|
|
else
|
|
{
|
|
vsf_cmdio_write(p_sess, FTP_NOSUCHPROT, "PROT not recognized.");
|
|
}
|
|
}
|
|
|
|
int
|
|
ssl_read(struct vsf_session* p_sess, void* p_ssl, char* p_buf, unsigned int len)
|
|
{
|
|
return ssl_read_common(p_sess, (SSL*) p_ssl, p_buf, len, SSL_read);
|
|
}
|
|
|
|
int
|
|
ssl_peek(struct vsf_session* p_sess, void* p_ssl, char* p_buf, unsigned int len)
|
|
{
|
|
return ssl_read_common(p_sess, (SSL*) p_ssl, p_buf, len, SSL_peek);
|
|
}
|
|
|
|
static int
|
|
ssl_read_common(struct vsf_session* p_sess,
|
|
SSL* p_void_ssl,
|
|
char* p_buf,
|
|
unsigned int len,
|
|
int (*p_ssl_func)(SSL*, void*, int))
|
|
{
|
|
int retval;
|
|
int err;
|
|
SSL* p_ssl = (SSL*) p_void_ssl;
|
|
do
|
|
{
|
|
retval = (*p_ssl_func)(p_ssl, p_buf, len);
|
|
err = SSL_get_error(p_ssl, retval);
|
|
}
|
|
while (retval < 0 && (err == SSL_ERROR_WANT_READ ||
|
|
err == SSL_ERROR_WANT_WRITE));
|
|
/* If we hit an EOF, make sure it was from the peer, not injected by the
|
|
* attacker.
|
|
*/
|
|
if (retval == 0 && SSL_get_shutdown(p_ssl) != SSL_RECEIVED_SHUTDOWN)
|
|
{
|
|
if (p_ssl == p_sess->p_control_ssl)
|
|
{
|
|
str_alloc_text(&debug_str, "Control");
|
|
}
|
|
else
|
|
{
|
|
str_alloc_text(&debug_str, "DATA");
|
|
}
|
|
str_append_text(&debug_str, " connection terminated without SSL shutdown.");
|
|
if (p_ssl != p_sess->p_control_ssl)
|
|
{
|
|
str_append_text(&debug_str,
|
|
" Buggy client! Integrity of upload cannot be asserted.");
|
|
}
|
|
vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
|
|
if (tunable_strict_ssl_read_eof)
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
int
|
|
ssl_write(void* p_ssl, const char* p_buf, unsigned int len)
|
|
{
|
|
int retval;
|
|
int err;
|
|
do
|
|
{
|
|
retval = SSL_write((SSL*) p_ssl, p_buf, len);
|
|
err = SSL_get_error((SSL*) p_ssl, retval);
|
|
}
|
|
while (retval < 0 && (err == SSL_ERROR_WANT_READ ||
|
|
err == SSL_ERROR_WANT_WRITE));
|
|
return retval;
|
|
}
|
|
|
|
int
|
|
ssl_write_str(void* p_ssl, const struct mystr* p_str)
|
|
{
|
|
unsigned int len = str_getlen(p_str);
|
|
int ret = SSL_write((SSL*) p_ssl, str_getbuf(p_str), len);
|
|
if ((unsigned int) ret != len)
|
|
{
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
ssl_read_into_str(struct vsf_session* p_sess, void* p_ssl, struct mystr* p_str)
|
|
{
|
|
unsigned int len = str_getlen(p_str);
|
|
int ret = ssl_read(p_sess, p_ssl, (char*) str_getbuf(p_str), len);
|
|
if (ret >= 0)
|
|
{
|
|
str_trunc(p_str, (unsigned int) ret);
|
|
}
|
|
else
|
|
{
|
|
str_empty(p_str);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
maybe_log_shutdown_state(struct vsf_session* p_sess)
|
|
{
|
|
if (tunable_debug_ssl)
|
|
{
|
|
int ret = SSL_get_shutdown(p_sess->p_data_ssl);
|
|
str_alloc_text(&debug_str, "SSL shutdown state is: ");
|
|
if (ret == 0)
|
|
{
|
|
str_append_text(&debug_str, "NONE");
|
|
}
|
|
else if (ret == SSL_SENT_SHUTDOWN)
|
|
{
|
|
str_append_text(&debug_str, "SSL_SENT_SHUTDOWN");
|
|
}
|
|
else if (ret == SSL_RECEIVED_SHUTDOWN)
|
|
{
|
|
str_append_text(&debug_str, "SSL_RECEIVED_SHUTDOWN");
|
|
}
|
|
else
|
|
{
|
|
str_append_ulong(&debug_str, ret);
|
|
}
|
|
vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
|
|
}
|
|
}
|
|
|
|
static void
|
|
maybe_log_ssl_error_state(struct vsf_session* p_sess, int ret)
|
|
{
|
|
if (tunable_debug_ssl)
|
|
{
|
|
str_alloc_text(&debug_str, "SSL ret: ");
|
|
str_append_ulong(&debug_str, ret);
|
|
str_append_text(&debug_str, ", SSL error: ");
|
|
str_append_text(&debug_str, get_ssl_error());
|
|
str_append_text(&debug_str, ", errno: ");
|
|
str_append_ulong(&debug_str, errno);
|
|
vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
|
|
}
|
|
}
|
|
|
|
int
|
|
ssl_data_close(struct vsf_session* p_sess)
|
|
{
|
|
int success = 1;
|
|
SSL* p_ssl = p_sess->p_data_ssl;
|
|
if (p_ssl)
|
|
{
|
|
int ret;
|
|
maybe_log_shutdown_state(p_sess);
|
|
|
|
/* Disable Nagle algorithm. We want the shutdown packet to be sent
|
|
* immediately, there's nothing coming after.
|
|
*/
|
|
vsf_sysutil_set_nodelay(SSL_get_fd(p_ssl));
|
|
|
|
/* This is a mess. Ideally, when we're the sender, we'd like to get to the
|
|
* SSL_RECEIVED_SHUTDOWN state to get a cryptographic guarantee that the
|
|
* peer received all the data and shut the connection down cleanly. It
|
|
* doesn't matter hugely apart from logging, but it's a nagging detail.
|
|
* Unfortunately, no FTP client I found was able to get sends into that
|
|
* state, so the best we can do is issue SSL_shutdown but not check the
|
|
* errors / returns. At least this enables the receiver to be sure of the
|
|
* integrity of the send in terms of unwanted truncation.
|
|
*/
|
|
ret = SSL_shutdown(p_ssl);
|
|
maybe_log_shutdown_state(p_sess);
|
|
if (ret == 0)
|
|
{
|
|
ret = SSL_shutdown(p_ssl);
|
|
maybe_log_shutdown_state(p_sess);
|
|
if (ret != 1)
|
|
{
|
|
if (tunable_strict_ssl_write_shutdown)
|
|
{
|
|
success = 0;
|
|
}
|
|
maybe_log_shutdown_state(p_sess);
|
|
maybe_log_ssl_error_state(p_sess, ret);
|
|
}
|
|
}
|
|
else if (ret < 0)
|
|
{
|
|
if (tunable_strict_ssl_write_shutdown)
|
|
{
|
|
success = 0;
|
|
}
|
|
maybe_log_ssl_error_state(p_sess, ret);
|
|
}
|
|
SSL_free(p_ssl);
|
|
p_sess->p_data_ssl = NULL;
|
|
}
|
|
return success;
|
|
}
|
|
|
|
int
|
|
ssl_accept(struct vsf_session* p_sess, int fd)
|
|
{
|
|
/* SECURITY: data SSL connections don't have any auth on them as part of the
|
|
* protocol. If a client sends an unfortunately optional client cert then
|
|
* we can check for a match between the control and data connections.
|
|
*/
|
|
SSL* p_ssl;
|
|
int reused;
|
|
if (p_sess->p_data_ssl != NULL)
|
|
{
|
|
die("p_data_ssl should be NULL.");
|
|
}
|
|
p_ssl = get_ssl(p_sess, fd);
|
|
if (p_ssl == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
p_sess->p_data_ssl = p_ssl;
|
|
setup_bio_callbacks(p_ssl);
|
|
reused = SSL_session_reused(p_ssl);
|
|
if (tunable_require_ssl_reuse && !reused)
|
|
{
|
|
str_alloc_text(&debug_str, "No SSL session reuse on data channel.");
|
|
vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
|
|
ssl_data_close(p_sess);
|
|
return 0;
|
|
}
|
|
if (str_getlen(&p_sess->control_cert_digest) > 0)
|
|
{
|
|
static struct mystr data_cert_digest;
|
|
if (!ssl_cert_digest(p_ssl, p_sess, &data_cert_digest))
|
|
{
|
|
str_alloc_text(&debug_str, "Missing cert on data channel.");
|
|
vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
|
|
ssl_data_close(p_sess);
|
|
return 0;
|
|
}
|
|
if (str_strcmp(&p_sess->control_cert_digest, &data_cert_digest))
|
|
{
|
|
str_alloc_text(&debug_str, "DIFFERENT cert on data channel.");
|
|
vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
|
|
ssl_data_close(p_sess);
|
|
return 0;
|
|
}
|
|
if (tunable_debug_ssl)
|
|
{
|
|
str_alloc_text(&debug_str, "Matching cert on data channel.");
|
|
vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
ssl_comm_channel_init(struct vsf_session* p_sess)
|
|
{
|
|
const struct vsf_sysutil_socketpair_retval retval =
|
|
vsf_sysutil_unix_stream_socketpair();
|
|
if (p_sess->ssl_consumer_fd != -1)
|
|
{
|
|
bug("ssl_consumer_fd active");
|
|
}
|
|
if (p_sess->ssl_slave_fd != -1)
|
|
{
|
|
bug("ssl_slave_fd active");
|
|
}
|
|
p_sess->ssl_consumer_fd = retval.socket_one;
|
|
p_sess->ssl_slave_fd = retval.socket_two;
|
|
}
|
|
|
|
void
|
|
ssl_comm_channel_set_consumer_context(struct vsf_session* p_sess)
|
|
{
|
|
if (p_sess->ssl_slave_fd == -1)
|
|
{
|
|
bug("ssl_slave_fd already closed");
|
|
}
|
|
vsf_sysutil_close(p_sess->ssl_slave_fd);
|
|
p_sess->ssl_slave_fd = -1;
|
|
}
|
|
|
|
void
|
|
ssl_comm_channel_set_producer_context(struct vsf_session* p_sess)
|
|
{
|
|
if (p_sess->ssl_consumer_fd == -1)
|
|
{
|
|
bug("ssl_consumer_fd already closed");
|
|
}
|
|
vsf_sysutil_close(p_sess->ssl_consumer_fd);
|
|
p_sess->ssl_consumer_fd = -1;
|
|
}
|
|
|
|
static SSL*
|
|
get_ssl(struct vsf_session* p_sess, int fd)
|
|
{
|
|
SSL* p_ssl = SSL_new(p_sess->p_ssl_ctx);
|
|
if (p_ssl == NULL)
|
|
{
|
|
if (tunable_debug_ssl)
|
|
{
|
|
str_alloc_text(&debug_str, "SSL_new failed");
|
|
vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
|
|
}
|
|
return NULL;
|
|
}
|
|
if (!SSL_set_fd(p_ssl, fd))
|
|
{
|
|
if (tunable_debug_ssl)
|
|
{
|
|
str_alloc_text(&debug_str, "SSL_set_fd failed");
|
|
vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
|
|
}
|
|
SSL_free(p_ssl);
|
|
return NULL;
|
|
}
|
|
if (SSL_accept(p_ssl) != 1)
|
|
{
|
|
const char* p_err = get_ssl_error();
|
|
if (tunable_debug_ssl)
|
|
{
|
|
str_alloc_text(&debug_str, "SSL_accept failed: ");
|
|
str_append_text(&debug_str, p_err);
|
|
vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
|
|
}
|
|
/* The RFC is quite clear that we can just close the control channel
|
|
* here.
|
|
*/
|
|
die(p_err);
|
|
}
|
|
if (tunable_debug_ssl)
|
|
{
|
|
const char* p_ssl_version = SSL_get_cipher_version(p_ssl);
|
|
const SSL_CIPHER* p_ssl_cipher = SSL_get_current_cipher(p_ssl);
|
|
const char* p_cipher_name = SSL_CIPHER_get_name(p_ssl_cipher);
|
|
X509* p_ssl_cert = SSL_get_peer_certificate(p_ssl);
|
|
int reused = SSL_session_reused(p_ssl);
|
|
str_alloc_text(&debug_str, "SSL version: ");
|
|
str_append_text(&debug_str, p_ssl_version);
|
|
str_append_text(&debug_str, ", SSL cipher: ");
|
|
str_append_text(&debug_str, p_cipher_name);
|
|
if (reused)
|
|
{
|
|
str_append_text(&debug_str, ", reused");
|
|
}
|
|
else
|
|
{
|
|
str_append_text(&debug_str, ", not reused");
|
|
}
|
|
if (p_ssl_cert != NULL)
|
|
{
|
|
str_append_text(&debug_str, ", CERT PRESENTED");
|
|
X509_free(p_ssl_cert);
|
|
}
|
|
else
|
|
{
|
|
str_append_text(&debug_str, ", no cert");
|
|
}
|
|
vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
|
|
}
|
|
return p_ssl;
|
|
}
|
|
|
|
static int
|
|
ssl_session_init(struct vsf_session* p_sess)
|
|
{
|
|
SSL* p_ssl = get_ssl(p_sess, VSFTP_COMMAND_FD);
|
|
if (p_ssl == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
p_sess->p_control_ssl = p_ssl;
|
|
(void) ssl_cert_digest(p_ssl, p_sess, &p_sess->control_cert_digest);
|
|
setup_bio_callbacks(p_ssl);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
ssl_cert_digest(SSL* p_ssl, struct vsf_session* p_sess, struct mystr* p_str)
|
|
{
|
|
X509* p_cert = SSL_get_peer_certificate(p_ssl);
|
|
unsigned int num_bytes = 0;
|
|
if (p_cert == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
str_reserve(p_str, EVP_MAX_MD_SIZE);
|
|
str_empty(p_str);
|
|
str_rpad(p_str, EVP_MAX_MD_SIZE);
|
|
if (!X509_digest(p_cert, EVP_sha256(), (unsigned char*) str_getbuf(p_str),
|
|
&num_bytes))
|
|
{
|
|
die("X509_digest failed");
|
|
}
|
|
X509_free(p_cert);
|
|
if (tunable_debug_ssl)
|
|
{
|
|
unsigned int i;
|
|
str_alloc_text(&debug_str, "Cert digest:");
|
|
for (i = 0; i < num_bytes; ++i)
|
|
{
|
|
str_append_char(&debug_str, ' ');
|
|
str_append_ulong(
|
|
&debug_str, (unsigned long) (unsigned char) str_get_char_at(p_str, i));
|
|
}
|
|
vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
|
|
}
|
|
str_trunc(p_str, num_bytes);
|
|
return 1;
|
|
}
|
|
|
|
static char*
|
|
get_ssl_error()
|
|
{
|
|
SSL_load_error_strings();
|
|
return ERR_error_string(ERR_get_error(), NULL);
|
|
}
|
|
|
|
static void setup_bio_callbacks(SSL* p_ssl)
|
|
{
|
|
BIO* p_bio = SSL_get_rbio(p_ssl);
|
|
BIO_set_callback(p_bio, bio_callback);
|
|
p_bio = SSL_get_wbio(p_ssl);
|
|
BIO_set_callback(p_bio, bio_callback);
|
|
}
|
|
|
|
static long
|
|
bio_callback(
|
|
BIO* p_bio, int oper, const char* p_arg, int argi, long argl, long ret)
|
|
{
|
|
int retval = 0;
|
|
int fd = 0;
|
|
(void) p_arg;
|
|
(void) argi;
|
|
(void) argl;
|
|
if (oper == (BIO_CB_READ | BIO_CB_RETURN) ||
|
|
oper == (BIO_CB_WRITE | BIO_CB_RETURN))
|
|
{
|
|
retval = (int) ret;
|
|
fd = BIO_get_fd(p_bio, NULL);
|
|
}
|
|
vsf_sysutil_check_pending_actions(kVSFSysUtilIO, retval, fd);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
ssl_verify_callback(int verify_ok, X509_STORE_CTX* p_ctx)
|
|
{
|
|
(void) p_ctx;
|
|
if (tunable_validate_cert)
|
|
{
|
|
return verify_ok;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
ssl_alpn_callback(SSL* p_ssl,
|
|
const unsigned char** p_out,
|
|
unsigned char* outlen,
|
|
const unsigned char* p_in,
|
|
unsigned int inlen,
|
|
void* p_arg) {
|
|
unsigned int i;
|
|
struct vsf_session* p_sess = (struct vsf_session*) p_arg;
|
|
int is_ok = 0;
|
|
|
|
(void) p_ssl;
|
|
|
|
/* Initialize just in case. */
|
|
*p_out = p_in;
|
|
*outlen = 0;
|
|
|
|
for (i = 0; i < inlen; ++i) {
|
|
unsigned int left = (inlen - i);
|
|
if (left < 4) {
|
|
continue;
|
|
}
|
|
if (p_in[i] == 3 && p_in[i + 1] == 'f' && p_in[i + 2] == 't' &&
|
|
p_in[i + 3] == 'p')
|
|
{
|
|
is_ok = 1;
|
|
*p_out = &p_in[i + 1];
|
|
*outlen = 3;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!is_ok)
|
|
{
|
|
str_alloc_text(&debug_str, "ALPN rejection");
|
|
vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
|
|
}
|
|
if (!is_ok || tunable_debug_ssl)
|
|
{
|
|
str_alloc_text(&debug_str, "ALPN data: ");
|
|
for (i = 0; i < inlen; ++i) {
|
|
str_append_char(&debug_str, p_in[i]);
|
|
}
|
|
vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
|
|
}
|
|
|
|
if (is_ok)
|
|
{
|
|
return SSL_TLSEXT_ERR_OK;
|
|
}
|
|
else
|
|
{
|
|
return SSL_TLSEXT_ERR_ALERT_FATAL;
|
|
}
|
|
}
|
|
|
|
static long
|
|
ssl_sni_callback(SSL* p_ssl, int* p_al, void* p_arg)
|
|
{
|
|
static struct mystr s_sni_expected_hostname;
|
|
static struct mystr s_sni_received_hostname;
|
|
|
|
int servername_type;
|
|
const char* p_sni_servername;
|
|
struct vsf_session* p_sess = (struct vsf_session*) p_arg;
|
|
int is_ok = 0;
|
|
|
|
(void) p_ssl;
|
|
(void) p_arg;
|
|
|
|
if (tunable_ssl_sni_hostname)
|
|
{
|
|
str_alloc_text(&s_sni_expected_hostname, tunable_ssl_sni_hostname);
|
|
}
|
|
|
|
/* The OpenSSL documentation says it is pre-initialized like this, but set
|
|
* it just in case.
|
|
*/
|
|
*p_al = SSL_AD_UNRECOGNIZED_NAME;
|
|
|
|
servername_type = SSL_get_servername_type(p_ssl);
|
|
p_sni_servername = SSL_get_servername(p_ssl, TLSEXT_NAMETYPE_host_name);
|
|
if (p_sni_servername != NULL) {
|
|
str_alloc_text(&s_sni_received_hostname, p_sni_servername);
|
|
}
|
|
|
|
if (str_isempty(&s_sni_expected_hostname))
|
|
{
|
|
is_ok = 1;
|
|
}
|
|
else if (servername_type != TLSEXT_NAMETYPE_host_name)
|
|
{
|
|
/* Fail. */
|
|
str_alloc_text(&debug_str, "SNI bad type: ");
|
|
str_append_ulong(&debug_str, servername_type);
|
|
vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
|
|
}
|
|
else
|
|
{
|
|
if (!str_strcmp(&s_sni_expected_hostname, &s_sni_received_hostname))
|
|
{
|
|
is_ok = 1;
|
|
}
|
|
else
|
|
{
|
|
str_alloc_text(&debug_str, "SNI rejection");
|
|
vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
|
|
}
|
|
}
|
|
|
|
if (!is_ok || tunable_debug_ssl)
|
|
{
|
|
str_alloc_text(&debug_str, "SNI hostname: ");
|
|
str_append_str(&debug_str, &s_sni_received_hostname);
|
|
vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
|
|
}
|
|
|
|
if (is_ok)
|
|
{
|
|
return SSL_TLSEXT_ERR_OK;
|
|
}
|
|
else
|
|
{
|
|
return SSL_TLSEXT_ERR_ALERT_FATAL;
|
|
}
|
|
}
|
|
|
|
void
|
|
ssl_add_entropy(struct vsf_session* p_sess)
|
|
{
|
|
/* Although each child does seem to have its different pool of entropy, I
|
|
* don't trust the interaction of OpenSSL's opaque RAND API and fork(). So
|
|
* throw a bit more in (only works on systems with /dev/urandom for now).
|
|
*/
|
|
int ret = RAND_load_file("/dev/urandom", 16);
|
|
if (ret != 16)
|
|
{
|
|
str_alloc_text(&debug_str, "Couldn't add extra OpenSSL entropy: ");
|
|
str_append_ulong(&debug_str, (unsigned long) ret);
|
|
vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str);
|
|
}
|
|
}
|
|
|
|
#else /* VSF_BUILD_SSL */
|
|
|
|
void
|
|
ssl_init(struct vsf_session* p_sess)
|
|
{
|
|
(void) p_sess;
|
|
die("SSL: ssl_enable is set but SSL support not compiled in");
|
|
}
|
|
|
|
void
|
|
ssl_control_handshake(struct vsf_session* p_sess)
|
|
{
|
|
(void) p_sess;
|
|
}
|
|
|
|
void
|
|
handle_auth(struct vsf_session* p_sess)
|
|
{
|
|
(void) p_sess;
|
|
}
|
|
|
|
void
|
|
handle_pbsz(struct vsf_session* p_sess)
|
|
{
|
|
(void) p_sess;
|
|
}
|
|
|
|
void
|
|
handle_prot(struct vsf_session* p_sess)
|
|
{
|
|
(void) p_sess;
|
|
}
|
|
|
|
int
|
|
ssl_read(struct vsf_session* p_sess, void* p_ssl, char* p_buf, unsigned int len)
|
|
{
|
|
(void) p_sess;
|
|
(void) p_ssl;
|
|
(void) p_buf;
|
|
(void) len;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
ssl_peek(struct vsf_session* p_sess, void* p_ssl, char* p_buf, unsigned int len)
|
|
{
|
|
(void) p_sess;
|
|
(void) p_ssl;
|
|
(void) p_buf;
|
|
(void) len;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
ssl_write(void* p_ssl, const char* p_buf, unsigned int len)
|
|
{
|
|
(void) p_ssl;
|
|
(void) p_buf;
|
|
(void) len;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
ssl_write_str(void* p_ssl, const struct mystr* p_str)
|
|
{
|
|
(void) p_ssl;
|
|
(void) p_str;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
ssl_accept(struct vsf_session* p_sess, int fd)
|
|
{
|
|
(void) p_sess;
|
|
(void) fd;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
ssl_data_close(struct vsf_session* p_sess)
|
|
{
|
|
(void) p_sess;
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
ssl_comm_channel_init(struct vsf_session* p_sess)
|
|
{
|
|
(void) p_sess;
|
|
}
|
|
|
|
void
|
|
ssl_comm_channel_set_consumer_context(struct vsf_session* p_sess)
|
|
{
|
|
(void) p_sess;
|
|
}
|
|
|
|
void
|
|
ssl_comm_channel_set_producer_context(struct vsf_session* p_sess)
|
|
{
|
|
(void) p_sess;
|
|
}
|
|
|
|
void
|
|
ssl_add_entropy(struct vsf_session* p_sess)
|
|
{
|
|
(void) p_sess;
|
|
}
|
|
|
|
int
|
|
ssl_read_into_str(struct vsf_session* p_sess, void* p_ssl, struct mystr* p_str)
|
|
{
|
|
(void) p_sess;
|
|
(void) p_ssl;
|
|
(void) p_str;
|
|
return -1;
|
|
}
|
|
|
|
#endif /* VSF_BUILD_SSL */
|
|
|