unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* [PATCH RFC] GnuTLS: Support TOFU certificate checking.
@ 2014-10-07 21:16 Toke Høiland-Jørgensen
  2014-10-07 21:35 ` Lars Magne Ingebrigtsen
  2014-10-08 14:52 ` Ted Zlatanov
  0 siblings, 2 replies; 35+ messages in thread
From: Toke Høiland-Jørgensen @ 2014-10-07 21:16 UTC (permalink / raw)
  To: emacs-devel; +Cc: Toke Høiland-Jørgensen

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



^ permalink raw reply related	[flat|nested] 35+ messages in thread

end of thread, other threads:[~2014-10-09 13:17 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-10-07 21:16 [PATCH RFC] GnuTLS: Support TOFU certificate checking Toke Høiland-Jørgensen
2014-10-07 21:35 ` Lars Magne Ingebrigtsen
2014-10-07 21:55   ` Toke Høiland-Jørgensen
2014-10-08 11:53     ` Lars Magne Ingebrigtsen
2014-10-08 11:58       ` Lars Magne Ingebrigtsen
2014-10-08 12:10       ` Toke Høiland-Jørgensen
2014-10-08 12:18         ` Lars Magne Ingebrigtsen
2014-10-08 12:39           ` Toke Høiland-Jørgensen
2014-10-08 12:42             ` Lars Magne Ingebrigtsen
2014-10-08 12:53           ` Eli Zaretskii
2014-10-08 12:56             ` Lars Magne Ingebrigtsen
2014-10-08 13:03               ` Eli Zaretskii
2014-10-08 13:06                 ` Lars Magne Ingebrigtsen
2014-10-08 13:17                   ` Eli Zaretskii
2014-10-08 13:25                     ` Lars Magne Ingebrigtsen
2014-10-08 13:38                       ` Eli Zaretskii
2014-10-08 13:47                         ` Lars Magne Ingebrigtsen
2014-10-08 13:59                           ` Toke Høiland-Jørgensen
2014-10-08 14:05                             ` Lars Magne Ingebrigtsen
2014-10-08 14:01                           ` Eli Zaretskii
2014-10-08 14:09                             ` Lars Magne Ingebrigtsen
2014-10-08 14:11                               ` Eli Zaretskii
2014-10-08 14:56                               ` Ted Zlatanov
2014-10-08 15:31                                 ` Lars Magne Ingebrigtsen
2014-10-08 15:37                                   ` Ted Zlatanov
2014-10-09  2:43                                     ` Stephen J. Turnbull
2014-10-09 13:17                                       ` Ted Zlatanov
2014-10-08 13:28                   ` Toke Høiland-Jørgensen
2014-10-08 14:52 ` Ted Zlatanov
2014-10-08 15:19   ` Toke Høiland-Jørgensen
2014-10-08 15:45     ` Ted Zlatanov
2014-10-08 16:09       ` Toke Høiland-Jørgensen
2014-10-08 16:52     ` Lars Magne Ingebrigtsen
2014-10-08 17:07       ` Toke Høiland-Jørgensen
2014-10-09 13:10         ` Ted Zlatanov

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).