Преглед на файлове

Initial NSS support for TLS.

This is enough to get a chutney network to bootstrap, though a bunch
of work remains.
Nick Mathewson преди 6 години
родител
ревизия
5205c7fd90
променени са 7 файла, в които са добавени 656 реда и са изтрити 306 реда
  1. 122 0
      src/lib/tls/tortls.c
  2. 21 2
      src/lib/tls/tortls.h
  3. 4 12
      src/lib/tls/tortls_internal.h
  4. 487 134
      src/lib/tls/tortls_nss.c
  5. 3 134
      src/lib/tls/tortls_openssl.c
  6. 12 5
      src/lib/tls/tortls_st.h
  7. 7 19
      src/test/test_tortls.c

+ 122 - 0
src/lib/tls/tortls.c

@@ -4,6 +4,7 @@
 /* See LICENSE for licensing information */
 
 #define TORTLS_PRIVATE
+#define TOR_X509_PRIVATE
 #include "lib/tls/x509.h"
 #include "lib/tls/x509_internal.h"
 #include "lib/tls/tortls.h"
@@ -14,6 +15,8 @@
 #include "lib/crypt_ops/crypto_rsa.h"
 #include "lib/crypt_ops/crypto_rand.h"
 
+#include <time.h>
+
 /** Global TLS contexts. We keep them here because nobody else needs
  * to touch them.
  *
@@ -31,6 +34,26 @@ tor_tls_context_get(int is_server)
   return is_server ? server_tls_context : client_tls_context;
 }
 
+/** Convert an errno (or a WSAerrno on windows) into a TOR_TLS_* error
+ * code. */
+int
+tor_errno_to_tls_error(int e)
+{
+  switch (e) {
+    case SOCK_ERRNO(ECONNRESET): // most common
+      return TOR_TLS_ERROR_CONNRESET;
+    case SOCK_ERRNO(ETIMEDOUT):
+      return TOR_TLS_ERROR_TIMEOUT;
+    case SOCK_ERRNO(EHOSTUNREACH):
+    case SOCK_ERRNO(ENETUNREACH):
+      return TOR_TLS_ERROR_NO_ROUTE;
+    case SOCK_ERRNO(ECONNREFUSED):
+      return TOR_TLS_ERROR_CONNREFUSED; // least common
+    default:
+      return TOR_TLS_ERROR_MISC;
+  }
+}
+
 /** Set *<b>link_cert_out</b> and *<b>id_cert_out</b> to the link certificate
  * and ID certificate that we're currently using for our V3 in-protocol
  * handshake's certificate chain.  If <b>server</b> is true, provide the certs
@@ -334,3 +357,102 @@ tor_tls_is_server(tor_tls_t *tls)
   tor_assert(tls);
   return tls->isServer;
 }
+
+/** Release resources associated with a TLS object.  Does not close the
+ * underlying file descriptor.
+ */
+void
+tor_tls_free_(tor_tls_t *tls)
+{
+  if (!tls)
+    return;
+  tor_assert(tls->ssl);
+  {
+    size_t r,w;
+    tor_tls_get_n_raw_bytes(tls,&r,&w); /* ensure written_by_tls is updated */
+  }
+  tor_tls_impl_free_(tls->ssl);
+  tls->ssl = NULL;
+#ifdef ENABLE_OPENSSL
+  tls->negotiated_callback = NULL;
+#endif
+  if (tls->context)
+    tor_tls_context_decref(tls->context);
+  tor_free(tls->address);
+  tls->magic = 0x99999999;
+  tor_free(tls);
+}
+
+/** If the provided tls connection is authenticated and has a
+ * certificate chain that is currently valid and signed, then set
+ * *<b>identity_key</b> to the identity certificate's key and return
+ * 0.  Else, return -1 and log complaints with log-level <b>severity</b>.
+ */
+int
+tor_tls_verify(int severity, tor_tls_t *tls, crypto_pk_t **identity)
+{
+  tor_x509_cert_impl_t *cert = NULL, *id_cert = NULL;
+  tor_x509_cert_t *peer_x509 = NULL, *id_x509 = NULL;
+  tor_assert(tls);
+  tor_assert(identity);
+  int rv = -1;
+
+  try_to_extract_certs_from_tls(severity, tls, &cert, &id_cert);
+  if (!cert)
+    goto done;
+  if (!id_cert) {
+    log_fn(severity,LD_PROTOCOL,"No distinct identity certificate found");
+    goto done;
+  }
+  peer_x509 = tor_x509_cert_new(cert);
+  id_x509 = tor_x509_cert_new(id_cert);
+  cert = id_cert = NULL; /* Prevent double-free */
+
+  if (! tor_tls_cert_is_valid(severity, peer_x509, id_x509, time(NULL), 0)) {
+    goto done;
+  }
+
+  *identity = tor_tls_cert_get_key(id_x509);
+  rv = 0;
+
+ done:
+  if (cert)
+    tor_x509_cert_impl_free_(cert);
+  if (id_cert)
+    tor_x509_cert_impl_free_(id_cert);
+  tor_x509_cert_free(peer_x509);
+  tor_x509_cert_free(id_x509);
+
+  return rv;
+}
+
+/** Check whether the certificate set on the connection <b>tls</b> is expired
+ * give or take <b>past_tolerance</b> seconds, or not-yet-valid give or take
+ * <b>future_tolerance</b> seconds. Return 0 for valid, -1 for failure.
+ *
+ * NOTE: you should call tor_tls_verify before tor_tls_check_lifetime.
+ */
+int
+tor_tls_check_lifetime(int severity, tor_tls_t *tls,
+                       time_t now,
+                       int past_tolerance, int future_tolerance)
+{
+  tor_x509_cert_t *cert;
+  int r = -1;
+
+  if (!(cert = tor_tls_get_peer_cert(tls)))
+    goto done;
+
+  if (tor_x509_check_cert_lifetime_internal(severity, cert->cert, now,
+                                            past_tolerance,
+                                            future_tolerance) < 0)
+    goto done;
+
+  r = 0;
+ done:
+  tor_x509_cert_free(cert);
+  /* Not expected to get invoked */
+  tls_log_errors(tls, LOG_WARN, LD_NET, "checking certificate lifetime");
+
+  return r;
+}

+ 21 - 2
src/lib/tls/tortls.h

@@ -13,10 +13,25 @@
 
 #include "lib/crypt_ops/crypto_rsa.h"
 #include "lib/testsupport/testsupport.h"
+#include "lib/net/nettypes.h"
 
 /* Opaque structure to hold a TLS connection. */
 typedef struct tor_tls_t tor_tls_t;
 
+#ifdef TORTLS_PRIVATE
+#ifdef ENABLE_OPENSSL
+struct ssl_st;
+struct ssl_ctx_st;
+struct ssl_session_st;
+typedef struct ssl_ctx_st tor_tls_context_impl_t;
+typedef struct ssl_st tor_tls_impl_t;
+#else
+struct PRFileDesc;
+typedef struct PRFileDesc tor_tls_context_impl_t;
+typedef struct PRFileDesc tor_tls_impl_t;
+#endif
+#endif
+
 struct tor_x509_cert_t;
 
 /* Possible return values for most tor_tls_* functions. */
@@ -73,7 +88,7 @@ int tor_tls_context_init(unsigned flags,
 void tor_tls_context_incref(tor_tls_context_t *ctx);
 void tor_tls_context_decref(tor_tls_context_t *ctx);
 tor_tls_context_t *tor_tls_context_get(int is_server);
-tor_tls_t *tor_tls_new(int sock, int is_server);
+tor_tls_t *tor_tls_new(tor_socket_t sock, int is_server);
 void tor_tls_set_logged_address(tor_tls_t *tls, const char *address);
 void tor_tls_set_renegotiate_callback(tor_tls_t *tls,
                                       void (*cb)(tor_tls_t *, void *arg),
@@ -121,13 +136,17 @@ MOCK_DECL(int,tor_tls_export_key_material,(
                      size_t context_len,
                      const char *label));
 
+#ifdef ENABLE_OPENSSL
 /* Log and abort if there are unhandled TLS errors in OpenSSL's error stack.
  */
 #define check_no_tls_errors() check_no_tls_errors_(__FILE__,__LINE__)
-
 void check_no_tls_errors_(const char *fname, int line);
+
 void tor_tls_log_one_error(tor_tls_t *tls, unsigned long err,
                            int severity, int domain, const char *doing);
+#else
+#define check_no_tls_errors() STMT_NIL
+#endif
 
 int tor_tls_get_my_certs(int server,
                          const struct tor_x509_cert_t **link_cert_out,

+ 4 - 12
src/lib/tls/tortls_internal.h

@@ -6,15 +6,11 @@
 #ifndef TORTLS_INTERNAL_H
 #define TORTLS_INTERNAL_H
 
-#ifdef ENABLE_OPENSSL
-struct ssl_st;
-struct ssl_ctx_st;
-struct ssl_session_st;
-#endif
-
 int tor_errno_to_tls_error(int e);
+#ifdef ENABLE_OPENSSL
 int tor_tls_get_error(tor_tls_t *tls, int r, int extra,
                   const char *doing, int severity, int domain);
+#endif
 MOCK_DECL(void, try_to_extract_certs_from_tls,
           (int severity, tor_tls_t *tls,
            tor_x509_cert_impl_t **cert_out,
@@ -31,13 +27,9 @@ int tor_tls_context_init_certificates(tor_tls_context_t *result,
                                       crypto_pk_t *identity,
                                       unsigned key_lifetime,
                                       unsigned flags);
+void tor_tls_impl_free_(tor_tls_impl_t *ssl);
 
-#ifdef ENABLE_OPENSSL
-void tor_tls_context_impl_free(struct ssl_ctx_st *);
-#else
-struct ssl_ctx_st; // XXXX replace
-void tor_tls_context_impl_free(struct ssl_ctx_st *);
-#endif
+void tor_tls_context_impl_free(tor_tls_context_impl_t *);
 
 #ifdef ENABLE_OPENSSL
 tor_tls_t *tor_tls_get_by_ssl(const struct ssl_st *ssl);

+ 487 - 134
src/lib/tls/tortls_nss.c

@@ -12,6 +12,7 @@
 #include "orconfig.h"
 
 #define TORTLS_PRIVATE
+#define TOR_X509_PRIVATE
 
 #ifdef _WIN32 /*wrkard for dtls1.h >= 0.9.8m of "#include <winsock.h>"*/
   #include <winsock2.h>
@@ -22,6 +23,9 @@
 #include "lib/crypt_ops/crypto_rand.h"
 #include "lib/crypt_ops/crypto_dh.h"
 #include "lib/crypt_ops/crypto_util.h"
+#include "lib/crypt_ops/crypto_nss_mgt.h"
+#include "lib/string/printf.h"
+
 #include "lib/tls/x509.h"
 #include "lib/tls/x509_internal.h"
 #include "lib/tls/tortls.h"
@@ -29,26 +33,16 @@
 #include "lib/tls/tortls_internal.h"
 #include "lib/log/util_bug.h"
 
-int
-tor_errno_to_tls_error(int e)
-{
-  (void)e;
-  // XXXX
-  return -1;
-}
-int
-tor_tls_get_error(tor_tls_t *tls, int r, int extra,
-                  const char *doing, int severity, int domain)
-{
-  (void)tls;
-  (void)r;
-  (void)extra;
-  (void)doing;
-  (void)severity;
-  (void)domain;
-  // XXXX
-  return -1;
-}
+#include <prio.h>
+// For access to raw sockets.
+#include <private/pprio.h>
+#include <ssl.h>
+#include <sslt.h>
+#include <sslproto.h>
+#include <certt.h>
+
+static SECStatus always_accept_cert_cb(void *, PRFileDesc *, PRBool, PRBool);
+
 MOCK_IMPL(void,
 try_to_extract_certs_from_tls,(int severity, tor_tls_t *tls,
                                tor_x509_cert_impl_t **cert_out,
@@ -57,14 +51,109 @@ try_to_extract_certs_from_tls,(int severity, tor_tls_t *tls,
   tor_assert(tls);
   tor_assert(cert_out);
   tor_assert(id_cert_out);
-  (void)severity;
-  // XXXX
+  (void) severity;
+
+  *cert_out = *id_cert_out = NULL;
+
+  CERTCertificate *peer = SSL_PeerCertificate(tls->ssl);
+  if (!peer)
+    return;
+  *cert_out = peer; /* Now owns pointer. */
+
+  CERTCertList *chain = SSL_PeerCertificateChain(tls->ssl);
+  CERTCertListNode *c = CERT_LIST_HEAD(chain);
+  for (; !CERT_LIST_END(c, chain); c = CERT_LIST_NEXT(c)) {
+    if (CERT_CompareCerts(c->cert, peer) == PR_FALSE) {
+      *id_cert_out = CERT_DupCertificate(c->cert);
+      break;
+    }
+  }
+  CERT_DestroyCertList(chain);
+}
+
+static bool
+we_like_ssl_cipher(SSLCipherAlgorithm ca)
+{
+  switch (ca) {
+    case ssl_calg_null: return false;
+    case ssl_calg_rc4: return false;
+    case ssl_calg_rc2: return false;
+    case ssl_calg_des: return false;
+    case ssl_calg_3des: return false; /* ???? */
+    case ssl_calg_idea: return false;
+    case ssl_calg_fortezza: return false;
+    case ssl_calg_camellia: return false;
+    case ssl_calg_seed: return false;
+
+    case ssl_calg_aes: return true;
+    case ssl_calg_aes_gcm: return true;
+    case ssl_calg_chacha20: return true;
+    default: return true;
+  }
+}
+static bool
+we_like_ssl_kea(SSLKEAType kt)
+{
+  switch (kt) {
+    case ssl_kea_null: return false;
+    case ssl_kea_rsa: return false; /* ??? */
+    case ssl_kea_fortezza: return false;
+    case ssl_kea_ecdh_psk: return false;
+    case ssl_kea_dh_psk: return false;
+
+    case ssl_kea_dh: return true;
+    case ssl_kea_ecdh: return true;
+    case ssl_kea_tls13_any: return true;
+
+    case ssl_kea_size: return true; /* prevent a warning. */
+    default: return true;
+  }
+}
+
+static bool
+we_like_mac_algorithm(SSLMACAlgorithm ma)
+{
+  switch (ma) {
+    case ssl_mac_null: return false;
+    case ssl_mac_md5: return false;
+    case ssl_hmac_md5: return false;
+
+    case ssl_mac_sha: return true;
+    case ssl_hmac_sha: return true;
+    case ssl_hmac_sha256: return true;
+    case ssl_mac_aead: return true;
+    case ssl_hmac_sha384: return true;
+    default: return true;
+  }
+}
+
+static bool
+we_like_auth_type(SSLAuthType at)
+{
+  switch (at) {
+    case ssl_auth_null: return false;
+    case ssl_auth_rsa_decrypt: return false;
+    case ssl_auth_dsa: return false;
+    case ssl_auth_kea: return false;
+
+    case ssl_auth_ecdsa: return true;
+    case ssl_auth_ecdh_rsa: return true;
+    case ssl_auth_ecdh_ecdsa: return true;
+    case ssl_auth_rsa_sign: return true;
+    case ssl_auth_rsa_pss: return true;
+    case ssl_auth_psk: return true;
+    case ssl_auth_tls13_any: return true;
+
+    case ssl_auth_size: return true; /* prevent a warning. */
+    default: return true;
+  }
 }
 
 tor_tls_context_t *
 tor_tls_context_new(crypto_pk_t *identity,
                     unsigned int key_lifetime, unsigned flags, int is_client)
 {
+  SECStatus s;
   tor_assert(identity);
 
   tor_tls_context_t *ctx = tor_malloc_zero(sizeof(tor_tls_context_t));
@@ -77,7 +166,128 @@ tor_tls_context_new(crypto_pk_t *identity,
     }
   }
 
-  // XXXX write the main body.
+  {
+    /* Create the "model" PRFileDesc that we will use to base others on. */
+    PRFileDesc *tcp = PR_NewTCPSocket();
+    if (!tcp)
+      goto err;
+
+    ctx->ctx = SSL_ImportFD(NULL, tcp);
+    if (!ctx->ctx) {
+      PR_Close(tcp);
+      goto err;
+    }
+  }
+
+  // Configure the certificate.
+  if (!is_client) {
+    s = SSL_ConfigServerCert(ctx->ctx,
+                             ctx->my_link_cert->cert,
+                             (SECKEYPrivateKey *)
+                               crypto_pk_get_nss_privkey(ctx->link_key),
+                             NULL, /* ExtraServerCertData */
+                             0 /* DataLen */);
+    if (s != SECSuccess)
+      goto err;
+  }
+
+  // We need a certificate from the other side.
+  if (is_client) {
+    // XXXX does this do anything?
+    s = SSL_OptionSet(ctx->ctx, SSL_REQUIRE_CERTIFICATE, PR_TRUE);
+    if (s != SECSuccess)
+      goto err;
+  }
+
+  // Always accept other side's cert; we'll check it ourselves in goofy
+  // tor ways.
+  s = SSL_AuthCertificateHook(ctx->ctx, always_accept_cert_cb, NULL);
+
+  // We allow simultaneous read and write.
+  s = SSL_OptionSet(ctx->ctx, SSL_ENABLE_FDX, PR_TRUE);
+  if (s != SECSuccess)
+    goto err;
+  // XXXX SSL_ROLLBACK_DETECTION??
+  // XXXX SSL_ENABLE_ALPN??
+
+  // Force client-mode or server_mode.
+  s = SSL_OptionSet(ctx->ctx,
+                is_client ? SSL_HANDSHAKE_AS_CLIENT : SSL_HANDSHAKE_AS_SERVER,
+                PR_TRUE);
+  if (s != SECSuccess)
+    goto err;
+
+  // Disable everything before TLS 1.0; support everything else.
+  {
+    SSLVersionRange vrange;
+    memset(&vrange, 0, sizeof(vrange));
+    s = SSL_VersionRangeGetSupported(ssl_variant_stream, &vrange);
+    if (s != SECSuccess)
+      goto err;
+    if (vrange.min < SSL_LIBRARY_VERSION_TLS_1_0)
+      vrange.min = SSL_LIBRARY_VERSION_TLS_1_0;
+    s = SSL_VersionRangeSet(ctx->ctx, &vrange);
+    if (s != SECSuccess)
+      goto err;
+  }
+
+  // Only support strong ciphers.
+  {
+    const PRUint16 *ciphers = SSL_GetImplementedCiphers();
+    const PRUint16 n_ciphers = SSL_GetNumImplementedCiphers();
+    PRUint16 i;
+    for (i = 0; i < n_ciphers; ++i) {
+      SSLCipherSuiteInfo info;
+      memset(&info, 0, sizeof(info));
+      s = SSL_GetCipherSuiteInfo(ciphers[i], &info, sizeof(info));
+      if (s != SECSuccess)
+        goto err;
+      if (BUG(info.cipherSuite != ciphers[i]))
+        goto err;
+      int disable = info.effectiveKeyBits < 128 ||
+        info.macBits < 128 ||
+        !we_like_ssl_cipher(info.symCipher) ||
+        !we_like_ssl_kea(info.keaType) ||
+        !we_like_mac_algorithm(info.macAlgorithm) ||
+        !we_like_auth_type(info.authType)/* Requires NSS 3.24 */;
+
+      s = SSL_CipherPrefSet(ctx->ctx, ciphers[i],
+                            disable ? PR_FALSE : PR_TRUE);
+      if (s != SECSuccess)
+        goto err;
+    }
+  }
+
+  // Only use DH and ECDH keys once.
+  s = SSL_OptionSet(ctx->ctx, SSL_REUSE_SERVER_ECDHE_KEY, PR_FALSE);
+  if (s != SECSuccess)
+    goto err;
+
+  // don't cache sessions.
+  s = SSL_OptionSet(ctx->ctx, SSL_NO_CACHE, PR_TRUE);
+  if (s != SECSuccess)
+    goto err;
+
+  // Enable DH.
+  s = SSL_OptionSet(ctx->ctx, SSL_ENABLE_SERVER_DHE, PR_TRUE);
+  if (s != SECSuccess)
+    goto err;
+
+  // Set DH and ECDH groups.
+  SSLNamedGroup groups[] = {
+      ssl_grp_ec_curve25519,
+      ssl_grp_ec_secp256r1,
+      ssl_grp_ec_secp224r1,
+      ssl_grp_ffdhe_2048,
+  };
+  s = SSL_NamedGroupConfig(ctx->ctx, groups, ARRAY_LENGTH(groups));
+  if (s != SECSuccess)
+    goto err;
+
+  // These features are off by default, so we don't need to disable them:
+  //   Session tickets
+  //   Renegotiation
+  //   Compression
 
   goto done;
  err:
@@ -88,11 +298,9 @@ tor_tls_context_new(crypto_pk_t *identity,
 }
 
 void
-tor_tls_context_impl_free(struct ssl_ctx_st *ctx)
+tor_tls_context_impl_free(tor_tls_context_impl_t *ctx)
 {
-  (void)ctx;
-  // XXXX
-  // XXXX openssl type.
+  PR_Close(ctx);
 }
 
 void
@@ -101,33 +309,82 @@ tor_tls_get_state_description(tor_tls_t *tls, char *buf, size_t sz)
   (void)tls;
   (void)buf;
   (void)sz;
-  // XXXX
+  // AFAICT, NSS doesn't expose its internal state.
+  buf[0]=0;
 }
 
 void
 tor_tls_init(void)
 {
-  // XXXX
+  /* We don't have any global setup to do yet, but that will change */
 }
+
 void
 tls_log_errors(tor_tls_t *tls, int severity, int domain,
                const char *doing)
 {
+  /* XXXX This implementation isn't right for NSS -- it logs the last error
+     whether anything actually failed or not. */
+
   (void)tls;
-  (void)severity;
-  (void)domain;
-  (void)doing;
-  // XXXX
+  PRErrorCode code = PORT_GetError();
+
+  const char *string = PORT_ErrorToString(code);
+  const char *name = PORT_ErrorToName(code);
+  char buf[16];
+  if (!string)
+    string = "<unrecognized>";
+  if (!name) {
+    tor_snprintf(buf, sizeof(buf), "%d", code);
+    name = buf;
+  }
+
+  if (doing) {
+    log_fn(severity, domain, "TLS error %s while %s: %s", name, doing, string);
+  } else {
+    log_fn(severity, domain, "TLS error %s: %s", name, string);
+  }
 }
 
 tor_tls_t *
-tor_tls_new(int sock, int is_server)
+tor_tls_new(tor_socket_t sock, int is_server)
 {
   (void)sock;
-  (void)is_server;
-  // XXXX
-  return NULL;
+  tor_tls_context_t *ctx = tor_tls_context_get(is_server);
+
+  PRFileDesc *tcp = PR_ImportTCPSocket(sock);
+  if (!tcp)
+    return NULL;
+
+  PRFileDesc *ssl = SSL_ImportFD(ctx->ctx, tcp);
+  if (!ssl) {
+    PR_Close(tcp);
+    return NULL;
+  }
+
+  tor_tls_t *tls = tor_malloc_zero(sizeof(tor_tls_t));
+  tls->magic = TOR_TLS_MAGIC;
+  tls->context = ctx;
+  tor_tls_context_incref(ctx);
+  tls->ssl = ssl;
+  tls->socket = sock;
+  tls->state = TOR_TLS_ST_HANDSHAKE;
+  tls->isServer = !!is_server;
+
+  if (!is_server) {
+    /* Set a random SNI */
+    char *fake_hostname = crypto_random_hostname(4,25, "www.",".com");
+    SSL_SetURL(tls->ssl, fake_hostname);
+    tor_free(fake_hostname);
+  }
+  SECStatus s = SSL_ResetHandshake(ssl, is_server ? PR_TRUE : PR_FALSE);
+  if (s != SECSuccess) {
+    crypto_nss_log_errors(LOG_WARN, "resetting handshake state");
+  }
+
+  return tls;
 }
+
 void
 tor_tls_set_renegotiate_callback(tor_tls_t *tls,
                                  void (*cb)(tor_tls_t *, void *arg),
@@ -136,131 +393,175 @@ tor_tls_set_renegotiate_callback(tor_tls_t *tls,
   tor_assert(tls);
   (void)cb;
   (void)arg;
-  // XXXX;
+
+  /* We don't support renegotiation-based TLS with NSS. */
 }
 
 void
-tor_tls_free_(tor_tls_t *tls)
+tor_tls_impl_free_(tor_tls_impl_t *tls)
 {
-  (void)tls;
-  // XXXX
+  // XXXX This will close the underlying fd, which our OpenSSL version does
+  // not do!
+
+  PR_Close(tls);
 }
 
 int
 tor_tls_peer_has_cert(tor_tls_t *tls)
 {
-  (void)tls;
-  // XXXX
-  return -1;
+  CERTCertificate *cert = SSL_PeerCertificate(tls->ssl);
+  int result = (cert != NULL);
+  CERT_DestroyCertificate(cert);
+  return result;
 }
+
 MOCK_IMPL(tor_x509_cert_t *,
 tor_tls_get_peer_cert,(tor_tls_t *tls))
 {
-  tor_assert(tls);
-  // XXXX
-  return NULL;
+  CERTCertificate *cert = SSL_PeerCertificate(tls->ssl);
+  if (cert)
+    return tor_x509_cert_new(cert);
+  else
+    return NULL;
 }
+
 MOCK_IMPL(tor_x509_cert_t *,
 tor_tls_get_own_cert,(tor_tls_t *tls))
 {
   tor_assert(tls);
-  // XXXX
-  return NULL;
-}
-int
-tor_tls_verify(int severity, tor_tls_t *tls, crypto_pk_t **identity)
-{
-  tor_assert(tls);
-  tor_assert(identity);
-  (void)severity;
-  // XXXX
-  return -1;
-}
-int
-tor_tls_check_lifetime(int severity,
-                       tor_tls_t *tls, time_t now,
-                       int past_tolerance,
-                       int future_tolerance)
-{
-  tor_assert(tls);
-  (void)severity;
-  (void)now;
-  (void)past_tolerance;
-  (void)future_tolerance;
-  // XXXX
-  return -1;
+  CERTCertificate *cert = SSL_LocalCertificate(tls->ssl);
+  if (cert)
+    return tor_x509_cert_new(cert);
+  else
+    return NULL;
 }
+
 MOCK_IMPL(int,
 tor_tls_read, (tor_tls_t *tls, char *cp, size_t len))
 {
   tor_assert(tls);
   tor_assert(cp);
-  (void)len;
-  // XXXX
-  return -1;
+  tor_assert(len < INT_MAX);
+
+  PRInt32 rv = PR_Read(tls->ssl, cp, (int)len);
+  // log_debug(LD_NET, "PR_Read(%zu) returned %d", n, (int)rv);
+  if (rv > 0) {
+    tls->n_read_since_last_check += rv;
+    return rv;
+  }
+  if (rv == 0)
+    return TOR_TLS_CLOSE;
+  PRErrorCode err = PORT_GetError();
+  if (err == PR_WOULD_BLOCK_ERROR) {
+    return TOR_TLS_WANTREAD; // XXXX ????
+  } else {
+    crypto_nss_log_errors(LOG_NOTICE, "reading"); // XXXX
+    return TOR_TLS_ERROR_MISC; // ????
+  }
 }
+
 int
 tor_tls_write(tor_tls_t *tls, const char *cp, size_t n)
 {
   tor_assert(tls);
-  tor_assert(cp);
-  (void)n;
-  // XXXX
-  return -1;
+  tor_assert(cp || n == 0);
+  tor_assert(n < INT_MAX);
+
+  PRInt32 rv = PR_Write(tls->ssl, cp, (int)n);
+  // log_debug(LD_NET, "PR_Write(%zu) returned %d", n, (int)rv);
+  if (rv > 0) {
+    tls->n_written_since_last_check += rv;
+    return rv;
+  }
+  if (rv == 0)
+    return TOR_TLS_ERROR_MISC;
+  PRErrorCode err = PORT_GetError();
+
+  if (err == PR_WOULD_BLOCK_ERROR) {
+    return TOR_TLS_WANTWRITE; // XXXX ????
+  } else {
+    crypto_nss_log_errors(LOG_NOTICE, "writing"); // XXXX
+    return TOR_TLS_ERROR_MISC; // ????
+  }
 }
+
 int
 tor_tls_handshake(tor_tls_t *tls)
 {
   tor_assert(tls);
-  // XXXX
-  return -1;
+  tor_assert(tls->state == TOR_TLS_ST_HANDSHAKE);
+
+  SECStatus s = SSL_ForceHandshake(tls->ssl);
+  if (s == SECSuccess) {
+    tls->state = TOR_TLS_ST_OPEN;
+    log_debug(LD_NET, "SSL handshake is supposedly complete.");
+    return tor_tls_finish_handshake(tls);
+  }
+  if (PORT_GetError() == PR_WOULD_BLOCK_ERROR)
+    return TOR_TLS_WANTREAD; /* XXXX What about wantwrite? */
+
+  return TOR_TLS_ERROR_MISC; // XXXX
 }
+
 int
 tor_tls_finish_handshake(tor_tls_t *tls)
 {
   tor_assert(tls);
-  // XXXX
-  return -1;
+  // We don't need to do any of the weird handshake nonsense stuff on NSS,
+  // since we only support recent handshakes.
+  return TOR_TLS_DONE;
 }
+
 void
 tor_tls_unblock_renegotiation(tor_tls_t *tls)
 {
   tor_assert(tls);
-  // XXXX
+  /* We don't support renegotiation with NSS. */
 }
+
 void
 tor_tls_block_renegotiation(tor_tls_t *tls)
 {
   tor_assert(tls);
-  // XXXX
+  /* We don't support renegotiation with NSS. */
 }
+
 void
 tor_tls_assert_renegotiation_unblocked(tor_tls_t *tls)
 {
   tor_assert(tls);
-  // XXXX
+  /* We don't support renegotiation with NSS. */
 }
+
 int
 tor_tls_shutdown(tor_tls_t *tls)
 {
   tor_assert(tls);
-  // XXXX
+  /* XXXX This is not actually used, so I'm not implementing it.  We can
+   * XXXX remove this function entirely someday. */
   return -1;
 }
+
 int
 tor_tls_get_pending_bytes(tor_tls_t *tls)
 {
   tor_assert(tls);
-  // XXXX
-  return -1;
+  int n = SSL_DataPending(tls->ssl);
+  if (n < 0) {
+    crypto_nss_log_errors(LOG_WARN, "Looking up pending bytes");
+    return 0;
+  }
+  return (int)n;
 }
+
 size_t
 tor_tls_get_forced_write_size(tor_tls_t *tls)
 {
   tor_assert(tls);
-  // XXXX
+  /* NSS doesn't have the same "forced write" restriction as openssl. */
   return 0;
 }
+
 void
 tor_tls_get_n_raw_bytes(tor_tls_t *tls,
                         size_t *n_read, size_t *n_written)
@@ -268,7 +569,13 @@ tor_tls_get_n_raw_bytes(tor_tls_t *tls,
   tor_assert(tls);
   tor_assert(n_read);
   tor_assert(n_written);
-  // XXXX
+  /* XXXX We don't curently have a way to measure this information correctly
+   * in NSS; we could do that with a PRIO layer, but it'll take a little
+   * coding.  For now, we just track the number of bytes sent _in_ the TLS
+   * stream.  Doing this will make our rate-limiting slightly inaccurate. */
+  *n_read = tls->n_read_since_last_check;
+  *n_written = tls->n_written_since_last_check;
+  tls->n_read_since_last_check = tls->n_written_since_last_check = 0;
 }
 
 int
@@ -281,54 +588,70 @@ tor_tls_get_buffer_sizes(tor_tls_t *tls,
   tor_assert(rbuf_bytes);
   tor_assert(wbuf_capacity);
   tor_assert(wbuf_bytes);
-  // XXXX
+
+  /* This is an acceptable way to say "we can't measure this." */
   return -1;
 }
+
 MOCK_IMPL(double,
 tls_get_write_overhead_ratio, (void))
 {
-  // XXXX
-  return 0.0;
+  /* XXX We don't currently have a way to measure this in NSS; we could do that
+   * XXX with a PRIO layer, but it'll take a little coding. */
+  return 0.95;
 }
 
 int
 tor_tls_used_v1_handshake(tor_tls_t *tls)
 {
   tor_assert(tls);
-  // XXXX
-  return -1;
-}
-int
-tor_tls_get_num_server_handshakes(tor_tls_t *tls)
-{
-  tor_assert(tls);
-  // XXXX
-  return -1;
+  /* We don't support or allow the V1 handshake with NSS.
+   */
+  return 0;
 }
+
 int
 tor_tls_server_got_renegotiate(tor_tls_t *tls)
 {
   tor_assert(tls);
-  // XXXX
-  return -1;
+  return 0; /* We don't support renegotiation with NSS */
 }
+
 MOCK_IMPL(int,
 tor_tls_cert_matches_key,(const tor_tls_t *tls,
                           const struct tor_x509_cert_t *cert))
 {
   tor_assert(tls);
   tor_assert(cert);
-  // XXXX
-  return 0;
+  int rv = 0;
+
+  CERTCertificate *peercert = SSL_PeerCertificate(tls->ssl);
+  if (!peercert)
+    goto done;
+  CERTSubjectPublicKeyInfo *peer_info = &peercert->subjectPublicKeyInfo;
+  CERTSubjectPublicKeyInfo *cert_info = &cert->cert->subjectPublicKeyInfo;
+  rv = SECOID_CompareAlgorithmID(&peer_info->algorithm,
+                                 &cert_info->algorithm) == 0 &&
+       SECITEM_ItemsAreEqual(&peer_info->subjectPublicKey,
+                             &cert_info->subjectPublicKey);
+
+ done:
+  if (peercert)
+    CERT_DestroyCertificate(peercert);
+  return rv;
 }
+
 MOCK_IMPL(int,
 tor_tls_get_tlssecrets,(tor_tls_t *tls, uint8_t *secrets_out))
 {
   tor_assert(tls);
   tor_assert(secrets_out);
-  // XXXX
+
+  /* There's no way to get this information out of NSS. */
+
   return -1;
 }
+
 MOCK_IMPL(int,
 tor_tls_export_key_material,(tor_tls_t *tls, uint8_t *secrets_out,
                              const uint8_t *context,
@@ -339,42 +662,72 @@ tor_tls_export_key_material,(tor_tls_t *tls, uint8_t *secrets_out,
   tor_assert(secrets_out);
   tor_assert(context);
   tor_assert(label);
-  (void)context_len;
-  // XXXX
-  return -1;
-}
+  tor_assert(strlen(label) <= UINT_MAX);
+  tor_assert(context_len <= UINT_MAX);
 
-void
-check_no_tls_errors_(const char *fname, int line)
-{
-  (void)fname;
-  (void)line;
-  // XXXX
-}
-void
-tor_tls_log_one_error(tor_tls_t *tls, unsigned long err,
-                      int severity, int domain, const char *doing)
-{
-  tor_assert(tls);
-  (void)err;
-  (void)severity;
-  (void)domain;
-  (void)doing;
-  // XXXX
+  SECStatus s;
+  s = SSL_ExportKeyingMaterial(tls->ssl,
+                               label, (unsigned)strlen(label),
+                               PR_TRUE, context, (unsigned)context_len,
+                               secrets_out, DIGEST256_LEN);
+
+  return (s == SECSuccess) ? 0 : -1;
 }
 
 const char *
 tor_tls_get_ciphersuite_name(tor_tls_t *tls)
 {
   tor_assert(tls);
-  // XXXX
-  return NULL;
+
+  SSLChannelInfo channel_info;
+  SSLCipherSuiteInfo cipher_info;
+
+  memset(&channel_info, 0, sizeof(channel_info));
+  memset(&cipher_info, 0, sizeof(cipher_info));
+
+  SECStatus s = SSL_GetChannelInfo(tls->ssl,
+                                   &channel_info, sizeof(channel_info));
+  if (s != SECSuccess)
+    return NULL;
+
+  s = SSL_GetCipherSuiteInfo(channel_info.cipherSuite,
+                             &cipher_info, sizeof(cipher_info));
+  if (s != SECSuccess)
+    return NULL;
+
+  return cipher_info.cipherSuiteName;
 }
 
+/** The group we should use for ecdhe when none was selected. */
+#define SEC_OID_TOR_DEFAULT_ECDHE_GROUP SEC_OID_ANSIX962_EC_PRIME256V1
+
 int
 evaluate_ecgroup_for_tls(const char *ecgroup)
 {
-  (void)ecgroup;
-  // XXXX
-  return -1;
+  SECOidTag tag;
+
+  if (!ecgroup)
+    tag = SEC_OID_TOR_DEFAULT_ECDHE_GROUP;
+  else if (!strcasecmp(ecgroup, "P256"))
+    tag = SEC_OID_ANSIX962_EC_PRIME256V1;
+  else if (!strcasecmp(ecgroup, "P224"))
+    tag = SEC_OID_SECG_EC_SECP224R1;
+  else
+    return 0;
+
+  /* I don't think we need any additional tests here for NSS */
+  (void) tag;
+
+  return 1;
+}
+
+static SECStatus
+always_accept_cert_cb(void *arg, PRFileDesc *ssl, PRBool checkSig,
+                      PRBool isServer)
+{
+  (void)arg;
+  (void)ssl;
+  (void)checkSig;
+  (void)isServer;
+  return SECSuccess;
 }

+ 3 - 134
src/lib/tls/tortls_openssl.c

@@ -244,26 +244,6 @@ tls_log_errors(tor_tls_t *tls, int severity, int domain, const char *doing)
   }
 }
 
-/** Convert an errno (or a WSAerrno on windows) into a TOR_TLS_* error
- * code. */
-int
-tor_errno_to_tls_error(int e)
-{
-  switch (e) {
-    case SOCK_ERRNO(ECONNRESET): // most common
-      return TOR_TLS_ERROR_CONNRESET;
-    case SOCK_ERRNO(ETIMEDOUT):
-      return TOR_TLS_ERROR_TIMEOUT;
-    case SOCK_ERRNO(EHOSTUNREACH):
-    case SOCK_ERRNO(ENETUNREACH):
-      return TOR_TLS_ERROR_NO_ROUTE;
-    case SOCK_ERRNO(ECONNREFUSED):
-      return TOR_TLS_ERROR_CONNREFUSED; // least common
-    default:
-      return TOR_TLS_ERROR_MISC;
-  }
-}
-
 #define CATCH_SYSCALL 1
 #define CATCH_ZERO    2
 
@@ -952,8 +932,6 @@ tor_tls_server_info_callback(const SSL *ssl, int type, int val)
     /* Check whether we're watching for renegotiates.  If so, this is one! */
     if (tls->negotiated_callback)
       tls->got_renegotiate = 1;
-    if (tls->server_handshake_count < 127) /*avoid any overflow possibility*/
-      ++tls->server_handshake_count;
   } else {
     log_warn(LD_BUG, "Couldn't look up the tls for an SSL*. How odd!");
     return;
@@ -1167,30 +1145,13 @@ tor_tls_assert_renegotiation_unblocked(tor_tls_t *tls)
 #endif /* defined(SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION) && ... */
 }
 
-/** Release resources associated with a TLS object.  Does not close the
- * underlying file descriptor.
- */
 void
-tor_tls_free_(tor_tls_t *tls)
+tor_tls_impl_free_(tor_tls_impl_t *ssl)
 {
-  if (!tls)
-    return;
-  tor_assert(tls->ssl);
-  {
-    size_t r,w;
-    tor_tls_get_n_raw_bytes(tls,&r,&w); /* ensure written_by_tls is updated */
-  }
 #ifdef SSL_set_tlsext_host_name
-  SSL_set_tlsext_host_name(tls->ssl, NULL);
+  SSL_set_tlsext_host_name(ssl, NULL);
 #endif
-  SSL_free(tls->ssl);
-  tls->ssl = NULL;
-  tls->negotiated_callback = NULL;
-  if (tls->context)
-    tor_tls_context_decref(tls->context);
-  tor_free(tls->address);
-  tls->magic = 0x99999999;
-  tor_free(tls);
+  SSL_free(ssl);
 }
 
 /** Underlying function for TLS reading.  Reads up to <b>len</b>
@@ -1509,90 +1470,6 @@ try_to_extract_certs_from_tls,(int severity, tor_tls_t *tls,
   *id_cert_out = id_cert;
 }
 
-/** If the provided tls connection is authenticated and has a
- * certificate chain that is currently valid and signed, then set
- * *<b>identity_key</b> to the identity certificate's key and return
- * 0.  Else, return -1 and log complaints with log-level <b>severity</b>.
- */
-int
-tor_tls_verify(int severity, tor_tls_t *tls, crypto_pk_t **identity_key)
-{
-  X509 *cert = NULL, *id_cert = NULL;
-  EVP_PKEY *id_pkey = NULL;
-  RSA *rsa;
-  int r = -1;
-
-  check_no_tls_errors();
-  *identity_key = NULL;
-
-  try_to_extract_certs_from_tls(severity, tls, &cert, &id_cert);
-  if (!cert)
-    goto done;
-  if (!id_cert) {
-    log_fn(severity,LD_PROTOCOL,"No distinct identity certificate found");
-    goto done;
-  }
-  tls_log_errors(tls, severity, LD_HANDSHAKE, "before verifying certificate");
-
-  if (!(id_pkey = X509_get_pubkey(id_cert)) ||
-      X509_verify(cert, id_pkey) <= 0) {
-    log_fn(severity,LD_PROTOCOL,"X509_verify on cert and pkey returned <= 0");
-    tls_log_errors(tls, severity, LD_HANDSHAKE, "verifying certificate");
-    goto done;
-  }
-
-  rsa = EVP_PKEY_get1_RSA(id_pkey);
-  if (!rsa)
-    goto done;
-  *identity_key = crypto_new_pk_from_openssl_rsa_(rsa);
-
-  r = 0;
-
- done:
-  if (cert)
-    X509_free(cert);
-  if (id_pkey)
-    EVP_PKEY_free(id_pkey);
-
-  /* This should never get invoked, but let's make sure in case OpenSSL
-   * acts unexpectedly. */
-  tls_log_errors(tls, LOG_WARN, LD_HANDSHAKE, "finishing tor_tls_verify");
-
-  return r;
-}
-
-/** Check whether the certificate set on the connection <b>tls</b> is expired
- * give or take <b>past_tolerance</b> seconds, or not-yet-valid give or take
- * <b>future_tolerance</b> seconds. Return 0 for valid, -1 for failure.
- *
- * NOTE: you should call tor_tls_verify before tor_tls_check_lifetime.
- */
-int
-tor_tls_check_lifetime(int severity, tor_tls_t *tls,
-                       time_t now,
-                       int past_tolerance, int future_tolerance)
-{
-  X509 *cert;
-  int r = -1;
-
-  if (!(cert = SSL_get_peer_certificate(tls->ssl)))
-    goto done;
-
-  if (tor_x509_check_cert_lifetime_internal(severity, cert, now,
-                                            past_tolerance,
-                                            future_tolerance) < 0)
-    goto done;
-
-  r = 0;
- done:
-  if (cert)
-    X509_free(cert);
-  /* Not expected to get invoked */
-  tls_log_errors(tls, LOG_WARN, LD_NET, "checking certificate lifetime");
-
-  return r;
-}
-
 /** Return the number of bytes available for reading from <b>tls</b>.
  */
 int
@@ -1690,14 +1567,6 @@ tor_tls_used_v1_handshake(tor_tls_t *tls)
   return ! tls->wasV2Handshake;
 }
 
-/** Return the number of server handshakes that we've noticed doing on
- * <b>tls</b>. */
-int
-tor_tls_get_num_server_handshakes(tor_tls_t *tls)
-{
-  return tls->server_handshake_count;
-}
-
 /** Return true iff the server TLS connection <b>tls</b> got the renegotiation
  * request it was waiting for. */
 int

+ 12 - 5
src/lib/tls/tortls_st.h

@@ -6,6 +6,8 @@
 #ifndef TOR_TORTLS_ST_H
 #define TOR_TORTLS_ST_H
 
+#include "lib/net/socket.h"
+
 #define TOR_TLS_MAGIC 0x71571571
 
 typedef enum {
@@ -17,7 +19,7 @@ typedef enum {
 
 struct tor_tls_context_t {
   int refcnt;
-  struct ssl_ctx_st *ctx;
+  tor_tls_context_impl_t *ctx;
   struct tor_x509_cert_t *my_link_cert;
   struct tor_x509_cert_t *my_id_cert;
   struct tor_x509_cert_t *my_auth_cert;
@@ -31,8 +33,9 @@ struct tor_tls_context_t {
 struct tor_tls_t {
   uint32_t magic;
   tor_tls_context_t *context; /** A link to the context object for this tls. */
-  struct ssl_st *ssl; /**< An OpenSSL SSL object. */
-  int socket; /**< The underlying file descriptor for this TLS connection. */
+  tor_tls_impl_t *ssl; /**< An OpenSSL SSL object or NSS PRFileDesc. */
+  tor_socket_t socket; /**< The underlying file descriptor for this TLS
+                        * connection. */
   char *address; /**< An address to log when describing this connection. */
   tor_tls_state_bitfield_t state : 3; /**< The current SSL state,
                                        * depending on which operations
@@ -45,11 +48,10 @@ struct tor_tls_t {
                                   * one certificate). */
   /** True iff we should call negotiated_callback when we're done reading. */
   unsigned int got_renegotiate:1;
+#ifdef ENABLE_OPENSSL
   /** Return value from tor_tls_classify_client_ciphers, or 0 if we haven't
    * called that function yet. */
   int8_t client_cipher_list_type;
-  /** Incremented every time we start the server side of a handshake. */
-  uint8_t server_handshake_count;
   size_t wantwrite_n; /**< 0 normally, >0 if we returned wantwrite last
                        * time. */
   /** Last values retrieved from BIO_number_read()/write(); see
@@ -62,6 +64,11 @@ struct tor_tls_t {
   void (*negotiated_callback)(tor_tls_t *tls, void *arg);
   /** Argument to pass to negotiated_callback. */
   void *callback_arg;
+#endif
+#ifdef ENABLE_NSS
+  size_t n_read_since_last_check;
+  size_t n_written_since_last_check;
+#endif
 };
 
 #endif

+ 7 - 19
src/test/test_tortls.c

@@ -72,6 +72,7 @@ test_tortls_err_to_string(void *data)
   (void)1;
 }
 
+#ifdef ENABLE_OPENSSL
 static int
 mock_tls_cert_matches_key(const tor_tls_t *tls, const tor_x509_cert_t *cert)
 {
@@ -105,6 +106,7 @@ test_tortls_tor_tls_get_error(void *data)
   crypto_pk_free(key2);
   tor_tls_free(tls);
 }
+#endif
 
 static void
 test_tortls_x509_cert_get_id_digests(void *ignored)
@@ -165,6 +167,7 @@ test_tortls_get_my_certs(void *ignored)
   (void)1;
 }
 
+#ifdef ENABLE_OPENSSL
 static void
 test_tortls_get_forced_write_size(void *ignored)
 {
@@ -203,23 +206,6 @@ test_tortls_used_v1_handshake(void *ignored)
   tor_free(tls);
 }
 
-static void
-test_tortls_get_num_server_handshakes(void *ignored)
-{
-  (void)ignored;
-  int ret;
-  tor_tls_t *tls;
-
-  tls = tor_malloc_zero(sizeof(tor_tls_t));
-
-  tls->server_handshake_count = 3;
-  ret = tor_tls_get_num_server_handshakes(tls);
-  tt_int_op(ret, OP_EQ, 3);
-
- done:
-  tor_free(tls);
-}
-
 static void
 test_tortls_server_got_renegotiate(void *ignored)
 {
@@ -236,6 +222,7 @@ test_tortls_server_got_renegotiate(void *ignored)
  done:
   tor_free(tls);
 }
+#endif
 
 static void
 test_tortls_evaluate_ecgroup_for_tls(void *ignored)
@@ -266,13 +253,14 @@ test_tortls_evaluate_ecgroup_for_tls(void *ignored)
 struct testcase_t tortls_tests[] = {
   LOCAL_TEST_CASE(errno_to_tls_error, 0),
   LOCAL_TEST_CASE(err_to_string, 0),
-  LOCAL_TEST_CASE(tor_tls_get_error, 0),
   LOCAL_TEST_CASE(x509_cert_get_id_digests, 0),
   LOCAL_TEST_CASE(get_my_certs, TT_FORK),
+#ifdef ENABLE_OPENSSL
+  LOCAL_TEST_CASE(tor_tls_get_error, 0),
   LOCAL_TEST_CASE(get_forced_write_size, 0),
   LOCAL_TEST_CASE(used_v1_handshake, TT_FORK),
-  LOCAL_TEST_CASE(get_num_server_handshakes, 0),
   LOCAL_TEST_CASE(server_got_renegotiate, 0),
+#endif
   LOCAL_TEST_CASE(evaluate_ecgroup_for_tls, 0),
   END_OF_TESTCASES
 };