diff options
Diffstat (limited to 'buckets/ssl_buckets.c')
-rw-r--r-- | buckets/ssl_buckets.c | 297 |
1 files changed, 210 insertions, 87 deletions
diff --git a/buckets/ssl_buckets.c b/buckets/ssl_buckets.c index 3f43543..c5a0e60 100644 --- a/buckets/ssl_buckets.c +++ b/buckets/ssl_buckets.c @@ -43,6 +43,7 @@ #include <apr_atomic.h> #include "serf.h" +#include "serf_private.h" #include "serf_bucket_util.h" #include <openssl/bio.h> @@ -64,8 +65,6 @@ #endif -/*#define SSL_VERBOSE*/ - /* * Here's an overview of the SSL bucket's relationship to OpenSSL and serf. * @@ -180,6 +179,10 @@ struct serf_ssl_context_t { EVP_PKEY *cached_cert_pw; apr_status_t pending_err; + + /* Status of a fatal error, returned on subsequent encrypt or decrypt + requests. */ + apr_status_t fatal_err; }; typedef struct { @@ -198,6 +201,47 @@ struct serf_ssl_certificate_t { int depth; }; +static void disable_compression(serf_ssl_context_t *ssl_ctx); + +#if SSL_VERBOSE +/* Log all ssl alerts that we receive from the server. */ +static void +apps_ssl_info_callback(const SSL *s, int where, int ret) +{ + const char *str; + int w; + w = where & ~SSL_ST_MASK; + + if (w & SSL_ST_CONNECT) + str = "SSL_connect"; + else if (w & SSL_ST_ACCEPT) + str = "SSL_accept"; + else + str = "undefined"; + + if (where & SSL_CB_LOOP) { + serf__log(SSL_VERBOSE, __FILE__, "%s:%s\n", str, + SSL_state_string_long(s)); + } + else if (where & SSL_CB_ALERT) { + str = (where & SSL_CB_READ) ? "read" : "write"; + serf__log(SSL_VERBOSE, __FILE__, "SSL3 alert %s:%s:%s\n", + str, + SSL_alert_type_string_long(ret), + SSL_alert_desc_string_long(ret)); + } + else if (where & SSL_CB_EXIT) { + if (ret == 0) + serf__log(SSL_VERBOSE, __FILE__, "%s:failed in %s\n", str, + SSL_state_string_long(s)); + else if (ret < 0) { + serf__log(SSL_VERBOSE, __FILE__, "%s:error in %s\n", str, + SSL_state_string_long(s)); + } + } +} +#endif + /* Returns the amount read. */ static int bio_bucket_read(BIO *bio, char *in, int inlen) { @@ -206,17 +250,14 @@ static int bio_bucket_read(BIO *bio, char *in, int inlen) apr_status_t status; apr_size_t len; -#ifdef SSL_VERBOSE - printf("bio_bucket_read called for %d bytes\n", inlen); -#endif + serf__log(SSL_VERBOSE, __FILE__, "bio_bucket_read called for %d bytes\n", + inlen); if (ctx->encrypt.status == SERF_ERROR_WAIT_CONN && BIO_should_read(ctx->bio)) { -#ifdef SSL_VERBOSE - printf("bio_bucket_read waiting: (%d %d %d)\n", + serf__log(SSL_VERBOSE, __FILE__, "bio_bucket_read waiting: (%d %d %d)\n", BIO_should_retry(ctx->bio), BIO_should_read(ctx->bio), BIO_get_retry_flags(ctx->bio)); -#endif /* Falling back... */ ctx->encrypt.exhausted_reset = 1; BIO_clear_retry_flags(bio); @@ -225,9 +266,9 @@ static int bio_bucket_read(BIO *bio, char *in, int inlen) status = serf_bucket_read(ctx->decrypt.pending, inlen, &data, &len); ctx->decrypt.status = status; -#ifdef SSL_VERBOSE - printf("bio_bucket_read received %d bytes (%d)\n", len, status); -#endif + + serf__log(SSL_VERBOSE, __FILE__, "bio_bucket_read received %d bytes (%d)\n", + len, status); if (!SERF_BUCKET_READ_ERROR(status)) { /* Oh suck. */ @@ -250,16 +291,14 @@ static int bio_bucket_write(BIO *bio, const char *in, int inl) serf_ssl_context_t *ctx = bio->ptr; serf_bucket_t *tmp; -#ifdef SSL_VERBOSE - printf("bio_bucket_write called for %d bytes\n", inl); -#endif + serf__log(SSL_VERBOSE, __FILE__, "bio_bucket_write called for %d bytes\n", + inl); + if (ctx->encrypt.status == SERF_ERROR_WAIT_CONN && !BIO_should_read(ctx->bio)) { -#ifdef SSL_VERBOSE - printf("bio_bucket_write waiting: (%d %d %d)\n", + serf__log(SSL_VERBOSE, __FILE__, "bio_bucket_write waiting: (%d %d %d)\n", BIO_should_retry(ctx->bio), BIO_should_read(ctx->bio), BIO_get_retry_flags(ctx->bio)); -#endif /* Falling back... */ ctx->encrypt.exhausted_reset = 1; BIO_clear_retry_flags(bio); @@ -424,6 +463,9 @@ validate_server_certificate(int cert_valid, X509_STORE_CTX *store_ctx) case X509_V_ERR_INVALID_CA: failures |= SERF_SSL_CERT_UNKNOWNCA; break; + case X509_V_ERR_CERT_REVOKED: + failures |= SERF_SSL_CERT_REVOKED; + break; default: failures |= SERF_SSL_CERT_UNKNOWN_FAILURE; break; @@ -455,9 +497,13 @@ validate_server_certificate(int cert_valid, X509_STORE_CTX *store_ctx) failures, cert); if (status == APR_SUCCESS) cert_valid = 1; - else + else { + /* Even if openssl found the certificate valid, the application + told us to reject it. */ + cert_valid = 0; /* Pass the error back to the caller through the context-run. */ ctx->pending_err = status; + } apr_pool_destroy(subpool); } @@ -490,7 +536,7 @@ validate_server_certificate(int cert_valid, X509_STORE_CTX *store_ctx) certs_len = 1; } else { int i; - + certs_len = sk_X509_num(chain); /* Room for all the certs and a trailing NULL. */ @@ -514,6 +560,9 @@ validate_server_certificate(int cert_valid, X509_STORE_CTX *store_ctx) if (status == APR_SUCCESS) { cert_valid = 1; } else { + /* Even if openssl found the certificate valid, the application + told us to reject it. */ + cert_valid = 0; /* Pass the error back to the caller through the context-run. */ ctx->pending_err = status; } @@ -534,18 +583,18 @@ static apr_status_t ssl_decrypt(void *baton, apr_size_t bufsize, const char *data; int ssl_len; -#ifdef SSL_VERBOSE - printf("ssl_decrypt: begin %d\n", bufsize); -#endif + if (ctx->fatal_err) + return ctx->fatal_err; + + serf__log(SSL_VERBOSE, __FILE__, "ssl_decrypt: begin %d\n", bufsize); /* Is there some data waiting to be read? */ ssl_len = SSL_read(ctx->ssl, buf, bufsize); if (ssl_len > 0) { -#ifdef SSL_VERBOSE - printf("ssl_decrypt: %d bytes (%d); status: %d; flags: %d\n", - ssl_len, bufsize, ctx->decrypt.status, - BIO_get_retry_flags(ctx->bio)); -#endif + serf__log(SSL_VERBOSE, __FILE__, + "ssl_decrypt: %d bytes (%d); status: %d; flags: %d\n", + ssl_len, bufsize, ctx->decrypt.status, + BIO_get_retry_flags(ctx->bio)); *len = ssl_len; return APR_SUCCESS; } @@ -555,10 +604,9 @@ static apr_status_t ssl_decrypt(void *baton, apr_size_t bufsize, if (!SERF_BUCKET_READ_ERROR(status) && priv_len) { serf_bucket_t *tmp; -#ifdef SSL_VERBOSE - printf("ssl_decrypt: read %d bytes (%d); status: %d\n", priv_len, - bufsize, status); -#endif + serf__log(SSL_VERBOSE, __FILE__, + "ssl_decrypt: read %d bytes (%d); status: %d\n", + priv_len, bufsize, status); tmp = serf_bucket_simple_copy_create(data, priv_len, ctx->decrypt.pending->allocator); @@ -581,29 +629,55 @@ static apr_status_t ssl_decrypt(void *baton, apr_size_t bufsize, break; case SSL_ERROR_SSL: *len = 0; - status = ctx->pending_err ? ctx->pending_err : APR_EGENERAL; - ctx->pending_err = 0; + if (ctx->pending_err) { + status = ctx->pending_err; + ctx->pending_err = 0; + } else { + ctx->fatal_err = status = SERF_ERROR_SSL_COMM_FAILED; + } break; default: *len = 0; - status = APR_EGENERAL; + ctx->fatal_err = status = SERF_ERROR_SSL_COMM_FAILED; break; } - } - else { + } else if (ssl_len == 0) { + /* The server shut down the connection. */ + int ssl_err, shutdown; + *len = 0; + + /* Check for SSL_RECEIVED_SHUTDOWN */ + shutdown = SSL_get_shutdown(ctx->ssl); + /* Check for SSL_ERROR_ZERO_RETURN */ + ssl_err = SSL_get_error(ctx->ssl, ssl_len); + + if (shutdown == SSL_RECEIVED_SHUTDOWN && + ssl_err == SSL_ERROR_ZERO_RETURN) { + /* The server closed the SSL session. While this doesn't + necessary mean the connection is closed, let's close + it here anyway. + We can optimize this later. */ + serf__log(SSL_VERBOSE, __FILE__, + "ssl_decrypt: SSL read error: server" + " shut down connection!\n"); + status = APR_EOF; + } else { + /* A fatal error occurred. */ + ctx->fatal_err = status = SERF_ERROR_SSL_COMM_FAILED; + } + } else { *len = ssl_len; -#ifdef SSL_VERBOSE - printf("---\n%s\n-(%d)-\n", buf, *len); -#endif + serf__log(SSL_MSG_VERBOSE, __FILE__, + "---\n%.*s\n-(%d)-\n", *len, buf, *len); } } else { *len = 0; } -#ifdef SSL_VERBOSE - printf("ssl_decrypt: %d %d %d\n", status, *len, - BIO_get_retry_flags(ctx->bio)); -#endif + serf__log(SSL_VERBOSE, __FILE__, + "ssl_decrypt: %d %d %d\n", status, *len, + BIO_get_retry_flags(ctx->bio)); + return status; } @@ -616,9 +690,10 @@ static apr_status_t ssl_encrypt(void *baton, apr_size_t bufsize, serf_ssl_context_t *ctx = baton; apr_status_t status; -#ifdef SSL_VERBOSE - printf("ssl_encrypt: begin %d\n", bufsize); -#endif + if (ctx->fatal_err) + return ctx->fatal_err; + + serf__log(SSL_VERBOSE, __FILE__, "ssl_encrypt: begin %d\n", bufsize); /* Try to read already encrypted but unread data first. */ status = serf_bucket_read(ctx->encrypt.pending, bufsize, &data, len); @@ -632,29 +707,28 @@ static apr_status_t ssl_encrypt(void *baton, apr_size_t bufsize, if (APR_STATUS_IS_EOF(status)) { status = APR_SUCCESS; } -#ifdef SSL_VERBOSE - printf("ssl_encrypt: %d %d %d (quick read)\n", status, *len, - BIO_get_retry_flags(ctx->bio)); -#endif + + serf__log(SSL_VERBOSE, __FILE__, "ssl_encrypt: %d %d %d (quick read)\n", + status, *len, BIO_get_retry_flags(ctx->bio)); + return status; } if (BIO_should_retry(ctx->bio) && BIO_should_write(ctx->bio)) { -#ifdef SSL_VERBOSE - printf("ssl_encrypt: %d %d %d (should write exit)\n", status, *len, - BIO_get_retry_flags(ctx->bio)); -#endif + serf__log(SSL_VERBOSE, __FILE__, + "ssl_encrypt: %d %d %d (should write exit)\n", + status, *len, BIO_get_retry_flags(ctx->bio)); + return APR_EAGAIN; } /* If we were previously blocked, unblock ourselves now. */ if (BIO_should_read(ctx->bio)) { -#ifdef SSL_VERBOSE - printf("ssl_encrypt: reset %d %d (%d %d %d)\n", status, - ctx->encrypt.status, - BIO_should_retry(ctx->bio), BIO_should_read(ctx->bio), - BIO_get_retry_flags(ctx->bio)); -#endif + serf__log(SSL_VERBOSE, __FILE__, "ssl_encrypt: reset %d %d (%d %d %d)\n", + status, ctx->encrypt.status, + BIO_should_retry(ctx->bio), BIO_should_read(ctx->bio), + BIO_get_retry_flags(ctx->bio)); + ctx->encrypt.status = APR_SUCCESS; ctx->encrypt.exhausted_reset = 0; } @@ -696,18 +770,20 @@ static apr_status_t ssl_encrypt(void *baton, apr_size_t bufsize, interim_bufsize -= vecs_data_len; interim_len = vecs_data_len; -#ifdef SSL_VERBOSE - printf("ssl_encrypt: bucket read %d bytes; status %d\n", - interim_len, status); - printf("---\n%s\n-(%d)-\n", vecs_data, interim_len); -#endif + serf__log(SSL_VERBOSE, __FILE__, + "ssl_encrypt: bucket read %d bytes; "\ + "status %d\n", interim_len, status); + serf__log(SSL_MSG_VERBOSE, __FILE__, "---\n%.*s\n-(%d)-\n", + interim_len, vecs_data, interim_len); + /* Stash our status away. */ ctx->encrypt.status = status; ssl_len = SSL_write(ctx->ssl, vecs_data, interim_len); -#ifdef SSL_VERBOSE - printf("ssl_encrypt: SSL write: %d\n", ssl_len); -#endif + + serf__log(SSL_VERBOSE, __FILE__, + "ssl_encrypt: SSL write: %d\n", ssl_len); + /* We're done. */ serf_bucket_mem_free(ctx->allocator, vecs_data); @@ -720,9 +796,10 @@ static apr_status_t ssl_encrypt(void *baton, apr_size_t bufsize, vecs, vecs_read); ssl_err = SSL_get_error(ctx->ssl, ssl_len); -#ifdef SSL_VERBOSE - printf("ssl_encrypt: SSL write error: %d\n", ssl_err); -#endif + + serf__log(SSL_VERBOSE, __FILE__, + "ssl_encrypt: SSL write error: %d\n", ssl_err); + if (ssl_err == SSL_ERROR_SYSCALL) { status = ctx->encrypt.status; if (SERF_BUCKET_READ_ERROR(status)) { @@ -735,12 +812,13 @@ static apr_status_t ssl_encrypt(void *baton, apr_size_t bufsize, status = SERF_ERROR_WAIT_CONN; } else { - status = APR_EGENERAL; + ctx->fatal_err = status = SERF_ERROR_SSL_COMM_FAILED; } } -#ifdef SSL_VERBOSE - printf("ssl_encrypt: SSL write error: %d %d\n", status, *len); -#endif + + serf__log(SSL_VERBOSE, __FILE__, + "ssl_encrypt: SSL write error: %d %d\n", + status, *len); } } } @@ -767,10 +845,9 @@ static apr_status_t ssl_encrypt(void *baton, apr_size_t bufsize, *len += vecs[i].iov_len; } -#ifdef SSL_VERBOSE - printf("ssl_encrypt read agg: %d %d %d %d\n", status, agg_status, - ctx->encrypt.status, *len); -#endif + serf__log(SSL_VERBOSE, __FILE__, + "ssl_encrypt read agg: %d %d %d %d\n", status, agg_status, + ctx->encrypt.status, *len); if (!agg_status) { status = agg_status; @@ -783,11 +860,11 @@ static apr_status_t ssl_encrypt(void *baton, apr_size_t bufsize, ctx->encrypt.status = SERF_ERROR_WAIT_CONN; } -#ifdef SSL_VERBOSE - printf("ssl_encrypt finished: %d %d (%d %d %d)\n", status, *len, - BIO_should_retry(ctx->bio), BIO_should_read(ctx->bio), - BIO_get_retry_flags(ctx->bio)); -#endif + serf__log(SSL_VERBOSE, __FILE__, + "ssl_encrypt finished: %d %d (%d %d %d)\n", status, *len, + BIO_should_retry(ctx->bio), BIO_should_read(ctx->bio), + BIO_get_retry_flags(ctx->bio)); + return status; } @@ -873,6 +950,20 @@ static void init_ssl_libraries(void) #if APR_HAS_THREADS int i, numlocks; #endif + +#ifdef SSL_VERBOSE + /* Warn when compile-time and run-time version of OpenSSL differ in + major/minor version number. */ + long libver = SSLeay(); + + if ((libver ^ OPENSSL_VERSION_NUMBER) & 0xFFF00000) { + serf__log(SSL_VERBOSE, __FILE__, + "Warning: OpenSSL library version mismatch, compile-time " + "was %lx, runtime is %lx.\n", + OPENSSL_VERSION_NUMBER, libver); + } +#endif + CRYPTO_malloc_init(); ERR_load_crypto_strings(); SSL_load_error_strings(); @@ -1107,6 +1198,7 @@ static serf_ssl_context_t *ssl_init_context(void) ssl_ctx->cached_cert = 0; ssl_ctx->cached_cert_pw = 0; ssl_ctx->pending_err = APR_SUCCESS; + ssl_ctx->fatal_err = APR_SUCCESS; ssl_ctx->cert_callback = NULL; ssl_ctx->cert_pw_callback = NULL; @@ -1116,6 +1208,8 @@ static serf_ssl_context_t *ssl_init_context(void) SSL_CTX_set_verify(ssl_ctx->ctx, SSL_VERIFY_PEER, validate_server_certificate); SSL_CTX_set_options(ssl_ctx->ctx, SSL_OP_ALL); + /* Disable SSL compression by default. */ + disable_compression(ssl_ctx); ssl_ctx->ssl = SSL_new(ssl_ctx->ctx); ssl_ctx->bio = BIO_new(&bio_bucket_method); @@ -1127,6 +1221,10 @@ static serf_ssl_context_t *ssl_init_context(void) SSL_set_app_data(ssl_ctx->ssl, ssl_ctx); +#if SSL_VERBOSE + SSL_CTX_set_info_callback(ssl_ctx->ctx, apps_ssl_info_callback); +#endif + ssl_ctx->encrypt.stream = NULL; ssl_ctx->encrypt.stream_next = NULL; ssl_ctx->encrypt.pending = serf_bucket_aggregate_create(allocator); @@ -1206,7 +1304,7 @@ apr_status_t serf_ssl_use_default_certificates(serf_ssl_context_t *ssl_ctx) int result = X509_STORE_set_default_paths(store); - return result ? APR_SUCCESS : APR_EGENERAL; + return result ? APR_SUCCESS : SERF_ERROR_SSL_CERT_FAILED; } apr_status_t serf_ssl_load_cert_file( @@ -1228,7 +1326,7 @@ apr_status_t serf_ssl_load_cert_file( } } - return APR_EGENERAL; + return SERF_ERROR_SSL_CERT_FAILED; } @@ -1240,7 +1338,7 @@ apr_status_t serf_ssl_trust_cert( int result = X509_STORE_add_cert(store, cert->ssl_cert); - return result ? APR_SUCCESS : APR_EGENERAL; + return result ? APR_SUCCESS : SERF_ERROR_SSL_CERT_FAILED; } @@ -1521,6 +1619,31 @@ const char *serf_ssl_cert_export( return encoded_cert; } +/* Disables compression for all SSL sessions. */ +static void disable_compression(serf_ssl_context_t *ssl_ctx) +{ +#ifdef SSL_OP_NO_COMPRESSION + SSL_CTX_set_options(ssl_ctx->ctx, SSL_OP_NO_COMPRESSION); +#endif +} + +apr_status_t serf_ssl_use_compression(serf_ssl_context_t *ssl_ctx, int enabled) +{ + if (enabled) { +#ifdef SSL_OP_NO_COMPRESSION + SSL_clear_options(ssl_ctx->ssl, SSL_OP_NO_COMPRESSION); + return APR_SUCCESS; +#endif + } else { +#ifdef SSL_OP_NO_COMPRESSION + SSL_set_options(ssl_ctx->ssl, SSL_OP_NO_COMPRESSION); + return APR_SUCCESS; +#endif + } + + return APR_EGENERAL; +} + static void serf_ssl_destroy_and_data(serf_bucket_t *bucket) { ssl_context_t *ctx = bucket->data; |