[openssl-users] Authentication over ECDHE
C.Wehrmeyer
c.wehrmeyer at gmx.de
Sat Dec 29 13:19:47 UTC 2018
I don't have access to the actual testing environments until Wednesday
next year, so I've had to create a private account.
> Which version of OpenSSL is this? (I don't remember if you said this
> already).
I'm not entirely sure, but I *think* it's 1.1.0.
=====================================================================
OK, so I've been reading the mails before going to sleep and spent some
time thinking and researching about this, and I've come to a conclusion:
OpenSSL is a goddamn mess, SSL_clear() is pretty much superfluous, and
as such shouldn't exist.
Why? Well, to quote Viktor here:
> DO NOT reuse the same SSL handle for multiple connections,
And that is fricking bullshit. Not the quote itself or the suggestion -
it's unlikely you had anything to do with the actual code - but the way
things have been thought through (or rather, have not been thought
through) by the library devs. I've written highly scalable libraries in
the past before, and one thing you always want to do there is to trim
fat. And "object allocation and initialisation" is something that you
very much want to trim fat of, not only for obvious reasons such as
malloc() and free() (or whatever OpenSSL uses as wrappers) being
complexity monsters, but also for cache reasons (loading different cache
line hurts performance). That's why you usually have functions like
XXX_clear() or XXX_reset(), which do exactly that - prepare an object
for another usage. memset() (or the OpenSSL equivalent of a secure
memset) your allocated resources. I don't really see the problem here.
Now add to that the fact that OpenSSL has been moving towards making its
structures opaque, thus falling into the same trap that Microsoft has
with COM and DirectX, and you can kind of see why, if you can't really
determine anymore WHERE your object is going to be stored, you at least
want to keep reusing it. This is not PHP, where people allocate memory
all willy-nilly, or C++, where people don't even have shame anymore to
use std::vector<std::strings> str_array instead of good old static const
char*const str_array[] while expecting things to be made faster by
invisible memory pools (and horribly failing at it), but C, where you
want to think about each step quite carefully.
Then OpenSSL even provides an SSL_clear function which is advertised
like this:
> SSL_clear - reset SSL object to allow another connection
, and then, only later, in a big warning block, decides to tell the
reader that this function only works when the stars align quite
correctly and you've sacrificed at least two virgins, because:
> The reset operation however keeps several settings of the last
> sessions
Then, as the documentation suggests, I read the entry for SSL_get_session:
> The ssl session contains all information required to re-establish the
> connection without a full handshake for SSL versions up to and
> including TLSv1.2. In TLSv1.3 the same is true, but sessions are
> established after the main handshake has occurred.
And at this point it all falls apart. From my understanding OpenSSL
keeps a session cache for servers so that key exchanges and protocol
handshakes can be avoided. Problem is, *we're using ECDHE, where the
last E stands for "ephemeral"*. In simple English: throw away the keys
after you're done, we want to have forward secrecy. And then OpenSSL
keeps a fresh copy of those for everyone who happened to be logged on at
this point. Heartbleed apparently wasn't enough of a warning. Oh, but
lets move everything to the heap so that it's more secure there now.
I don't want to reuse a session with ephemeral keys; I want to reuse an
object that is supposed to already have resources allocated for doing
its job, as is indicated by the documentation of this function except
for a small note at the end that tells you that the devs didn't really
think about what "ephemeral" means.
Creating a new SSL object (EVEN FROM AN EXISTING SSL_CTX object) entails:
- allocating the memory for the object itself on the heap (via
OPENSSL_zalloc)
- creating and managing a new lock for the object, and who knows for
much more subobjects
- creating a duplicate of the cipher suite stack (which isn't even a
flat copy, but something that can cause the code to call OPENSSL_malloc
*twice* in the worst case)
- creating a duplicate of the certificates (which I don't even use, but
that doesn't stop the code of ssl_cert_dup() to call OPENSSL_zalloc *in
its very first line!*)
- setting up a bunch of callbacks
- copying 32 bytes for a sid_ctx
- creating an X509_VERIFY_PARAM object (*which calls OPENSSL_zalloc
again*) as well as creating a deep copy of the SSL_CTX's parameter via
X509_VERIFY_PARAM_inherit(), with Thor knows how many copies hidden in
all those *set* and *deep_copy* routines
- copying EC point formats from the context - deep again, of course, at
least that's what OPENSSL_memdup() makes me think
- copying supported group informations, and of course deep again!
- deep-copying an ALPN object
- SSL_clear()-ing the object (no, really!)
- deep-copying a CRYPTO_EX_DATA object via CRYPTO_new_ex_data ... at
this point, is anyone surprised here that timing attacks against crypto
are *still* so successful? Because I'm not. Not at all.
I didn't bother looking up what freeing entails - it's obvious to anyone
at this point that OpenSSL is a severe victim of feature creep, that its
memory allocation scheme is a mess, and long story short: I will NOT
free a perfectly fine object just because of incompetent devs' chutzpah
expecting their users to allocate memory dynamically en mass for no
goddamn reason whenever a new connection comes in. Fix your goddamn code.
And don't give me any "trust us, we're experienced programmers"
bullshit. I've *seen* ssl/record/ssl3_record.c:
> static const unsigned char ssl3_pad_1[48] = {
> 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
> 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
> 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
> 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
> 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
> 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36
> };
> static const unsigned char ssl3_pad_2[48] = {
> 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
> 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
> 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
> 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
> 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
> 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c
> };
What's wrong with that, you ask? Let me show you how I'd have done that:
> static const unsigned char ssl3_pad_1[] =
> {
> "66666666"
> "66666666"
> "66666666"
> "66666666"
> "66666666"
> "66666666"
> };
>
> static const unsigned char*ssl3_pad_2[] =
> {
> "\\\\\\\\\\\\\\\\"
> "\\\\\\\\\\\\\\\\"
> "\\\\\\\\\\\\\\\\"
> "\\\\\\\\\\\\\\\\"
> "\\\\\\\\\\\\\\\\"
> "\\\\\\\\\\\\\\\\"
> };
So, no. I don't trust anyone. Especially not this mess of a code.
More information about the openssl-users
mailing list