From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= Newsgroups: gmane.emacs.devel Subject: [PATCH RFC] GnuTLS: Support TOFU certificate checking. Date: Tue, 7 Oct 2014 23:16:05 +0200 Message-ID: <1412716565-7786-1-git-send-email-toke@toke.dk> NNTP-Posting-Host: plane.gmane.org X-Trace: ger.gmane.org 1412716625 24602 80.91.229.3 (7 Oct 2014 21:17:05 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Tue, 7 Oct 2014 21:17:05 +0000 (UTC) Cc: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Tue Oct 07 23:16:58 2014 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1Xbc7u-0006F2-Bb for ged-emacs-devel@m.gmane.org; Tue, 07 Oct 2014 23:16:58 +0200 Original-Received: from localhost ([::1]:60922 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Xbc7t-0006BN-Gm for ged-emacs-devel@m.gmane.org; Tue, 07 Oct 2014 17:16:57 -0400 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:54164) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Xbc7l-0006B8-VF for emacs-devel@gnu.org; Tue, 07 Oct 2014 17:16:54 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Xbc7e-0002eJ-AF for emacs-devel@gnu.org; Tue, 07 Oct 2014 17:16:49 -0400 Original-Received: from mail2.tohojo.dk ([77.235.48.147]:34297) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Xbc7d-0002dv-V2 for emacs-devel@gnu.org; Tue, 07 Oct 2014 17:16:42 -0400 X-Virus-Scanned: amavisd-new at mail2.tohojo.dk DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=toke.dk; s=201310; t=1412716529; bh=1lu9ccMLFkFPhr3rkEsEyJmmoYxriUXbFeHfQPnjmjw=; h=From:To:Cc:Subject:Date; b=GGjzCBV+f+RMpiQES/W0FkhczHPCf3kqX6AYMNYc4mtMS/LtIHDvvmQeqRzx1ucMM JhJVkcktS4TOBT2IYKKglbf90ZffPjh5xQaGxNEnJOslYBRl5dMZVfZxD1IlKef0F0 yeSf04KxbWgIr/E1HepU+SgJuZT02nGD7kBFQskA= Original-Received: by alrua-x1.borgediget.toke.dk (Postfix, from userid 1000) id E00B02E369; Tue, 7 Oct 2014 23:16:30 +0200 (CEST) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 77.235.48.147 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.devel:175097 Archived-At: This implements rudimentary Trust On First Use certificate checking in gnutls.c. This is useful for protecting against MITM attacks when connecting to servers using self-signed certificates, as well as to guard against rogue or compromised CAs issuing illegitimate certificates that would otherwise be accepted. To test: (require 'gnutls) (setq gnutls-verify-error '((".*" :tofu)) (open-gnutls-stream "test" nil "google.com" 443) ; this should fail To add the certificate to the trust store, execute (in a shell) `gnutls-cli --tofu -p 443 google.com` and answer yes when it asks whether to trust the certificate. Doing so should cause the open to success the next time around. As noted above, this is quite rudimentary as it is right now, and will probably need to be expanded. However, I wanted to solicit some feedback first, and so here's a few questions: 1. Would this be viable to include at all? And if so, is this the right way to go about it? 2. There's currently no way to add a certificate to the store when it's first seen. What's the best way to go about this? I'm not sure how to actually communicate with the user from within gnutls-boot. 3. Currently this uses the global (well, per-user) GnuTLS certificate store. My thought was this makes the most sense (it's still per-port, so the same hostname can have different certificates for different services). But is it? 4. Setting gnutls-verify-error to t does not activate TOFU checking. The reasoning behind this is that TOFU works somewhat differently than the other trust models (CA and hostname matching), and so is a feature that is probably best left for people to explicitly ask for. Is this reasonable? 5. Any other comments. For instance, I have only tested this on Linux, so not sure if I fudged up all the library loading magic for W32... Thanks in advance for looking this over. As an aside, while testing this I found that using customize to set gnutls-verify-error, a doubly-nested list of symbols would end up getting passed to gnutls-boot, which would then subsequently fail all the membership tests on it. This seems to stem from the fact that customize produces list entries of the form '((".*" (:tofu))) while gnutls-negotiate passes the cdr of each matched entry to cl-mapcan. Not sure which should be fixed, so I just set it manually while testing, but thought I'd point it out :) Cheers, -Toke --- lisp/net/gnutls.el | 10 +++++++--- src/gnutls.c | 43 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/lisp/net/gnutls.el b/lisp/net/gnutls.el index 0c650f3..9809b0f 100644 --- a/lisp/net/gnutls.el +++ b/lisp/net/gnutls.el @@ -63,7 +63,8 @@ set this variable to \"normal:-dhe-rsa\"." (const ".*" :tag "Any hostname") regexp) (set (const :trustfiles) - (const :hostname)))))) + (const :hostname) + (const :tofu)))))) (defcustom gnutls-trustfiles '( @@ -123,7 +124,8 @@ GnuTLS connection, including specifying the credential type, trust and key files, and priority string." (gnutls-negotiate :process (open-network-stream name buffer host service) :type 'gnutls-x509pki - :hostname host)) + :hostname host + :service service)) (define-error 'gnutls-error "GnuTLS error") @@ -133,7 +135,7 @@ trust and key files, and priority string." (cl-defun gnutls-negotiate (&rest spec - &key process type hostname priority-string + &key process type hostname service priority-string trustfiles crlfiles keylist min-prime-bits verify-flags verify-error verify-hostname-error &allow-other-keys) @@ -144,6 +146,7 @@ Note arguments are passed CL style, :type TYPE instead of just TYPE. TYPE is `gnutls-x509pki' (default) or `gnutls-anon'. Use nil for the default. PROCESS is a process returned by `open-network-stream'. HOSTNAME is the remote hostname. It must be a valid string. +SERVICE is the remote service. It will be formatted as a string. PRIORITY-STRING is as per the GnuTLS docs, default is \"NORMAL\". TRUSTFILES is a list of CA bundles. It defaults to `gnutls-trustfiles'. CRLFILES is a list of CRL files. @@ -226,6 +229,7 @@ defaults to GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT." (setq params `(:priority ,priority-string :hostname ,hostname + :service ,(format "%s" service) :loglevel ,gnutls-log-level :min-prime-bits ,min-prime-bits :trustfiles ,trustfiles diff --git a/src/gnutls.c b/src/gnutls.c index 5d48f78..a08ec8e 100644 --- a/src/gnutls.c +++ b/src/gnutls.c @@ -43,11 +43,13 @@ static bool gnutls_global_initialized; /* The following are for the property list of `gnutls-boot'. */ static Lisp_Object QCgnutls_bootprop_priority; static Lisp_Object QCgnutls_bootprop_trustfiles; +static Lisp_Object QCgnutls_bootprop_tofu; static Lisp_Object QCgnutls_bootprop_keylist; static Lisp_Object QCgnutls_bootprop_crlfiles; static Lisp_Object QCgnutls_bootprop_callbacks; static Lisp_Object QCgnutls_bootprop_loglevel; static Lisp_Object QCgnutls_bootprop_hostname; +static Lisp_Object QCgnutls_bootprop_service; static Lisp_Object QCgnutls_bootprop_min_prime_bits; static Lisp_Object QCgnutls_bootprop_verify_flags; static Lisp_Object QCgnutls_bootprop_verify_error; @@ -141,6 +143,9 @@ DEF_GNUTLS_FN (void, gnutls_transport_set_push_function, (gnutls_session_t, gnutls_push_func)); DEF_GNUTLS_FN (int, gnutls_x509_crt_check_hostname, (gnutls_x509_crt_t, const char *)); +DEF_GNUTLS_FN (int, gnutls_verify_stored_pubkey, + (const char *, gnutls_tdb_t, const char *,const char *, + gnutls_cerificate_type_t,const gnutls_datum_t *,unsigned int)); DEF_GNUTLS_FN (void, gnutls_x509_crt_deinit, (gnutls_x509_crt_t)); DEF_GNUTLS_FN (int, gnutls_x509_crt_import, (gnutls_x509_crt_t, const gnutls_datum_t *, @@ -202,6 +207,7 @@ init_gnutls_functions (void) LOAD_GNUTLS_FN (library, gnutls_transport_set_pull_function); LOAD_GNUTLS_FN (library, gnutls_transport_set_push_function); LOAD_GNUTLS_FN (library, gnutls_x509_crt_check_hostname); + LOAD_GNUTLS_FN (library, gnutls_verify_stored_pubkey); LOAD_GNUTLS_FN (library, gnutls_x509_crt_deinit); LOAD_GNUTLS_FN (library, gnutls_x509_crt_import); LOAD_GNUTLS_FN (library, gnutls_x509_crt_init); @@ -257,6 +263,7 @@ init_gnutls_functions (void) #endif #define fn_gnutls_transport_set_ptr2 gnutls_transport_set_ptr2 #define fn_gnutls_x509_crt_check_hostname gnutls_x509_crt_check_hostname +#define fn_gnutls_verify_stored_pubkey gnutls_verify_stored_pubkey #define fn_gnutls_x509_crt_deinit gnutls_x509_crt_deinit #define fn_gnutls_x509_crt_import gnutls_x509_crt_import #define fn_gnutls_x509_crt_init gnutls_x509_crt_init @@ -738,6 +745,8 @@ PROPLIST is a property list with the following keys: :hostname is a string naming the remote host. +:service is a port number naming the remote service. + :priority is a GnuTLS priority string, defaults to "NORMAL". :trustfiles is a list of PEM-encoded trust files for `gnutls-x509pki'. @@ -759,7 +768,8 @@ instead. :verify-error is a list of symbols to express verification checks or `t' to do all checks. Currently it can contain `:trustfiles' and -`:hostname' to verify the certificate or the hostname respectively. +`:hostname' to verify the certificate or the hostname respectively, as +well as `:tofu' to turn on Trust On First Use mode. :min-prime-bits is the minimum accepted number of bits the client will accept in Diffie-Hellman key exchange. @@ -795,6 +805,7 @@ one trustfile (usually a CA bundle). */) char const *priority_string_ptr = "NORMAL"; /* default priority string. */ unsigned int peer_verification; char *c_hostname; + char *c_service; /* Placeholders for the property list elements. */ Lisp_Object priority_string; @@ -804,6 +815,7 @@ one trustfile (usually a CA bundle). */) /* Lisp_Object callbacks; */ Lisp_Object loglevel; Lisp_Object hostname; + Lisp_Object service; Lisp_Object verify_error; Lisp_Object prime_bits; @@ -818,6 +830,7 @@ one trustfile (usually a CA bundle). */) error ("Invalid GnuTLS credential type"); hostname = Fplist_get (proplist, QCgnutls_bootprop_hostname); + service = Fplist_get (proplist, QCgnutls_bootprop_service); priority_string = Fplist_get (proplist, QCgnutls_bootprop_priority); trustfiles = Fplist_get (proplist, QCgnutls_bootprop_trustfiles); keylist = Fplist_get (proplist, QCgnutls_bootprop_keylist); @@ -839,6 +852,10 @@ one trustfile (usually a CA bundle). */) error ("gnutls-boot: invalid :hostname parameter (not a string)"); c_hostname = SSDATA (hostname); + if (!STRINGP (service)) + error ("gnutls-boot: invalid :service parameter (not a string)"); + c_service = SSDATA (service); + state = XPROCESS (proc)->gnutls_state; if (TYPE_RANGED_INTEGERP (int, loglevel)) @@ -1141,6 +1158,28 @@ one trustfile (usually a CA bundle). */) c_hostname); } } + + if (!NILP (Fmember (QCgnutls_bootprop_tofu, verify_error))) + { + ret = fn_gnutls_verify_stored_pubkey(NULL, NULL, c_hostname, c_service, GNUTLS_CRT_X509, gnutls_verify_cert_list, 0); + if (ret == GNUTLS_E_NO_CERTIFICATE_FOUND) + { + fn_gnutls_x509_crt_deinit (gnutls_verify_cert); + emacs_gnutls_deinit (proc); + error ("No TOFU trust entry found for hostname \"%s\" and service \"%s\"", c_hostname, c_service); + } + else if (ret == GNUTLS_E_CERTIFICATE_KEY_MISMATCH) + { + fn_gnutls_x509_crt_deinit (gnutls_verify_cert); + emacs_gnutls_deinit (proc); + error ("TOFU trust MISMATCH for hostname \"%s\" and service \"%s\"", c_hostname, c_service); + } + else if (ret < GNUTLS_E_SUCCESS) + { + fn_gnutls_x509_crt_deinit (gnutls_verify_cert); + return gnutls_make_error (ret); + } + } fn_gnutls_x509_crt_deinit (gnutls_verify_cert); } @@ -1189,8 +1228,10 @@ syms_of_gnutls (void) DEFSYM (Qgnutls_anon, "gnutls-anon"); DEFSYM (Qgnutls_x509pki, "gnutls-x509pki"); DEFSYM (QCgnutls_bootprop_hostname, ":hostname"); + DEFSYM (QCgnutls_bootprop_service, ":service"); DEFSYM (QCgnutls_bootprop_priority, ":priority"); DEFSYM (QCgnutls_bootprop_trustfiles, ":trustfiles"); + DEFSYM (QCgnutls_bootprop_tofu, ":tofu"); DEFSYM (QCgnutls_bootprop_keylist, ":keylist"); DEFSYM (QCgnutls_bootprop_crlfiles, ":crlfiles"); DEFSYM (QCgnutls_bootprop_callbacks, ":callbacks"); -- 2.1.2