Verify the TLS connection's peer.
authorWerner Koch <wk@gnupg.org>
Thu, 3 Apr 2014 11:33:28 +0000 (13:33 +0200)
committerWerner Koch <wk@gnupg.org>
Thu, 3 Apr 2014 11:33:28 +0000 (13:33 +0200)
* src/http.c (http_session_s): Add fields verify and servername.
(tls_ca_certlist): New.
(http_register_tls_ca): New.
(http_session_new): Set the CA certs into the credentials.
(send_request): Store the servername.
(http_verify_server_credentials): New.
* src/t-http.c (main): Register CAs.
(verify_callback): Call the new verify function.
* src/tlssupport.c (verify_callback): Ditto.
* src/tls-ca.pem: New.
--

We should implement a better system than to read the CA certs from the
file every time.  Keeping default credentials object would thus be
useful.

tls-ca.pem has certificates for stripe.com.  They use different root
CA and even a 1014 bit one.  The whole PKIX is anyway broken, so who
cares.  I considered to check just the fingerprint of the actual
certificate but that won't allow for an easy certificate replacement

.gitignore
src/Makefile.am
src/http.c
src/http.h
src/t-http.c
src/tls-ca.pem [new file with mode: 0644]
src/tlssupport.c

index f7ca90a..17dd524 100644 (file)
@@ -32,3 +32,4 @@
 /src/stripe.testkey
 /src/t-connection
 /src/out2
+/payproc-*.tar.bz2
index 9337b37..4fe27bd 100644 (file)
@@ -16,7 +16,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, see <http://www.gnu.org/licenses/>.
 
-EXTRA_DIST = cJSON.readme
+EXTRA_DIST = cJSON.readme tls-ca.pem
 
 
 bin_PROGRAMS = payprocd
index 9683ff7..9e1caef 100644 (file)
@@ -88,6 +88,7 @@
 #endif
 #ifdef HTTP_USE_GNUTLS
 # include <gnutls/gnutls.h>
+# include <gnutls/x509.h>
 #endif /*HTTP_USE_GNUTLS*/
 #ifdef HTTP_USE_POLARSSL
 # error Support for PolarSSL has not yet been added
@@ -217,14 +218,18 @@ struct cookie_s
 };
 typedef struct cookie_s *cookie_t;
 
-static gpg_error_t (*tls_callback) (http_t, http_session_t, int);
-
 /* The session object. */
 struct http_session_s
 {
 #ifdef HTTP_USE_GNUTLS
   gnutls_certificate_credentials_t certcred;
   gnutls_session_t tls_session;
+  struct {
+    int done;      /* Verifciation has been done.  */
+    int rc;        /* GnuTLS verification return code.  */
+    unsigned int status; /* Verification status.  */
+  } verify;
+  char *servername; /* Malloced server name.  */
 #else
   int dummy;
 #endif
@@ -263,6 +268,12 @@ struct http_context_s
 };
 
 
+/* The global callback for the verification fucntion.  */
+static gpg_error_t (*tls_callback) (http_t, http_session_t, int);
+
+/* The list of files with trusted CA certificates.  */
+static strlist_t tls_ca_certlist;
+
 
 \f
 #if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
@@ -462,6 +473,20 @@ http_register_tls_callback (gpg_error_t (*cb)(http_t, http_session_t, int))
 }
 
 
+/* Register a CA certificate for future use.  The certificate is
+   expected to be in FNAME.  PEM format is assume if FNAME has a
+   suffix of ".pem" */
+void
+http_register_tls_ca (const char *fname)
+{
+  strlist_t sl;
+
+  sl = add_to_strlist (&tls_ca_certlist, fname);
+  if (*sl->d && !strcmp (sl->d + strlen (sl->d) - 4, ".pem"))
+    sl->flags = 1;
+}
+
+
 /* Create a new session object which is currently used to enable TLS
    support.  It may eventually allow reusing existing connections.  */
 gpg_error_t
@@ -480,6 +505,7 @@ http_session_new (http_session_t *r_session, const char *tls_priority)
   {
     const char *errpos;
     int rc;
+    strlist_t sl;
 
     rc = gnutls_certificate_allocate_credentials (&sess->certcred);
     if (rc < 0)
@@ -490,6 +516,16 @@ http_session_new (http_session_t *r_session, const char *tls_priority)
         goto leave;
       }
 
+    for (sl = tls_ca_certlist; sl; sl = sl->next)
+      {
+        rc = gnutls_certificate_set_x509_trust_file
+          (sess->certcred, sl->d,
+           (sl->flags & 1)? GNUTLS_X509_FMT_PEM : GNUTLS_X509_FMT_DER);
+        if (rc < 0)
+          log_info ("setting CA from file '%s' failed: %s\n",
+                    sl->d, gnutls_strerror (rc));
+      }
+
     rc = gnutls_init (&sess->tls_session, GNUTLS_CLIENT);
     if (rc < 0)
       {
@@ -544,6 +580,7 @@ http_session_release (http_session_t sess)
     gnutls_deinit (sess->tls_session);
   if (sess->certcred)
     gnutls_certificate_free_credentials (sess->certcred);
+  xfree (sess->servername);
 #endif /*HTTP_USE_GNUTLS*/
 
   xfree (sess);
@@ -1265,6 +1302,14 @@ send_request (http_t hd, const char *auth,
     {
       int rc;
 
+      xfree (hd->session->servername);
+      hd->session->servername = xtrystrdup (server);
+      if (!hd->session->servername)
+        {
+          err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+          return err;
+        }
+
       rc = gnutls_server_name_set (hd->session->tls_session,
                                    GNUTLS_NAME_DNS,
                                    server, strlen (server));
@@ -1369,6 +1414,7 @@ send_request (http_t hd, const char *auth,
 
       if (tls_callback)
         {
+          hd->session->verify.done = 0;
           err = tls_callback (hd, hd->session, 0);
           if (err)
             {
@@ -2295,3 +2341,107 @@ cookie_close (void *cookie)
   xfree (c);
   return 0;
 }
+
+
+
+\f
+/* Verify the credentials of the server.  Returns 0 on success and
+   store the result in the session object.  */
+gpg_error_t
+http_verify_server_credentials (http_session_t sess)
+{
+#ifdef HTTP_USE_GNUTLS
+  static const char const errprefix[] = "TLS verification of peer failed";
+  int rc;
+  unsigned int status;
+  const char *hostname;
+  const gnutls_datum_t *certlist;
+  unsigned int certlistlen;
+  gnutls_x509_crt_t cert;
+
+  sess->verify.done = 1;
+  sess->verify.status = 0;
+  sess->verify.rc = GNUTLS_E_CERTIFICATE_ERROR;
+
+  if (gnutls_certificate_type_get (sess->tls_session) != GNUTLS_CRT_X509)
+    {
+      log_error ("%s: %s\n", errprefix, "not an X.509 certificate");
+      sess->verify.rc = GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE;
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  rc = gnutls_certificate_verify_peers2 (sess->tls_session, &status);
+  if (rc)
+    {
+      log_error ("%s: %s\n", errprefix, gnutls_strerror (rc));
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+  if (status)
+    {
+      log_error ("%s: status=0x%04x\n", errprefix, status);
+      sess->verify.status = status;
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  hostname = sess->servername;
+  if (!hostname || !strchr (hostname, '.'))
+    {
+      log_error ("%s: %s\n", errprefix, "hostname missing");
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  certlist = gnutls_certificate_get_peers (sess->tls_session, &certlistlen);
+  if (!certlistlen)
+    {
+      log_error ("%s: %s\n", errprefix, "server did not send a certificate");
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  /* log_debug ("Server sent %u certs\n", certlistlen); */
+  /* { */
+  /*   int i; */
+  /*   char fname[50]; */
+  /*   FILE *fp; */
+
+  /*   for (i=0; i < certlistlen; i++) */
+  /*     { */
+  /*       snprintf (fname, sizeof fname, "xc_%d.der", i); */
+  /*       fp = fopen (fname, "wb"); */
+  /*       if (!fp) */
+  /*         log_fatal ("Failed to create '%s'\n", fname); */
+  /*       if (fwrite (certlist[i].data, certlist[i].size, 1, fp) != 1) */
+  /*         log_fatal ("Error writing to '%s'\n", fname); */
+  /*       fclose (fp); */
+  /*     } */
+  /* } */
+
+  rc = gnutls_x509_crt_init (&cert);
+  if (rc < 0)
+    {
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  rc = gnutls_x509_crt_import (cert, &certlist[0], GNUTLS_X509_FMT_DER);
+  if (rc < 0)
+    {
+      log_error ("%s: %s: %s\n", errprefix, "error importing certificate",
+                 gnutls_strerror (rc));
+      gnutls_x509_crt_deinit (cert);
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  if (!gnutls_x509_crt_check_hostname (cert, hostname))
+    {
+      log_error ("%s: %s\n", errprefix, "hostname does not match");
+      gnutls_x509_crt_deinit (cert);
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  gnutls_x509_crt_deinit (cert);
+  sess->verify.rc = 0;
+  return 0;  /* Verification succeeded.  */
+#else /*!HTTP_USE_GNUTLS*/
+  (void)sess;
+  return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+#endif
+}
index 3912d79..894de36 100644 (file)
@@ -91,6 +91,7 @@ struct http_context_s;
 typedef struct http_context_s *http_t;
 
 void http_register_tls_callback (gpg_error_t (*cb)(http_t,http_session_t,int));
+void http_register_tls_ca (const char *fname);
 
 gpg_error_t http_session_new (http_session_t *r_session,
                               const char *tls_priority);
@@ -135,6 +136,7 @@ estream_t http_get_write_ptr (http_t hd);
 unsigned int http_get_status_code (http_t hd);
 const char *http_get_header (http_t hd, const char *name);
 const char **http_get_header_names (http_t hd);
+gpg_error_t http_verify_server_credentials (http_session_t sess);
 
 char *http_escape_string (const char *string, const char *specials);
 char *http_escape_data (const void *data, size_t datalen, const char *specials);
index 69bd7dc..55d0d7c 100644 (file)
@@ -43,7 +43,7 @@
 
 #ifdef HTTP_USE_GNUTLS
 # include <gnutls/gnutls.h>
-static gnutls_dh_params_t dh_params;
+/* static gnutls_dh_params_t dh_params; */
 #endif /*HTTP_USE_GNUTLS*/
 
 
@@ -97,10 +97,8 @@ static gpg_error_t
 verify_callback (http_t hd, http_session_t session, int reserved)
 {
   (void)hd;
-  (void)session;
   (void)reserved;
-  log_info ("verification of certificates skipped\n");
-  return 0;
+  return http_verify_server_credentials (session);
 }
 
 
@@ -137,6 +135,9 @@ main (int argc, char **argv)
   if (rc)
     log_error ("gnutls_global_init failed: %s\n", gnutls_strerror (rc));
 
+  http_register_tls_callback (verify_callback);
+  http_register_tls_ca ("tls-ca.pem");
+
   err = http_session_new (&session, NULL);
   if (err)
     log_error ("http_session_new failed: %s\n", gpg_strerror (err));
@@ -158,7 +159,6 @@ main (int argc, char **argv)
   /* gnutls_global_set_log_level (2); */
 
 #endif /*HTTP_USE_GNUTLS*/
-  http_register_tls_callback (verify_callback);
 
   rc = http_parse_uri (&uri, *argv, 1);
   if (rc)
diff --git a/src/tls-ca.pem b/src/tls-ca.pem
new file mode 100644 (file)
index 0000000..1a9ec03
--- /dev/null
@@ -0,0 +1,73 @@
+Issuer ...: /CN=Baltimore CyberTrust Root/OU=CyberTrust/O=Baltimore/C=IE
+Serial ...: 020000B9
+Subject ..: /CN=Baltimore CyberTrust Root/OU=CyberTrust/O=Baltimore/C=IE
+
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
+RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
+VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
+DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
+ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
+VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
+mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
+IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
+mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
+XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
+dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
+jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
+BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
+DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
+9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
+jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
+Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
+ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
+R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
+-----END CERTIFICATE-----
+
+Issuer ...: /CN=DigiCert High Assurance EV Root CA/OU=www.digicert.com/O=DigiCert Inc/C=US
+Serial ...: 02AC5C266A0B409B8F0B79F2AE462577
+Subject ..: /CN=DigiCert High Assurance EV Root CA/OU=www.digicert.com/O=DigiCert Inc/C=US
+
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
+PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
+xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
+Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
+hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
+EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
+FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
+nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
+eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
+hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
+Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
+vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
++OkuE6N36B9K
+-----END CERTIFICATE-----
+
+Issuer ...: /CN=GTE CyberTrust Global Root/OU=GTE CyberTrust Solutions, Inc./O=GTE Corporation/C=US
+Serial ...: 01A5
+Subject ..: /CN=GTE CyberTrust Global Root/OU=GTE CyberTrust Solutions, Inc./O=GTE Corporation/C=US
+
+-----BEGIN CERTIFICATE-----
+MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD
+VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv
+bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv
+b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH
+iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS
+r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4
+04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r
+GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9
+3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P
+lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
+-----END CERTIFICATE-----
index ec0ba05..f75c30f 100644 (file)
@@ -39,10 +39,8 @@ static gpg_error_t
 verify_callback (http_t hd, http_session_t session, int reserved)
 {
   (void)hd;
-  (void)session;
   (void)reserved;
-  log_info ("verification of certificates skipped\n");
-  return 0;
+  return http_verify_server_credentials (session);
 }
 
 
@@ -57,6 +55,7 @@ init_tls_subsystem (void)
     log_fatal ("gnutls_global_init failed: %s\n", gnutls_strerror (rc));
 
   http_register_tls_callback (verify_callback);
+  http_register_tls_ca ("/etc/payproc/tls-ca.pem");
 }
 
 /* Deinitialize the TLS subsystem.  Thisis intended to be run from an