CVE-2016-2113: s4:lib/tls: implement infrastructure to do peer verification
authorStefan Metzmacher <metze@samba.org>
Wed, 23 Dec 2015 15:17:04 +0000 (16:17 +0100)
committerStefan Metzmacher <metze@samba.org>
Tue, 12 Apr 2016 17:25:25 +0000 (19:25 +0200)
BUG: https://bugzilla.samba.org/show_bug.cgi?id=11752

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: G√ľnther Deschner <gd@samba.org>
source4/lib/tls/tls.h
source4/lib/tls/tls_tstream.c
source4/lib/tls/wscript
source4/libcli/ldap/ldap_client.c
source4/librpc/rpc/dcerpc_roh.c

index 71e6cfb072f6dc58210abc7364566bef1e347cfb..91eeaae6bf2081632a4931a8eece304402bb60e7 100644 (file)
@@ -61,10 +61,33 @@ const struct socket_ops *socket_tls_ops(enum socket_type type);
 struct tstream_context;
 struct tstream_tls_params;
 
+enum tls_verify_peer_state {
+       TLS_VERIFY_PEER_NO_CHECK = 0,
+#define TLS_VERIFY_PEER_NO_CHECK_STRING "no_check"
+
+       TLS_VERIFY_PEER_CA_ONLY = 10,
+#define TLS_VERIFY_PEER_CA_ONLY_STRING "ca_only"
+
+       TLS_VERIFY_PEER_CA_AND_NAME_IF_AVAILABLE = 20,
+#define TLS_VERIFY_PEER_CA_AND_NAME_IF_AVAILABLE_STRING \
+               "ca_and_name_if_available"
+
+       TLS_VERIFY_PEER_CA_AND_NAME = 30,
+#define TLS_VERIFY_PEER_CA_AND_NAME_STRING "ca_and_name"
+
+       TLS_VERIFY_PEER_AS_STRICT_AS_POSSIBLE = 9999,
+#define TLS_VERIFY_PEER_AS_STRICT_AS_POSSIBLE_STRING \
+               "as_strict_as_possible"
+};
+
+const char *tls_verify_peer_string(enum tls_verify_peer_state verify_peer);
+
 NTSTATUS tstream_tls_params_client(TALLOC_CTX *mem_ctx,
                                   const char *ca_file,
                                   const char *crl_file,
                                   const char *tls_priority,
+                                  enum tls_verify_peer_state verify_peer,
+                                  const char *peer_name,
                                   struct tstream_tls_params **_tlsp);
 
 NTSTATUS tstream_tls_params_server(TALLOC_CTX *mem_ctx,
index 5c3e9f1142eebc479bac4e1127c2df920e01ec36..5045e88eec6109dd300040ff5d37ea8daa109ea2 100644 (file)
 #include "includes.h"
 #include "system/network.h"
 #include "system/filesys.h"
+#include "system/time.h"
 #include "../util/tevent_unix.h"
 #include "../lib/tsocket/tsocket.h"
 #include "../lib/tsocket/tsocket_internal.h"
+#include "../lib/util/util_net.h"
 #include "lib/tls/tls.h"
 
 #if ENABLE_GNUTLS
 #include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
 
 #define DH_BITS 2048
 
 typedef gnutls_datum gnutls_datum_t;
 #endif
 
+/*
+ * define our own values in a high range
+ */
+#ifndef HAVE_DECL_GNUTLS_CERT_EXPIRED
+#define GNUTLS_CERT_EXPIRED                    0x10000000
+#define REQUIRE_CERT_TIME_CHECKS 1
+#endif
+#ifndef HAVE_DECL_GNUTLS_CERT_NOT_ACTIVATED
+#define GNUTLS_CERT_NOT_ACTIVATED              0x20000000
+#ifndef REQUIRE_CERT_TIME_CHECKS
+#define REQUIRE_CERT_TIME_CHECKS 1
+#endif
+#endif
+#ifndef HAVE_DECL_GNUTLS_CERT_UNEXPECTED_OWNER
+#define GNUTLS_CERT_UNEXPECTED_OWNER           0x40000000
+#endif
+
 #endif /* ENABLE_GNUTLS */
 
+const char *tls_verify_peer_string(enum tls_verify_peer_state verify_peer)
+{
+       switch (verify_peer) {
+       case TLS_VERIFY_PEER_NO_CHECK:
+               return TLS_VERIFY_PEER_NO_CHECK_STRING;
+
+       case TLS_VERIFY_PEER_CA_ONLY:
+               return TLS_VERIFY_PEER_CA_ONLY_STRING;
+
+       case TLS_VERIFY_PEER_CA_AND_NAME_IF_AVAILABLE:
+               return TLS_VERIFY_PEER_CA_AND_NAME_IF_AVAILABLE_STRING;
+
+       case TLS_VERIFY_PEER_CA_AND_NAME:
+               return TLS_VERIFY_PEER_CA_AND_NAME_STRING;
+
+       case TLS_VERIFY_PEER_AS_STRICT_AS_POSSIBLE:
+               return TLS_VERIFY_PEER_AS_STRICT_AS_POSSIBLE_STRING;
+       }
+
+       return "unknown tls_verify_peer_state";
+}
+
 static const struct tstream_context_ops tstream_tls_ops;
 
 struct tstream_tls {
@@ -46,6 +88,9 @@ struct tstream_tls {
        gnutls_session tls_session;
 #endif /* ENABLE_GNUTLS */
 
+       enum tls_verify_peer_state verify_peer;
+       const char *peer_name;
+
        struct tevent_context *current_ev;
 
        struct tevent_immediate *retry_im;
@@ -871,6 +916,8 @@ struct tstream_tls_params {
        const char *tls_priority;
 #endif /* ENABLE_GNUTLS */
        bool tls_enabled;
+       enum tls_verify_peer_state verify_peer;
+       const char *peer_name;
 };
 
 static int tstream_tls_params_destructor(struct tstream_tls_params *tlsp)
@@ -897,6 +944,8 @@ NTSTATUS tstream_tls_params_client(TALLOC_CTX *mem_ctx,
                                   const char *ca_file,
                                   const char *crl_file,
                                   const char *tls_priority,
+                                  enum tls_verify_peer_state verify_peer,
+                                  const char *peer_name,
                                   struct tstream_tls_params **_tlsp)
 {
 #if ENABLE_GNUTLS
@@ -914,6 +963,21 @@ NTSTATUS tstream_tls_params_client(TALLOC_CTX *mem_ctx,
 
        talloc_set_destructor(tlsp, tstream_tls_params_destructor);
 
+       tlsp->verify_peer = verify_peer;
+       if (peer_name != NULL) {
+               tlsp->peer_name = talloc_strdup(tlsp, peer_name);
+               if (tlsp->peer_name == NULL) {
+                       talloc_free(tlsp);
+                       return NT_STATUS_NO_MEMORY;
+               }
+       } else if (tlsp->verify_peer >= TLS_VERIFY_PEER_CA_AND_NAME) {
+               DEBUG(0,("TLS failed to missing peer_name - "
+                        "with 'tls verify peer = %s'\n",
+                        tls_verify_peer_string(tlsp->verify_peer)));
+               talloc_free(tlsp);
+               return NT_STATUS_INVALID_PARAMETER_MIX;
+       }
+
        ret = gnutls_certificate_allocate_credentials(&tlsp->x509_cred);
        if (ret != GNUTLS_E_SUCCESS) {
                DEBUG(0,("TLS %s - %s\n", __location__, gnutls_strerror(ret)));
@@ -931,6 +995,13 @@ NTSTATUS tstream_tls_params_client(TALLOC_CTX *mem_ctx,
                        talloc_free(tlsp);
                        return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
                }
+       } else if (tlsp->verify_peer >= TLS_VERIFY_PEER_CA_ONLY) {
+               DEBUG(0,("TLS failed to missing cafile %s - "
+                        "with 'tls verify peer = %s'\n",
+                        ca_file,
+                        tls_verify_peer_string(tlsp->verify_peer)));
+               talloc_free(tlsp);
+               return NT_STATUS_INVALID_PARAMETER_MIX;
        }
 
        if (crl_file && *crl_file && file_exist(crl_file)) {
@@ -943,6 +1014,13 @@ NTSTATUS tstream_tls_params_client(TALLOC_CTX *mem_ctx,
                        talloc_free(tlsp);
                        return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
                }
+       } else if (tlsp->verify_peer >= TLS_VERIFY_PEER_AS_STRICT_AS_POSSIBLE) {
+               DEBUG(0,("TLS failed to missing crlfile %s - "
+                        "with 'tls verify peer = %s'\n",
+                        crl_file,
+                        tls_verify_peer_string(tlsp->verify_peer)));
+               talloc_free(tlsp);
+               return NT_STATUS_INVALID_PARAMETER_MIX;
        }
 
        tlsp->tls_priority = talloc_strdup(tlsp, tls_priority);
@@ -997,6 +1075,13 @@ struct tevent_req *_tstream_tls_connect_send(TALLOC_CTX *mem_ctx,
        talloc_set_destructor(tlss, tstream_tls_destructor);
 
        tlss->plain_stream = plain_stream;
+       tlss->verify_peer = tls_params->verify_peer;
+       if (tls_params->peer_name != NULL) {
+               tlss->peer_name = talloc_strdup(tlss, tls_params->peer_name);
+               if (tevent_req_nomem(tlss->peer_name, req)) {
+                       return tevent_req_post(req, ev);
+               }
+       }
 
        tlss->current_ev = ev;
        tlss->retry_im = tevent_create_immediate(tlss);
@@ -1362,6 +1447,170 @@ static void tstream_tls_retry_handshake(struct tstream_context *stream)
                return;
        }
 
+       if (tlss->verify_peer >= TLS_VERIFY_PEER_CA_ONLY) {
+               unsigned int status = UINT32_MAX;
+               bool ip = true;
+               const char *hostname = NULL;
+#ifndef HAVE_GNUTLS_CERTIFICATE_VERIFY_PEERS3
+               bool need_crt_checks = false;
+#endif
+
+               if (tlss->peer_name != NULL) {
+                       ip = is_ipaddress(tlss->peer_name);
+               }
+
+               if (!ip) {
+                       hostname = tlss->peer_name;
+               }
+
+               if (tlss->verify_peer == TLS_VERIFY_PEER_CA_ONLY) {
+                       hostname = NULL;
+               }
+
+               if (tlss->verify_peer >= TLS_VERIFY_PEER_CA_AND_NAME) {
+                       if (hostname == NULL) {
+                               DEBUG(1,("TLS %s - no hostname available for "
+                                        "verify_peer[%s] and peer_name[%s]\n",
+                                        __location__,
+                                        tls_verify_peer_string(tlss->verify_peer),
+                                        tlss->peer_name));
+                               tlss->error = EINVAL;
+                               tevent_req_error(req, tlss->error);
+                               return;
+                       }
+               }
+
+#ifdef HAVE_GNUTLS_CERTIFICATE_VERIFY_PEERS3
+               ret = gnutls_certificate_verify_peers3(tlss->tls_session,
+                                                      hostname,
+                                                      &status);
+               if (ret != GNUTLS_E_SUCCESS) {
+                       DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret)));
+                       tlss->error = EIO;
+                       tevent_req_error(req, tlss->error);
+                       return;
+               }
+#else /* not HAVE_GNUTLS_CERTIFICATE_VERIFY_PEERS3 */
+               ret = gnutls_certificate_verify_peers2(tlss->tls_session, &status);
+               if (ret != GNUTLS_E_SUCCESS) {
+                       DEBUG(1,("TLS %s - %s\n", __location__, gnutls_strerror(ret)));
+                       tlss->error = EIO;
+                       tevent_req_error(req, tlss->error);
+                       return;
+               }
+
+               if (status == 0) {
+                       if (hostname != NULL) {
+                               need_crt_checks = true;
+                       }
+#ifdef REQUIRE_CERT_TIME_CHECKS
+                       need_crt_checks = true;
+#endif
+               }
+
+               if (need_crt_checks) {
+                       gnutls_x509_crt crt;
+                       const gnutls_datum *cert_list;
+                       unsigned int cert_list_size = 0;
+#ifdef REQUIRE_CERT_TIME_CHECKS
+                       time_t now = time(NULL);
+                       time_t tret = -1;
+#endif
+
+                       cert_list = gnutls_certificate_get_peers(tlss->tls_session,
+                                                                &cert_list_size);
+                       if (cert_list == NULL) {
+                               cert_list_size = 0;
+                       }
+                       if (cert_list_size == 0) {
+                               DEBUG(1,("TLS %s - cert_list_size == 0\n",
+                                        __location__));
+                               tlss->error = EIO;
+                               tevent_req_error(req, tlss->error);
+                               return;
+                       }
+
+                       ret = gnutls_x509_crt_init(&crt);
+                       if (ret != GNUTLS_E_SUCCESS) {
+                               DEBUG(1,("TLS %s - %s\n", __location__,
+                                        gnutls_strerror(ret)));
+                               tlss->error = EIO;
+                               tevent_req_error(req, tlss->error);
+                               return;
+                       }
+                       ret = gnutls_x509_crt_import(crt,
+                                                    &cert_list[0],
+                                                    GNUTLS_X509_FMT_DER);
+                       if (ret != GNUTLS_E_SUCCESS) {
+                               DEBUG(1,("TLS %s - %s\n", __location__,
+                                        gnutls_strerror(ret)));
+                               gnutls_x509_crt_deinit(crt);
+                               tlss->error = EIO;
+                               tevent_req_error(req, tlss->error);
+                               return;
+                       }
+
+                       if (hostname != NULL) {
+                               ret = gnutls_x509_crt_check_hostname(crt,
+                                                                    hostname);
+                               if (ret == 0) {
+                                       status |= GNUTLS_CERT_INVALID;
+                                       status |= GNUTLS_CERT_UNEXPECTED_OWNER;
+                               }
+                       }
+
+#ifndef HAVE_DECL_GNUTLS_CERT_NOT_ACTIVATED
+                       /*
+                        * GNUTLS_CERT_NOT_ACTIVATED is defined by ourself
+                        */
+                       tret = gnutls_x509_crt_get_activation_time(crt);
+                       if ((tret == -1) || (now > tret)) {
+                               status |= GNUTLS_CERT_INVALID;
+                               status |= GNUTLS_CERT_NOT_ACTIVATED;
+                       }
+#endif
+#ifndef HAVE_DECL_GNUTLS_CERT_EXPIRED
+                       /*
+                        * GNUTLS_CERT_EXPIRED is defined by ourself
+                        */
+                       tret = gnutls_certificate_expiration_time_peers(tlss->tls_session);
+                       if ((tret == -1) || (now > tret)) {
+                               status |= GNUTLS_CERT_INVALID;
+                               status |= GNUTLS_CERT_EXPIRED;
+                       }
+#endif
+                       gnutls_x509_crt_deinit(crt);
+               }
+#endif
+
+               if (status != 0) {
+                       DEBUG(1,("TLS %s - check failed for "
+                                "verify_peer[%s] and peer_name[%s] "
+                                "status 0x%x (%s%s%s%s%s%s%s%s)\n",
+                                __location__,
+                                tls_verify_peer_string(tlss->verify_peer),
+                                tlss->peer_name,
+                                status,
+                                status & GNUTLS_CERT_INVALID ? "invalid " : "",
+                                status & GNUTLS_CERT_REVOKED ? "revoked " : "",
+                                status & GNUTLS_CERT_SIGNER_NOT_FOUND ?
+                                       "signer_not_found " : "",
+                                status & GNUTLS_CERT_SIGNER_NOT_CA ?
+                                       "signer_not_ca " : "",
+                                status & GNUTLS_CERT_INSECURE_ALGORITHM ?
+                                       "insecure_algorithm " : "",
+                                status & GNUTLS_CERT_NOT_ACTIVATED ?
+                                       "not_activated " : "",
+                                status & GNUTLS_CERT_EXPIRED ?
+                                       "expired " : "",
+                                status & GNUTLS_CERT_UNEXPECTED_OWNER ?
+                                       "unexptected_owner " : ""));
+                       tlss->error = EINVAL;
+                       tevent_req_error(req, tlss->error);
+                       return;
+               }
+       }
+
        tevent_req_done(req);
 #else /* ENABLE_GNUTLS */
        tevent_req_error(req, ENOSYS);
index 20834092d5f138dfaa29a81079f620f9808f6988..ecde36028801d2e25785daddc38cb96f0ccecdf6 100644 (file)
@@ -53,6 +53,11 @@ def configure(conf):
     conf.CHECK_FUNCS_IN('gnutls_global_init', 'gnutls',
                         headers='gnutls/gnutls.h')
 
+    conf.CHECK_FUNCS_IN('gnutls_certificate_verify_peers3', 'gnutls',
+                        headers='gnutls/gnutls.h')
+    conf.CHECK_DECLS('GNUTLS_CERT_EXPIRED GNUTLS_CERT_NOT_ACTIVATED GNUTLS_CERT_UNEXPECTED_OWNER',
+                     headers='gnutls/gnutls.h gnutls/x509.h')
+
     conf.CHECK_VARIABLE('gnutls_x509_crt_set_version',
                         headers='gnutls/gnutls.h gnutls/x509.h',
                         define='HAVE_GNUTLS_X509_CRT_SET_VERSION',
index 97a83cec4ab38988c98ec4d40c7d1cd607357b2b..152d5b9dbc7cce97ebb06dc8edd723fbfc0b0f13 100644 (file)
@@ -475,6 +475,8 @@ _PUBLIC_ struct composite_context *ldap_connect_send(struct ldap_connection *con
                                                           ca_file,
                                                           crl_file,
                                                           tls_priority,
+                                                          TLS_VERIFY_PEER_NO_CHECK,
+                                                          NULL,
                                                           &state->tls_params);
                        if (!NT_STATUS_IS_OK(status)) {
                                composite_error(result, status);
index 61a22a799444d26f433c53912c7a67c323e1917d..c4842fb8cb64c04344066b378883982f0e15fc55 100644 (file)
@@ -187,6 +187,8 @@ struct tevent_req *dcerpc_pipe_open_roh_send(struct dcecli_connection *conn,
        if (use_tls) {
                status = tstream_tls_params_client(state->roh, NULL, NULL,
                                                   lpcfg_tls_priority(lp_ctx),
+                                                  TLS_VERIFY_PEER_NO_CHECK,
+                                                  NULL,
                                                   &state->tls_params);
                if (!NT_STATUS_IS_OK(status)) {
                        DEBUG(0,("%s: Failed tstream_tls_params_client - %s\n",