/* * stunnel TLS offloading and load-balancing proxy * Copyright (C) 1998-2017 Michal Trojnara * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see . * * Linking stunnel statically or dynamically with other modules is making * a combined work based on stunnel. Thus, the terms and conditions of * the GNU General Public License cover the whole combination. * * In addition, as a special exception, the copyright holder of stunnel * gives you permission to combine stunnel with free software programs or * libraries that are released under the GNU LGPL and with code included * in the standard release of OpenSSL under the OpenSSL License (or * modified versions of such code, with unchanged license). You may copy * and distribute such a system following the terms of the GNU GPL for * stunnel and the licenses of the other code concerned. * * Note that people who make modified versions of stunnel are not obligated * to grant this special exception for their modified versions; it is their * choice whether to do so. The GNU General Public License gives permission * to release a modified version without this exception; this exception * also makes it possible to release a modified version which carries * forward this exception. */ #include "common.h" #include "prototypes.h" /**************************************** prototypes */ /* verify initialization */ NOEXPORT void set_client_CA_list(SERVICE_OPTIONS *section); NOEXPORT void auth_warnings(SERVICE_OPTIONS *); NOEXPORT int crl_init(SERVICE_OPTIONS *section); NOEXPORT int load_file_lookup(X509_STORE *, char *); NOEXPORT int add_dir_lookup(X509_STORE *, char *); /* verify callback */ NOEXPORT int verify_callback(int, X509_STORE_CTX *); NOEXPORT int verify_checks(CLI *, int, X509_STORE_CTX *); NOEXPORT int cert_check(CLI *, X509_STORE_CTX *, int); #if OPENSSL_VERSION_NUMBER>=0x10002000L NOEXPORT int cert_check_subject(CLI *, X509_STORE_CTX *); #endif /* OPENSSL_VERSION_NUMBER>=0x10002000L */ NOEXPORT int cert_check_local(X509_STORE_CTX *); NOEXPORT int compare_pubkeys(X509 *, X509 *); #ifndef OPENSSL_NO_OCSP NOEXPORT int ocsp_check(CLI *, X509_STORE_CTX *); NOEXPORT int ocsp_request(CLI *, X509_STORE_CTX *, OCSP_CERTID *, char *); NOEXPORT OCSP_RESPONSE *ocsp_get_response(CLI *, OCSP_REQUEST *, char *); #endif /* utility functions */ #ifndef OPENSSL_NO_OCSP NOEXPORT X509 *get_current_issuer(X509_STORE_CTX *); NOEXPORT void log_time(const int, const char *, ASN1_TIME *); #endif /**************************************** verify initialization */ int verify_init(SERVICE_OPTIONS *section) { int verify_mode=0; /* CA initialization */ if(section->ca_file || section->ca_dir) { if(!SSL_CTX_load_verify_locations(section->ctx, section->ca_file, section->ca_dir)) { sslerror("SSL_CTX_load_verify_locations"); return 1; /* FAILED */ } } if(section->ca_file && !section->option.client) set_client_CA_list(section); /* only performed on the server */ /* CRL initialization */ if(section->crl_file || section->crl_dir) if(crl_init(section)) return 1; /* FAILED */ /* verify callback setup */ if(section->option.request_cert) { verify_mode|=SSL_VERIFY_PEER; if(section->option.require_cert && !section->redirect_addr.names) verify_mode|=SSL_VERIFY_FAIL_IF_NO_PEER_CERT; } SSL_CTX_set_verify(section->ctx, verify_mode, verify_callback); auth_warnings(section); return 0; /* OK */ } /* trusted CA names sent to clients for client cert selection */ NOEXPORT void set_client_CA_list(SERVICE_OPTIONS *section) { STACK_OF(X509_NAME) *ca_dn; s_log(LOG_DEBUG, "Client CA list: %s", section->ca_file); ca_dn=SSL_load_client_CA_file(section->ca_file); SSL_CTX_set_client_CA_list(section->ctx, ca_dn); print_client_CA_list(ca_dn); } NOEXPORT int crl_init(SERVICE_OPTIONS *section) { X509_STORE *store; store=SSL_CTX_get_cert_store(section->ctx); if(section->crl_file) { if(load_file_lookup(store, section->crl_file)) return 1; /* FAILED */ } if(section->crl_dir) { #if OPENSSL_VERSION_NUMBER<0x10100000L /* do not cache CRLs (only required with OpenSSL version < 1.0.0) */ store->cache=0; #endif if(add_dir_lookup(store, section->crl_dir)) return 1; /* FAILED */ } X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK); return 0; /* OK */ } NOEXPORT int load_file_lookup(X509_STORE *store, char *name) { X509_LOOKUP *lookup; lookup=X509_STORE_add_lookup(store, X509_LOOKUP_file()); if(!lookup) { sslerror("X509_STORE_add_lookup(X509_LOOKUP_file)"); return 1; /* FAILED */ } if(!X509_load_crl_file(lookup, name, X509_FILETYPE_PEM)) { s_log(LOG_ERR, "Failed to load %s revocation lookup file", name); sslerror("X509_load_crl_file"); return 1; /* FAILED */ } s_log(LOG_DEBUG, "Loaded %s revocation lookup file", name); return 0; /* OK */ } NOEXPORT int add_dir_lookup(X509_STORE *store, char *name) { X509_LOOKUP *lookup; lookup=X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir()); if(!lookup) { sslerror("X509_STORE_add_lookup(X509_LOOKUP_hash_dir)"); return 1; /* FAILED */ } if(!X509_LOOKUP_add_dir(lookup, name, X509_FILETYPE_PEM)) { s_log(LOG_ERR, "Failed to add %s revocation lookup directory", name); sslerror("X509_LOOKUP_add_dir"); return 1; /* FAILED */ } s_log(LOG_DEBUG, "Added %s revocation lookup directory", name); return 0; /* OK */ } /* issue warnings on insecure/missing authentication */ NOEXPORT void auth_warnings(SERVICE_OPTIONS *section) { #ifndef OPENSSL_NO_PSK if(section->psk_keys) return; #endif /* !defined(OPENSSL_NO_PSK) */ /* for servers it is usually okay to accept all client certificates signed by a specified certificate authority */ if(!section->option.client) return; if(section->option.verify_peer) /* verify_peer does not depend on PKI */ return; if(section->option.verify_chain) { #if OPENSSL_VERSION_NUMBER>=0x10002000L if(section->check_email || section->check_host || section->check_ip) return; #endif /* OPENSSL_VERSION_NUMBER>=0x10002000L */ s_log(LOG_WARNING, "Service [%s] uses \"verifyChain\" without subject checks", section->servname); #if OPENSSL_VERSION_NUMBER<0x10002000L s_log(LOG_WARNING, "Rebuild your stunnel against OpenSSL version 1.0.2 or higher"); #endif /* OPENSSL_VERSION_NUMBER<0x10002000L */ s_log(LOG_WARNING, "Use \"checkHost\" or \"checkIP\" to restrict trusted certificates"); return; } s_log(LOG_WARNING, "Service [%s] needs authentication to prevent MITM attacks", section->servname); } /**************************************** verify callback */ NOEXPORT int verify_callback(int preverify_ok, X509_STORE_CTX *callback_ctx) { /* our verify callback function */ SSL *ssl; CLI *c; /* retrieve application specific data */ ssl=X509_STORE_CTX_get_ex_data(callback_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); c=SSL_get_ex_data(ssl, index_ssl_cli); if(!c->opt->option.verify_chain && !c->opt->option.verify_peer) { s_log(LOG_INFO, "Certificate verification disabled"); return 1; /* accept */ } if(verify_checks(c, preverify_ok, callback_ctx)) { if(!SSL_SESSION_set_ex_data(SSL_get_session(ssl), index_session_authenticated, (void *)(-1))) { sslerror("SSL_SESSION_set_ex_data"); return 0; /* reject */ } return 1; /* accept */ } if(c->opt->option.client || c->opt->protocol) return 0; /* reject */ if(c->opt->redirect_addr.names) return 1; /* accept */ return 0; /* reject */ } NOEXPORT int verify_checks(CLI *c, int preverify_ok, X509_STORE_CTX *callback_ctx) { X509 *cert; int depth; char *subject; cert=X509_STORE_CTX_get_current_cert(callback_ctx); depth=X509_STORE_CTX_get_error_depth(callback_ctx); subject=X509_NAME2text(X509_get_subject_name(cert)); s_log(LOG_DEBUG, "Verification started at depth=%d: %s", depth, subject); if(!cert_check(c, callback_ctx, preverify_ok)) { s_log(LOG_WARNING, "Rejected by CERT at depth=%d: %s", depth, subject); str_free(subject); return 0; /* reject */ } #ifndef OPENSSL_NO_OCSP if((c->opt->ocsp_url || c->opt->option.aia) && !ocsp_check(c, callback_ctx)) { s_log(LOG_WARNING, "Rejected by OCSP at depth=%d: %s", depth, subject); str_free(subject); return 0; /* reject */ } #endif /* !defined(OPENSSL_NO_OCSP) */ s_log(depth ? LOG_INFO : LOG_NOTICE, "Certificate accepted at depth=%d: %s", depth, subject); str_free(subject); return 1; /* accept */ } /**************************************** certificate checking */ NOEXPORT int cert_check(CLI *c, X509_STORE_CTX *callback_ctx, int preverify_ok) { int err=X509_STORE_CTX_get_error(callback_ctx); int depth=X509_STORE_CTX_get_error_depth(callback_ctx); if(preverify_ok) { s_log(LOG_DEBUG, "CERT: Pre-verification succeeded"); } else { /* remote site sent an invalid certificate */ if(c->opt->option.verify_chain || (depth==0 && err!=X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY && err!=X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE && err!=X509_V_ERR_CERT_UNTRUSTED)) { s_log(LOG_WARNING, "CERT: Pre-verification error: %s", X509_verify_cert_error_string(err)); /* retain the STORE_CTX error produced by pre-verification */ return 0; /* reject */ } s_log(LOG_INFO, "CERT: Pre-verification error ignored: %s", X509_verify_cert_error_string(err)); } if(depth==0) { /* additional peer certificate checks */ #if OPENSSL_VERSION_NUMBER>=0x10002000L if(!cert_check_subject(c, callback_ctx)) return 0; /* reject */ #endif /* OPENSSL_VERSION_NUMBER>=0x10002000L */ if(c->opt->option.verify_peer && !cert_check_local(callback_ctx)) return 0; /* reject */ } return 1; /* accept */ } #if OPENSSL_VERSION_NUMBER>=0x10002000L NOEXPORT int cert_check_subject(CLI *c, X509_STORE_CTX *callback_ctx) { X509 *cert=X509_STORE_CTX_get_current_cert(callback_ctx); NAME_LIST *ptr; char *peername=NULL; if(c->opt->check_host) { for(ptr=c->opt->check_host; ptr; ptr=ptr->next) if(X509_check_host(cert, ptr->name, 0, 0, &peername)>0) break; if(!ptr) { s_log(LOG_WARNING, "CERT: No matching host name found"); return 0; /* reject */ } s_log(LOG_INFO, "CERT: Host name \"%s\" matched with \"%s\"", ptr->name, peername); OPENSSL_free(peername); } if(c->opt->check_email) { for(ptr=c->opt->check_email; ptr; ptr=ptr->next) if(X509_check_email(cert, ptr->name, 0, 0)>0) break; if(!ptr) { s_log(LOG_WARNING, "CERT: No matching email address found"); return 0; /* reject */ } s_log(LOG_INFO, "CERT: Email address \"%s\" matched", ptr->name); } if(c->opt->check_ip) { for(ptr=c->opt->check_ip; ptr; ptr=ptr->next) if(X509_check_ip_asc(cert, ptr->name, 0)>0) break; if(!ptr) { s_log(LOG_WARNING, "CERT: No matching IP address found"); return 0; /* reject */ } s_log(LOG_INFO, "CERT: IP address \"%s\" matched", ptr->name); } return 1; /* accept */ } #endif /* OPENSSL_VERSION_NUMBER>=0x10002000L */ #if OPENSSL_VERSION_NUMBER>=0x10000000L /* modern implementation for OpenSSL version >= 1.0.0 */ NOEXPORT int cert_check_local(X509_STORE_CTX *callback_ctx) { X509 *cert; X509_NAME *subject; STACK_OF(X509) *sk; int i; cert=X509_STORE_CTX_get_current_cert(callback_ctx); subject=X509_get_subject_name(cert); #if OPENSSL_VERSION_NUMBER<0x10100006L #define X509_STORE_CTX_get1_certs X509_STORE_get1_certs #endif /* modern API allows retrieving multiple matching certificates */ sk=X509_STORE_CTX_get1_certs(callback_ctx, subject); if(sk) { for(i=0; i=0x10000000L */ NOEXPORT int compare_pubkeys(X509 *c1, X509 *c2) { ASN1_BIT_STRING *k1=X509_get0_pubkey_bitstr(c1); ASN1_BIT_STRING *k2=X509_get0_pubkey_bitstr(c2); if(!k1 || !k2 || k1->length!=k2->length || k1->length<0 || safe_memcmp(k1->data, k2->data, (size_t)k1->length)) return 0; /* reject */ s_log(LOG_INFO, "CERT: Locally installed certificate matched"); return 1; /* accept */ } /**************************************** OCSP checking */ #ifndef OPENSSL_NO_OCSP #ifdef DEFINE_STACK_OF /* defined in openssl/safestack.h: * DEFINE_SPECIAL_STACK_OF(OPENSSL_STRING, char) */ #else /* DEFINE_STACK_OF */ #ifndef sk_OPENSSL_STRING_num #define sk_OPENSSL_STRING_num(st) sk_num(st) #endif /* sk_OPENSSL_STRING_num */ #ifndef sk_OPENSSL_STRING_value #define sk_OPENSSL_STRING_value(st, i) sk_value((st),(i)) #endif /* sk_OPENSSL_STRING_value */ #endif /* DEFINE_STACK_OF */ NOEXPORT int ocsp_check(CLI *c, X509_STORE_CTX *callback_ctx) { X509 *cert; OCSP_CERTID *cert_id; STACK_OF(OPENSSL_STRING) *aia; int i, ocsp_status=V_OCSP_CERTSTATUS_UNKNOWN, saved_error; char *url; /* the original error code is restored unless we report our own error */ saved_error=X509_STORE_CTX_get_error(callback_ctx); /* get the current certificate ID */ cert=X509_STORE_CTX_get_current_cert(callback_ctx); if(!cert) { s_log(LOG_ERR, "OCSP: Failed to get the current certificate"); X509_STORE_CTX_set_error(callback_ctx, X509_V_ERR_APPLICATION_VERIFICATION); return 0; /* reject */ } if(!X509_NAME_cmp(X509_get_subject_name(cert), X509_get_issuer_name(cert))) { s_log(LOG_DEBUG, "OCSP: Ignoring root certificate"); return 1; /* accept */ } cert_id=OCSP_cert_to_id(NULL, cert, get_current_issuer(callback_ctx)); if(!cert_id) { sslerror("OCSP: OCSP_cert_to_id"); X509_STORE_CTX_set_error(callback_ctx, X509_V_ERR_APPLICATION_VERIFICATION); return 0; /* reject */ } /* use the responder specified in the configuration file */ if(c->opt->ocsp_url) { s_log(LOG_NOTICE, "OCSP: Connecting the configured responder \"%s\"", c->opt->ocsp_url); if(ocsp_request(c, callback_ctx, cert_id, c->opt->ocsp_url)!= V_OCSP_CERTSTATUS_GOOD) { OCSP_CERTID_free(cert_id); return 0; /* reject */ } } /* use the responder from AIA (Authority Information Access) */ if(c->opt->option.aia && (aia=X509_get1_ocsp(cert))) { for(i=0; iopt->option.nonce) OCSP_request_add1_nonce(request, NULL, -1); /* send the request and get a response */ response=ocsp_get_response(c, request, url); if(!response) goto cleanup; response_status=OCSP_response_status(response); if(response_status!=OCSP_RESPONSE_STATUS_SUCCESSFUL) { s_log(LOG_ERR, "OCSP: Responder error: %d: %s", response_status, OCSP_response_status_str(response_status)); goto cleanup; } /* verify the response */ basic_response=OCSP_response_get1_basic(response); if(!basic_response) { sslerror("OCSP: OCSP_response_get1_basic"); goto cleanup; } if(c->opt->option.nonce && OCSP_check_nonce(request, basic_response)<=0) { s_log(LOG_ERR, "OCSP: Invalid or unsupported nonce"); goto cleanup; } if(OCSP_basic_verify(basic_response, X509_STORE_CTX_get0_chain(callback_ctx), SSL_CTX_get_cert_store(c->opt->ctx), c->opt->ocsp_flags)<=0) { sslerror("OCSP: OCSP_basic_verify"); goto cleanup; } if(!OCSP_resp_find_status(basic_response, cert_id, &ocsp_status, &reason, &revoked_at, &this_update, &next_update)) { sslerror("OCSP: OCSP_resp_find_status"); goto cleanup; } s_log(LOG_INFO, "OCSP: Status: %s", OCSP_cert_status_str(ocsp_status)); log_time(LOG_INFO, "OCSP: This update", this_update); log_time(LOG_INFO, "OCSP: Next update", next_update); /* check if the response is valid for at least one minute */ if(!OCSP_check_validity(this_update, next_update, 60, -1)) { sslerror("OCSP: OCSP_check_validity"); ocsp_status=V_OCSP_CERTSTATUS_UNKNOWN; goto cleanup; } switch(ocsp_status) { case V_OCSP_CERTSTATUS_GOOD: s_log(LOG_NOTICE, "OCSP: Certificate accepted"); break; case V_OCSP_CERTSTATUS_REVOKED: if(reason==-1) s_log(LOG_ERR, "OCSP: Certificate revoked"); else s_log(LOG_ERR, "OCSP: Certificate revoked: %d: %s", reason, OCSP_crl_reason_str(reason)); log_time(LOG_NOTICE, "OCSP: Revoked at", revoked_at); ctx_err=X509_V_ERR_CERT_REVOKED; break; case V_OCSP_CERTSTATUS_UNKNOWN: s_log(LOG_WARNING, "OCSP: Unknown verification status"); } cleanup: if(request) OCSP_REQUEST_free(request); if(response) OCSP_RESPONSE_free(response); if(basic_response) OCSP_BASICRESP_free(basic_response); if(ocsp_status!=V_OCSP_CERTSTATUS_GOOD) X509_STORE_CTX_set_error(callback_ctx, ctx_err); return ocsp_status; } NOEXPORT OCSP_RESPONSE *ocsp_get_response(CLI *c, OCSP_REQUEST *req, char *url) { BIO *bio=NULL; OCSP_REQ_CTX *req_ctx=NULL; OCSP_RESPONSE *resp=NULL; char *host=NULL, *port=NULL, *path=NULL; SOCKADDR_UNION addr; int ssl; /* parse the OCSP URL */ if(!OCSP_parse_url(url, &host, &port, &path, &ssl)) { s_log(LOG_ERR, "OCSP: Failed to parse the OCSP URL"); goto cleanup; } if(ssl) { s_log(LOG_ERR, "OCSP: TLS not supported for OCSP" " - an additional stunnel service needs to be defined"); goto cleanup; } if(!hostport2addr(&addr, host, port, 0)) { s_log(LOG_ERR, "OCSP: Failed to resolve the OCSP responder address"); goto cleanup; } /* connect specified OCSP responder */ c->fd=s_socket(addr.sa.sa_family, SOCK_STREAM, 0, 1, "OCSP: socket"); if(c->fd==INVALID_SOCKET) goto cleanup; if(s_connect(c, &addr, addr_len(&addr))) goto cleanup; bio=BIO_new_socket((int)c->fd, BIO_NOCLOSE); if(!bio) { sslerror("OCSP: BIO_new_socket"); goto cleanup; } s_log(LOG_DEBUG, "OCSP: Connected %s:%s", host, port); /* initialize an HTTP request with the POST method */ #if OPENSSL_VERSION_NUMBER>=0x10000000L req_ctx=OCSP_sendreq_new(bio, path, NULL, -1); #else /* there is no way to send the Host header with older OpenSSL versions */ req_ctx=OCSP_sendreq_new(bio, path, req, -1); #endif if(!req_ctx) { sslerror("OCSP: OCSP_sendreq_new"); goto cleanup; } #if OPENSSL_VERSION_NUMBER>=0x10000000L /* add the HTTP headers */ if(!OCSP_REQ_CTX_add1_header(req_ctx, "Host", host)) { sslerror("OCSP: OCSP_REQ_CTX_add1_header"); goto cleanup; } if(!OCSP_REQ_CTX_add1_header(req_ctx, "User-Agent", "stunnel")) { sslerror("OCSP: OCSP_REQ_CTX_add1_header"); goto cleanup; } /* add the remaining HTTP headers and the OCSP request body */ if(!OCSP_REQ_CTX_set1_req(req_ctx, req)) { sslerror("OCSP: OCSP_REQ_CTX_set1_req"); goto cleanup; } #endif /* OCSP protocol communication loop */ while(OCSP_sendreq_nbio(&resp, req_ctx)==-1) { s_poll_init(c->fds); s_poll_add(c->fds, c->fd, BIO_should_read(bio), BIO_should_write(bio)); switch(s_poll_wait(c->fds, c->opt->timeout_busy, 0)) { case -1: sockerror("OCSP: s_poll_wait"); goto cleanup; case 0: s_log(LOG_INFO, "OCSP: s_poll_wait: TIMEOUTbusy exceeded"); goto cleanup; } } #if 0 s_log(LOG_DEBUG, "OCSP: context state: 0x%x", *(int *)req_ctx); #endif /* http://www.mail-archive.com/openssl-users@openssl.org/msg61691.html */ if(resp) { s_log(LOG_DEBUG, "OCSP: Response received"); } else { if(ERR_peek_error()) sslerror("OCSP: OCSP_sendreq_nbio"); else /* OpenSSL error: OCSP_sendreq_nbio does not use OCSPerr */ s_log(LOG_ERR, "OCSP: OCSP_sendreq_nbio: OpenSSL internal error"); } cleanup: if(req_ctx) OCSP_REQ_CTX_free(req_ctx); if(bio) BIO_free_all(bio); if(c->fd!=INVALID_SOCKET) { closesocket(c->fd); c->fd=INVALID_SOCKET; /* avoid double close on cleanup */ } if(host) OPENSSL_free(host); if(port) OPENSSL_free(port); if(path) OPENSSL_free(path); return resp; } /* find the issuer certificate without lookups */ NOEXPORT X509 *get_current_issuer(X509_STORE_CTX *callback_ctx) { STACK_OF(X509) *chain; int depth; chain=X509_STORE_CTX_get0_chain(callback_ctx); depth=X509_STORE_CTX_get_error_depth(callback_ctx); if(depth