/** * * \file tls.c * @brief getdns TLS functions */ /* * Copyright (c) 2018, NLnet Labs * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the names of the copyright holders nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL Verisign, Inc. BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include "debug.h" #include "context.h" #include "tls.h" static int _getdns_tls_verify_always_ok(int ok, X509_STORE_CTX *ctx) { # if defined(STUB_DEBUG) && STUB_DEBUG char buf[8192]; X509 *cert; int err; int depth; cert = X509_STORE_CTX_get_current_cert(ctx); err = X509_STORE_CTX_get_error(ctx); depth = X509_STORE_CTX_get_error_depth(ctx); if (cert) X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf)); else strcpy(buf, ""); DEBUG_STUB("DEBUG Cert verify: depth=%d verify=%d err=%d subject=%s errorstr=%s\n", depth, ok, err, buf, X509_verify_cert_error_string(err)); # else /* defined(STUB_DEBUG) && STUB_DEBUG */ (void)ok; (void)ctx; # endif /* #else defined(STUB_DEBUG) && STUB_DEBUG */ return 1; } static _getdns_tls_x509* _getdns_tls_x509_new(X509* cert) { _getdns_tls_x509* res; if (!cert) return NULL; res = malloc(sizeof(_getdns_tls_x509)); if (res) res->ssl = cert; return res; } #ifdef USE_WINSOCK /* For windows, the CA trust store is not read by openssl. Add code to open the trust store using wincrypt API and add the root certs into openssl trust store */ static int add_WIN_cacerts_to_openssl_store(SSL_CTX* tls_ctx) { HCERTSTORE hSystemStore; PCCERT_CONTEXT pTargetCert = NULL; DEBUG_STUB("%s %-35s: %s\n", STUB_DEBUG_SETUP_TLS, __FUNC__, "Adding Windows certificates from system root store to CA store"); /* load just once per context lifetime for this version of getdns TODO: dynamically update CA trust changes as they are available */ if (!tls_ctx) return 0; /* Call wincrypt's CertOpenStore to open the CA root store. */ if ((hSystemStore = CertOpenStore( CERT_STORE_PROV_SYSTEM, 0, 0, /* NOTE: mingw does not have this const: replace with 1 << 16 from code CERT_SYSTEM_STORE_CURRENT_USER, */ 1 << 16, L"root")) == 0) { return 0; } X509_STORE* store = SSL_CTX_get_cert_store(tls_ctx); if (!store) return 0; /* failure if the CA store is empty or the call fails */ if ((pTargetCert = CertEnumCertificatesInStore( hSystemStore, pTargetCert)) == 0) { DEBUG_STUB("%s %-35s: %s\n", STUB_DEBUG_SETUP_TLS, __FUNC__, "CA certificate store for Windows is empty."); return 0; } /* iterate over the windows cert store and add to openssl store */ do { X509 *cert1 = d2i_X509(NULL, (const unsigned char **)&pTargetCert->pbCertEncoded, pTargetCert->cbCertEncoded); if (!cert1) { /* return error if a cert fails */ DEBUG_STUB("%s %-35s: %s %d:%s\n", STUB_DEBUG_SETUP_TLS, __FUNC__, "Unable to parse certificate in memory", ERR_get_error(), ERR_error_string(ERR_get_error(), NULL)); return 0; } else { /* return error if a cert add to store fails */ if (X509_STORE_add_cert(store, cert1) == 0) { unsigned long error = ERR_peek_last_error(); /* Ignore error X509_R_CERT_ALREADY_IN_HASH_TABLE which means the * certificate is already in the store. */ if(ERR_GET_LIB(error) != ERR_LIB_X509 || ERR_GET_REASON(error) != X509_R_CERT_ALREADY_IN_HASH_TABLE) { DEBUG_STUB("%s %-35s: %s %d:%s\n", STUB_DEBUG_SETUP_TLS, __FUNC__, "Error adding certificate", ERR_get_error(), ERR_error_string(ERR_get_error(), NULL)); X509_free(cert1); return 0; } } X509_free(cert1); } } while ((pTargetCert = CertEnumCertificatesInStore( hSystemStore, pTargetCert)) != 0); /* Clean up memory and quit. */ if (pTargetCert) CertFreeCertificateContext(pTargetCert); if (hSystemStore) { if (!CertCloseStore( hSystemStore, 0)) return 0; } DEBUG_STUB("%s %-35s: %s\n", STUB_DEBUG_SETUP_TLS, __FUNC__, "Completed adding Windows certificates to CA store successfully"); return 1; } #endif void _getdns_tls_init() { OPENSSL_init_crypto( OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); (void)OPENSSL_init_ssl(0, NULL); } _getdns_tls_context* _getdns_tls_context_new() { _getdns_tls_context* res; if (!(res = malloc(sizeof(struct _getdns_tls_context)))) return NULL; /* Create client context, use TLS v1.2 only for now */ # ifdef HAVE_TLS_CLIENT_METHOD res->ssl = SSL_CTX_new(TLS_client_method()); # else res->ssl = SSL_CTX_new(TLSv1_2_client_method()); # endif if(res->ssl == NULL) { free(res); return NULL; } return res; } getdns_return_t _getdns_tls_context_free(_getdns_tls_context* ctx) { if (!ctx || !ctx->ssl) return GETDNS_RETURN_INVALID_PARAMETER; SSL_CTX_free(ctx->ssl); free(ctx); return GETDNS_RETURN_GOOD; } getdns_return_t _getdns_tls_context_set_min_proto_1_2(_getdns_tls_context* ctx) { #ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION if (!ctx || !ctx->ssl) return GETDNS_RETURN_INVALID_PARAMETER; if (!SSL_CTX_set_min_proto_version(ctx->ssl, TLS1_2_VERSION)) return GETDNS_RETURN_BAD_CONTEXT; return GETDNS_RETURN_GOOD; #else (void) ctx; return GETDNS_RETURN_NOT_IMPLEMENTED; #endif } getdns_return_t _getdns_tls_context_set_cipher_list(_getdns_tls_context* ctx, const char* list) { if (!ctx || !ctx->ssl) return GETDNS_RETURN_INVALID_PARAMETER; if (!SSL_CTX_set_cipher_list(ctx->ssl, list)) return GETDNS_RETURN_BAD_CONTEXT; return GETDNS_RETURN_GOOD; } getdns_return_t _getdns_tls_context_set_curves_list(_getdns_tls_context* ctx, const char* list) { if (!ctx || !ctx->ssl) return GETDNS_RETURN_INVALID_PARAMETER; #if HAVE_TLS_CTX_CURVES_LIST if (list && !SSL_CTX_set1_curves_list(ctx->ssl, list)) return GETDNS_RETURN_BAD_CONTEXT; #else (void) list; #endif return GETDNS_RETURN_GOOD; } getdns_return_t _getdns_tls_context_set_ca(_getdns_tls_context* ctx, const char* file, const char* path) { if (!ctx || !ctx->ssl) return GETDNS_RETURN_INVALID_PARAMETER; if ((file || path) && SSL_CTX_load_verify_locations(ctx->ssl, file, path)) return GETDNS_RETURN_GOOD; /* pass */ #ifndef USE_WINSOCK else if (SSL_CTX_set_default_verify_paths(ctx->ssl)) return GETDNS_RETURN_GOOD; #else else if (add_WIN_cacerts_to_openssl_store(ctx->ssl)) return GETDNS_RETURN_GOOD; #endif /* USE_WINSOCK */ return GETDNS_RETURN_GENERIC_ERROR; } _getdns_tls_connection* _getdns_tls_connection_new(_getdns_tls_context* ctx, int fd) { _getdns_tls_connection* res; if (!ctx || !ctx->ssl) return NULL; if (!(res = malloc(sizeof(struct _getdns_tls_connection)))) return NULL; res->ssl = SSL_new(ctx->ssl); if (!res->ssl) { free(res); return NULL; } if (!SSL_set_fd(res->ssl, fd)) { SSL_free(res->ssl); free(res); return NULL; } /* Connection is a client. */ SSL_set_connect_state(res->ssl); /* If non-application data received, retry read. */ SSL_set_mode(res->ssl, SSL_MODE_AUTO_RETRY); return res; } getdns_return_t _getdns_tls_connection_free(_getdns_tls_connection* conn) { if (!conn || !conn->ssl) return GETDNS_RETURN_INVALID_PARAMETER; SSL_free(conn->ssl); free(conn); return GETDNS_RETURN_GOOD; } getdns_return_t _getdns_tls_connection_shutdown(_getdns_tls_connection* conn) { if (!conn || !conn->ssl) return GETDNS_RETURN_INVALID_PARAMETER; switch (SSL_shutdown(conn->ssl)) { case 0: return GETDNS_RETURN_CONTEXT_UPDATE_FAIL; case 1: return GETDNS_RETURN_GOOD; default: return GETDNS_RETURN_GENERIC_ERROR; } } getdns_return_t _getdns_tls_connection_set_cipher_list(_getdns_tls_connection* conn, const char* list) { if (!conn || !conn->ssl) return GETDNS_RETURN_INVALID_PARAMETER; if (!SSL_set_cipher_list(conn->ssl, list)) return GETDNS_RETURN_BAD_CONTEXT; return GETDNS_RETURN_GOOD; } getdns_return_t _getdns_tls_connection_set_curves_list(_getdns_tls_connection* conn, const char* list) { if (!conn || !conn->ssl) return GETDNS_RETURN_INVALID_PARAMETER; #if HAVE_TLS_CONN_CURVES_LIST if (list && !SSL_set1_curves_list(conn->ssl, list)) return GETDNS_RETURN_BAD_CONTEXT; #else (void) list; #endif return GETDNS_RETURN_GOOD; } getdns_return_t _getdns_tls_connection_set_session(_getdns_tls_connection* conn, _getdns_tls_session* s) { if (!conn || !conn->ssl || !s || !s->ssl) return GETDNS_RETURN_INVALID_PARAMETER; if (!SSL_set_session(conn->ssl, s->ssl)) return GETDNS_RETURN_GENERIC_ERROR; return GETDNS_RETURN_GOOD; } _getdns_tls_session* _getdns_tls_connection_get_session(_getdns_tls_connection* conn) { _getdns_tls_session* res; if (!conn || !conn->ssl) return NULL; if (!(res = malloc(sizeof(struct _getdns_tls_session)))) return NULL; res->ssl = SSL_get1_session(conn->ssl); if (!res->ssl) { free(res); return NULL; } return res; } const char* _getdns_tls_connection_get_version(_getdns_tls_connection* conn) { if (!conn || !conn->ssl) return NULL; return SSL_get_version(conn->ssl); } getdns_return_t _getdns_tls_connection_do_handshake(_getdns_tls_connection* conn) { int r; int err; if (!conn || !conn->ssl) return GETDNS_RETURN_INVALID_PARAMETER; ERR_clear_error(); r = SSL_do_handshake(conn->ssl); if (r == 1) return GETDNS_RETURN_GOOD; err = SSL_get_error(conn->ssl, r); switch (err) { case SSL_ERROR_WANT_READ: return GETDNS_RETURN_TLS_WANT_READ; case SSL_ERROR_WANT_WRITE: return GETDNS_RETURN_TLS_WANT_WRITE; default: return GETDNS_RETURN_GENERIC_ERROR; } } _getdns_tls_x509* _getdns_tls_connection_get_peer_certificate(_getdns_tls_connection* conn) { if (!conn || !conn->ssl) return NULL; return _getdns_tls_x509_new(SSL_get_peer_certificate(conn->ssl)); } getdns_return_t _getdns_tls_connection_is_session_reused(_getdns_tls_connection* conn) { if (!conn || !conn->ssl) return GETDNS_RETURN_INVALID_PARAMETER; if (SSL_session_reused(conn->ssl)) return GETDNS_RETURN_GOOD; else return GETDNS_RETURN_TLS_CONNECTION_FRESH; } getdns_return_t _getdns_tls_connection_setup_hostname_auth(_getdns_tls_connection* conn, const char* auth_name) { if (!conn || !conn->ssl || !auth_name) return GETDNS_RETURN_INVALID_PARAMETER; SSL_set_tlsext_host_name(conn->ssl, auth_name); /* Set up native OpenSSL hostname verification */ X509_VERIFY_PARAM *param; param = SSL_get0_param(conn->ssl); X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); X509_VERIFY_PARAM_set1_host(param, auth_name, 0); return GETDNS_RETURN_GOOD; } getdns_return_t _getdns_tls_connection_set_host_pinset(_getdns_tls_connection* conn, const char* auth_name, const sha256_pin_t* pinset) { if (!conn || !conn->ssl || !auth_name) return GETDNS_RETURN_INVALID_PARAMETER; int osr = SSL_dane_enable(conn->ssl, *auth_name ? auth_name : NULL); (void) osr; DEBUG_STUB("%s %-35s: DEBUG: SSL_dane_enable(\"%s\") -> %d\n" , STUB_DEBUG_SETUP_TLS, __FUNC__, upstream->tls_auth_name, osr); SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, _getdns_tls_verify_always_ok); const sha256_pin_t *pin_p; size_t n_pins = 0; for (pin_p = pinset; pin_p; pin_p = pin_p->next) { osr = SSL_dane_tlsa_add(conn->ssl, 2, 1, 1, (unsigned char *)pin_p->pin, SHA256_DIGEST_LENGTH); DEBUG_STUB("%s %-35s: DEBUG: SSL_dane_tlsa_add() -> %d\n" , STUB_DEBUG_SETUP_TLS, __FUNC__, osr); if (osr > 0) ++n_pins; osr = SSL_dane_tlsa_add(conn->ssl, 3, 1, 1, (unsigned char *)pin_p->pin, SHA256_DIGEST_LENGTH); DEBUG_STUB("%s %-35s: DEBUG: SSL_dane_tlsa_add() -> %d\n" , STUB_DEBUG_SETUP_TLS, __FUNC__, osr); if (osr > 0) ++n_pins; } return GETDNS_RETURN_GOOD; } getdns_return_t _getdns_tls_connection_verify(_getdns_tls_connection* conn, long* errnum, const char** errmsg) { if (!conn || !conn->ssl) return GETDNS_RETURN_INVALID_PARAMETER; long verify_result = SSL_get_verify_result(conn->ssl); switch (verify_result) { case X509_V_OK: return GETDNS_RETURN_GOOD; case X509_V_ERR_DANE_NO_MATCH: if (errnum) *errnum = 0; if (errmsg) *errmsg = "Pinset validation failure"; return GETDNS_RETURN_GENERIC_ERROR; default: if (errnum) *errnum = verify_result; if (errmsg) *errmsg = X509_verify_cert_error_string(verify_result); return GETDNS_RETURN_GENERIC_ERROR; } } getdns_return_t _getdns_tls_connection_read(_getdns_tls_connection* conn, uint8_t* buf, size_t to_read, size_t* read) { int sread; if (!conn || !conn->ssl || !read) return GETDNS_RETURN_INVALID_PARAMETER; ERR_clear_error(); sread = SSL_read(conn->ssl, buf, to_read); if (sread <= 0) { switch (SSL_get_error(conn->ssl, sread)) { case SSL_ERROR_WANT_READ: return GETDNS_RETURN_TLS_WANT_READ; case SSL_ERROR_WANT_WRITE: return GETDNS_RETURN_TLS_WANT_WRITE; default: return GETDNS_RETURN_GENERIC_ERROR; } } *read = sread; return GETDNS_RETURN_GOOD; } getdns_return_t _getdns_tls_connection_write(_getdns_tls_connection* conn, uint8_t* buf, size_t to_write, size_t* written) { int swritten; if (!conn || !conn->ssl || !written) return GETDNS_RETURN_INVALID_PARAMETER; ERR_clear_error(); swritten = SSL_write(conn->ssl, buf, to_write); if (swritten <= 0) { switch(SSL_get_error(conn->ssl, swritten)) { case SSL_ERROR_WANT_READ: /* SSL_write will not do partial writes, because * SSL_MODE_ENABLE_PARTIAL_WRITE is not default, * but the write could fail because of renegotiation. * In that case SSL_get_error() will return * SSL_ERROR_WANT_READ or, SSL_ERROR_WANT_WRITE. * Return for retry in such cases. */ return GETDNS_RETURN_TLS_WANT_READ; case SSL_ERROR_WANT_WRITE: return GETDNS_RETURN_TLS_WANT_WRITE; default: return GETDNS_RETURN_GENERIC_ERROR; } } *written = swritten; return GETDNS_RETURN_GOOD; } getdns_return_t _getdns_tls_session_free(_getdns_tls_session* s) { if (!s || !s->ssl) return GETDNS_RETURN_INVALID_PARAMETER; SSL_SESSION_free(s->ssl); free(s); return GETDNS_RETURN_GOOD; } getdns_return_t _getdns_tls_get_api_information(getdns_dict* dict) { if (! getdns_dict_set_int( dict, "openssl_build_version_number", OPENSSL_VERSION_NUMBER) #ifdef HAVE_OPENSSL_VERSION_NUM && ! getdns_dict_set_int( dict, "openssl_version_number", OpenSSL_version_num()) #endif #ifdef HAVE_OPENSSL_VERSION && ! getdns_dict_util_set_string( dict, "openssl_version_string", OpenSSL_version(OPENSSL_VERSION)) && ! getdns_dict_util_set_string( dict, "openssl_cflags", OpenSSL_version(OPENSSL_CFLAGS)) && ! getdns_dict_util_set_string( dict, "openssl_built_on", OpenSSL_version(OPENSSL_BUILT_ON)) && ! getdns_dict_util_set_string( dict, "openssl_platform", OpenSSL_version(OPENSSL_PLATFORM)) && ! getdns_dict_util_set_string( dict, "openssl_dir", OpenSSL_version(OPENSSL_DIR)) && ! getdns_dict_util_set_string( dict, "openssl_engines_dir", OpenSSL_version(OPENSSL_ENGINES_DIR)) #endif ) return GETDNS_RETURN_GOOD; return GETDNS_RETURN_GENERIC_ERROR; } void _getdns_tls_x509_free(_getdns_tls_x509* cert) { if (cert && cert->ssl) X509_free(cert->ssl); free(cert); } int _getdns_tls_x509_to_der(_getdns_tls_x509* cert, uint8_t** buf) { if (!cert || !cert->ssl) return 0; return i2d_X509(cert->ssl, buf); } /* tls.c */