As we've moved torstorm through internal testing and external beta stage, we've been working to iteratively harden prospective attack surfaces for the entire service, as-deployed. This is a new tool and a new use-case, both to us and in general terms (a few folks have done tor2web services for several years, on and off, but large-scale production deployments are not in evidence, as far as we've yet noted), and security improvements for such a system are, by definition, iterative as well as baked-in at initial design stage.
One are we're currently tightening up is cipher suite selection for the torstorm <--> cryptostorm_exitnode leg of the topology. In that leg, the HTTPS wrapper is the sole boundary between plaintext and a passive-reconnaissance attacker. As such, ensuring good cipher suite selection is of nontrivial relevance to overall systems-level security for members making use of torstorm.
The initial beta build of the service relied on a pythonic backend to act as de-facto webserver. This placed some practical limits on the degrees of freedom we had in choosing our data-in-transit encryption framework for the service (yes, we can of course recode entire chunks of that backend, if necessary, to support just about any open cryptographic algorithms. However, as I've emphasised elsewhere, this kind of "roll your own" crypto implementation is hideously easy to screw up, in practice. So it's not something we jump into unless all other options are closed off.
Fortunately, we developed an alternative approach to the server-side resources that gets us back into widely-deployed web server terrain: nginx. That's a tool we know and use routinely for "creative" (read: weird) stuff like our tokenizer and other such oddities. So it's an ideal match for the torstorm deployment environment.
Once we're at that point, the question of cipher suite cascades comes into play. Our initial, somewhat default install produces a less than idea cipher cascade list:
Eek.
In addition to a big pile of weird cipher mismatches for a bunch of (mostly old) browser iterations, there's some cipher suites in that list that I'd not make use of for protection of a shopping list, let alone torstorm sessions!
At this point, I started manually pruning a meta-listing of all possible cipher selections supported by nginx (and our version of openssl: 1.0.1j); out goes SSL3, of course. And to hell with TLS 1.0 because it's just SSL3 in disguise wrt cipher vulns... RC4 is pretty much a broken mess at this point (and that's only on the civilian side - who knows what the NSA et al have up their sleeves), so we should really stick to GCM. Snip, snip, snip...
But, hold on.
Torstorm really isn't just a web browser for routine web-stuff. It's specifically for .onion Tor hidden services - one can safely infer that, in general, such bidirectional network sessions are going to want pretty strong protections against passive attackers, in particular. So instead of starting with the entire list of possible ciphers and pruning down, how about I build a "wishlist" of the ciphers I personally have come to conclude are worthy of production deployments? Yes, that's more like it: a kid in the proverbial candy story.

That list, it turns out, is pretty damned short. It's also a list that will - and this is why we've posted up this summary of the cipher selections, mostly - result in quite a few older browsers being unable to use torstorm. That's unfortunate, but we feel it's worth the cost. No session is, in many high-security scenarios, better than a session that is effectively leaky. That's not to say we're unconcerned with limiting access to the service; rather, just as with cryptostorm more broadly, we feel this is a realistic decision involving real-world risk mitigation and inescapable facts concerning many older cipher suites being used in HTTPS/TLS-based web deployments.
So what does that list look like. In a nutshell...
Code: Select all
ssl_ciphers "AES256+EECDH:AES256+EDH";
ssl_protocols TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
Yay! I'm a happy crypto-nerd.
But then we've got the matter of DH input parameters. Specifically...
All versions of nginx as of 1.4.4 rely on OpenSSL for input parameters to Diffie-Hellman (DH). Unfortunately, this means that Ephemeral Diffie-Hellman (DHE) will use OpenSSL's defaults, which include a 1024-bit key for the key-exchange.
Hence this:
Code: Select all
openssl genrsa -out tor2web-key.pem 4096
...and the corresponding steps to make these materials available server-side, etc.
Now, our preferred mechanism for asymmetric key generation and corresponding so-called "perfect forward secrecy" in the channel is ECDHE. That means, by definition, there's an elliptic curve being used to generate handshake parameters for each asymmetric re-key. If we don't specify it, there's a default somewhere that gets used. A bit of poking shows this is what nginx is assuming for us, at this point:
Code: Select all
ssl_ecdh_curve secp384r1;
This does not make me happy. At all.

Now things get complex and time-consuming, all of the sudden...
Here's what our openssl compile has to say for itself:
[email protected]# openssl ecparam -list_curves
secp112r1 : SECG/WTLS curve over a 112 bit prime field
secp112r2 : SECG curve over a 112 bit prime field
secp128r1 : SECG curve over a 128 bit prime field
secp128r2 : SECG curve over a 128 bit prime field
secp160k1 : SECG curve over a 160 bit prime field
secp160r1 : SECG curve over a 160 bit prime field
secp160r2 : SECG/WTLS curve over a 160 bit prime field
secp192k1 : SECG curve over a 192 bit prime field
secp224k1 : SECG curve over a 224 bit prime field
secp224r1 : NIST/SECG curve over a 224 bit prime field
secp256k1 : SECG curve over a 256 bit prime field
secp384r1 : NIST/SECG curve over a 384 bit prime field
secp521r1 : NIST/SECG curve over a 521 bit prime field
prime192v1: NIST/X9.62/SECG curve over a 192 bit prime field
prime192v2: X9.62 curve over a 192 bit prime field
prime192v3: X9.62 curve over a 192 bit prime field
prime239v1: X9.62 curve over a 239 bit prime field
prime239v2: X9.62 curve over a 239 bit prime field
prime239v3: X9.62 curve over a 239 bit prime field
prime256v1: X9.62/SECG curve over a 256 bit prime field
sect113r1 : SECG curve over a 113 bit binary field
sect113r2 : SECG curve over a 113 bit binary field
sect131r1 : SECG/WTLS curve over a 131 bit binary field
sect131r2 : SECG curve over a 131 bit binary field
sect163k1 : NIST/SECG/WTLS curve over a 163 bit binary field
sect163r1 : SECG curve over a 163 bit binary field
sect163r2 : NIST/SECG curve over a 163 bit binary field
sect193r1 : SECG curve over a 193 bit binary field
sect193r2 : SECG curve over a 193 bit binary field
sect233k1 : NIST/SECG/WTLS curve over a 233 bit binary field
sect233r1 : NIST/SECG/WTLS curve over a 233 bit binary field
sect239k1 : SECG curve over a 239 bit binary field
sect283k1 : NIST/SECG curve over a 283 bit binary field
sect283r1 : NIST/SECG curve over a 283 bit binary field
sect409k1 : NIST/SECG curve over a 409 bit binary field
sect409r1 : NIST/SECG curve over a 409 bit binary field
sect571k1 : NIST/SECG curve over a 571 bit binary field
sect571r1 : NIST/SECG curve over a 571 bit binary field
c2pnb163v1: X9.62 curve over a 163 bit binary field
c2pnb163v2: X9.62 curve over a 163 bit binary field
c2pnb163v3: X9.62 curve over a 163 bit binary field
c2pnb176v1: X9.62 curve over a 176 bit binary field
c2tnb191v1: X9.62 curve over a 191 bit binary field
c2tnb191v2: X9.62 curve over a 191 bit binary field
c2tnb191v3: X9.62 curve over a 191 bit binary field
c2pnb208w1: X9.62 curve over a 208 bit binary field
c2tnb239v1: X9.62 curve over a 239 bit binary field
c2tnb239v2: X9.62 curve over a 239 bit binary field
c2tnb239v3: X9.62 curve over a 239 bit binary field
c2pnb272w1: X9.62 curve over a 272 bit binary field
c2pnb304w1: X9.62 curve over a 304 bit binary field
c2tnb359v1: X9.62 curve over a 359 bit binary field
c2pnb368w1: X9.62 curve over a 368 bit binary field
c2tnb431r1: X9.62 curve over a 431 bit binary field
wap-wsg-idm-ecid-wtls1: WTLS curve over a 113 bit binary field
wap-wsg-idm-ecid-wtls3: NIST/SECG/WTLS curve over a 163 bit binary field
wap-wsg-idm-ecid-wtls4: SECG curve over a 113 bit binary field
wap-wsg-idm-ecid-wtls5: X9.62 curve over a 163 bit binary field
wap-wsg-idm-ecid-wtls6: SECG/WTLS curve over a 112 bit prime field
wap-wsg-idm-ecid-wtls7: SECG/WTLS curve over a 160 bit prime field
wap-wsg-idm-ecid-wtls8: WTLS curve over a 112 bit prime field
wap-wsg-idm-ecid-wtls9: WTLS curve over a 160 bit prime field
wap-wsg-idm-ecid-wtls10: NIST/SECG/WTLS curve over a 233 bit binary field
wap-wsg-idm-ecid-wtls11: NIST/SECG/WTLS curve over a 233 bit binary field
wap-wsg-idm-ecid-wtls12: WTLS curvs over a 224 bit prime field
Oakley-EC2N-3:
IPSec/IKE/Oakley curve #3 over a 155 bit binary field.
Not suitable for ECDSA.
Questionable extension field!
Oakley-EC2N-4:
IPSec/IKE/Oakley curve #4 over a 185 bit binary field.
Not suitable for ECDSA.
Questionable extension field!
Meh.
Here's why the 'meh' happens:
Basically, there's all sorts of serious mines hidden in those curve options. For example, this little gem:
Last year Schneier, after having firsthand access to the Snowden documents (some of them, for a limited period of time), made this somewhat enigmatic observation - which was often misconstrued as suggesting that ECC was "pwned by the NSA" but is in fact a huge red flag for NIST-validated curves and their attendant generation parameters:
Prefer conventional discrete-log-based systems over elliptic-curve systems; the latter have constants that the NSA influences when they can.
Here's ./'s summary of the whole thing (which is more or less accurate):
"In the wake of Bruce Schneier's statements that he no longer trusts the constants selected for elliptic curve cryptography, people have started trying to reproduce the process that led to those constants being selected ... and found it cannot be done. As background, the most basic standard elliptic curves used for digital signatures and other cryptography are called the SEC random curves (SEC is 'Standards for Efficient Cryptography'), a good example being secp256r1. The random numbers in these curve parameters were supposed to be selected via a "verifiably random" process (output of SHA1 on some seed), which is a reasonable way to obtain a nothing up my sleeve number if the input to the hash function is trustworthy, like a small counter or the digits of PI. Unfortunately it turns out the actual inputs used were opaque 256 bit numbers, chosen ad-hoc with no justifications provided. Worse, the curve parameters for SEC were generated by head of elliptic curve research at the NSA — opening the possibility that they were found via a brute force search for a publicly unknown class of weak curves. Although no attack against the selected values are currently known, it's common practice to never use unexplainable magic numbers in cryptography standards, especially when those numbers are being chosen by intelligence agencies. Now that the world received strong confirmation that the much more obscure and less widely used standard Dual_EC_DRBG was in fact an NSA undercover operation, NIST re-opened the confirmed-bad standards for public comment. Unless NIST/the NSA can explain why the random curve seed values are trustworthy, it might be time to re-evaluate all NIST based elliptic curve crypto in general."
(it's worth noting that, since these 2013 revelations, nothing's been disclosed or even really mooted seriously that questions these conclusions; true, the backdooring has not been formally proved via mathematical analysis - which is astonishingly difficult to do, in the best of circumstances, in such matters - but the smoking guns still smoke and the surrounding fact patter of other NSA malfeasance = Dual_EC_DRBG, anyone - adds up to a totality of circumstances impossible to ignore)
Sigh. There's some least-bad options... sorta. None gives me much confidence.
Then OpenSSL's current version 1.0.1j doesn't support alot of the ECC curves that we do like, in particular Curve25519. That's possible, of course - there's some nicely-done libraries out there, and likely that's where we go with this in the medium term (as in our main network cipher framework) - but making on-the-fly edits to OpenSSL's C is not something we'd do iteratively. Hence, what's out there that's (more or less) known-stable, and has decent curves?
Brainpool.
Yes, I know. There's considerable controversy here (perhaps a stronger word is really most appropriate). DJB & Co. pretty well lay out the issues...
And, yes, we much prefer 25519. But Brainpool - or some curves in Brainpool - are a hell of alot better than NIST garbage. But OpenSSL doesn't have production Brainpool support. Yet. Well... almost. Their nearly-production version 1.0.2 does:
patches extending OpenSSL's built-in set of EC curves by the Brainpool
curves (see RFC 5639) have been around since 2010 - see for instance
http://openssl.6102.n7.nabble.com/opens ... 41171.html
http://rt.openssl.org/Ticket/Display.ht ... pass=guest
Pleased to see that finally, three years later, they have been included
in the upcoming version 1.0.2. - I have been able to verify this from
http://mirrors.ibiblio.org/openssl/snap ... 125.tar.gz
In particular since the usual NIST curves got under pressure recently:
http://it.slashdot.org/firehose.pl?op=v ... 11/1224252
it is important to have some less debatable alternatives available for
general use ASAP. When can we expect version 1.0.2 to be released?
So, off to compile OpenSSL 1.0.2, and then re-compile nginx, and then choose a curve from Brainpool. The obligatory compile adventures ensue, until at last here's the new curves that OpenSSL spits out on our machine, as options:
brainpoolP160r1: RFC 5639 curve over a 160 bit prime field
brainpoolP160t1: RFC 5639 curve over a 160 bit prime field
brainpoolP192r1: RFC 5639 curve over a 192 bit prime field
brainpoolP192t1: RFC 5639 curve over a 192 bit prime field
brainpoolP224r1: RFC 5639 curve over a 224 bit prime field
brainpoolP224t1: RFC 5639 curve over a 224 bit prime field
brainpoolP256r1: RFC 5639 curve over a 256 bit prime field
brainpoolP256t1: RFC 5639 curve over a 256 bit prime field
brainpoolP320r1: RFC 5639 curve over a 320 bit prime field
brainpoolP320t1: RFC 5639 curve over a 320 bit prime field
brainpoolP384r1: RFC 5639 curve over a 384 bit prime field
brainpoolP384t1: RFC 5639 curve over a 384 bit prime field
brainpoolP512r1: RFC 5639 curve over a 512 bit prime field
brainpoolP512t1: RFC 5639 curve over a 512 bit prime field
Still not making my nipples hard, but at least better (I think, based on what I've seen in the literature). From the list, I choose brainpoolP512r1, we plug it into nginx as a parameter, and let's see what happens...
Code: Select all
(11:02:04 PM) df: accept4(6, {sa_family=AF_INET, sin_port=htons(43924), sin_addr=inet_addr("64.41.200.102")}, [16], SOCK_NONBLOCK) = 10
(11:02:04 PM) df: epoll_ctl(8, EPOLL_CTL_ADD, 10, {EPOLLIN|EPOLLET|0x2000, {u32=12725904, u64=12725904}}) = 0
(11:02:04 PM) df: epoll_wait(8, {{EPOLLIN, {u32=12725904, u64=12725904}}}, 512, 39374) = 1
(11:02:04 PM) df: recvfrom(10, "\26", 1, MSG_PEEK, NULL, NULL) = 1
(11:02:04 PM) df: read(10, "\26\3\1\0\265\1\0\0\261\3\3", 11) = 11
(11:02:04 PM) df: read(10, "w\372\277\274\21\314\243 \10\220\0\305\26\2059\346\201K\341y\317]\222\367\354\371\343?\n\36\27="..., 175) = 175
(11:02:04 PM) df: write(10, "\26\3\3\0b\2\0\0^\3\3\242\22vo\2151\371rz\310i\3200\237\330\302\245Z\225[\303"..., 2375) = 2375
(11:02:04 PM) df: read(10, 0xcb0573, 33093) = -1 EAGAIN (Resource temporarily unavailable)
(11:02:04 PM) df: epoll_wait(8, {{EPOLLIN, {u32=12725904, u64=12725904}}}, 512, 39373) = 1
(11:02:04 PM) df: read(10, "\26\3\3\0f\20\0\0ba\4\270K\264\340_\333\370a\203M\216\242\207'G\373b;F%S"..., 33093) = 107
(11:02:04 PM) df: read(10, 0xcb0573, 33093) = -1 EAGAIN (Resource temporarily unavailable)
(11:02:04 PM) df: epoll_wait(8, {{EPOLLIN, {u32=12725904, u64=12725904}}}, 512, 39317) = 1
(11:02:04 PM) df: read(10, "\24\3\3\0\1\1\26\3\3\0$Y\346\347\223\344,GT\264\33\366\247\247\273\235\363\16p\257\375."..., 33093) = 47
(11:02:05 PM) df: write(10, "\24\3\3\0\1\1\26\3\3\0$\205\313\253\333\365\3m\356\276\261f_\256\372\344\324\25LT\3365"..., 47) = 47
(11:02:05 PM) df: --- SIGSEGV (Segmentation fault) @ 0 (0) ---
(11:02:05 PM) df: Process 4689 detached
Oops.

The usual to'ing & systracing goes on until we hunt down the fiddly bits that need to be fiddled. Fiddling ensues, and nginx seems to be settled into a life in partnership with OpenSSL 1.0.2.
Let's take a test drive over to @IvanRistic's SSL Labs testing page, and see what happens:
Derp. Perhaps we're just throwing enough Brainpool-y weirdness at it that it's not sure what's up. How about CA Security Council's tool?
Well ok then. Let's look at some packets (via tcpdump) in transit and see if we've somehow (implausibly) broken HTTPS and caused it to barf up plaintext, mysteriously:
Code: Select all
(12:21:43 AM) df: 0x0530: 0906 0355 0406 1302 5345 3114 3012 0603 ...U....SE1.0...
(12:21:43 AM) df: 0x0540: 5504 0a13 0b41 6464 5472 7573 7420 4142 U....AddTrust.AB
(12:21:43 AM) df: 0x0550: 3126 3024 0603 5504 0b13 1d41 6464 5472 1&0$..U....AddTr
(12:21:43 AM) df: 0x0560: 7573 7420 4578 7465 726e 616c 2054 5450 ust.External.TTP
(12:21:43 AM) df: 0x0570: 204e 6574 776f 726b .Network
Doesn't look that way, at face value anyhow.
There you. It appears we've got curve brainpoolP512r1 in production and fairly secure against known backdoors. Needs quite a bit more testing before we declare it full production, obviously - and our beta testing warnings in the project launch announcement very much stay in effect. Still and all, not a bad spot of work - lots of detours and roundabouts, but in the end we might just have done something useful with our time.
Next up? Proper Curve25519 deployment via NaCl libraries... unless someone's got an existing way to bump it into OpenSSL, that doesn't suck. Which should be entertaining, either way.
- - - - - -
It's far easier to simply choose default settings, especially when it comes to crypto. Once you go out of default-land, things can get complex. Plus, which non-defaults are best? And when you start messing with new compiles to wrap in well-proven cipher capabilities that - for whatever obscure reasons - aren't yet part of a given package, it can get a bit interesting. This time could have been spent writing clever marketing campaigns, or sending out press releases, or bribing "VPN review" websites into saying good things. That's what everyone else does, to be blunt about it.
But, look: why fucking bother with crypto in the first place, if you deploy broken shit? Honestly. Why? If you know it's backdoored, what are you gaining by pushing it out into production? The NIST ECC curves are backdoored - put a gun to my head and make me take sides in a final bet, and that's where I put my chit. Who has those "magic numbers" that allow for a trivial reconstruction of point-pairs? Dunno. And I am not even interested in spending the fucking time to figure out who may or may not, because... if there's stuff that we are pretty sure isn't backdoored (or at least we don't know of them yet, and so on), how about we use that instead. Seems reasonable.
It's not like non-backdoored curves "cost more" or something - it's all open code, open algorithms. And the alleged "performance hit" of this stuff is, in our production context, a total fantastical creation. It doesn't exist.
Yes, it takes more work to do it right (or as right as one can, with what one knows and has available). We think that's an interesting process and, quite often, a spot of fun as well. It can also result in horrific bughunt deathmarches, days without sleep, missed launch dates, derision if it breaks post-production, and a general sense that one is always skirting that fractal boundary between real success and roiling chaos.
But the real reason this isn't done more often, I think, is this: it's not really visible. Sure, you can pull pcaps and (in theory) confirm we're using brainpoolP512r1- well, someone with a hell of alot of capability in such things can (that's no wireshark capability we'e seen floating around thus far). In practice, nobody does this. Nobody. So it's all invisible, behind-the-scenes stuff. Likely to never even be noticed by members who are using it.
When we do stuff right, nothing happens. When secure systems don't break, no information leaks. Nothing bad happens to members. Nothing happens. We chew on this stuff and do our best to make it good, with the goal of nothing happening. Which, of course, is not exciting news for the most part: someone uses cryptostorm, an attack they don't know is taking place fails because we did something right, months or years ago, in our architecture or deployed framework. And... nothing happens.
Tokens sold due to such work? Zero, basically. Bills paid as a consequence of doing it right? None, for the most part. That's just reality. We'd do much, much better financially if we paid off a news website to hype our alien-proof technology than we are by actually producing a service that isn't riddled with obvious security fail. It's a bit of a bitter pill, sometimes, to watch it all play out.
Ha, fuck it anyhow! Doing it right feels good. It's hard, and frustrating, and often under-appreciated. Who really cares? That's not the point. The point is that doing it right feels good - because it's done right. And lots of good things flow from that simple stance. Do it right, as best you can. Then keep doing it better. How about that?
How about it, indeed.
Cheers,
- ~ pj