|
@@ -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;
|
|
|
}
|