One of the things that we did in RavenDB 4.0 was support running on Linux, which meant giving up on Windows Authentication. To the nitpickers among you, I’m aware that we can do LDAP authentication, and we can do Windows Auth over HTTP/S on Linux. Let's say that given the expected results, all I can wish you is that you’ll have to debug/deploy/support such a system for real.
Giving up on Windows authentication in general, even on Windows, is something that we did with great relief. Nothing like getting an urgent call from a customer and trying to figure out why a certain user isn’t authenticated and trying to figure out the trust relationship between different domains, forests, DMZ, and a whole host of other stuff that has an utter lack of anything related to us.
I hate those kinds of cases.
In fact, I think that my new ill wish phrase will become, “May you get a 2 a.m. call to support Kerberus authentication problems on a Linux server in the DMZ to a nested Windows domain.”
But that might be going too far and might damage my Karma.
That leads us to API key authentication. And for that, we want to be able to get authenticate against the server and get a token that we can then use in future requests. An additional benefit is that by building our own system, we are able to actually own the entire thing and support it much better.
A side issue here is that we need to support and maintain security-critical code, which I’m not so happy about. But owning this also gives me the option of doing things in a more optimized fashion. And in this case, we want to handle authentication in as few network round trips as possible — ideally, only one.
That is a bit challenging since, on the first request, we know nothing about the server. I actually implemented a couple of attempts to do so, but pretty much all of them were vulnerable to some degree after we did security analysis on them. You can look here for some of the details, so we gave that up in favor of mostly single round-trip authentication.
The very first time the client starts, it will use a well-known endpoint to get the server public key. When we need to authenticate, we’ll generate our own key pair and use it and the server public key to encrypt a hash of the secret key with the client’s public key, then send our own public key and the encrypted data to the server for authentication.
In return, the server will validate the client based on the hash of the password and the client public key, generate an authentication token, encrypt that with the client’s public key and send it back.
Now, this is a bit complex, I’ll admit, and it is utterly unnecessary if the users are using HTTPS, as they should be. However, there are actually quite a few deployments where HTTPS isn’t used — mostly inside organizations where they choose to deploy over HTTP to avoid the complexity of cert management/update/etc.
Our goal is to prevent leakage of the secret key over the network even in the case that the admin didn’t setup things securely. Note that in this case (not using HTTPS), anyone listening on the network is going to be able to grab the authentication token (which is valid for about half an hour), but that is out of scope for this discussion.
We can assume that communications between client and server are not snoopable, given that they are using public keys to exchange the data. We also ensure that the size of the data is always the same, so there is no information leakage there.
A bad actor can pretend to be a server and fool a client into sending the authentication request using the bad actor’s public key instead of the server. This is done because we don’t have trust chains. The result is that the bad actor now has the hash of the password with the public key of the client (which is also known).
So, the bad guy can try to do some offline password guessing. We mitigate that using secret keys that are a minimum of 192 bits and generated using cryptographically secured RNG.
The bad actor can, of course, send the data as is to the server, which will authenticate it normally, but since it uses the client’s public key both for the hash validation and for the encryption of the reply, the bad actor wouldn’t be able to get the token.
On the client side, after the first time that the client hits the server, it will be able to cache the server public key, and any future authentication token refresh can be done in a single roundtrip.