Update to 7.40.1
[platform/upstream/curl.git] / lib / vtls / gtls.c
index e9f60b5..550a0ed 100644 (file)
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2014, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2015, 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
@@ -32,6 +32,7 @@
 
 #ifdef USE_GNUTLS
 
+#include <gnutls/abstract.h>
 #include <gnutls/gnutls.h>
 #include <gnutls/x509.h>
 
@@ -97,6 +98,14 @@ static bool gtls_inited = FALSE;
 #      define HAS_ALPN
 #    endif
 #  endif
+
+#  if (GNUTLS_VERSION_NUMBER >= 0x03020d)
+#    define HAS_OCSP
+#  endif
+#endif
+
+#ifdef HAS_OCSP
+# include <gnutls/ocsp.h>
 #endif
 
 /*
@@ -306,8 +315,6 @@ static CURLcode handshake(struct connectdata *conn,
         gnutls_record_get_direction(session)?
         ssl_connect_2_writing:ssl_connect_2_reading;
       continue;
-      if(nonblocking)
-        return CURLE_OK;
     }
     else if((rc < 0) && !gnutls_error_is_fatal(rc)) {
       const char *strerr = NULL;
@@ -619,6 +626,7 @@ gtls_connect_step1(struct connectdata *conn,
       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");
@@ -663,6 +671,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,6 +695,62 @@ gtls_connect_step1(struct connectdata *conn,
   return CURLE_OK;
 }
 
+static CURLcode pkp_pin_peer_pubkey(gnutls_x509_crt_t cert,
+                                    const char *pinnedpubkey)
+{
+  /* Scratch */
+  size_t len1 = 0, len2 = 0;
+  unsigned char *buff1 = NULL;
+
+  gnutls_pubkey_t key = NULL;
+
+  /* Result is returned to caller */
+  int ret = 0;
+  CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+
+  /* if a path wasn't specified, don't pin */
+  if(NULL == pinnedpubkey)
+    return CURLE_OK;
+
+  if(NULL == cert)
+    return result;
+
+  do {
+    /* Begin Gyrations to get the public key     */
+    gnutls_pubkey_init(&key);
+
+    ret = gnutls_pubkey_import_x509(key, cert, 0);
+    if(ret < 0)
+      break; /* failed */
+
+    ret = gnutls_pubkey_export(key, GNUTLS_X509_FMT_DER, NULL, &len1);
+    if(ret != GNUTLS_E_SHORT_MEMORY_BUFFER || len1 == 0)
+      break; /* failed */
+
+    buff1 = malloc(len1);
+    if(NULL == buff1)
+      break; /* failed */
+
+    len2 = len1;
+
+    ret = gnutls_pubkey_export(key, GNUTLS_X509_FMT_DER, buff1, &len2);
+    if(ret < 0 || len1 != len2)
+      break; /* failed */
+
+    /* End Gyrations */
+
+    /* The one good exit point */
+    result = Curl_pin_peer_pubkey(pinnedpubkey, buff1, len1);
+  } while(0);
+
+  if(NULL != key)
+    gnutls_pubkey_deinit(key);
+
+  Curl_safefree(buff1);
+
+  return result;
+}
+
 static Curl_recv gtls_recv;
 static Curl_send gtls_send;
 
@@ -698,7 +772,7 @@ gtls_connect_step3(struct connectdata *conn,
   struct SessionHandle *data = conn->data;
   gnutls_session_t session = conn->ssl[sockindex].session;
   int rc;
-  int incache;
+  bool incache;
   void *ssl_sessionid;
 #ifdef HAS_ALPN
   gnutls_datum_t proto;
@@ -766,6 +840,23 @@ 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) {
+      if(verify_status & GNUTLS_CERT_REVOKED)
+        failf(data, "SSL server certificate was REVOKED\n");
+      else
+        failf(data, "SSL server certificate status verification FAILED");
+
+      return CURLE_SSL_INVALIDCERTSTATUS;
+    }
+    else
+      infof(data, "SSL server certificate status verification OK\n");
+  }
+  else
+    infof(data, "SSL server certificate status verification SKIPPED\n");
+#endif
+
   /* initialize an X.509 certificate structure. */
   gnutls_x509_crt_init(&x509_cert);
 
@@ -779,10 +870,12 @@ gtls_connect_step3(struct connectdata *conn,
     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);
+    gnutls_x509_crt_deinit(x509_issuer);
     unload_file(issuerp);
     if(rc <= 0) {
       failf(data, "server certificate issuer check failed (IssuerCert: %s)",
             data->set.ssl.issuercert?data->set.ssl.issuercert:"none");
+      gnutls_x509_crt_deinit(x509_cert);
       return CURLE_SSL_ISSUER_ERROR;
     }
     infof(data,"\t server certificate issuer check OK (Issuer Cert: %s)\n",
@@ -868,6 +961,7 @@ gtls_connect_step3(struct connectdata *conn,
   if(certclock == (time_t)-1) {
     if(data->set.ssl.verifypeer) {
       failf(data, "server cert expiration date verify failed");
+      gnutls_x509_crt_deinit(x509_cert);
       return CURLE_SSL_CONNECT_ERROR;
     }
     else
@@ -877,6 +971,7 @@ gtls_connect_step3(struct connectdata *conn,
     if(certclock < time(NULL)) {
       if(data->set.ssl.verifypeer) {
         failf(data, "server certificate expiration date has passed.");
+        gnutls_x509_crt_deinit(x509_cert);
         return CURLE_PEER_FAILED_VERIFICATION;
       }
       else
@@ -891,6 +986,7 @@ gtls_connect_step3(struct connectdata *conn,
   if(certclock == (time_t)-1) {
     if(data->set.ssl.verifypeer) {
       failf(data, "server cert activation date verify failed");
+      gnutls_x509_crt_deinit(x509_cert);
       return CURLE_SSL_CONNECT_ERROR;
     }
     else
@@ -900,6 +996,7 @@ gtls_connect_step3(struct connectdata *conn,
     if(certclock > time(NULL)) {
       if(data->set.ssl.verifypeer) {
         failf(data, "server certificate not activated yet.");
+        gnutls_x509_crt_deinit(x509_cert);
         return CURLE_PEER_FAILED_VERIFICATION;
       }
       else
@@ -909,6 +1006,16 @@ gtls_connect_step3(struct connectdata *conn,
       infof(data, "\t server certificate activation date OK\n");
   }
 
+  ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY];
+  if(ptr) {
+    result = pkp_pin_peer_pubkey(x509_cert, ptr);
+    if(result != CURLE_OK) {
+      failf(data, "SSL: public key does not match pinned public key!");
+      gnutls_x509_crt_deinit(x509_cert);
+      return result;
+    }
+  }
+
   /* Show:
 
   - ciphers used
@@ -976,7 +1083,7 @@ gtls_connect_step3(struct connectdata *conn,
         conn->negnpn = NPN_HTTP1_1;
       }
     }
-    else {
+    else if(connssl->asked_for_h2) {
       infof(data, "ALPN, server did not agree to a protocol\n");
     }
   }
@@ -1079,12 +1186,12 @@ Curl_gtls_connect(struct connectdata *conn,
                   int sockindex)
 
 {
-  CURLcode retcode;
+  CURLcode result;
   bool done = FALSE;
 
-  retcode = gtls_connect_common(conn, sockindex, FALSE, &done);
-  if(retcode)
-    return retcode;
+  result = gtls_connect_common(conn, sockindex, FALSE, &done);
+  if(result)
+    return result;
 
   DEBUGASSERT(done);
 
@@ -1232,10 +1339,10 @@ static ssize_t gtls_recv(struct connectdata *conn, /* connection data */
   if(ret == GNUTLS_E_REHANDSHAKE) {
     /* BLOCKING call, this is bad but a work-around for now. Fixing this "the
        proper way" takes a whole lot of work. */
-    CURLcode rc = handshake(conn, num, FALSE, FALSE);
-    if(rc)
+    CURLcode result = handshake(conn, num, FALSE, FALSE);
+    if(result)
       /* handshake() writes error message on its own */
-      *curlcode = rc;
+      *curlcode = result;
     else
       *curlcode = CURLE_AGAIN; /* then return as if this was a wouldblock */
     return -1;
@@ -1261,16 +1368,15 @@ size_t Curl_gtls_version(char *buffer, size_t size)
   return snprintf(buffer, size, "GnuTLS/%s", gnutls_check_version(NULL));
 }
 
-int Curl_gtls_seed(struct SessionHandle *data)
+#ifndef USE_GNUTLS_NETTLE
+static int Curl_gtls_seed(struct SessionHandle *data)
 {
   /* we have the "SSL is seeded" boolean static to prevent multiple
      time-consuming seedings in vain */
   static bool ssl_seeded = FALSE;
 
   /* Quickly add a bit of entropy */
-#ifndef USE_GNUTLS_NETTLE
   gcry_fast_random_poll();
-#endif
 
   if(!ssl_seeded || data->set.str[STRING_SSL_RANDOM_FILE] ||
      data->set.str[STRING_SSL_EGDSOCKET]) {
@@ -1284,18 +1390,22 @@ int Curl_gtls_seed(struct SessionHandle *data)
   }
   return 0;
 }
+#endif
 
-void Curl_gtls_random(struct SessionHandle *data,
-                      unsigned char *entropy,
-                      size_t length)
+/* data might be NULL! */
+int Curl_gtls_random(struct SessionHandle *data,
+                     unsigned char *entropy,
+                     size_t length)
 {
 #if defined(USE_GNUTLS_NETTLE)
   (void)data;
   gnutls_rnd(GNUTLS_RND_RANDOM, entropy, length);
 #elif defined(USE_GNUTLS)
-  Curl_gtls_seed(data); /* Initiate the seed if not already done */
+  if(data)
+    Curl_gtls_seed(data); /* Initiate the seed if not already done */
   gcry_randomize(entropy, length, GCRY_STRONG_RANDOM);
 #endif
+  return 0;
 }
 
 void Curl_gtls_md5sum(unsigned char *tmp, /* input */
@@ -1317,4 +1427,13 @@ void Curl_gtls_md5sum(unsigned char *tmp, /* input */
 #endif
 }
 
+bool Curl_gtls_cert_status_request(void)
+{
+#ifdef HAS_OCSP
+  return TRUE;
+#else
+  return FALSE;
+#endif
+}
+
 #endif /* USE_GNUTLS */