/* * stunnel Universal SSL tunnel * Copyright (C) 1998-2013 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 */ static int load_file_lookup(X509_STORE *, char *); static int add_dir_lookup(X509_STORE *, char *); /* verify callback */ static int verify_callback(int, X509_STORE_CTX *); static int cert_check(CLI *c, X509_STORE_CTX *, int); static int crl_check(CLI *c, X509_STORE_CTX *); #ifdef HAVE_OSSL_OCSP_H static int ocsp_check(CLI *c, X509_STORE_CTX *); static OCSP_RESPONSE *ocsp_get_response(CLI *, OCSP_REQUEST *); #endif /* utility functions */ static void log_time(const int, const char *, ASN1_TIME *); /**************************************** verify initialization */ int verify_init(SERVICE_OPTIONS *section) { if(section->verify_level<0) return 0; /* OK - no certificate verification */ if(section->verify_level>=2 && !section->ca_file && !section->ca_dir) { s_log(LOG_ERR, "Either CApath or CAfile has to be used for authentication"); return 1; /* FAILED */ } section->revocation_store=X509_STORE_new(); if(!section->revocation_store) { sslerror("X509_STORE_new"); return 1; /* FAILED */ } if(section->ca_file) { if(!SSL_CTX_load_verify_locations(section->ctx, section->ca_file, NULL)) { s_log(LOG_ERR, "Error loading verify certificates from %s", section->ca_file); sslerror("SSL_CTX_load_verify_locations"); return 1; /* FAILED */ } /* list of trusted CAs for the client to choose the right cert */ SSL_CTX_set_client_CA_list(section->ctx, SSL_load_client_CA_file(section->ca_file)); s_log(LOG_DEBUG, "Loaded verify certificates from %s", section->ca_file); if(load_file_lookup(section->revocation_store, section->ca_file)) return 1; /* FAILED */ } if(section->ca_dir) { if(!SSL_CTX_load_verify_locations(section->ctx, NULL, section->ca_dir)) { s_log(LOG_ERR, "Error setting verify directory to %s", section->ca_dir); sslerror("SSL_CTX_load_verify_locations"); return 1; /* FAILED */ } s_log(LOG_DEBUG, "Verify directory set to %s", section->ca_dir); add_dir_lookup(section->revocation_store, section->ca_dir); } if(section->crl_file) if(load_file_lookup(section->revocation_store, section->crl_file)) return 1; /* FAILED */ if(section->crl_dir) { section->revocation_store->cache=0; /* don't cache CRLs */ add_dir_lookup(section->revocation_store, section->crl_dir); } SSL_CTX_set_verify(section->ctx, SSL_VERIFY_PEER | (section->verify_level>=2 ? SSL_VERIFY_FAIL_IF_NO_PEER_CERT : 0), verify_callback); if(section->ca_dir && section->verify_level>=3) s_log(LOG_INFO, "Peer certificate location %s", section->ca_dir); return 0; /* OK */ } static 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"); return 1; /* FAILED */ } if(!X509_LOOKUP_load_file(lookup, name, X509_FILETYPE_PEM)) { s_log(LOG_ERR, "Failed to load %s revocation lookup file", name); sslerror("X509_LOOKUP_load_file"); return 1; /* FAILED */ } s_log(LOG_DEBUG, "Loaded %s revocation lookup file", name); return 0; /* OK */ } static 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"); 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 */ } /**************************************** verify callback */ static int verify_callback(int preverify_ok, X509_STORE_CTX *callback_ctx) { /* our verify callback function */ SSL *ssl; CLI *c; X509 *cert; int depth; char *subject_name; /* 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, cli_index); cert=X509_STORE_CTX_get_current_cert(callback_ctx); depth=X509_STORE_CTX_get_error_depth(callback_ctx); /* certificate name for logging */ subject_name=X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); s_log(LOG_DEBUG, "Starting certificate verification: depth=%d, %s", depth, subject_name); if(!cert_check(c, callback_ctx, preverify_ok)) { s_log(LOG_WARNING, "Certificate check failed: depth=%d, %s", depth, subject_name); OPENSSL_free(subject_name); return 0; /* reject connection */ } if(!crl_check(c, callback_ctx)) { s_log(LOG_WARNING, "CRL check failed: depth=%d, %s", depth, subject_name); OPENSSL_free(subject_name); return 0; /* reject connection */ } #ifdef HAVE_OSSL_OCSP_H if(c->opt->option.ocsp && !ocsp_check(c, callback_ctx)) { s_log(LOG_WARNING, "OCSP check failed: depth=%d, %s", depth, subject_name); OPENSSL_free(subject_name); return 0; /* reject connection */ } #endif /* HAVE_OSSL_OCSP_H */ /* errnum=X509_STORE_CTX_get_error(ctx); */ s_log(LOG_NOTICE, "Certificate accepted: depth=%d, %s", depth, subject_name); OPENSSL_free(subject_name); return 1; /* accept connection */ } /**************************************** certificate checking */ static int cert_check(CLI *c, X509_STORE_CTX *callback_ctx, int preverify_ok) { X509_OBJECT obj; #if OPENSSL_VERSION_NUMBER>=0x0090700fL ASN1_BIT_STRING *local_key, *peer_key; #endif X509 *cert; int depth; if(c->opt->verify_level<1) { s_log(LOG_INFO, "CERT: Verification not enabled"); return 1; /* accept connection */ } cert=X509_STORE_CTX_get_current_cert(callback_ctx); depth=X509_STORE_CTX_get_error_depth(callback_ctx); if(!preverify_ok) { /* remote site specified a certificate, but it's not correct */ if(c->opt->verify_level>=4 && depth>0) { s_log(LOG_INFO, "CERT: Invalid CA certificate ignored"); return 1; /* accept connection */ } else { s_log(LOG_WARNING, "CERT: Verification error: %s", X509_verify_cert_error_string( X509_STORE_CTX_get_error(callback_ctx))); return 0; /* reject connection */ } } if(c->opt->verify_level>=3 && depth==0) { if(X509_STORE_get_by_subject(callback_ctx, X509_LU_X509, X509_get_subject_name(cert), &obj)!=1) { s_log(LOG_WARNING, "CERT: Certificate not found in local repository"); return 0; /* reject connection */ } #if OPENSSL_VERSION_NUMBER>=0x0090700fL peer_key=X509_get0_pubkey_bitstr(cert); local_key=X509_get0_pubkey_bitstr(obj.data.x509); if(!peer_key || !local_key || peer_key->length!=local_key->length || memcmp(peer_key->data, local_key->data, local_key->length)) { s_log(LOG_WARNING, "CERT: Public keys do not match"); return 0; /* reject connection */ } #endif s_log(LOG_INFO, "CERT: Locally installed certificate matched"); } return 1; /* accept connection */ } /**************************************** CRL checking */ /* based on BSD-style licensed code of mod_ssl */ static int crl_check(CLI *c, X509_STORE_CTX *callback_ctx) { X509_STORE_CTX store_ctx; X509_OBJECT obj; X509_NAME *subject; X509_NAME *issuer; X509 *cert; X509_CRL *crl; X509_REVOKED *revoked; EVP_PKEY *pubkey; long serial; int i, n, rc; char *cp; ASN1_TIME *last_update=NULL, *next_update=NULL; /* determine certificate ingredients in advance */ cert=X509_STORE_CTX_get_current_cert(callback_ctx); subject=X509_get_subject_name(cert); issuer=X509_get_issuer_name(cert); /* try to retrieve a CRL corresponding to the _subject_ of * the current certificate in order to verify it's integrity */ memset((char *)&obj, 0, sizeof obj); X509_STORE_CTX_init(&store_ctx, c->opt->revocation_store, NULL, NULL); rc=X509_STORE_get_by_subject(&store_ctx, X509_LU_CRL, subject, &obj); X509_STORE_CTX_cleanup(&store_ctx); crl=obj.data.crl; if(rc>0 && crl) { cp=X509_NAME_oneline(subject, NULL, 0); s_log(LOG_INFO, "CRL: issuer: %s", cp); OPENSSL_free(cp); last_update=X509_CRL_get_lastUpdate(crl); next_update=X509_CRL_get_nextUpdate(crl); log_time(LOG_INFO, "CRL: last update", last_update); log_time(LOG_INFO, "CRL: next update", next_update); /* verify the signature on this CRL */ pubkey=X509_get_pubkey(cert); if(X509_CRL_verify(crl, pubkey)<=0) { s_log(LOG_WARNING, "CRL: Invalid signature"); X509_STORE_CTX_set_error(callback_ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE); X509_OBJECT_free_contents(&obj); if(pubkey) EVP_PKEY_free(pubkey); return 0; /* reject connection */ } if(pubkey) EVP_PKEY_free(pubkey); /* check date of CRL to make sure it's not expired */ if(!next_update) { s_log(LOG_WARNING, "CRL: Invalid nextUpdate field"); X509_STORE_CTX_set_error(callback_ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD); X509_OBJECT_free_contents(&obj); return 0; /* reject connection */ } if(X509_cmp_current_time(next_update)<0) { s_log(LOG_WARNING, "CRL: CRL Expired - revoking all certificates"); X509_STORE_CTX_set_error(callback_ctx, X509_V_ERR_CRL_HAS_EXPIRED); X509_OBJECT_free_contents(&obj); return 0; /* reject connection */ } X509_OBJECT_free_contents(&obj); } /* try to retrieve a CRL corresponding to the _issuer_ of * the current certificate in order to check for revocation */ memset((char *)&obj, 0, sizeof obj); X509_STORE_CTX_init(&store_ctx, c->opt->revocation_store, NULL, NULL); rc=X509_STORE_get_by_subject(&store_ctx, X509_LU_CRL, issuer, &obj); X509_STORE_CTX_cleanup(&store_ctx); crl=obj.data.crl; if(rc>0 && crl) { /* check if the current certificate is revoked by this CRL */ n=sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl)); for(i=0; iserialNumber, X509_get_serialNumber(cert)) == 0) { serial=ASN1_INTEGER_get(revoked->serialNumber); cp=X509_NAME_oneline(issuer, NULL, 0); s_log(LOG_WARNING, "CRL: Certificate with serial %ld (0x%lX) " "revoked per CRL from issuer %s", serial, serial, cp); OPENSSL_free(cp); X509_STORE_CTX_set_error(callback_ctx, X509_V_ERR_CERT_REVOKED); X509_OBJECT_free_contents(&obj); return 0; /* reject connection */ } } X509_OBJECT_free_contents(&obj); } return 1; /* accept connection */ } #ifdef HAVE_OSSL_OCSP_H /**************************************** OCSP checking */ /* TODO: check OCSP server specified in the certificate */ static int ocsp_check(CLI *c, X509_STORE_CTX *callback_ctx) { int error, retval=0; X509 *cert; X509 *issuer=NULL; OCSP_CERTID *certID; OCSP_REQUEST *request=NULL; OCSP_RESPONSE *response=NULL; OCSP_BASICRESP *basicResponse=NULL; ASN1_GENERALIZEDTIME *revoked_at=NULL, *this_update=NULL, *next_update=NULL; int status, reason; /* get current certificate ID */ cert=X509_STORE_CTX_get_current_cert(callback_ctx); /* get current cert */ if(X509_STORE_CTX_get1_issuer(&issuer, callback_ctx, cert)!=1) { sslerror("OCSP: X509_STORE_CTX_get1_issuer"); goto cleanup; } certID=OCSP_cert_to_id(0, cert, issuer); if(!certID) { sslerror("OCSP: OCSP_cert_to_id"); goto cleanup; } /* build request */ request=OCSP_REQUEST_new(); if(!request) { sslerror("OCSP: OCSP_REQUEST_new"); goto cleanup; } if(!OCSP_request_add0_id(request, certID)) { sslerror("OCSP: OCSP_request_add0_id"); goto cleanup; } OCSP_request_add1_nonce(request, 0, -1); /* send the request and get a response */ response=ocsp_get_response(c, request); if(!response) goto cleanup; error=OCSP_response_status(response); if(error!=OCSP_RESPONSE_STATUS_SUCCESSFUL) { s_log(LOG_WARNING, "OCSP: Responder error: %d: %s", error, OCSP_response_status_str(error)); goto cleanup; } s_log(LOG_DEBUG, "OCSP: Response received"); /* verify the response */ basicResponse=OCSP_response_get1_basic(response); if(!basicResponse) { sslerror("OCSP: OCSP_response_get1_basic"); goto cleanup; } if(OCSP_check_nonce(request, basicResponse)<=0) { sslerror("OCSP: OCSP_check_nonce"); goto cleanup; } if(OCSP_basic_verify(basicResponse, NULL, c->opt->revocation_store, c->opt->ocsp_flags)<=0) { sslerror("OCSP: OCSP_basic_verify"); goto cleanup; } if(!OCSP_resp_find_status(basicResponse, certID, &status, &reason, &revoked_at, &this_update, &next_update)) { sslerror("OCSP: OCSP_resp_find_status"); goto cleanup; } s_log(LOG_NOTICE, "OCSP: Status: %d: %s", status, OCSP_cert_status_str(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"); goto cleanup; } if(status==V_OCSP_CERTSTATUS_REVOKED) { if(reason==-1) s_log(LOG_WARNING, "OCSP: Certificate revoked"); else s_log(LOG_WARNING, "OCSP: Certificate revoked: %d: %s", reason, OCSP_crl_reason_str(reason)); log_time(LOG_NOTICE, "OCSP: Revoked at", revoked_at); goto cleanup; } retval=1; /* accept connection */ cleanup: if(issuer) X509_free(issuer); if(request) OCSP_REQUEST_free(request); if(response) OCSP_RESPONSE_free(response); if(basicResponse) OCSP_BASICRESP_free(basicResponse); return retval; } static OCSP_RESPONSE *ocsp_get_response(CLI *c, OCSP_REQUEST *req) { BIO *bio=NULL; OCSP_REQ_CTX *req_ctx=NULL; OCSP_RESPONSE *resp=NULL; int err; /* connect specified OCSP server (responder) */ c->fd=s_socket(c->opt->ocsp_addr.sa.sa_family, SOCK_STREAM, 0, 1, "OCSP: socket (auth_user)"); if(c->fd<0) goto cleanup; if(connect_blocking(c, &c->opt->ocsp_addr, addr_len(&c->opt->ocsp_addr))) goto cleanup; bio=BIO_new_fd(c->fd, BIO_NOCLOSE); if(!bio) goto cleanup; s_log(LOG_DEBUG, "OCSP: server connected"); /* OCSP protocol communication loop */ req_ctx=OCSP_sendreq_new(bio, c->opt->ocsp_path, req, -1); if(!req_ctx) { sslerror("OCSP: OCSP_sendreq_new"); goto cleanup; } 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)); err=s_poll_wait(c->fds, c->opt->timeout_busy, 0); if(err==-1) sockerror("OCSP: s_poll_wait"); if(err==0) s_log(LOG_INFO, "OCSP: s_poll_wait: TIMEOUTbusy exceeded"); if(err<=0) goto cleanup; } /* s_log(LOG_DEBUG, "OCSP: context state: 0x%x", *(int *)req_ctx); */ /* http://www.mail-archive.com/openssl-users@openssl.org/msg61691.html */ if(!resp) { 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>=0) { closesocket(c->fd); c->fd=-1; /* avoid double close on cleanup */ } return resp; } #endif /* HAVE_OSSL_OCSP_H */ static void log_time(const int level, const char *txt, ASN1_TIME *t) { char *cp; BIO *bio; int n; if(!t) return; bio=BIO_new(BIO_s_mem()); if(!bio) return; ASN1_TIME_print(bio, t); n=BIO_pending(bio); cp=str_alloc(n+1); n=BIO_read(bio, cp, n); if(n<0) { BIO_free(bio); str_free(cp); return; } cp[n]='\0'; BIO_free(bio); s_log(level, "%s: %s", txt, cp); str_free(cp); } /* end of verify.c */