Imported Upstream version 7.48.0
[platform/upstream/curl.git] / lib / vtls / gtls.c
index 5d4e48a..1fef54b 100644 (file)
@@ -5,11 +5,11 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2014, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2016, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
- * are also available at http://curl.haxx.se/docs/copyright.html.
+ * are also available at https://curl.haxx.se/docs/copyright.html.
  *
  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
  * copies of the Software, and permit persons to whom the Software is
@@ -39,6 +39,7 @@
 #ifdef USE_GNUTLS_NETTLE
 #include <gnutls/crypto.h>
 #include <nettle/md5.h>
+#include <nettle/sha2.h>
 #else
 #include <gcrypt.h>
 #endif
 #include "select.h"
 #include "rawstr.h"
 #include "warnless.h"
-
-#define _MPRINTF_REPLACE /* use our functions only */
-#include <curl/mprintf.h>
+#include "x509asn1.h"
+#include "curl_printf.h"
 #include "curl_memory.h"
 /* The last #include file should be: */
 #include "memdebug.h"
 
 /*
  Some hackish cast macros based on:
- http://library.gnome.org/devel/glib/unstable/glib-Type-Conversion-Macros.html
+ https://developer.gnome.org/glib/unstable/glib-Type-Conversion-Macros.html
 */
 #ifndef GNUTLS_POINTER_TO_INT_CAST
 #define GNUTLS_POINTER_TO_INT_CAST(p) ((int) (long) (p))
@@ -92,14 +92,23 @@ static bool gtls_inited = FALSE;
 #    define GNUTLS_MAPS_WINSOCK_ERRORS 1
 #  endif
 
-#  ifdef USE_NGHTTP2
-#    undef HAS_ALPN
-#    if (GNUTLS_VERSION_NUMBER >= 0x030200)
-#      define HAS_ALPN
-#    endif
+#  if (GNUTLS_VERSION_NUMBER >= 0x030200)
+#    define HAS_ALPN
+#  endif
+
+#  if (GNUTLS_VERSION_NUMBER >= 0x03020d)
+#    define HAS_OCSP
+#  endif
+
+#  if (GNUTLS_VERSION_NUMBER >= 0x030306)
+#    define HAS_CAPATH
 #  endif
 #endif
 
+#ifdef HAS_OCSP
+# include <gnutls/ocsp.h>
+#endif
+
 /*
  * Custom push and pull callback functions used by GNU TLS to read and write
  * to the socket.  These functions are simple wrappers to send() and recv()
@@ -204,7 +213,7 @@ static void showtime(struct SessionHandle *data,
 
   snprintf(data->state.buffer,
            BUFSIZE,
-           "\t %s: %s, %02d %s %4d %02d:%02d:%02d GMT\n",
+           "\t %s: %s, %02d %s %4d %02d:%02d:%02d GMT",
            text,
            Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
            tm->tm_mday,
@@ -223,7 +232,7 @@ static gnutls_datum_t load_file (const char *file)
   long filelen;
   void *ptr;
 
-  if(!(f = fopen(file, "r")))
+  if(!(f = fopen(file, "rb")))
     return loaded_file;
   if(fseek(f, 0, SEEK_END) != 0
      || (filelen = ftell(f)) < 0
@@ -319,7 +328,8 @@ static CURLcode handshake(struct connectdata *conn,
       if(strerr == NULL)
         strerr = gnutls_strerror(rc);
 
-      failf(data, "gnutls_handshake() warning: %s", strerr);
+      infof(data, "gnutls_handshake() warning: %s\n", strerr);
+      continue;
     }
     else if(rc < 0) {
       const char *strerr = NULL;
@@ -392,10 +402,6 @@ gtls_connect_step1(struct connectdata *conn,
   const char* prioritylist;
   const char *err = NULL;
 #endif
-#ifdef HAS_ALPN
-  int protocols_size = 2;
-  gnutls_datum_t protocols[2];
-#endif
 
   if(conn->ssl[sockindex].state == ssl_connection_complete)
     /* to make us tolerant against being called more than once for the
@@ -463,6 +469,32 @@ gtls_connect_step1(struct connectdata *conn,
             rc, data->set.ssl.CAfile);
   }
 
+#ifdef HAS_CAPATH
+  if(data->set.ssl.CApath) {
+    /* set the trusted CA cert directory */
+    rc = gnutls_certificate_set_x509_trust_dir(conn->ssl[sockindex].cred,
+                                                data->set.ssl.CApath,
+                                                GNUTLS_X509_FMT_PEM);
+    if(rc < 0) {
+      infof(data, "error reading ca cert file %s (%s)\n",
+            data->set.ssl.CAfile, gnutls_strerror(rc));
+      if(data->set.ssl.verifypeer)
+        return CURLE_SSL_CACERT_BADFILE;
+    }
+    else
+      infof(data, "found %d certificates in %s\n",
+            rc, data->set.ssl.CApath);
+  }
+#endif
+
+#ifdef CURL_CA_FALLBACK
+  /* use system ca certificate store as fallback */
+  if(data->set.ssl.verifypeer &&
+     !(data->set.ssl.CAfile || data->set.ssl.CApath)) {
+    gnutls_certificate_set_x509_system_trust(conn->ssl[sockindex].cred);
+  }
+#endif
+
   if(data->set.ssl.CRLfile) {
     /* set the CRL list file */
     rc = gnutls_certificate_set_x509_crl_file(conn->ssl[sockindex].cred,
@@ -609,33 +641,67 @@ gtls_connect_step1(struct connectdata *conn,
 #endif
 
 #ifdef HAS_ALPN
-  if(data->set.httpversion == CURL_HTTP_VERSION_2_0) {
-    if(data->set.ssl_enable_alpn) {
-      protocols[0].data = NGHTTP2_PROTO_VERSION_ID;
-      protocols[0].size = NGHTTP2_PROTO_VERSION_ID_LEN;
-      protocols[1].data = ALPN_HTTP_1_1;
-      protocols[1].size = ALPN_HTTP_1_1_LENGTH;
-      gnutls_alpn_set_protocols(session, protocols, protocols_size, 0);
-      infof(data, "ALPN, offering %s, %s\n", NGHTTP2_PROTO_VERSION_ID,
-            ALPN_HTTP_1_1);
-      connssl->asked_for_h2 = TRUE;
-    }
-    else {
-      infof(data, "SSL, can't negotiate HTTP/2.0 without ALPN\n");
+  if(data->set.ssl_enable_alpn) {
+    int cur = 0;
+    gnutls_datum_t protocols[2];
+
+#ifdef USE_NGHTTP2
+    if(data->set.httpversion >= CURL_HTTP_VERSION_2) {
+      protocols[cur].data = (unsigned char *)NGHTTP2_PROTO_VERSION_ID;
+      protocols[cur].size = NGHTTP2_PROTO_VERSION_ID_LEN;
+      cur++;
+      infof(data, "ALPN, offering %s\n", NGHTTP2_PROTO_VERSION_ID);
     }
+#endif
+
+    protocols[cur].data = (unsigned char *)ALPN_HTTP_1_1;
+    protocols[cur].size = ALPN_HTTP_1_1_LENGTH;
+    cur++;
+    infof(data, "ALPN, offering %s\n", ALPN_HTTP_1_1);
+
+    gnutls_alpn_set_protocols(session, protocols, cur, 0);
   }
 #endif
 
   if(data->set.str[STRING_CERT]) {
-    if(gnutls_certificate_set_x509_key_file(
-         conn->ssl[sockindex].cred,
-         data->set.str[STRING_CERT],
-         data->set.str[STRING_KEY] ?
-         data->set.str[STRING_KEY] : data->set.str[STRING_CERT],
-         do_file_type(data->set.str[STRING_CERT_TYPE]) ) !=
-       GNUTLS_E_SUCCESS) {
-      failf(data, "error reading X.509 key or certificate file");
+    if(data->set.str[STRING_KEY_PASSWD]) {
+#if HAVE_GNUTLS_CERTIFICATE_SET_X509_KEY_FILE2
+      const unsigned int supported_key_encryption_algorithms =
+        GNUTLS_PKCS_USE_PKCS12_3DES | GNUTLS_PKCS_USE_PKCS12_ARCFOUR |
+        GNUTLS_PKCS_USE_PKCS12_RC2_40 | GNUTLS_PKCS_USE_PBES2_3DES |
+        GNUTLS_PKCS_USE_PBES2_AES_128 | GNUTLS_PKCS_USE_PBES2_AES_192 |
+        GNUTLS_PKCS_USE_PBES2_AES_256;
+      rc = gnutls_certificate_set_x509_key_file2(
+           conn->ssl[sockindex].cred,
+           data->set.str[STRING_CERT],
+           data->set.str[STRING_KEY] ?
+           data->set.str[STRING_KEY] : data->set.str[STRING_CERT],
+           do_file_type(data->set.str[STRING_CERT_TYPE]),
+           data->set.str[STRING_KEY_PASSWD],
+           supported_key_encryption_algorithms);
+      if(rc != GNUTLS_E_SUCCESS) {
+        failf(data,
+              "error reading X.509 potentially-encrypted key file: %s",
+              gnutls_strerror(rc));
+        return CURLE_SSL_CONNECT_ERROR;
+      }
+#else
+      failf(data, "gnutls lacks support for encrypted key files");
       return CURLE_SSL_CONNECT_ERROR;
+#endif
+    }
+    else {
+      rc = gnutls_certificate_set_x509_key_file(
+           conn->ssl[sockindex].cred,
+           data->set.str[STRING_CERT],
+           data->set.str[STRING_KEY] ?
+           data->set.str[STRING_KEY] : data->set.str[STRING_CERT],
+           do_file_type(data->set.str[STRING_CERT_TYPE]) );
+      if(rc != GNUTLS_E_SUCCESS) {
+        failf(data, "error reading X.509 key or certificate file: %s",
+              gnutls_strerror(rc));
+        return CURLE_SSL_CONNECT_ERROR;
+      }
     }
   }
 
@@ -644,13 +710,21 @@ gtls_connect_step1(struct connectdata *conn,
   if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) {
     rc = gnutls_credentials_set(session, GNUTLS_CRD_SRP,
                                 conn->ssl[sockindex].srp_client_cred);
-    if(rc != GNUTLS_E_SUCCESS)
+    if(rc != GNUTLS_E_SUCCESS) {
       failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc));
+      return CURLE_SSL_CONNECT_ERROR;
+    }
   }
   else
 #endif
+  {
     rc = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE,
                                 conn->ssl[sockindex].cred);
+    if(rc != GNUTLS_E_SUCCESS) {
+      failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc));
+      return CURLE_SSL_CONNECT_ERROR;
+    }
+  }
 
   /* set the connection handle (file descriptor for the socket) */
   gnutls_transport_set_ptr(session,
@@ -663,6 +737,16 @@ gtls_connect_step1(struct connectdata *conn,
   /* lowat must be set to zero when using custom push and pull functions. */
   gnutls_transport_set_lowat(session, 0);
 
+#ifdef HAS_OCSP
+  if(data->set.ssl.verifystatus) {
+    rc = gnutls_ocsp_status_request_enable_client(session, NULL, 0, NULL);
+    if(rc != GNUTLS_E_SUCCESS) {
+      failf(data, "gnutls_ocsp_status_request_enable_client() failed: %d", rc);
+      return CURLE_SSL_CONNECT_ERROR;
+    }
+  }
+#endif
+
   /* This might be a reconnect, so we check for a session ID in the cache
      to speed up things */
 
@@ -677,7 +761,8 @@ gtls_connect_step1(struct connectdata *conn,
   return CURLE_OK;
 }
 
-static CURLcode pkp_pin_peer_pubkey(gnutls_x509_crt_t cert,
+static CURLcode pkp_pin_peer_pubkey(struct SessionHandle *data,
+                                    gnutls_x509_crt_t cert,
                                     const char *pinnedpubkey)
 {
   /* Scratch */
@@ -722,7 +807,7 @@ static CURLcode pkp_pin_peer_pubkey(gnutls_x509_crt_t cert,
     /* End Gyrations */
 
     /* The one good exit point */
-    result = Curl_pin_peer_pubkey(pinnedpubkey, buff1, len1);
+    result = Curl_pin_peer_pubkey(data, pinnedpubkey, buff1, len1);
   } while(0);
 
   if(NULL != key)
@@ -742,8 +827,8 @@ gtls_connect_step3(struct connectdata *conn,
 {
   unsigned int cert_list_size;
   const gnutls_datum_t *chainp;
-  unsigned int verify_status;
-  gnutls_x509_crt_t x509_cert,x509_issuer;
+  unsigned int verify_status = 0;
+  gnutls_x509_crt_t x509_cert, x509_issuer;
   gnutls_datum_t issuerp;
   char certbuf[256] = ""; /* big enough? */
   size_t size;
@@ -761,6 +846,16 @@ gtls_connect_step3(struct connectdata *conn,
 #endif
   CURLcode result = CURLE_OK;
 
+  gnutls_protocol_t version = gnutls_protocol_get_version(session);
+
+  /* the name of the cipher suite used, e.g. ECDHE_RSA_AES_256_GCM_SHA384. */
+  ptr = gnutls_cipher_suite_get_name(gnutls_kx_get(session),
+                                     gnutls_cipher_get(session),
+                                     gnutls_mac_get(session));
+
+  infof(data, "SSL connection using %s / %s\n",
+        gnutls_protocol_get_name(version), ptr);
+
   /* This function will return the peer's raw certificate (chain) as sent by
      the peer. These certificates are in raw format (DER encoded for
      X.509). In case of a X.509 then a certificate list may be present. The
@@ -791,6 +886,23 @@ gtls_connect_step3(struct connectdata *conn,
     infof(data, "\t common name: WARNING couldn't obtain\n");
   }
 
+  if(data->set.ssl.certinfo && chainp) {
+    unsigned int i;
+
+    result = Curl_ssl_init_certinfo(data, cert_list_size);
+    if(result)
+      return result;
+
+    for(i = 0; i < cert_list_size; i++) {
+      const char *beg = (const char *) chainp[i].data;
+      const char *end = beg + chainp[i].size;
+
+      result = Curl_extract_certinfo(conn, i, beg, end);
+      if(result)
+        return result;
+    }
+  }
+
   if(data->set.ssl.verifypeer) {
     /* This function will try to verify the peer's certificate and return its
        status (trusted, invalid etc.). The value of status should be one or
@@ -822,6 +934,111 @@ gtls_connect_step3(struct connectdata *conn,
   else
     infof(data, "\t server certificate verification SKIPPED\n");
 
+#ifdef HAS_OCSP
+  if(data->set.ssl.verifystatus) {
+    if(gnutls_ocsp_status_request_is_checked(session, 0) == 0) {
+      gnutls_datum_t status_request;
+      gnutls_ocsp_resp_t ocsp_resp;
+
+      gnutls_ocsp_cert_status_t status;
+      gnutls_x509_crl_reason_t reason;
+
+      rc = gnutls_ocsp_status_request_get(session, &status_request);
+
+      infof(data, "\t server certificate status verification FAILED\n");
+
+      if(rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
+        failf(data, "No OCSP response received");
+        return CURLE_SSL_INVALIDCERTSTATUS;
+      }
+
+      if(rc < 0) {
+        failf(data, "Invalid OCSP response received");
+        return CURLE_SSL_INVALIDCERTSTATUS;
+      }
+
+      gnutls_ocsp_resp_init(&ocsp_resp);
+
+      rc = gnutls_ocsp_resp_import(ocsp_resp, &status_request);
+      if(rc < 0) {
+        failf(data, "Invalid OCSP response received");
+        return CURLE_SSL_INVALIDCERTSTATUS;
+      }
+
+      rc = gnutls_ocsp_resp_get_single(ocsp_resp, 0, NULL, NULL, NULL, NULL,
+                                       &status, NULL, NULL, NULL, &reason);
+
+      switch(status) {
+      case GNUTLS_OCSP_CERT_GOOD:
+        break;
+
+      case GNUTLS_OCSP_CERT_REVOKED: {
+        const char *crl_reason;
+
+        switch(reason) {
+          default:
+          case GNUTLS_X509_CRLREASON_UNSPECIFIED:
+            crl_reason = "unspecified reason";
+            break;
+
+          case GNUTLS_X509_CRLREASON_KEYCOMPROMISE:
+            crl_reason = "private key compromised";
+            break;
+
+          case GNUTLS_X509_CRLREASON_CACOMPROMISE:
+            crl_reason = "CA compromised";
+            break;
+
+          case GNUTLS_X509_CRLREASON_AFFILIATIONCHANGED:
+            crl_reason = "affiliation has changed";
+            break;
+
+          case GNUTLS_X509_CRLREASON_SUPERSEDED:
+            crl_reason = "certificate superseded";
+            break;
+
+          case GNUTLS_X509_CRLREASON_CESSATIONOFOPERATION:
+            crl_reason = "operation has ceased";
+            break;
+
+          case GNUTLS_X509_CRLREASON_CERTIFICATEHOLD:
+            crl_reason = "certificate is on hold";
+            break;
+
+          case GNUTLS_X509_CRLREASON_REMOVEFROMCRL:
+            crl_reason = "will be removed from delta CRL";
+            break;
+
+          case GNUTLS_X509_CRLREASON_PRIVILEGEWITHDRAWN:
+            crl_reason = "privilege withdrawn";
+            break;
+
+          case GNUTLS_X509_CRLREASON_AACOMPROMISE:
+            crl_reason = "AA compromised";
+            break;
+        }
+
+        failf(data, "Server certificate was revoked: %s", crl_reason);
+        break;
+      }
+
+      default:
+      case GNUTLS_OCSP_CERT_UNKNOWN:
+        failf(data, "Server certificate status is unknown");
+        break;
+      }
+
+      gnutls_ocsp_resp_deinit(ocsp_resp);
+
+      return CURLE_SSL_INVALIDCERTSTATUS;
+    }
+    else
+      infof(data, "\t server certificate status verification OK\n");
+  }
+  else
+    infof(data, "\t server certificate status verification SKIPPED\n");
+#endif
+
   /* initialize an X.509 certificate structure. */
   gnutls_x509_crt_init(&x509_cert);
 
@@ -834,7 +1051,7 @@ gtls_connect_step3(struct connectdata *conn,
     gnutls_x509_crt_init(&x509_issuer);
     issuerp = load_file(data->set.ssl.issuercert);
     gnutls_x509_crt_import(x509_issuer, &issuerp, GNUTLS_X509_FMT_PEM);
-    rc = gnutls_x509_crt_check_issuer(x509_cert,x509_issuer);
+    rc = gnutls_x509_crt_check_issuer(x509_cert, x509_issuer);
     gnutls_x509_crt_deinit(x509_issuer);
     unload_file(issuerp);
     if(rc <= 0) {
@@ -843,7 +1060,7 @@ gtls_connect_step3(struct connectdata *conn,
       gnutls_x509_crt_deinit(x509_cert);
       return CURLE_SSL_ISSUER_ERROR;
     }
-    infof(data,"\t server certificate issuer check OK (Issuer Cert: %s)\n",
+    infof(data, "\t server certificate issuer check OK (Issuer Cert: %s)\n",
           data->set.ssl.issuercert?data->set.ssl.issuercert:"none");
   }
 
@@ -973,7 +1190,7 @@ gtls_connect_step3(struct connectdata *conn,
 
   ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY];
   if(ptr) {
-    result = pkp_pin_peer_pubkey(x509_cert, ptr);
+    result = pkp_pin_peer_pubkey(data, x509_cert, ptr);
     if(result != CURLE_OK) {
       failf(data, "SSL: public key does not match pinned public key!");
       gnutls_x509_crt_deinit(x509_cert);
@@ -983,7 +1200,6 @@ gtls_connect_step3(struct connectdata *conn,
 
   /* Show:
 
-  - ciphers used
   - subject
   - start date
   - expire date
@@ -1023,14 +1239,6 @@ gtls_connect_step3(struct connectdata *conn,
   /* the *_get_name() says "NULL" if GNUTLS_COMP_NULL is returned */
   infof(data, "\t compression: %s\n", ptr);
 
-  /* the name of the cipher used. ie 3DES. */
-  ptr = gnutls_cipher_get_name(gnutls_cipher_get(session));
-  infof(data, "\t cipher: %s\n", ptr);
-
-  /* the MAC algorithms name. ie SHA1 */
-  ptr = gnutls_mac_get_name(gnutls_mac_get(session));
-  infof(data, "\t MAC: %s\n", ptr);
-
 #ifdef HAS_ALPN
   if(data->set.ssl_enable_alpn) {
     rc = gnutls_alpn_get_selected_protocol(session, &proto);
@@ -1038,19 +1246,21 @@ gtls_connect_step3(struct connectdata *conn,
       infof(data, "ALPN, server accepted to use %.*s\n", proto.size,
           proto.data);
 
+#ifdef USE_NGHTTP2
       if(proto.size == NGHTTP2_PROTO_VERSION_ID_LEN &&
-        memcmp(NGHTTP2_PROTO_VERSION_ID, proto.data,
-        NGHTTP2_PROTO_VERSION_ID_LEN) == 0) {
-        conn->negnpn = NPN_HTTP2;
+         !memcmp(NGHTTP2_PROTO_VERSION_ID, proto.data,
+                 NGHTTP2_PROTO_VERSION_ID_LEN)) {
+        conn->negnpn = CURL_HTTP_VERSION_2;
       }
-      else if(proto.size == ALPN_HTTP_1_1_LENGTH && memcmp(ALPN_HTTP_1_1,
-          proto.data, ALPN_HTTP_1_1_LENGTH) == 0) {
-        conn->negnpn = NPN_HTTP1_1;
+      else
+#endif
+      if(proto.size == ALPN_HTTP_1_1_LENGTH &&
+         !memcmp(ALPN_HTTP_1_1, proto.data, ALPN_HTTP_1_1_LENGTH)) {
+        conn->negnpn = CURL_HTTP_VERSION_1_1;
       }
     }
-    else if(connssl->asked_for_h2) {
+    else
       infof(data, "ALPN, server did not agree to a protocol\n");
-    }
   }
 #endif
 
@@ -1182,12 +1392,6 @@ static ssize_t gtls_send(struct connectdata *conn,
   return rc;
 }
 
-void Curl_gtls_close_all(struct SessionHandle *data)
-{
-  /* FIX: make the OpenSSL code more generic and use parts of it here */
-  (void)data;
-}
-
 static void close_one(struct connectdata *conn,
                       int idx)
 {
@@ -1392,4 +1596,32 @@ void Curl_gtls_md5sum(unsigned char *tmp, /* input */
 #endif
 }
 
+void Curl_gtls_sha256sum(const unsigned char *tmp, /* input */
+                      size_t tmplen,
+                      unsigned char *sha256sum, /* output */
+                      size_t sha256len)
+{
+#if defined(USE_GNUTLS_NETTLE)
+  struct sha256_ctx SHA256pw;
+  sha256_init(&SHA256pw);
+  sha256_update(&SHA256pw, (unsigned int)tmplen, tmp);
+  sha256_digest(&SHA256pw, (unsigned int)sha256len, sha256sum);
+#elif defined(USE_GNUTLS)
+  gcry_md_hd_t SHA256pw;
+  gcry_md_open(&SHA256pw, GCRY_MD_SHA256, 0);
+  gcry_md_write(SHA256pw, tmp, tmplen);
+  memcpy(sha256sum, gcry_md_read (SHA256pw, 0), sha256len);
+  gcry_md_close(SHA256pw);
+#endif
+}
+
+bool Curl_gtls_cert_status_request(void)
+{
+#ifdef HAS_OCSP
+  return TRUE;
+#else
+  return FALSE;
+#endif
+}
+
 #endif /* USE_GNUTLS */