Imported Upstream version 7.44.0
[platform/upstream/curl.git] / lib / vtls / nss.c
index dd83a9d..91727c7 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
 #include "select.h"
 #include "vtls.h"
 #include "llist.h"
-
-#define _MPRINTF_REPLACE /* use the internal *printf() functions */
-#include <curl/mprintf.h>
-
+#include "curl_printf.h"
 #include "nssg.h"
 #include <nspr.h>
 #include <nss.h>
 #include <base64.h>
 #include <cert.h>
 #include <prerror.h>
+#include <keyhi.h>        /* for SECKEY_DestroyPublicKey() */
+
+#define NSSVERNUM ((NSS_VMAJOR<<16)|(NSS_VMINOR<<8)|NSS_VPATCH)
+
+#if NSSVERNUM >= 0x030f00 /* 3.15.0 */
+#include <ocsp.h>
+#endif
 
-#include "curl_memory.h"
 #include "rawstr.h"
 #include "warnless.h"
 #include "x509asn1.h"
 
-/* The last #include file should be: */
+/* The last #include files should be: */
+#include "curl_memory.h"
 #include "memdebug.h"
 
 #define SSL_DIR "/etc/pki/nssdb"
@@ -639,6 +643,34 @@ static SECStatus nss_auth_cert_hook(void *arg, PRFileDesc *fd, PRBool checksig,
                                     PRBool isServer)
 {
   struct connectdata *conn = (struct connectdata *)arg;
+
+#ifdef SSL_ENABLE_OCSP_STAPLING
+  if(conn->data->set.ssl.verifystatus) {
+    SECStatus cacheResult;
+
+    const SECItemArray *csa = SSL_PeerStapledOCSPResponses(fd);
+    if(!csa) {
+      failf(conn->data, "Invalid OCSP response");
+      return SECFailure;
+    }
+
+    if(csa->len == 0) {
+      failf(conn->data, "No OCSP response received");
+      return SECFailure;
+    }
+
+    cacheResult = CERT_CacheOCSPResponseFromSideChannel(
+      CERT_GetDefaultCertDB(), SSL_PeerCertificate(fd),
+      PR_Now(), &csa->items[0], arg
+    );
+
+    if(cacheResult != SECSuccess) {
+      failf(conn->data, "Invalid OCSP response");
+      return cacheResult;
+    }
+  }
+#endif
+
   if(!conn->data->set.ssl.verifypeer) {
     infof(conn->data, "skipping SSL peer certificate verification\n");
     return SECSuccess;
@@ -652,7 +684,6 @@ static SECStatus nss_auth_cert_hook(void *arg, PRFileDesc *fd, PRBool checksig,
  */
 static void HandshakeCallback(PRFileDesc *sock, void *arg)
 {
-#ifdef USE_NGHTTP2
   struct connectdata *conn = (struct connectdata*) arg;
   unsigned int buflenmax = 50;
   unsigned char buf[50];
@@ -668,8 +699,7 @@ static void HandshakeCallback(PRFileDesc *sock, void *arg)
     switch(state) {
     case SSL_NEXT_PROTO_NO_SUPPORT:
     case SSL_NEXT_PROTO_NO_OVERLAP:
-      if(connssl->asked_for_h2)
-        infof(conn->data, "TLS, neither ALPN nor NPN succeeded\n");
+      infof(conn->data, "ALPN/NPN, server did not agree to a protocol\n");
       return;
 #ifdef SSL_ENABLE_ALPN
     case SSL_NEXT_PROTO_SELECTED:
@@ -681,22 +711,80 @@ static void HandshakeCallback(PRFileDesc *sock, void *arg)
       break;
     }
 
+#ifdef USE_NGHTTP2
     if(buflen == NGHTTP2_PROTO_VERSION_ID_LEN &&
-       memcmp(NGHTTP2_PROTO_VERSION_ID, buf, NGHTTP2_PROTO_VERSION_ID_LEN)
-       == 0) {
-      conn->negnpn = NPN_HTTP2;
+       !memcmp(NGHTTP2_PROTO_VERSION_ID, buf, NGHTTP2_PROTO_VERSION_ID_LEN)) {
+      conn->negnpn = CURL_HTTP_VERSION_2_0;
     }
-    else if(buflen == ALPN_HTTP_1_1_LENGTH && memcmp(ALPN_HTTP_1_1, buf,
-                                                     ALPN_HTTP_1_1_LENGTH)) {
-      conn->negnpn = NPN_HTTP1_1;
+    else
+#endif
+    if(buflen == ALPN_HTTP_1_1_LENGTH &&
+       !memcmp(ALPN_HTTP_1_1, buf, ALPN_HTTP_1_1_LENGTH)) {
+      conn->negnpn = CURL_HTTP_VERSION_1_1;
     }
   }
-#else
-  (void)sock;
-  (void)arg;
-#endif
 }
 
+#if NSSVERNUM >= 0x030f04 /* 3.15.4 */
+static SECStatus CanFalseStartCallback(PRFileDesc *sock, void *client_data,
+                                       PRBool *canFalseStart)
+{
+  struct connectdata *conn = client_data;
+  struct SessionHandle *data = conn->data;
+
+  SSLChannelInfo channelInfo;
+  SSLCipherSuiteInfo cipherInfo;
+
+  SECStatus rv;
+  PRBool negotiatedExtension;
+
+  *canFalseStart = PR_FALSE;
+
+  if(SSL_GetChannelInfo(sock, &channelInfo, sizeof(channelInfo)) != SECSuccess)
+    return SECFailure;
+
+  if(SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo,
+                            sizeof(cipherInfo)) != SECSuccess)
+    return SECFailure;
+
+  /* Prevent version downgrade attacks from TLS 1.2, and avoid False Start for
+   * TLS 1.3 and later. See https://bugzilla.mozilla.org/show_bug.cgi?id=861310
+   */
+  if(channelInfo.protocolVersion != SSL_LIBRARY_VERSION_TLS_1_2)
+    goto end;
+
+  /* Only allow ECDHE key exchange algorithm.
+   * See https://bugzilla.mozilla.org/show_bug.cgi?id=952863 */
+  if(cipherInfo.keaType != ssl_kea_ecdh)
+    goto end;
+
+  /* Prevent downgrade attacks on the symmetric cipher. We do not allow CBC
+   * mode due to BEAST, POODLE, and other attacks on the MAC-then-Encrypt
+   * design. See https://bugzilla.mozilla.org/show_bug.cgi?id=1109766 */
+  if(cipherInfo.symCipher != ssl_calg_aes_gcm)
+    goto end;
+
+  /* Enforce ALPN or NPN to do False Start, as an indicator of server
+   * compatibility. */
+  rv = SSL_HandshakeNegotiatedExtension(sock, ssl_app_layer_protocol_xtn,
+                                        &negotiatedExtension);
+  if(rv != SECSuccess || !negotiatedExtension) {
+    rv = SSL_HandshakeNegotiatedExtension(sock, ssl_next_proto_nego_xtn,
+                                          &negotiatedExtension);
+  }
+
+  if(rv != SECSuccess || !negotiatedExtension)
+    goto end;
+
+  *canFalseStart = PR_TRUE;
+
+  infof(data, "Trying TLS False Start\n");
+
+end:
+  return SECSuccess;
+}
+#endif
+
 static void display_cert_info(struct SessionHandle *data,
                               CERTCertificate *cert)
 {
@@ -830,7 +918,7 @@ static SECStatus BadCertHandler(void *arg, PRFileDesc *sock)
 static SECStatus check_issuer_cert(PRFileDesc *sock,
                                    char *issuer_nickname)
 {
-  CERTCertificate *cert,*cert_issuer,*issuer;
+  CERTCertificate *cert, *cert_issuer, *issuer;
   SECStatus res=SECSuccess;
   void *proto_win = NULL;
 
@@ -841,7 +929,7 @@ static SECStatus check_issuer_cert(PRFileDesc *sock,
   */
 
   cert = SSL_PeerCertificate(sock);
-  cert_issuer = CERT_FindCertIssuer(cert,PR_Now(),certUsageObjectSigner);
+  cert_issuer = CERT_FindCertIssuer(cert, PR_Now(), certUsageObjectSigner);
 
   proto_win = SSL_RevealPinArg(sock);
   issuer = PK11_FindCertFromNickname(issuer_nickname, proto_win);
@@ -858,6 +946,53 @@ static SECStatus check_issuer_cert(PRFileDesc *sock,
   return res;
 }
 
+static CURLcode cmp_peer_pubkey(struct ssl_connect_data *connssl,
+                                const char *pinnedpubkey)
+{
+  CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+  struct SessionHandle *data = connssl->data;
+  CERTCertificate *cert;
+
+  if(!pinnedpubkey)
+    /* no pinned public key specified */
+    return CURLE_OK;
+
+  /* get peer certificate */
+  cert = SSL_PeerCertificate(connssl->handle);
+  if(cert) {
+    /* extract public key from peer certificate */
+    SECKEYPublicKey *pubkey = CERT_ExtractPublicKey(cert);
+    if(pubkey) {
+      /* encode the public key as DER */
+      SECItem *cert_der = PK11_DEREncodePublicKey(pubkey);
+      if(cert_der) {
+        /* compare the public key with the pinned public key */
+        result = Curl_pin_peer_pubkey(pinnedpubkey,
+                                      cert_der->data,
+                                      cert_der->len);
+        SECITEM_FreeItem(cert_der, PR_TRUE);
+      }
+      SECKEY_DestroyPublicKey(pubkey);
+    }
+    CERT_DestroyCertificate(cert);
+  }
+
+  /* report the resulting status */
+  switch(result) {
+  case CURLE_OK:
+    infof(data, "pinned public key verified successfully!\n");
+    break;
+  case CURLE_SSL_PINNEDPUBKEYNOTMATCH:
+    failf(data, "failed to verify pinned public key");
+    break;
+  default:
+    /* OOM, etc. */
+    break;
+  }
+
+  return result;
+}
+
 /**
  *
  * Callback to pick the SSL client certificate.
@@ -999,6 +1134,7 @@ static PRStatus nspr_io_close(PRFileDesc *fd)
   return close_fn(fd);
 }
 
+/* data might be NULL */
 static CURLcode nss_init_core(struct SessionHandle *data, const char *cert_dir)
 {
   NSSInitParameters initparams;
@@ -1036,6 +1172,7 @@ static CURLcode nss_init_core(struct SessionHandle *data, const char *cert_dir)
   return CURLE_SSL_CACERT_BADFILE;
 }
 
+/* data might be NULL */
 static CURLcode nss_init(struct SessionHandle *data)
 {
   char *cert_dir;
@@ -1114,12 +1251,14 @@ int Curl_nss_init(void)
   return 1;
 }
 
+/* data might be NULL */
 CURLcode Curl_nss_force_init(struct SessionHandle *data)
 {
   CURLcode result;
   if(!nss_initlock) {
-    failf(data, "unable to initialize NSS, curl_global_init() should have "
-                "been called with CURL_GLOBAL_SSL or CURL_GLOBAL_ALL");
+    if(data)
+      failf(data, "unable to initialize NSS, curl_global_init() should have "
+                  "been called with CURL_GLOBAL_SSL or CURL_GLOBAL_ALL");
     return CURLE_FAILED_INIT;
   }
 
@@ -1210,10 +1349,8 @@ void Curl_nss_close(struct connectdata *conn, int sockindex)
        * authentication data from a previous connection. */
       SSL_InvalidateSession(connssl->handle);
 
-    if(connssl->client_nickname != NULL) {
-      free(connssl->client_nickname);
-      connssl->client_nickname = NULL;
-    }
+    free(connssl->client_nickname);
+    connssl->client_nickname = NULL;
     /* destroy all NSS objects in order to avoid failure of NSS shutdown */
     Curl_llist_destroy(connssl->obj_list, NULL);
     connssl->obj_list = NULL;
@@ -1224,15 +1361,6 @@ void Curl_nss_close(struct connectdata *conn, int sockindex)
   }
 }
 
-/*
- * This function is called when the 'data' struct is going away. Close
- * down everything and free all resources!
- */
-void Curl_nss_close_all(struct SessionHandle *data)
-{
-  (void)data;
-}
-
 /* return true if NSS can provide error code (and possibly msg) for the
    error */
 static bool is_nss_error(CURLcode err)
@@ -1429,16 +1557,6 @@ static CURLcode nss_setup_connect(struct connectdata *conn, int sockindex)
     SSL_LIBRARY_VERSION_TLS_1_0   /* max */
   };
 
-#ifdef USE_NGHTTP2
-#if defined(SSL_ENABLE_NPN) || defined(SSL_ENABLE_ALPN)
-  unsigned int alpn_protos_len = NGHTTP2_PROTO_VERSION_ID_LEN +
-      ALPN_HTTP_1_1_LENGTH + 2;
-  unsigned char alpn_protos[NGHTTP2_PROTO_VERSION_ID_LEN + ALPN_HTTP_1_1_LENGTH
-      + 2];
-  int cur = 0;
-#endif
-#endif
-
   connssl->data = data;
 
   /* list of all NSS objects we need to destroy in Curl_nss_close() */
@@ -1618,43 +1736,57 @@ static CURLcode nss_setup_connect(struct connectdata *conn, int sockindex)
     SSL_SetPKCS11PinArg(connssl->handle, data->set.str[STRING_KEY_PASSWD]);
   }
 
-#ifdef USE_NGHTTP2
-  if(data->set.httpversion == CURL_HTTP_VERSION_2_0) {
+#ifdef SSL_ENABLE_OCSP_STAPLING
+  if(data->set.ssl.verifystatus) {
+    if(SSL_OptionSet(connssl->handle, SSL_ENABLE_OCSP_STAPLING, PR_TRUE)
+        != SECSuccess)
+      goto error;
+  }
+#endif
+
 #ifdef SSL_ENABLE_NPN
-    if(data->set.ssl_enable_npn) {
-      if(SSL_OptionSet(connssl->handle, SSL_ENABLE_NPN, PR_TRUE) != SECSuccess)
-        goto error;
-    }
+  if(SSL_OptionSet(connssl->handle, SSL_ENABLE_NPN, data->set.ssl_enable_npn
+        ? PR_TRUE : PR_FALSE) != SECSuccess)
+    goto error;
 #endif
 
 #ifdef SSL_ENABLE_ALPN
-    if(data->set.ssl_enable_alpn) {
-      if(SSL_OptionSet(connssl->handle, SSL_ENABLE_ALPN, PR_TRUE)
-          != SECSuccess)
-        goto error;
-    }
+  if(SSL_OptionSet(connssl->handle, SSL_ENABLE_ALPN, data->set.ssl_enable_alpn
+        ? PR_TRUE : PR_FALSE) != SECSuccess)
+    goto error;
+#endif
+
+#if NSSVERNUM >= 0x030f04 /* 3.15.4 */
+  if(data->set.ssl.falsestart) {
+    if(SSL_OptionSet(connssl->handle, SSL_ENABLE_FALSE_START, PR_TRUE)
+        != SECSuccess)
+      goto error;
+
+    if(SSL_SetCanFalseStartCallback(connssl->handle, CanFalseStartCallback,
+        conn) != SECSuccess)
+      goto error;
+  }
 #endif
 
 #if defined(SSL_ENABLE_NPN) || defined(SSL_ENABLE_ALPN)
-    if(data->set.ssl_enable_npn || data->set.ssl_enable_alpn) {
-      alpn_protos[cur] = NGHTTP2_PROTO_VERSION_ID_LEN;
-      cur++;
-      memcpy(&alpn_protos[cur], NGHTTP2_PROTO_VERSION_ID,
+  if(data->set.ssl_enable_npn || data->set.ssl_enable_alpn) {
+    int cur = 0;
+    unsigned char protocols[128];
+
+#ifdef USE_NGHTTP2
+    if(data->set.httpversion == CURL_HTTP_VERSION_2_0) {
+      protocols[cur++] = NGHTTP2_PROTO_VERSION_ID_LEN;
+      memcpy(&protocols[cur], NGHTTP2_PROTO_VERSION_ID,
           NGHTTP2_PROTO_VERSION_ID_LEN);
       cur += NGHTTP2_PROTO_VERSION_ID_LEN;
-      alpn_protos[cur] = ALPN_HTTP_1_1_LENGTH;
-      cur++;
-      memcpy(&alpn_protos[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
-
-      if(SSL_SetNextProtoNego(connssl->handle, alpn_protos, alpn_protos_len)
-          != SECSuccess)
-        goto error;
-      connssl->asked_for_h2 = TRUE;
-    }
-    else {
-      infof(data, "SSL, can't negotiate HTTP/2.0 with neither NPN nor ALPN\n");
     }
 #endif
+    protocols[cur++] = ALPN_HTTP_1_1_LENGTH;
+    memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
+    cur += ALPN_HTTP_1_1_LENGTH;
+
+    if(SSL_SetNextProtoNego(connssl->handle, protocols, cur) != SECSuccess)
+      goto error;
   }
 #endif
 
@@ -1715,7 +1847,7 @@ static CURLcode nss_do_connect(struct connectdata *conn, int sockindex)
     }
 
     if(SECFailure == ret) {
-      infof(data,"SSL certificate issuer check failed\n");
+      infof(data, "SSL certificate issuer check failed\n");
       result = CURLE_SSL_ISSUER_ERROR;
       goto error;
     }
@@ -1724,6 +1856,11 @@ static CURLcode nss_do_connect(struct connectdata *conn, int sockindex)
     }
   }
 
+  result = cmp_peer_pubkey(connssl, data->set.str[STRING_SSL_PINNEDPUBLICKEY]);
+  if(result)
+    /* status already printed */
+    goto error;
+
   return CURLE_OK;
 
 error:
@@ -1870,6 +2007,7 @@ size_t Curl_nss_version(char *buffer, size_t size)
   return snprintf(buffer, size, "NSS/%s", NSS_VERSION);
 }
 
+/* data might be NULL */
 int Curl_nss_seed(struct SessionHandle *data)
 {
   /* make sure that NSS is initialized */
@@ -1881,14 +2019,11 @@ int Curl_nss_random(struct SessionHandle *data,
                     unsigned char *entropy,
                     size_t length)
 {
-  if(data)
-    Curl_nss_seed(data);  /* Initiate the seed if not already done */
+  Curl_nss_seed(data);  /* Initiate the seed if not already done */
 
-  if(SECSuccess != PK11_GenerateRandom(entropy, curlx_uztosi(length))) {
-    /* no way to signal a failure from here, we have to abort */
-    failf(data, "PK11_GenerateRandom() failed, calling abort()...");
-    abort();
-  }
+  if(SECSuccess != PK11_GenerateRandom(entropy, curlx_uztosi(length)))
+    /* signal a failure */
+    return -1;
 
   return 0;
 }
@@ -1906,4 +2041,34 @@ void Curl_nss_md5sum(unsigned char *tmp, /* input */
   PK11_DestroyContext(MD5pw, PR_TRUE);
 }
 
+void Curl_nss_sha256sum(const unsigned char *tmp, /* input */
+                     size_t tmplen,
+                     unsigned char *sha256sum, /* output */
+                     size_t sha256len)
+{
+  PK11Context *SHA256pw = PK11_CreateDigestContext(SEC_OID_SHA256);
+  unsigned int SHA256out;
+
+  PK11_DigestOp(SHA256pw, tmp, curlx_uztoui(tmplen));
+  PK11_DigestFinal(SHA256pw, sha256sum, &SHA256out, curlx_uztoui(sha256len));
+  PK11_DestroyContext(SHA256pw, PR_TRUE);
+}
+
+bool Curl_nss_cert_status_request(void)
+{
+#ifdef SSL_ENABLE_OCSP_STAPLING
+  return TRUE;
+#else
+  return FALSE;
+#endif
+}
+
+bool Curl_nss_false_start(void) {
+#if NSSVERNUM >= 0x030f04 /* 3.15.4 */
+  return TRUE;
+#else
+  return FALSE;
+#endif
+}
+
 #endif /* USE_NSS */