| /* ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| * |
| * ---- |
| * |
| * Originally developed by Aaron Bannert and Justin Erenkrantz, eBuilt. |
| */ |
| |
| #define APR_WANT_MEMFUNC |
| #include <apr_want.h> |
| #include <apr_pools.h> |
| #include <apr_network_io.h> |
| #include <apr_portable.h> |
| #include <apr_strings.h> |
| #include <apr_base64.h> |
| #include <apr_version.h> |
| #include <apr_atomic.h> |
| |
| #include "serf.h" |
| #include "serf_private.h" |
| #include "serf_bucket_util.h" |
| |
| #include <openssl/bio.h> |
| #include <openssl/ssl.h> |
| #include <openssl/err.h> |
| #include <openssl/pkcs12.h> |
| #include <openssl/x509v3.h> |
| #ifndef OPENSSL_NO_OCSP /* requires openssl 0.9.7 or later */ |
| #include <openssl/ocsp.h> |
| #endif |
| |
| #ifndef APR_ARRAY_PUSH |
| #define APR_ARRAY_PUSH(ary,type) (*((type *)apr_array_push(ary))) |
| #endif |
| |
| #if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x10100000L |
| #define USE_LEGACY_OPENSSL |
| #define X509_STORE_get0_param(store) store->param |
| #endif |
| |
| |
| /* |
| * Here's an overview of the SSL bucket's relationship to OpenSSL and serf. |
| * |
| * HTTP request: SSLENCRYPT(REQUEST) |
| * [context.c reads from SSLENCRYPT and writes out to the socket] |
| * HTTP response: RESPONSE(SSLDECRYPT(SOCKET)) |
| * [handler function reads from RESPONSE which in turn reads from SSLDECRYPT] |
| * |
| * HTTP request read call path: |
| * |
| * write_to_connection |
| * |- serf_bucket_read on SSLENCRYPT |
| * |- serf_ssl_read |
| * |- serf_databuf_read |
| * |- common_databuf_prep |
| * |- ssl_encrypt |
| * |- 1. Try to read pending encrypted data; If available, return. |
| * |- 2. Try to read from ctx->stream [REQUEST bucket] |
| * |- 3. Call SSL_write with read data |
| * |- ... |
| * |- bio_bucket_read can be called |
| * |- read data from ctx->decrypt.stream |
| * |- bio_bucket_write with encrypted data |
| * |- store in sink |
| * |- 4. If successful, read pending encrypted data and return. |
| * |- 5. If fails, place read data back in ctx->stream |
| * |
| * HTTP response read call path: |
| * |
| * read_from_connection |
| * |- acceptor |
| * |- handler |
| * |- ... |
| * |- serf_bucket_read(SSLDECRYPT) |
| * |- serf_ssl_read |
| * |- serf_databuf_read |
| * |- ssl_decrypt |
| * |- Call SSL_read() |
| * |- ... |
| * |- bio_bucket_read |
| * |- read data from ctx->decrypt.stream |
| * |- bio_bucket_write can be called |
| * |- store in sink |
| * |- If data read, return it. |
| * |- If an error, set the STATUS value and return. |
| * |
| */ |
| |
| typedef struct bucket_list { |
| serf_bucket_t *bucket; |
| struct bucket_list *next; |
| } bucket_list_t; |
| |
| typedef struct serf_ssl_stream_t { |
| /* Helper to read data. Wraps stream. */ |
| serf_databuf_t databuf; |
| |
| /* Our source for more data. */ |
| serf_bucket_t *stream; |
| |
| /* The next set of buckets */ |
| bucket_list_t *stream_next; |
| } serf_ssl_stream_t; |
| |
| struct serf_ssl_context_t { |
| /* How many open buckets refer to this context. */ |
| int refcount; |
| |
| /* The pool that this context uses. */ |
| apr_pool_t *pool; |
| |
| /* The allocator associated with the above pool. */ |
| serf_bucket_alloc_t *allocator; |
| |
| /* Internal OpenSSL parameters */ |
| SSL_CTX *ctx; |
| SSL *ssl; |
| BIO *bio; |
| BIO_METHOD *biom; |
| |
| serf_ssl_stream_t encrypt; |
| serf_ssl_stream_t decrypt; |
| |
| /* The status of the last thing we read or wrote. */ |
| apr_status_t crypt_status; |
| |
| /* Encrypted data waiting to be written. */ |
| serf_bucket_t *encrypt_pending; |
| |
| /* Should we read before we can write again? */ |
| int want_read; |
| int handshake_done; |
| |
| /* Client cert callbacks */ |
| serf_ssl_need_client_cert_t cert_callback; |
| void *cert_userdata; |
| apr_pool_t *cert_cache_pool; |
| const char *cert_file_success; |
| |
| /* Client cert PW callbacks */ |
| serf_ssl_need_cert_password_t cert_pw_callback; |
| void *cert_pw_userdata; |
| apr_pool_t *cert_pw_cache_pool; |
| const char *cert_pw_success; |
| |
| /* Server cert callbacks */ |
| serf_ssl_need_server_cert_t server_cert_callback; |
| serf_ssl_server_cert_chain_cb_t server_cert_chain_callback; |
| void *server_cert_userdata; |
| |
| const char *cert_path; |
| |
| X509 *cached_cert; |
| 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; |
| |
| /* Flag is set to 1 when a renegotiation is in progress. */ |
| int renegotiation; |
| int handshake_finished; /* True after SSL internal connection is through |
| the handshake */ |
| |
| const char *selected_protocol; /* Cached protocol value once available */ |
| /* Protocol callback */ |
| serf_ssl_protocol_result_cb_t protocol_callback; |
| void *protocol_userdata; |
| |
| serf_config_t *config; |
| }; |
| |
| typedef struct ssl_context_t { |
| /* The bucket-independent ssl context that this bucket is associated with */ |
| serf_ssl_context_t *ssl_ctx; |
| |
| /* Pointer to the 'right' databuf. */ |
| serf_databuf_t *databuf; |
| |
| /* Pointer to our stream, so we can find it later. */ |
| serf_bucket_t **our_stream; |
| } ssl_context_t; |
| |
| struct serf_ssl_certificate_t { |
| X509 *ssl_cert; |
| int depth; |
| }; |
| |
| static void disable_compression(serf_ssl_context_t *ssl_ctx); |
| static char * |
| pstrdup_escape_nul_bytes(const char *buf, int len, apr_pool_t *pool); |
| |
| static const char *ssl_get_selected_protocol(serf_ssl_context_t *context); |
| |
| #ifdef SERF_LOGGING_ENABLED |
| /* 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; |
| serf_ssl_context_t *ctx; |
| int w; |
| int in_write = (where & SSL_CB_WRITE); |
| const char *read_write_str = (in_write ? "write" : "read"); |
| int ssl_error = SSL_get_error(s, ret); |
| |
| ctx = SSL_get_app_data(s); |
| |
| 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(LOGLVL_DEBUG, LOGCOMP_SSL, __FILE__, ctx->config, |
| "%s:%s\n", str, SSL_state_string_long(s)); |
| } |
| else if (where & SSL_CB_ALERT) { |
| serf__log(LOGLVL_WARNING, LOGCOMP_SSL, __FILE__, ctx->config, |
| "SSL %s alert: %s: %s\n", |
| read_write_str, |
| SSL_alert_type_string_long(ret), |
| SSL_alert_desc_string_long(ret)); |
| } |
| else if (where & SSL_CB_EXIT) { |
| int level; |
| const char *how = (ret == 0) ? "failed" : "error"; |
| |
| if (ret < 0 && ssl_error != SSL_ERROR_WANT_READ) |
| level = LOGLVL_ERROR; |
| else if (ret == 0) |
| level = LOGLVL_WARNING; |
| else if (ssl_error != SSL_ERROR_WANT_READ) |
| level = LOGLVL_INFO; |
| else |
| level = LOGLVL_DEBUG; |
| |
| if (ret > 0) { |
| /* ret > 0: Just a state change; not an error */ |
| serf__log(level, LOGCOMP_SSL, __FILE__, ctx->config, |
| "%s: %s (%d)\n", |
| str, SSL_state_string_long(s), |
| ctx->crypt_status); |
| } |
| else if (ssl_error == 0) { |
| serf__log(level, LOGCOMP_SSL, __FILE__, ctx->config, |
| "%s:%s %s in %s, status=%d\n", |
| str, read_write_str, how, SSL_state_string_long(s), |
| ctx->crypt_status); |
| } |
| else if (ssl_error != SSL_ERROR_SYSCALL) { |
| serf__log(level, LOGCOMP_SSL, __FILE__, ctx->config, |
| "%s:%s %s in %s: ssl_error=%d, status=%d\n", |
| str, read_write_str, how, SSL_state_string_long(s), |
| ssl_error, ctx->crypt_status); |
| } |
| else { |
| serf__log(level, LOGCOMP_SSL, __FILE__, ctx->config, |
| "%s:%s %s in %s: status=%d\n", |
| str, read_write_str, how, SSL_state_string_long(s), |
| ctx->crypt_status); |
| } |
| } |
| } |
| #endif |
| |
| |
| /* Listens for the SSL renegotiate ciphers alert and report it back to the |
| serf context. */ |
| static void |
| detect_renegotiate(const SSL *s, int where, int ret) |
| { |
| /* This callback overrides the SSL state logging callback, so call it here |
| (if logging is compiled in). */ |
| #ifdef SERF_LOGGING_ENABLED |
| apps_ssl_info_callback(s, where, ret); |
| #endif |
| |
| /* The server asked to renegotiate the SSL session. */ |
| #ifndef USE_LEGACY_OPENSSL |
| if (SSL_get_state(s) == TLS_ST_SW_HELLO_REQ) { |
| #else |
| if (SSL_state(s) == SSL_ST_RENEGOTIATE) { |
| #endif |
| serf_ssl_context_t *ssl_ctx = SSL_get_app_data(s); |
| |
| ssl_ctx->renegotiation = 1; |
| ssl_ctx->fatal_err = SERF_ERROR_SSL_NEGOTIATE_IN_PROGRESS; |
| } |
| } |
| |
| static void log_ssl_error(serf_ssl_context_t *ctx) |
| { |
| unsigned long e = ERR_get_error(); |
| serf__log(LOGLVL_ERROR, LOGCOMP_SSL, __FILE__, ctx->config, |
| "SSL Error: %s\n", ERR_error_string(e, NULL)); |
| |
| } |
| |
| static void bio_set_data(BIO *bio, void *data) |
| { |
| #ifndef USE_LEGACY_OPENSSL |
| BIO_set_data(bio, data); |
| #else |
| bio->ptr = data; |
| #endif |
| } |
| |
| static void *bio_get_data(BIO *bio) |
| { |
| #ifndef USE_LEGACY_OPENSSL |
| return BIO_get_data(bio); |
| #else |
| return bio->ptr; |
| #endif |
| } |
| |
| /* Returns the amount read. */ |
| static int bio_bucket_read(BIO *bio, char *in, int inlen) |
| { |
| serf_ssl_context_t *ctx = bio_get_data(bio); |
| const char *data; |
| apr_status_t status; |
| apr_size_t len; |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_SSL, __FILE__, ctx->config, |
| "bio_bucket_read called for %d bytes\n", inlen); |
| |
| BIO_clear_retry_flags(bio); /* Clear retry hints */ |
| |
| /* The server initiated a renegotiation and we were instructed to report |
| that as an error asap. */ |
| if (ctx->renegotiation) { |
| ctx->crypt_status = SERF_ERROR_SSL_NEGOTIATE_IN_PROGRESS; |
| return -1; |
| } |
| |
| status = serf_bucket_read(ctx->decrypt.stream, inlen, &data, &len); |
| ctx->crypt_status = status; |
| ctx->want_read = FALSE; |
| |
| if (SERF_BUCKET_READ_ERROR(status)) { |
| return -1; /* Raises: SSL_ERROR_SYSCALL; caller reads crypt_status */ |
| } |
| |
| if (status && !APR_STATUS_IS_EOF(status)) { |
| BIO_set_retry_read(bio); /* Signal SSL: Retry later */ |
| } |
| |
| if (! len) { |
| return -1; /* Raises: SSL_ERROR_SYSCALL; caller reads crypt_status */ |
| } |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_SSL, __FILE__, ctx->config, |
| "bio_bucket_read received %"APR_SIZE_T_FMT" bytes (%d)\n", len, status); |
| |
| memcpy(in, data, len); |
| return len; |
| } |
| |
| /* Returns the amount written. */ |
| static int bio_bucket_write(BIO *bio, const char *in, int inl) |
| { |
| serf_ssl_context_t *ctx = bio_get_data(bio); |
| serf_bucket_t *tmp; |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_SSL, __FILE__, ctx->config, |
| "bio_bucket_write called for %d bytes\n", inl); |
| |
| BIO_clear_retry_flags(bio); /* Clear retry hints */ |
| |
| /* The server initiated a renegotiation and we were instructed to report |
| that as an error asap. */ |
| if (ctx->renegotiation) { |
| ctx->crypt_status = SERF_ERROR_SSL_NEGOTIATE_IN_PROGRESS; |
| return -1; |
| } |
| |
| ctx->crypt_status = APR_SUCCESS; |
| |
| tmp = serf_bucket_simple_copy_create(in, inl, |
| ctx->encrypt_pending->allocator); |
| |
| serf_bucket_aggregate_append(ctx->encrypt_pending, tmp); |
| |
| return inl; |
| } |
| |
| /* Returns the amount read. */ |
| static int bio_file_read(BIO *bio, char *in, int inlen) |
| { |
| apr_file_t *file = bio_get_data(bio); |
| apr_status_t status; |
| apr_size_t len; |
| |
| len = inlen; |
| status = apr_file_read(file, in, &len); |
| |
| if (!SERF_BUCKET_READ_ERROR(status)) { |
| /* Oh suck. */ |
| if (APR_STATUS_IS_EOF(status)) { |
| return -1; |
| } else { |
| return len; |
| } |
| } |
| |
| return -1; |
| } |
| |
| /* Returns the amount written. */ |
| static int bio_file_write(BIO *bio, const char *in, int inl) |
| { |
| apr_file_t *file = bio_get_data(bio); |
| apr_size_t nbytes; |
| |
| BIO_clear_retry_flags(bio); |
| |
| nbytes = inl; |
| apr_file_write(file, in, &nbytes); |
| |
| return nbytes; |
| } |
| |
| static int bio_file_gets(BIO *bio, char *in, int inlen) |
| { |
| apr_file_t *file = bio_get_data(bio); |
| apr_status_t status; |
| |
| status = apr_file_gets(in, inlen, file); |
| |
| if (! status) { |
| return (int)strlen(in); |
| } else if (APR_STATUS_IS_EOF(status)) { |
| return 0; |
| } else { |
| return -1; /* Signal generic error */ |
| } |
| } |
| |
| static int bio_bucket_create(BIO *bio) |
| { |
| #ifndef USE_LEGACY_OPENSSL |
| BIO_set_shutdown(bio, 1); |
| BIO_set_init(bio, 1); |
| BIO_set_data(bio, NULL); |
| #else |
| bio->shutdown = 1; |
| bio->init = 1; |
| bio->num = -1; |
| bio->ptr = NULL; |
| #endif |
| |
| return 1; |
| } |
| |
| static int bio_bucket_destroy(BIO *bio) |
| { |
| /* Did we already free this? */ |
| if (bio == NULL) { |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static long bio_bucket_ctrl(BIO *bio, int cmd, long num, void *ptr) |
| { |
| long ret = 1; |
| |
| switch (cmd) { |
| default: |
| /* abort(); */ |
| break; |
| case BIO_CTRL_FLUSH: |
| /* At this point we can't force a flush. */ |
| break; |
| case BIO_CTRL_PUSH: |
| case BIO_CTRL_POP: |
| ret = 0; |
| break; |
| } |
| return ret; |
| } |
| |
| #ifdef USE_LEGACY_OPENSSL |
| static BIO_METHOD bio_bucket_method = { |
| BIO_TYPE_MEM, |
| "Serf SSL encryption and decryption buckets", |
| bio_bucket_write, |
| bio_bucket_read, |
| NULL, /* Is this called? */ |
| NULL, /* Is this called? */ |
| bio_bucket_ctrl, |
| bio_bucket_create, |
| bio_bucket_destroy, |
| #ifdef OPENSSL_VERSION_NUMBER |
| NULL /* sslc does not have the callback_ctrl field */ |
| #endif |
| }; |
| |
| static BIO_METHOD bio_file_method = { |
| BIO_TYPE_FILE, |
| "Wrapper around APR file structures", |
| bio_file_write, |
| bio_file_read, |
| NULL, /* Is this called? */ |
| bio_file_gets, /* Is this called? */ |
| bio_bucket_ctrl, |
| bio_bucket_create, |
| bio_bucket_destroy, |
| #ifdef OPENSSL_VERSION_NUMBER |
| NULL /* sslc does not have the callback_ctrl field */ |
| #endif |
| }; |
| #endif |
| |
| static BIO_METHOD *bio_meth_bucket_new(void) |
| { |
| BIO_METHOD *biom = NULL; |
| |
| #ifndef USE_LEGACY_OPENSSL |
| biom = BIO_meth_new(BIO_TYPE_MEM, |
| "Serf SSL encryption and decryption buckets"); |
| if (biom) { |
| BIO_meth_set_write(biom, bio_bucket_write); |
| BIO_meth_set_read(biom, bio_bucket_read); |
| BIO_meth_set_ctrl(biom, bio_bucket_ctrl); |
| BIO_meth_set_create(biom, bio_bucket_create); |
| BIO_meth_set_destroy(biom, bio_bucket_destroy); |
| } |
| #else |
| biom = &bio_bucket_method; |
| #endif |
| |
| return biom; |
| } |
| |
| static BIO_METHOD *bio_meth_file_new(void) |
| { |
| BIO_METHOD *biom = NULL; |
| |
| #ifndef USE_LEGACY_OPENSSL |
| biom = BIO_meth_new(BIO_TYPE_FILE, "Wrapper around APR file structures"); |
| if (biom) { |
| BIO_meth_set_write(biom, bio_file_write); |
| BIO_meth_set_read(biom, bio_file_read); |
| BIO_meth_set_gets(biom, bio_file_gets); |
| BIO_meth_set_ctrl(biom, bio_bucket_ctrl); |
| BIO_meth_set_create(biom, bio_bucket_create); |
| BIO_meth_set_destroy(biom, bio_bucket_destroy); |
| } |
| #else |
| biom = &bio_file_method; |
| #endif |
| |
| return biom; |
| } |
| |
| static void bio_meth_free(BIO_METHOD *biom) |
| { |
| #ifndef USE_LEGACY_OPENSSL |
| BIO_meth_free(biom); |
| #endif |
| } |
| |
| #ifndef OPENSSL_NO_TLSEXT |
| /* Callback called when the server response has some OCSP info. |
| Returns 1 if the application accepts the OCSP response as successful, |
| 0 in case of error. |
| */ |
| static int ocsp_callback(SSL *ssl, void *baton) |
| { |
| serf_ssl_context_t *ctx = (serf_ssl_context_t*)baton; |
| OCSP_RESPONSE *response; |
| const unsigned char *resp_der; |
| int len; |
| long resp_status; |
| int failures = 0; |
| int cert_valid = 0; |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_SSL, __FILE__, ctx->config, |
| "OCSP callback called.\n"); |
| len = SSL_get_tlsext_status_ocsp_resp(ssl, &resp_der); |
| |
| if (!resp_der) { |
| /* TODO: hard fail vs soft fail */ |
| /* No response sent */ |
| return SSL_TLSEXT_ERR_ALERT_FATAL; |
| } |
| response = d2i_OCSP_RESPONSE(NULL, &resp_der, len); |
| if (!response) { |
| /* Error parsing OCSP response - tell the app? */ |
| return SSL_TLSEXT_ERR_ALERT_FATAL; |
| } |
| |
| /* Did the server get a valid response from the OCSP responder */ |
| resp_status = OCSP_response_status(response); |
| switch (resp_status) { |
| case OCSP_RESPONSE_STATUS_SUCCESSFUL: |
| break; |
| case OCSP_RESPONSE_STATUS_MALFORMEDREQUEST: |
| case OCSP_RESPONSE_STATUS_INTERNALERROR: |
| case OCSP_RESPONSE_STATUS_SIGREQUIRED: |
| case OCSP_RESPONSE_STATUS_UNAUTHORIZED: |
| failures |= SERF_SSL_OCSP_RESPONDER_ERROR; |
| break; |
| case OCSP_RESPONSE_STATUS_TRYLATER: |
| failures |= SERF_SSL_OCSP_RESPONDER_TRYLATER; |
| break; |
| default: |
| failures |= SERF_SSL_OCSP_RESPONDER_UNKNOWN_FAILURE; |
| break; |
| } |
| |
| /* TODO: check certificate status */ |
| |
| OCSP_RESPONSE_free(response); |
| |
| if (ctx->server_cert_callback && failures) { |
| apr_status_t status; |
| |
| /* TODO: try to find which certificate this is about. */ |
| |
| /* Callback for further verification. */ |
| status = ctx->server_cert_callback(ctx->server_cert_userdata, |
| failures, NULL); |
| if (status == APR_SUCCESS) |
| cert_valid = 1; |
| else { |
| /* The application is not happy with the OCSP response status. */ |
| cert_valid = 0; |
| |
| /* Pass the error back to the caller through the context-run. */ |
| ctx->pending_err = status; |
| } |
| } |
| |
| /* If OCSP stapling was enabled, an error was reported but no callback set, |
| fail with an error. */ |
| if (!cert_valid && |
| !ctx->server_cert_chain_callback && |
| !ctx->server_cert_callback) |
| { |
| ctx->pending_err = SERF_ERROR_SSL_CERT_FAILED; |
| } |
| |
| return cert_valid; |
| } |
| #endif |
| |
| typedef enum san_copy_t { |
| EscapeNulAndCopy = 0, |
| ErrorOnNul = 1, |
| } san_copy_t; |
| |
| |
| /* get_subject_alt_names can run in two modes: |
| COPY_ACTION = ErrorOnNul: return an error status if the san's (if any) contain |
| \0 chars. In this mode, SAN_ARR and POOL aren't used and can be NULL. |
| COPY_ACTION = EscapeNulAndCopy: copy the san's to the SAN_ARR array. Any \0 |
| chars are escaped as '\00', the memory is allocated in pool POOL. |
| */ |
| static apr_status_t |
| get_subject_alt_names(apr_array_header_t **san_arr, X509 *ssl_cert, |
| san_copy_t copy_action, apr_pool_t *pool) |
| { |
| STACK_OF(GENERAL_NAME) *names; |
| |
| /* assert: copy_action == ErrorOnNul || (san_arr && pool) */ |
| |
| if (san_arr) { |
| *san_arr = NULL; |
| } |
| |
| /* Get subjectAltNames */ |
| names = X509_get_ext_d2i(ssl_cert, NID_subject_alt_name, NULL, NULL); |
| if (names) { |
| int names_count = sk_GENERAL_NAME_num(names); |
| int name_idx; |
| |
| if (san_arr) |
| *san_arr = apr_array_make(pool, names_count, sizeof(char*)); |
| for (name_idx = 0; name_idx < names_count; name_idx++) { |
| char *p = NULL; |
| GENERAL_NAME *nm = sk_GENERAL_NAME_value(names, name_idx); |
| |
| switch (nm->type) { |
| case GEN_DNS: |
| if (copy_action == ErrorOnNul && |
| strlen((const char *)nm->d.ia5->data) != nm->d.ia5->length) |
| return SERF_ERROR_SSL_CERT_FAILED; |
| if (san_arr && *san_arr) |
| p = pstrdup_escape_nul_bytes((const char *)nm->d.ia5->data, |
| nm->d.ia5->length, |
| pool); |
| break; |
| default: |
| /* Don't know what to do - skip. */ |
| break; |
| } |
| |
| if (p) { |
| APR_ARRAY_PUSH(*san_arr, char*) = p; |
| } |
| } |
| sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t validate_cert_hostname(X509 *server_cert, apr_pool_t *pool) |
| { |
| char buf[1024]; |
| int length; |
| apr_status_t ret; |
| |
| ret = get_subject_alt_names(NULL, server_cert, ErrorOnNul, NULL); |
| if (ret) { |
| return ret; |
| } else { |
| /* Fail if the subject's CN field contains \0 characters. */ |
| X509_NAME *subject = X509_get_subject_name(server_cert); |
| if (!subject) |
| return SERF_ERROR_SSL_CERT_FAILED; |
| |
| length = X509_NAME_get_text_by_NID(subject, NID_commonName, buf, 1024); |
| if (length != -1) |
| if (strlen(buf) != length) |
| return SERF_ERROR_SSL_CERT_FAILED; |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| static int |
| validate_server_certificate(int cert_valid, X509_STORE_CTX *store_ctx) |
| { |
| SSL *ssl; |
| serf_ssl_context_t *ctx; |
| X509 *server_cert; |
| int depth; |
| int failures = 0; |
| apr_status_t status; |
| |
| ssl = X509_STORE_CTX_get_ex_data(store_ctx, |
| SSL_get_ex_data_X509_STORE_CTX_idx()); |
| ctx = SSL_get_app_data(ssl); |
| |
| server_cert = X509_STORE_CTX_get_current_cert(store_ctx); |
| depth = X509_STORE_CTX_get_error_depth(store_ctx); |
| |
| /* If the certification was found invalid, get the error and convert it to |
| something our caller will understand. */ |
| if (! cert_valid) { |
| int err = X509_STORE_CTX_get_error(store_ctx); |
| |
| switch(err) { |
| case X509_V_ERR_CERT_NOT_YET_VALID: |
| failures |= SERF_SSL_CERT_NOTYETVALID; |
| break; |
| case X509_V_ERR_CERT_HAS_EXPIRED: |
| failures |= SERF_SSL_CERT_EXPIRED; |
| break; |
| case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: |
| case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: |
| failures |= SERF_SSL_CERT_SELF_SIGNED; |
| break; |
| case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: |
| case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: |
| case X509_V_ERR_CERT_UNTRUSTED: |
| case X509_V_ERR_INVALID_CA: |
| case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: |
| failures |= SERF_SSL_CERT_UNKNOWNCA; |
| break; |
| case X509_V_ERR_CERT_REVOKED: |
| failures |= SERF_SSL_CERT_REVOKED; |
| break; |
| case X509_V_ERR_UNABLE_TO_GET_CRL: |
| failures |= SERF_SSL_CERT_UNABLE_TO_GET_CRL; |
| break; |
| default: |
| serf__log(LOGLVL_WARNING, LOGCOMP_SSL, __FILE__, |
| ctx->config, |
| "validate_server_certificate, unknown cert " |
| "failure %d at depth %d.\n", err, depth); |
| failures |= SERF_SSL_CERT_UNKNOWN_FAILURE; |
| break; |
| } |
| } |
| |
| /* Validate hostname */ |
| status = validate_cert_hostname(server_cert, ctx->pool); |
| if (status) |
| failures |= SERF_SSL_CERT_INVALID_HOST; |
| |
| /* Check certificate expiry dates. */ |
| if (X509_cmp_current_time(X509_get_notBefore(server_cert)) >= 0) { |
| failures |= SERF_SSL_CERT_NOTYETVALID; |
| } |
| else if (X509_cmp_current_time(X509_get_notAfter(server_cert)) <= 0) { |
| failures |= SERF_SSL_CERT_EXPIRED; |
| } |
| |
| if (ctx->server_cert_callback && |
| (depth == 0 || failures)) { |
| serf_ssl_certificate_t *cert; |
| apr_pool_t *subpool; |
| |
| apr_pool_create(&subpool, ctx->pool); |
| |
| cert = apr_palloc(subpool, sizeof(serf_ssl_certificate_t)); |
| cert->ssl_cert = server_cert; |
| cert->depth = depth; |
| |
| /* Callback for further verification. */ |
| status = ctx->server_cert_callback(ctx->server_cert_userdata, |
| failures, cert); |
| 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; |
| } |
| apr_pool_destroy(subpool); |
| } |
| |
| if (ctx->server_cert_chain_callback |
| && (depth == 0 || failures)) { |
| STACK_OF(X509) *chain; |
| const serf_ssl_certificate_t **certs; |
| int certs_len; |
| apr_pool_t *subpool; |
| |
| apr_pool_create(&subpool, ctx->pool); |
| |
| /* Borrow the chain to pass to the callback. */ |
| chain = X509_STORE_CTX_get_chain(store_ctx); |
| |
| /* If the chain can't be retrieved, just pass the current |
| certificate. */ |
| /* ### can this actually happen with _get_chain() ? */ |
| if (!chain) { |
| serf_ssl_certificate_t *cert = apr_palloc(subpool, sizeof(*cert)); |
| |
| cert->ssl_cert = server_cert; |
| cert->depth = depth; |
| |
| /* Room for the server_cert and a trailing NULL. */ |
| certs = apr_palloc(subpool, sizeof(*certs) * 2); |
| certs[0] = cert; |
| |
| certs_len = 1; |
| } else { |
| int i; |
| |
| certs_len = sk_X509_num(chain); |
| |
| /* Room for all the certs and a trailing NULL. */ |
| certs = apr_palloc(subpool, sizeof(*certs) * (certs_len + 1)); |
| for (i = 0; i < certs_len; ++i) { |
| serf_ssl_certificate_t *cert; |
| |
| cert = apr_palloc(subpool, sizeof(*cert)); |
| cert->ssl_cert = sk_X509_value(chain, i); |
| cert->depth = i; |
| |
| certs[i] = cert; |
| } |
| } |
| certs[certs_len] = NULL; |
| |
| /* Callback for further verification. */ |
| status = ctx->server_cert_chain_callback(ctx->server_cert_userdata, |
| failures, depth, |
| certs, certs_len); |
| 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; |
| } |
| |
| apr_pool_destroy(subpool); |
| } |
| |
| /* Return a specific error if the server certificate is not accepted by |
| OpenSSL and the application has not set callbacks to override this. */ |
| if (!cert_valid && |
| !ctx->server_cert_chain_callback && |
| !ctx->server_cert_callback) |
| { |
| ctx->pending_err = SERF_ERROR_SSL_CERT_FAILED; |
| } |
| |
| return cert_valid; |
| } |
| |
| /* Helper function to convert the ssl error code contained in ret_code + the |
| ssl context to a proper serf status code. |
| |
| If DO_WANT_READ is true, handle the SSL_ERROR_WANT_* as SERF_ERROR_WAIT_CONN |
| and set ctx->want_read. |
| */ |
| static apr_status_t status_from_ssl_error(serf_ssl_context_t *ctx, |
| int ret_code, |
| int do_want_read) |
| { |
| int ssl_err = SSL_get_error(ctx->ssl, ret_code); |
| apr_status_t status; |
| |
| switch (ssl_err) { |
| case SSL_ERROR_SYSCALL: |
| /* One of the bio functions returned a failure by returning -1. |
| Return the underlying status that caused OpenSSL to fail. |
| |
| There is no ssl status to log here, as the only reason |
| the call failed is that our data delivery function didn't |
| deliver data. And even that is already logged by the info |
| callback if you turn up the logging level high enough. */ |
| status = ctx->crypt_status; |
| break; |
| |
| case SSL_ERROR_WANT_READ: |
| if (do_want_read) |
| ctx->want_read = TRUE; |
| /* Fall through */ |
| |
| case SSL_ERROR_WANT_WRITE: |
| status = do_want_read ? SERF_ERROR_WAIT_CONN : APR_EAGAIN; |
| break; |
| |
| case SSL_ERROR_SSL: |
| if (ctx->pending_err) { |
| status = ctx->pending_err; |
| ctx->pending_err = APR_SUCCESS; |
| } else { |
| /*unsigned long l = ERR_peek_error(); |
| int lib = ERR_GET_LIB(l); |
| int func = ERR_GET_FUNC(l); |
| int reason = ERR_GET_REASON(l);*/ |
| |
| /* ### Detect more specific errors? |
| When lib is ERR_LIB_SSL, then reason is one of the |
| many SSL_R_XXXX reasons in ssl.h |
| */ |
| |
| if (SSL_in_init(ctx->ssl)) |
| ctx->fatal_err = SERF_ERROR_SSL_SETUP_FAILED; |
| else |
| ctx->fatal_err = SERF_ERROR_SSL_COMM_FAILED; |
| |
| status = ctx->fatal_err; |
| log_ssl_error(ctx); |
| } |
| break; |
| default: |
| status = ctx->fatal_err = SERF_ERROR_SSL_COMM_FAILED; |
| log_ssl_error(ctx); |
| break; |
| } |
| |
| return status; |
| } |
| |
| /* Explicitly perform the SSL handshake without waiting for the first |
| write */ |
| static apr_status_t ssl_handshake(serf_ssl_context_t *ctx, |
| int do_want_read) |
| { |
| int ssl_result; |
| const unsigned char *data; |
| apr_size_t len; |
| |
| /* If we have a cached session, use it to allow speeding up the handshake */ |
| if (ctx->config |
| && !serf__config_store_get_ssl_session(ctx->config, &data, &len)) { |
| |
| SSL_SESSION *sess; |
| |
| sess = d2i_SSL_SESSION(NULL, &data, (long)len); |
| |
| SSL_set_session(ctx->ssl, sess); |
| } |
| |
| ctx->crypt_status = APR_SUCCESS; /* Clear before calling SSL */ |
| ssl_result = SSL_do_handshake(ctx->ssl); |
| if (ssl_result <= 0) { |
| apr_status_t status = status_from_ssl_error(ctx, ssl_result, |
| do_want_read); |
| |
| if (SERF_BUCKET_READ_ERROR(status)) { |
| return status; |
| } |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| /* This function reads an encrypted stream and returns the decrypted stream. |
| Implements serf_databuf_reader_t */ |
| static apr_status_t ssl_decrypt(void *baton, apr_size_t bufsize, |
| char *buf, apr_size_t *len) |
| { |
| serf_ssl_context_t *ctx = baton; |
| apr_status_t status; |
| int ssl_len; |
| |
| if (ctx->fatal_err) |
| return ctx->fatal_err; |
| |
| if (!ctx->handshake_done) { |
| |
| ctx->handshake_done = TRUE; |
| |
| status = ssl_handshake(ctx, FALSE); |
| |
| if (SERF_BUCKET_READ_ERROR(status)) { |
| *len = 0; |
| return status; |
| } |
| } |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_SSL, __FILE__, ctx->config, |
| "ssl_decrypt: begin %" APR_SIZE_T_FMT "\n", bufsize); |
| |
| ctx->want_read = FALSE; /* Reading now */ |
| ctx->crypt_status = APR_SUCCESS; /* Clear before calling SSL */ |
| |
| /* When an SSL_read() operation has to be repeated because of |
| SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, it must be repeated |
| with the same arguments. |
| |
| Luckily we can assume that we are called from the databuffer |
| implementation */ |
| /* Is there some data waiting to be read? */ |
| ssl_len = SSL_read(ctx->ssl, buf, bufsize); |
| if (ssl_len < 0) { |
| |
| *len = 0; |
| status = status_from_ssl_error(ctx, ssl_len, FALSE); |
| } 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(LOGLVL_ERROR, LOGCOMP_SSL, __FILE__, ctx->config, |
| "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; |
| log_ssl_error(ctx); |
| } |
| } else { |
| *len = ssl_len; |
| status = ctx->crypt_status; |
| serf__log(LOGLVL_DEBUG, LOGCOMP_SSLMSG, __FILE__, ctx->config, |
| "---\n%.*s\n-(%"APR_SIZE_T_FMT")-\n", (int)*len, buf, *len); |
| } |
| |
| |
| if (!ctx->handshake_finished |
| && !SERF_BUCKET_READ_ERROR(status)) { |
| |
| apr_status_t s = APR_SUCCESS; |
| |
| /* Once we got through the initial handshake, we should have received |
| the ALPN information if there is such information. */ |
| ctx->handshake_finished = SSL_is_init_finished(ctx->ssl) |
| #ifndef USE_LEGACY_OPENSSL |
| || (SSL_get_state(ctx->ssl) == TLS_ST_OK); |
| #else |
| || (SSL_state(ctx->ssl) |
| & SSL_CB_HANDSHAKE_DONE); |
| #endif |
| |
| /* Call the protocol callback as soon as possible as this triggers |
| pipelining data for the selected protocol. */ |
| if (ctx->protocol_callback) { |
| const char *protocol = ssl_get_selected_protocol(ctx); |
| |
| /* When ctx->init_finished is TRUE protocol will never be NULL, |
| reporting the final result if not already handled */ |
| if (protocol) { |
| s = ctx->protocol_callback(ctx->protocol_userdata, protocol); |
| ctx->protocol_callback = NULL; |
| } |
| } |
| |
| if (SERF_BUCKET_READ_ERROR(s)) { |
| serf__log(LOGLVL_ERROR, LOGCOMP_SSL, __FILE__, ctx->config, |
| "ssl_decrypt: negotiation reported: %d\n", status); |
| status = s; |
| } |
| } |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_SSL, __FILE__, ctx->config, |
| "ssl_decrypt: %d %"APR_SIZE_T_FMT"\n", status, *len); |
| |
| return status; |
| } |
| |
| /* This function reads a decrypted stream and returns an encrypted stream. |
| Implements serf_databuf_reader_t */ |
| static apr_status_t ssl_encrypt(void *baton, apr_size_t bufsize, |
| char *buf, apr_size_t *len) |
| { |
| const char *data; |
| apr_size_t interim_bufsize; |
| serf_ssl_context_t *ctx = baton; |
| apr_status_t status; |
| |
| if (ctx->fatal_err) |
| return ctx->fatal_err; |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_SSL, __FILE__, ctx->config, |
| "ssl_encrypt: begin %"APR_SIZE_T_FMT"\n", bufsize); |
| |
| if (!ctx->handshake_done) { |
| |
| ctx->handshake_done = TRUE; |
| |
| status = ssl_handshake(ctx, TRUE); |
| |
| if (SERF_BUCKET_READ_ERROR(status)) { |
| return status; |
| } |
| } |
| |
| /* Try to read already encrypted but unread data first. */ |
| status = serf_bucket_read(ctx->encrypt_pending, bufsize, &data, len); |
| if (SERF_BUCKET_READ_ERROR(status)) { |
| return status; |
| } |
| |
| /* Aha, we read something. Return that now. */ |
| if (*len) { |
| memcpy(buf, data, *len); |
| if (APR_STATUS_IS_EOF(status)) { |
| status = APR_SUCCESS; |
| } |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_SSL, __FILE__, ctx->config, |
| "ssl_encrypt: %d %"APR_SIZE_T_FMT" (quick read)\n", |
| status, *len); |
| |
| return status; |
| } |
| |
| /* Oh well, read from our stream now. */ |
| interim_bufsize = bufsize; |
| do { |
| apr_size_t interim_len; |
| |
| if (!ctx->want_read) { |
| struct iovec vecs[SERF__STD_IOV_COUNT]; |
| int vecs_read; |
| |
| status = serf_bucket_read_iovec(ctx->encrypt.stream, |
| interim_bufsize, |
| COUNT_OF(vecs), vecs, |
| &vecs_read); |
| |
| if (!SERF_BUCKET_READ_ERROR(status) && vecs_read) { |
| char *vecs_data; |
| int i, cur, vecs_data_len; |
| int ssl_len; |
| |
| /* Combine the buffers of the iovec into one buffer, as |
| that is with SSL_write requires. */ |
| vecs_data_len = 0; |
| for (i = 0; i < vecs_read; i++) { |
| vecs_data_len += vecs[i].iov_len; |
| } |
| |
| vecs_data = serf_bucket_mem_alloc(ctx->allocator, |
| vecs_data_len); |
| |
| cur = 0; |
| for (i = 0; i < vecs_read; i++) { |
| memcpy(vecs_data + cur, vecs[i].iov_base, vecs[i].iov_len); |
| cur += vecs[i].iov_len; |
| } |
| |
| interim_bufsize -= vecs_data_len; |
| interim_len = vecs_data_len; |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_SSL, __FILE__, ctx->config, |
| "ssl_encrypt: bucket read %"APR_SIZE_T_FMT" bytes; "\ |
| "status %d\n", interim_len, status); |
| |
| /* When an SSL_write() operation has to be repeated because of |
| SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, it MUST be |
| repeated with the same arguments. |
| |
| Unless SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER is set... |
| ... which we now do. |
| */ |
| ctx->crypt_status = APR_SUCCESS; /* Clear before calling SSL */ |
| ssl_len = SSL_write(ctx->ssl, vecs_data, interim_len); |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_SSL, __FILE__, ctx->config, |
| "ssl_encrypt: SSL write: %d\n", ssl_len); |
| |
| /* If we failed to write... */ |
| if (ssl_len <= 0) { |
| |
| /* Ah, bugger. We need to put that data back. |
| Note: use the copy here, we do not own the original iovec |
| data buffer so it will be freed on next read. */ |
| serf_bucket_t *vecs_copy = |
| serf_bucket_simple_own_create(vecs_data, |
| vecs_data_len, |
| ctx->allocator); |
| serf_bucket_aggregate_prepend(ctx->encrypt.stream, |
| vecs_copy); |
| |
| status = status_from_ssl_error(ctx, ssl_len, TRUE); |
| } else { |
| /* We're done with this data. */ |
| serf_bucket_mem_free(ctx->allocator, vecs_data); |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_SSL, __FILE__, ctx->config, |
| "---\n%.*s\n-(%"APR_SIZE_T_FMT")-\n", |
| (int)interim_len, vecs_data, interim_len); |
| |
| } |
| } |
| } |
| else { |
| interim_len = 0; |
| *len = 0; |
| status = ctx->crypt_status; |
| |
| if (!status) { |
| status = APR_EAGAIN; /* Exit loop */ |
| } |
| } |
| |
| } while (!status && interim_bufsize); |
| |
| /* Okay, we exhausted our underlying stream. */ |
| if (!SERF_BUCKET_READ_ERROR(status)) { |
| apr_status_t agg_status; |
| struct iovec vecs[SERF__STD_IOV_COUNT]; |
| int vecs_read, i; |
| |
| /* We read something! */ |
| agg_status = serf_bucket_read_iovec(ctx->encrypt_pending, bufsize, |
| COUNT_OF(vecs), vecs, &vecs_read); |
| *len = 0; |
| for (i = 0; i < vecs_read; i++) { |
| memcpy(buf + *len, vecs[i].iov_base, vecs[i].iov_len); |
| *len += vecs[i].iov_len; |
| } |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_SSL, __FILE__, ctx->config, |
| "ssl_encrypt read agg: %d %d %d %"APR_SIZE_T_FMT"\n", status, agg_status, |
| ctx->crypt_status, *len); |
| |
| if (!agg_status) { |
| status = APR_SUCCESS; |
| } |
| } |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_SSL, __FILE__, ctx->config, |
| "ssl_encrypt finished: %d %"APR_SIZE_T_FMT"\n", status, *len); |
| |
| return status; |
| } |
| |
| #if APR_HAS_THREADS && defined(USE_LEGACY_OPENSSL) |
| static apr_pool_t *ssl_pool; |
| static apr_thread_mutex_t **ssl_locks; |
| |
| typedef struct CRYPTO_dynlock_value { |
| apr_thread_mutex_t *lock; |
| } CRYPTO_dynlock_value; |
| |
| static CRYPTO_dynlock_value *ssl_dyn_create(const char* file, int line) |
| { |
| CRYPTO_dynlock_value *l; |
| apr_status_t rv; |
| |
| l = apr_palloc(ssl_pool, sizeof(CRYPTO_dynlock_value)); |
| rv = apr_thread_mutex_create(&l->lock, APR_THREAD_MUTEX_DEFAULT, ssl_pool); |
| if (rv != APR_SUCCESS) { |
| /* FIXME: return error here */ |
| } |
| return l; |
| } |
| |
| static void ssl_dyn_lock(int mode, CRYPTO_dynlock_value *l, const char *file, |
| int line) |
| { |
| if (mode & CRYPTO_LOCK) { |
| apr_thread_mutex_lock(l->lock); |
| } |
| else if (mode & CRYPTO_UNLOCK) { |
| apr_thread_mutex_unlock(l->lock); |
| } |
| } |
| |
| static void ssl_dyn_destroy(CRYPTO_dynlock_value *l, const char *file, |
| int line) |
| { |
| apr_thread_mutex_destroy(l->lock); |
| } |
| |
| static void ssl_lock(int mode, int n, const char *file, int line) |
| { |
| if (mode & CRYPTO_LOCK) { |
| apr_thread_mutex_lock(ssl_locks[n]); |
| } |
| else if (mode & CRYPTO_UNLOCK) { |
| apr_thread_mutex_unlock(ssl_locks[n]); |
| } |
| } |
| |
| static unsigned long ssl_id(void) |
| { |
| /* FIXME: This is lame and not portable. -aaron */ |
| return (unsigned long) apr_os_thread_current(); |
| } |
| |
| static apr_status_t cleanup_ssl(void *data) |
| { |
| CRYPTO_set_locking_callback(NULL); |
| CRYPTO_set_id_callback(NULL); |
| CRYPTO_set_dynlock_create_callback(NULL); |
| CRYPTO_set_dynlock_lock_callback(NULL); |
| CRYPTO_set_dynlock_destroy_callback(NULL); |
| |
| return APR_SUCCESS; |
| } |
| |
| #endif |
| |
| #if !APR_VERSION_AT_LEAST(1,0,0) |
| #define apr_atomic_cas32(mem, with, cmp) apr_atomic_cas(mem, with, cmp) |
| #endif |
| |
| enum ssl_init_e |
| { |
| INIT_UNINITIALIZED = 0, |
| INIT_BUSY = 1, |
| INIT_DONE = 2 |
| }; |
| |
| static volatile apr_uint32_t have_init_ssl = INIT_UNINITIALIZED; |
| |
| static void init_ssl_libraries(void) |
| { |
| apr_uint32_t val; |
| |
| val = apr_atomic_cas32(&have_init_ssl, INIT_BUSY, INIT_UNINITIALIZED); |
| |
| if (!val) { |
| #if APR_HAS_THREADS && defined(USE_LEGACY_OPENSSL) |
| int i, numlocks; |
| #endif |
| |
| #ifdef SERF_LOGGING_ENABLED |
| /* 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(LOGLVL_WARNING, LOGCOMP_SSL, __FILE__, NULL, |
| "Warning: OpenSSL library version mismatch, compile-" |
| "time was %lx, runtime is %lx.\n", |
| OPENSSL_VERSION_NUMBER, libver); |
| } |
| #endif |
| |
| #ifndef USE_LEGACY_OPENSSL |
| OPENSSL_malloc_init(); |
| #else |
| CRYPTO_malloc_init(); |
| #endif |
| ERR_load_crypto_strings(); |
| SSL_load_error_strings(); |
| SSL_library_init(); |
| OpenSSL_add_all_algorithms(); |
| |
| #if APR_HAS_THREADS && defined(USE_LEGACY_OPENSSL) |
| numlocks = CRYPTO_num_locks(); |
| apr_pool_create(&ssl_pool, NULL); |
| ssl_locks = apr_palloc(ssl_pool, sizeof(apr_thread_mutex_t*)*numlocks); |
| for (i = 0; i < numlocks; i++) { |
| apr_status_t rv; |
| |
| /* Intraprocess locks don't /need/ a filename... */ |
| rv = apr_thread_mutex_create(&ssl_locks[i], |
| APR_THREAD_MUTEX_DEFAULT, ssl_pool); |
| if (rv != APR_SUCCESS) { |
| /* FIXME: error out here */ |
| } |
| } |
| CRYPTO_set_locking_callback(ssl_lock); |
| CRYPTO_set_id_callback(ssl_id); |
| CRYPTO_set_dynlock_create_callback(ssl_dyn_create); |
| CRYPTO_set_dynlock_lock_callback(ssl_dyn_lock); |
| CRYPTO_set_dynlock_destroy_callback(ssl_dyn_destroy); |
| |
| apr_pool_cleanup_register(ssl_pool, NULL, cleanup_ssl, cleanup_ssl); |
| #endif |
| apr_atomic_cas32(&have_init_ssl, INIT_DONE, INIT_BUSY); |
| } |
| else |
| { |
| /* Make sure we don't continue before the initialization in another |
| thread has completed */ |
| while (val != INIT_DONE) { |
| apr_sleep(APR_USEC_PER_SEC / 1000); |
| |
| val = apr_atomic_cas32(&have_init_ssl, |
| INIT_UNINITIALIZED, |
| INIT_UNINITIALIZED); |
| } |
| } |
| } |
| |
| static int ssl_need_client_cert(SSL *ssl, X509 **cert, EVP_PKEY **pkey) |
| { |
| serf_ssl_context_t *ctx = SSL_get_app_data(ssl); |
| apr_status_t status; |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_SSL, __FILE__, ctx->config, |
| "Server requests a client certificate.\n"); |
| |
| if (ctx->cached_cert) { |
| *cert = ctx->cached_cert; |
| *pkey = ctx->cached_cert_pw; |
| return 1; |
| } |
| |
| while (ctx->cert_callback) { |
| const char *cert_path; |
| apr_file_t *cert_file; |
| BIO *bio; |
| BIO_METHOD *biom; |
| PKCS12 *p12; |
| int i; |
| int retrying_success = 0; |
| |
| if (ctx->cert_file_success) { |
| status = APR_SUCCESS; |
| cert_path = ctx->cert_file_success; |
| ctx->cert_file_success = NULL; |
| retrying_success = 1; |
| } else { |
| status = ctx->cert_callback(ctx->cert_userdata, &cert_path); |
| } |
| |
| if (status || !cert_path) { |
| break; |
| } |
| |
| /* Load the x.509 cert file stored in PKCS12 */ |
| status = apr_file_open(&cert_file, cert_path, APR_READ, APR_OS_DEFAULT, |
| ctx->pool); |
| |
| /* TODO: this will hang indefintely when the file can't be found. */ |
| if (status) { |
| continue; |
| } |
| |
| biom = bio_meth_file_new(); |
| bio = BIO_new(biom); |
| bio_set_data(bio, cert_file); |
| |
| ctx->cert_path = cert_path; |
| p12 = d2i_PKCS12_bio(bio, NULL); |
| BIO_free(bio); |
| apr_file_close(cert_file); |
| |
| i = PKCS12_parse(p12, NULL, pkey, cert, NULL); |
| |
| if (i == 1) { |
| PKCS12_free(p12); |
| bio_meth_free(biom); |
| ctx->cached_cert = *cert; |
| ctx->cached_cert_pw = *pkey; |
| if (!retrying_success && ctx->cert_cache_pool) { |
| const char *c; |
| |
| c = apr_pstrdup(ctx->cert_cache_pool, ctx->cert_path); |
| |
| apr_pool_userdata_setn(c, "serf:ssl:cert", |
| apr_pool_cleanup_null, |
| ctx->cert_cache_pool); |
| } |
| return 1; |
| } |
| else { |
| int err = ERR_get_error(); |
| ERR_clear_error(); |
| if (ERR_GET_LIB(err) == ERR_LIB_PKCS12 && |
| ERR_GET_REASON(err) == PKCS12_R_MAC_VERIFY_FAILURE) { |
| if (ctx->cert_pw_callback) { |
| const char *password; |
| |
| if (ctx->cert_pw_success) { |
| status = APR_SUCCESS; |
| password = ctx->cert_pw_success; |
| ctx->cert_pw_success = NULL; |
| } else { |
| status = ctx->cert_pw_callback(ctx->cert_pw_userdata, |
| ctx->cert_path, |
| &password); |
| } |
| |
| if (!status && password) { |
| i = PKCS12_parse(p12, password, pkey, cert, NULL); |
| if (i == 1) { |
| PKCS12_free(p12); |
| bio_meth_free(biom); |
| ctx->cached_cert = *cert; |
| ctx->cached_cert_pw = *pkey; |
| if (!retrying_success && ctx->cert_cache_pool) { |
| const char *c; |
| |
| c = apr_pstrdup(ctx->cert_cache_pool, |
| ctx->cert_path); |
| |
| apr_pool_userdata_setn(c, "serf:ssl:cert", |
| apr_pool_cleanup_null, |
| ctx->cert_cache_pool); |
| } |
| if (!retrying_success && ctx->cert_pw_cache_pool) { |
| const char *c; |
| |
| c = apr_pstrdup(ctx->cert_pw_cache_pool, |
| password); |
| |
| apr_pool_userdata_setn(c, "serf:ssl:certpw", |
| apr_pool_cleanup_null, |
| ctx->cert_pw_cache_pool); |
| } |
| return 1; |
| } |
| } |
| } |
| PKCS12_free(p12); |
| bio_meth_free(biom); |
| return 0; |
| } |
| else { |
| serf__log(LOGLVL_ERROR, LOGCOMP_SSL, __FILE__, ctx->config, |
| "OpenSSL cert error: %d %d %d\n", ERR_GET_LIB(err), |
| ERR_GET_FUNC(err), |
| ERR_GET_REASON(err)); |
| PKCS12_free(p12); |
| bio_meth_free(biom); |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| |
| void serf_ssl_client_cert_provider_set( |
| serf_ssl_context_t *context, |
| serf_ssl_need_client_cert_t callback, |
| void *data, |
| void *cache_pool) |
| { |
| context->cert_callback = callback; |
| context->cert_userdata = data; |
| context->cert_cache_pool = cache_pool; |
| if (context->cert_cache_pool) { |
| apr_pool_userdata_get((void**)&context->cert_file_success, |
| "serf:ssl:cert", cache_pool); |
| } |
| } |
| |
| |
| void serf_ssl_client_cert_password_set( |
| serf_ssl_context_t *context, |
| serf_ssl_need_cert_password_t callback, |
| void *data, |
| void *cache_pool) |
| { |
| context->cert_pw_callback = callback; |
| context->cert_pw_userdata = data; |
| context->cert_pw_cache_pool = cache_pool; |
| if (context->cert_pw_cache_pool) { |
| apr_pool_userdata_get((void**)&context->cert_pw_success, |
| "serf:ssl:certpw", cache_pool); |
| } |
| } |
| |
| |
| void serf_ssl_server_cert_callback_set( |
| serf_ssl_context_t *context, |
| serf_ssl_need_server_cert_t callback, |
| void *data) |
| { |
| context->server_cert_callback = callback; |
| context->server_cert_userdata = data; |
| } |
| |
| void serf_ssl_server_cert_chain_callback_set( |
| serf_ssl_context_t *context, |
| serf_ssl_need_server_cert_t cert_callback, |
| serf_ssl_server_cert_chain_cb_t cert_chain_callback, |
| void *data) |
| { |
| context->server_cert_callback = cert_callback; |
| context->server_cert_chain_callback = cert_chain_callback; |
| context->server_cert_userdata = data; |
| } |
| |
| static int ssl_new_session(SSL *ssl, SSL_SESSION *session) |
| { |
| serf_ssl_context_t *ctx = SSL_get_app_data(ssl); |
| void *mem; |
| unsigned char *der_data; |
| apr_size_t der_len; |
| |
| if (!ctx->config) |
| return 0; |
| |
| der_len = i2d_SSL_SESSION(session, NULL); |
| |
| mem = serf_bucket_mem_alloc(ctx->allocator, der_len); |
| der_data = mem; |
| if (der_len == i2d_SSL_SESSION(session, &der_data)) { |
| /* der_data was modified by i2d_SSL_SESSION(), so |
| we store the original pointer */ |
| (void)serf__config_store_set_ssl_session(ctx->config, |
| mem, der_len); |
| } |
| |
| serf_bucket_mem_free(ctx->allocator, mem); |
| |
| return 0; |
| } |
| |
| static serf_ssl_context_t *ssl_init_context(serf_bucket_alloc_t *allocator) |
| { |
| serf_ssl_context_t *ssl_ctx; |
| |
| init_ssl_libraries(); |
| |
| ssl_ctx = serf_bucket_mem_alloc(allocator, sizeof(*ssl_ctx)); |
| |
| ssl_ctx->refcount = 0; |
| ssl_ctx->pool = serf_bucket_allocator_get_pool(allocator); |
| ssl_ctx->allocator = allocator; |
| |
| /* Use the best possible protocol version, but disable the broken SSLv2/3 */ |
| ssl_ctx->ctx = SSL_CTX_new(SSLv23_client_method()); |
| SSL_CTX_set_options(ssl_ctx->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); |
| |
| /* Allow calling SSL_write() with different buffer pointers */ |
| SSL_CTX_set_mode(ssl_ctx->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); |
| |
| SSL_CTX_set_client_cert_cb(ssl_ctx->ctx, ssl_need_client_cert); |
| 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->renegotiation = 0; |
| ssl_ctx->config = NULL; |
| |
| ssl_ctx->cert_callback = NULL; |
| ssl_ctx->cert_pw_callback = NULL; |
| ssl_ctx->server_cert_callback = NULL; |
| ssl_ctx->server_cert_chain_callback = NULL; |
| |
| ssl_ctx->selected_protocol = ""; |
| ssl_ctx->handshake_finished = FALSE; |
| ssl_ctx->protocol_callback = NULL; |
| ssl_ctx->protocol_userdata = NULL; |
| |
| 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->biom = bio_meth_bucket_new(); |
| ssl_ctx->bio = BIO_new(ssl_ctx->biom); |
| bio_set_data(ssl_ctx->bio, ssl_ctx); |
| |
| SSL_set_bio(ssl_ctx->ssl, ssl_ctx->bio, ssl_ctx->bio); |
| |
| /* Enable SSL callback to store the SSL session state to allow |
| optimized resumption later. */ |
| SSL_CTX_sess_set_new_cb(ssl_ctx->ctx, ssl_new_session); |
| |
| SSL_set_connect_state(ssl_ctx->ssl); |
| |
| SSL_set_app_data(ssl_ctx->ssl, ssl_ctx); |
| |
| #ifdef SERF_LOGGING_ENABLED |
| 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); |
| serf_databuf_init(&ssl_ctx->encrypt.databuf); |
| ssl_ctx->encrypt.databuf.read = ssl_encrypt; |
| ssl_ctx->encrypt.databuf.read_baton = ssl_ctx; |
| |
| ssl_ctx->decrypt.stream = NULL; |
| serf_databuf_init(&ssl_ctx->decrypt.databuf); |
| ssl_ctx->decrypt.databuf.read = ssl_decrypt; |
| ssl_ctx->decrypt.databuf.read_baton = ssl_ctx; |
| |
| ssl_ctx->crypt_status = APR_SUCCESS; |
| ssl_ctx->want_read = FALSE; |
| ssl_ctx->handshake_done = FALSE; |
| |
| return ssl_ctx; |
| } |
| |
| static apr_status_t ssl_free_context( |
| serf_ssl_context_t *ssl_ctx) |
| { |
| /* If never had the pending buckets, don't try to free them. */ |
| if (ssl_ctx->encrypt_pending != NULL) { |
| serf_bucket_destroy(ssl_ctx->encrypt_pending); |
| } |
| |
| /* SSL_free implicitly frees the underlying BIO. */ |
| SSL_free(ssl_ctx->ssl); |
| SSL_CTX_free(ssl_ctx->ctx); |
| bio_meth_free(ssl_ctx->biom); |
| |
| serf_bucket_mem_free(ssl_ctx->allocator, ssl_ctx); |
| |
| return APR_SUCCESS; |
| } |
| |
| static serf_bucket_t * serf_bucket_ssl_create( |
| serf_ssl_context_t *ssl_ctx, |
| serf_bucket_alloc_t *allocator, |
| const serf_bucket_type_t *type) |
| { |
| ssl_context_t *ctx; |
| |
| ctx = serf_bucket_mem_alloc(allocator, sizeof(*ctx)); |
| if (!ssl_ctx) { |
| ctx->ssl_ctx = ssl_init_context(allocator); |
| } |
| else { |
| ctx->ssl_ctx = ssl_ctx; |
| } |
| ctx->ssl_ctx->refcount++; |
| |
| return serf_bucket_create(type, allocator, ctx); |
| } |
| |
| apr_status_t serf_ssl_set_hostname(serf_ssl_context_t *context, |
| const char * hostname) |
| { |
| #ifdef SSL_set_tlsext_host_name |
| if (SSL_set_tlsext_host_name(context->ssl, hostname) != 1) { |
| ERR_clear_error(); |
| } |
| return APR_SUCCESS; |
| #endif |
| return APR_ENOTIMPL; |
| } |
| |
| apr_status_t serf_ssl_negotiate_protocol(serf_ssl_context_t *context, |
| const char *protocols, |
| serf_ssl_protocol_result_cb_t callback, |
| void *callback_data) |
| { |
| apr_pool_t *subpool; |
| unsigned char *raw_header; |
| unsigned char *at; |
| const char *next; |
| apr_size_t raw_len = strlen(protocols)+1; |
| apr_size_t len; |
| |
| if (raw_len >= 65536) |
| return APR_EINVAL; |
| |
| /* The OpenSSL api wants the value in network format. |
| A length byte followed by the value for all items. */ |
| |
| apr_pool_create(&subpool, context->pool); |
| |
| at = raw_header = apr_palloc(subpool, raw_len); |
| |
| while ((next = strchr(protocols, ','))) { |
| len = (next - protocols); |
| |
| if (len > 255) { |
| apr_pool_destroy(subpool); |
| return APR_EINVAL; |
| } |
| |
| *at = (unsigned char)len; |
| at++; |
| memcpy(at, protocols, len); |
| at += len; |
| |
| protocols = next + 1; |
| } |
| |
| len = strlen(protocols); |
| if (len > 255) { |
| apr_pool_destroy(subpool); |
| return APR_EINVAL; |
| } |
| |
| *at = (unsigned char)len; |
| at++; |
| memcpy(at, protocols, len); |
| at += len; |
| |
| #if OPENSSL_VERSION_NUMBER >= 0x10002000L /* >= 1.0.2 */ |
| if (SSL_set_alpn_protos(context->ssl, raw_header, raw_len)) { |
| ERR_clear_error(); |
| } |
| apr_pool_destroy(subpool); |
| |
| context->protocol_callback = callback; |
| context->protocol_userdata = callback_data; |
| context->selected_protocol = NULL; |
| return APR_SUCCESS; |
| #else |
| apr_pool_destroy(subpool); |
| return APR_ENOTIMPL; |
| #endif |
| } |
| |
| /* Gets the protocol selected by the server via ALPN. Returns NULL if |
| * ALPN and "" if no selection is made. |
| * |
| * ### Should we make this public as serf_ssl_get_selected_protocol(), |
| * or is the callback the only relevant scenario? |
| */ |
| static const char *ssl_get_selected_protocol(serf_ssl_context_t *context) |
| { |
| if (! context->selected_protocol) { |
| #if OPENSSL_VERSION_NUMBER >= 0x10002000L /* >= 1.0.2 */ |
| const unsigned char *data = NULL; |
| unsigned len = 0; |
| |
| SSL_get0_alpn_selected(context->ssl, &data, &len); |
| |
| if (data && len) |
| context->selected_protocol = apr_pstrmemdup(context->pool, |
| (void*)data, len); |
| else if (context->handshake_finished) |
| context->selected_protocol = ""; |
| #endif |
| } |
| return context->selected_protocol; |
| } |
| |
| /* Pool cleanup function for certificates */ |
| static apr_status_t free_ssl_cert(void *data) |
| { |
| X509_free(data); |
| return APR_SUCCESS; |
| } |
| |
| apr_status_t serf_ssl_use_default_certificates(serf_ssl_context_t *ssl_ctx) |
| { |
| X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx->ctx); |
| |
| int result = X509_STORE_set_default_paths(store); |
| |
| return result ? APR_SUCCESS : SERF_ERROR_SSL_CERT_FAILED; |
| } |
| |
| apr_status_t serf_ssl_load_cert_file( |
| serf_ssl_certificate_t **cert, |
| const char *file_path, |
| apr_pool_t *pool) |
| { |
| apr_file_t *cert_file; |
| apr_status_t status; |
| BIO *bio; |
| BIO_METHOD *biom; |
| X509 *ssl_cert; |
| |
| /* We use an apr file instead of an stdio.h file to avoid usage problems |
| on Windows. See http://www.openssl.org/support/faq.html#prog2 */ |
| status = apr_file_open(&cert_file, file_path, APR_READ, APR_OS_DEFAULT, |
| pool); |
| |
| if (status) { |
| return status; |
| } |
| |
| init_ssl_libraries(); |
| |
| biom = bio_meth_file_new(); |
| bio = BIO_new(biom); |
| bio_set_data(bio, cert_file); |
| |
| ssl_cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); |
| |
| apr_file_close(cert_file); |
| BIO_free(bio); |
| bio_meth_free(biom); |
| |
| if (ssl_cert) { |
| *cert = apr_palloc(pool, sizeof(serf_ssl_certificate_t)); |
| (*cert)->ssl_cert = ssl_cert; |
| |
| apr_pool_cleanup_register(pool, ssl_cert, free_ssl_cert, |
| apr_pool_cleanup_null); |
| |
| return APR_SUCCESS; |
| } |
| #if 0 |
| else { |
| /* If we'd have had a serf context *, we could have used serf logging */ |
| ERR_print_errors_fp(stderr); |
| } |
| #endif |
| |
| return SERF_ERROR_SSL_CERT_FAILED; |
| } |
| |
| |
| apr_status_t serf_ssl_trust_cert( |
| serf_ssl_context_t *ssl_ctx, |
| serf_ssl_certificate_t *cert) |
| { |
| X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx->ctx); |
| |
| int result = X509_STORE_add_cert(store, cert->ssl_cert); |
| |
| return result ? APR_SUCCESS : SERF_ERROR_SSL_CERT_FAILED; |
| } |
| |
| apr_status_t serf_ssl_check_crl(serf_ssl_context_t *ssl_ctx, int enabled) |
| { |
| X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx->ctx); |
| |
| if (enabled) { |
| X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK| |
| X509_V_FLAG_CRL_CHECK_ALL); |
| } else { |
| X509_VERIFY_PARAM_clear_flags(X509_STORE_get0_param(store), X509_V_FLAG_CRL_CHECK| |
| X509_V_FLAG_CRL_CHECK_ALL); |
| } |
| return APR_SUCCESS; |
| } |
| |
| apr_status_t serf_ssl_add_crl_from_file(serf_ssl_context_t *ssl_ctx, |
| const char *file_path, |
| apr_pool_t *pool) |
| { |
| apr_file_t *crl_file; |
| X509_CRL *crl = NULL; |
| X509_STORE *store; |
| BIO *bio; |
| BIO_METHOD *biom; |
| int result; |
| apr_status_t status; |
| |
| status = apr_file_open(&crl_file, file_path, APR_READ, APR_OS_DEFAULT, |
| pool); |
| if (status) { |
| return status; |
| } |
| |
| biom = bio_meth_file_new(); |
| bio = BIO_new(biom); |
| bio_set_data(bio, crl_file); |
| |
| crl = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL); |
| |
| apr_file_close(crl_file); |
| BIO_free(bio); |
| bio_meth_free(biom); |
| |
| store = SSL_CTX_get_cert_store(ssl_ctx->ctx); |
| |
| result = X509_STORE_add_crl(store, crl); |
| if (!result) { |
| log_ssl_error(ssl_ctx); |
| return SERF_ERROR_SSL_CERT_FAILED; |
| } |
| |
| /* TODO: free crl when closing ssl session */ |
| return serf_ssl_check_crl(ssl_ctx, 1); |
| } |
| |
| apr_status_t |
| serf_ssl_check_cert_status_request(serf_ssl_context_t *ssl_ctx, int enabled) |
| { |
| |
| #ifndef OPENSSL_NO_TLSEXT |
| SSL_CTX_set_tlsext_status_cb(ssl_ctx->ctx, ocsp_callback); |
| SSL_CTX_set_tlsext_status_arg(ssl_ctx->ctx, ssl_ctx); |
| SSL_set_tlsext_status_type(ssl_ctx->ssl, TLSEXT_STATUSTYPE_ocsp); |
| return APR_SUCCESS; |
| #endif |
| return APR_ENOTIMPL; |
| } |
| |
| serf_bucket_t *serf_bucket_ssl_decrypt_create( |
| serf_bucket_t *stream, |
| serf_ssl_context_t *ssl_ctx, |
| serf_bucket_alloc_t *allocator) |
| { |
| serf_bucket_t *bkt; |
| ssl_context_t *ctx; |
| |
| bkt = serf_bucket_ssl_create(ssl_ctx, allocator, |
| &serf_bucket_type_ssl_decrypt); |
| |
| ctx = bkt->data; |
| |
| ctx->databuf = &ctx->ssl_ctx->decrypt.databuf; |
| if (ctx->ssl_ctx->decrypt.stream != NULL) { |
| return NULL; |
| } |
| ctx->ssl_ctx->decrypt.stream = stream; |
| ctx->our_stream = &ctx->ssl_ctx->decrypt.stream; |
| |
| return bkt; |
| } |
| |
| |
| serf_ssl_context_t *serf_bucket_ssl_decrypt_context_get( |
| serf_bucket_t *bucket) |
| { |
| ssl_context_t *ctx = bucket->data; |
| return ctx->ssl_ctx; |
| } |
| |
| |
| serf_bucket_t *serf_bucket_ssl_encrypt_create( |
| serf_bucket_t *stream, |
| serf_ssl_context_t *ssl_ctx, |
| serf_bucket_alloc_t *allocator) |
| { |
| serf_bucket_t *bkt; |
| ssl_context_t *ctx; |
| |
| bkt = serf_bucket_ssl_create(ssl_ctx, allocator, |
| &serf_bucket_type_ssl_encrypt); |
| |
| ctx = bkt->data; |
| |
| ctx->databuf = &ctx->ssl_ctx->encrypt.databuf; |
| ctx->our_stream = &ctx->ssl_ctx->encrypt.stream; |
| if (ctx->ssl_ctx->encrypt.stream == NULL) { |
| serf_bucket_t *tmp = serf_bucket_aggregate_create(stream->allocator); |
| serf_bucket_aggregate_append(tmp, stream); |
| ctx->ssl_ctx->encrypt.stream = tmp; |
| if (ctx->ssl_ctx->config) { |
| serf_bucket_set_config(ssl_ctx->encrypt.stream, |
| ctx->ssl_ctx->config); |
| } |
| } |
| else { |
| bucket_list_t *new_list; |
| |
| new_list = serf_bucket_mem_alloc(ctx->ssl_ctx->allocator, |
| sizeof(*new_list)); |
| new_list->bucket = stream; |
| new_list->next = NULL; |
| if (ctx->ssl_ctx->encrypt.stream_next == NULL) { |
| ctx->ssl_ctx->encrypt.stream_next = new_list; |
| } |
| else { |
| bucket_list_t *scan = ctx->ssl_ctx->encrypt.stream_next; |
| |
| while (scan->next != NULL) |
| scan = scan->next; |
| scan->next = new_list; |
| } |
| } |
| |
| return bkt; |
| } |
| |
| |
| serf_ssl_context_t *serf_bucket_ssl_encrypt_context_get( |
| serf_bucket_t *bucket) |
| { |
| ssl_context_t *ctx = bucket->data; |
| return ctx->ssl_ctx; |
| } |
| |
| /* Functions to read a serf_ssl_certificate structure. */ |
| |
| /* Takes a counted length string and escapes any NUL bytes so that |
| * it can be used as a C string. NUL bytes are escaped as 3 characters |
| * "\00" (that's a literal backslash). |
| * The returned string is allocated in POOL. |
| */ |
| static char * |
| pstrdup_escape_nul_bytes(const char *buf, int len, apr_pool_t *pool) |
| { |
| int i, nul_count = 0; |
| char *ret; |
| |
| /* First determine if there are any nul bytes in the string. */ |
| for (i = 0; i < len; i++) { |
| if (buf[i] == '\0') |
| nul_count++; |
| } |
| |
| if (nul_count == 0) { |
| /* There aren't so easy case to just copy the string */ |
| ret = apr_pstrdup(pool, buf); |
| } else { |
| /* There are so we have to replace nul bytes with escape codes |
| * Proper length is the length of the original string, plus |
| * 2 times the number of nulls (for two digit hex code for |
| * the value) + the trailing null. */ |
| char *pos; |
| ret = pos = apr_palloc(pool, len + 2 * nul_count + 1); |
| for (i = 0; i < len; i++) { |
| if (buf[i] != '\0') { |
| *(pos++) = buf[i]; |
| } else { |
| *(pos++) = '\\'; |
| *(pos++) = '0'; |
| *(pos++) = '0'; |
| } |
| } |
| *pos = '\0'; |
| } |
| |
| return ret; |
| } |
| |
| /* Creates a hash_table with keys (E, CN, OU, O, L, ST and C). Any NUL bytes in |
| these fields in the certificate will be escaped as \00. */ |
| static apr_hash_t * |
| convert_X509_NAME_to_table(X509_NAME *org, apr_pool_t *pool) |
| { |
| char buf[1024]; |
| int ret; |
| |
| apr_hash_t *tgt = apr_hash_make(pool); |
| |
| ret = X509_NAME_get_text_by_NID(org, |
| NID_commonName, |
| buf, 1024); |
| if (ret != -1) |
| apr_hash_set(tgt, "CN", APR_HASH_KEY_STRING, |
| pstrdup_escape_nul_bytes(buf, ret, pool)); |
| ret = X509_NAME_get_text_by_NID(org, |
| NID_pkcs9_emailAddress, |
| buf, 1024); |
| if (ret != -1) |
| apr_hash_set(tgt, "E", APR_HASH_KEY_STRING, |
| pstrdup_escape_nul_bytes(buf, ret, pool)); |
| ret = X509_NAME_get_text_by_NID(org, |
| NID_organizationalUnitName, |
| buf, 1024); |
| if (ret != -1) |
| apr_hash_set(tgt, "OU", APR_HASH_KEY_STRING, |
| pstrdup_escape_nul_bytes(buf, ret, pool)); |
| ret = X509_NAME_get_text_by_NID(org, |
| NID_organizationName, |
| buf, 1024); |
| if (ret != -1) |
| apr_hash_set(tgt, "O", APR_HASH_KEY_STRING, |
| pstrdup_escape_nul_bytes(buf, ret, pool)); |
| ret = X509_NAME_get_text_by_NID(org, |
| NID_localityName, |
| buf, 1024); |
| if (ret != -1) |
| apr_hash_set(tgt, "L", APR_HASH_KEY_STRING, |
| pstrdup_escape_nul_bytes(buf, ret, pool)); |
| ret = X509_NAME_get_text_by_NID(org, |
| NID_stateOrProvinceName, |
| buf, 1024); |
| if (ret != -1) |
| apr_hash_set(tgt, "ST", APR_HASH_KEY_STRING, |
| pstrdup_escape_nul_bytes(buf, ret, pool)); |
| ret = X509_NAME_get_text_by_NID(org, |
| NID_countryName, |
| buf, 1024); |
| if (ret != -1) |
| apr_hash_set(tgt, "C", APR_HASH_KEY_STRING, |
| pstrdup_escape_nul_bytes(buf, ret, pool)); |
| |
| return tgt; |
| } |
| |
| |
| int serf_ssl_cert_depth(const serf_ssl_certificate_t *cert) |
| { |
| return cert->depth; |
| } |
| |
| |
| apr_hash_t *serf_ssl_cert_issuer( |
| const serf_ssl_certificate_t *cert, |
| apr_pool_t *pool) |
| { |
| X509_NAME *issuer = X509_get_issuer_name(cert->ssl_cert); |
| |
| if (!issuer) |
| return NULL; |
| |
| return convert_X509_NAME_to_table(issuer, pool); |
| } |
| |
| |
| apr_hash_t *serf_ssl_cert_subject( |
| const serf_ssl_certificate_t *cert, |
| apr_pool_t *pool) |
| { |
| X509_NAME *subject = X509_get_subject_name(cert->ssl_cert); |
| |
| if (!subject) |
| return NULL; |
| |
| return convert_X509_NAME_to_table(subject, pool); |
| } |
| |
| |
| apr_hash_t *serf_ssl_cert_certificate( |
| const serf_ssl_certificate_t *cert, |
| apr_pool_t *pool) |
| { |
| apr_hash_t *tgt = apr_hash_make(pool); |
| unsigned int md_size; |
| unsigned char md[EVP_MAX_MD_SIZE]; |
| BIO *bio; |
| apr_array_header_t *san_arr; |
| |
| /* sha1 fingerprint */ |
| if (X509_digest(cert->ssl_cert, EVP_sha1(), md, &md_size)) { |
| unsigned int i; |
| const char hex[] = "0123456789ABCDEF"; |
| char fingerprint[EVP_MAX_MD_SIZE * 3]; |
| |
| for (i=0; i<md_size; i++) { |
| fingerprint[3*i] = hex[(md[i] & 0xf0) >> 4]; |
| fingerprint[(3*i)+1] = hex[(md[i] & 0x0f)]; |
| fingerprint[(3*i)+2] = ':'; |
| } |
| if (md_size > 0) |
| fingerprint[(3*(md_size-1))+2] = '\0'; |
| else |
| fingerprint[0] = '\0'; |
| |
| apr_hash_set(tgt, "sha1", APR_HASH_KEY_STRING, |
| apr_pstrdup(pool, fingerprint)); |
| } |
| |
| /* set expiry dates */ |
| bio = BIO_new(BIO_s_mem()); |
| if (bio) { |
| ASN1_TIME *notBefore, *notAfter; |
| char buf[256]; |
| |
| memset (buf, 0, sizeof (buf)); |
| notBefore = X509_get_notBefore(cert->ssl_cert); |
| if (ASN1_TIME_print(bio, notBefore)) { |
| BIO_read(bio, buf, 255); |
| apr_hash_set(tgt, "notBefore", APR_HASH_KEY_STRING, |
| apr_pstrdup(pool, buf)); |
| } |
| memset (buf, 0, sizeof (buf)); |
| notAfter = X509_get_notAfter(cert->ssl_cert); |
| if (ASN1_TIME_print(bio, notAfter)) { |
| BIO_read(bio, buf, 255); |
| apr_hash_set(tgt, "notAfter", APR_HASH_KEY_STRING, |
| apr_pstrdup(pool, buf)); |
| } |
| } |
| BIO_free(bio); |
| |
| /* Get subjectAltNames */ |
| if (!get_subject_alt_names(&san_arr, cert->ssl_cert, EscapeNulAndCopy, pool)) |
| apr_hash_set(tgt, "subjectAltName", APR_HASH_KEY_STRING, san_arr); |
| |
| return tgt; |
| } |
| |
| |
| const char *serf_ssl_cert_export( |
| const serf_ssl_certificate_t *cert, |
| apr_pool_t *pool) |
| { |
| char *binary_cert; |
| char *encoded_cert; |
| int len; |
| unsigned char *unused; |
| |
| /* find the length of the DER encoding. */ |
| len = i2d_X509(cert->ssl_cert, NULL); |
| if (len < 0) { |
| return NULL; |
| } |
| |
| binary_cert = apr_palloc(pool, len); |
| unused = (unsigned char *)binary_cert; |
| len = i2d_X509(cert->ssl_cert, &unused); /* unused is incremented */ |
| if (len < 0) { |
| return NULL; |
| } |
| |
| encoded_cert = apr_palloc(pool, apr_base64_encode_len(len)); |
| apr_base64_encode(encoded_cert, binary_cert, len); |
| |
| 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; |
| |
| if (!--ctx->ssl_ctx->refcount) { |
| ssl_free_context(ctx->ssl_ctx); |
| } |
| |
| serf_default_destroy_and_data(bucket); |
| } |
| |
| static void serf_ssl_decrypt_destroy_and_data(serf_bucket_t *bucket) |
| { |
| ssl_context_t *ctx = bucket->data; |
| |
| serf_bucket_destroy(*ctx->our_stream); |
| |
| serf_ssl_destroy_and_data(bucket); |
| } |
| |
| static void serf_ssl_encrypt_destroy_and_data(serf_bucket_t *bucket) |
| { |
| ssl_context_t *ctx = bucket->data; |
| serf_ssl_context_t *ssl_ctx = ctx->ssl_ctx; |
| |
| if (ssl_ctx->encrypt.stream == *ctx->our_stream) { |
| serf_bucket_destroy(*ctx->our_stream); |
| serf_bucket_destroy(ssl_ctx->encrypt_pending); |
| |
| /* Reset our status and databuf. */ |
| ssl_ctx->crypt_status = APR_SUCCESS; |
| ssl_ctx->encrypt.databuf.status = APR_SUCCESS; |
| |
| /* Advance to the next stream - if we have one. */ |
| if (ssl_ctx->encrypt.stream_next == NULL) { |
| ssl_ctx->encrypt.stream = NULL; |
| ssl_ctx->encrypt_pending = NULL; |
| } |
| else { |
| bucket_list_t *cur; |
| |
| cur = ssl_ctx->encrypt.stream_next; |
| ssl_ctx->encrypt.stream = cur->bucket; |
| ssl_ctx->encrypt_pending = |
| serf_bucket_aggregate_create(cur->bucket->allocator); |
| ssl_ctx->encrypt.stream_next = cur->next; |
| serf_bucket_mem_free(ssl_ctx->allocator, cur); |
| } |
| } |
| else { |
| /* Ah, darn. We haven't sent this one along yet. */ |
| return; |
| } |
| serf_ssl_destroy_and_data(bucket); |
| } |
| |
| static apr_status_t serf_ssl_read(serf_bucket_t *bucket, |
| apr_size_t requested, |
| const char **data, apr_size_t *len) |
| { |
| ssl_context_t *ctx = bucket->data; |
| |
| return serf_databuf_read(ctx->databuf, requested, data, len); |
| } |
| |
| static apr_status_t serf_ssl_readline(serf_bucket_t *bucket, |
| int acceptable, int *found, |
| const char **data, |
| apr_size_t *len) |
| { |
| ssl_context_t *ctx = bucket->data; |
| |
| return serf_databuf_readline(ctx->databuf, acceptable, found, data, len); |
| } |
| |
| static apr_status_t serf_ssl_peek(serf_bucket_t *bucket, |
| const char **data, |
| apr_size_t *len) |
| { |
| ssl_context_t *ctx = bucket->data; |
| |
| return serf_databuf_peek(ctx->databuf, data, len); |
| } |
| |
| static apr_status_t serf_ssl_set_config(serf_bucket_t *bucket, |
| serf_config_t *config) |
| { |
| ssl_context_t *ctx = bucket->data; |
| serf_ssl_context_t *ssl_ctx = ctx->ssl_ctx; |
| apr_status_t err_status = APR_SUCCESS; |
| const char *pipelining; |
| apr_status_t status; |
| |
| if (ssl_ctx->config == config) |
| return APR_SUCCESS; /* Don't loop */ |
| |
| ssl_ctx->config = config; |
| |
| /* Distribute the shared config as much as possible. */ |
| if (ssl_ctx) { |
| if (ssl_ctx->encrypt.stream) { |
| status = serf_bucket_set_config(ssl_ctx->encrypt.stream, config); |
| if (status) |
| err_status = status; |
| } |
| if (ssl_ctx->decrypt.stream) { |
| status = serf_bucket_set_config(ssl_ctx->decrypt.stream, config); |
| if (status) |
| err_status = status; |
| } |
| } |
| |
| if (config) { |
| status = serf_config_get_string(config, SERF_CONFIG_CONN_PIPELINING, |
| &pipelining); |
| if (status) |
| return status; |
| |
| if (strcmp(pipelining, "Y") == 0) { |
| SSL_CTX_set_info_callback(ssl_ctx->ctx, detect_renegotiate); |
| } |
| } |
| |
| return err_status; |
| } |
| |
| const serf_bucket_type_t serf_bucket_type_ssl_encrypt = { |
| "SSLENCRYPT", |
| serf_ssl_read, |
| serf_ssl_readline, |
| serf_default_read_iovec, |
| serf_default_read_for_sendfile, |
| serf_buckets_are_v2, |
| serf_ssl_peek, |
| serf_ssl_encrypt_destroy_and_data, |
| serf_default_read_bucket, |
| serf_default_get_remaining, |
| serf_ssl_set_config, |
| }; |
| |
| const serf_bucket_type_t serf_bucket_type_ssl_decrypt = { |
| "SSLDECRYPT", |
| serf_ssl_read, |
| serf_ssl_readline, |
| serf_default_read_iovec, |
| serf_default_read_for_sendfile, |
| serf_buckets_are_v2, |
| serf_ssl_peek, |
| serf_ssl_decrypt_destroy_and_data, |
| serf_default_read_bucket, |
| serf_default_get_remaining, |
| serf_ssl_set_config, |
| }; |