It's Christmas time people and by now you should all know that it is time for an irrelevant tech guide on something I randomly found interesting. This year nothing screams christmas spirit like hot cocoa and some good ol' mTLS authentication.
Consider a situation where some client system needs to connect to another system hosting perhaps an API. But we need to narrow this down more, the client must connect very frequently perhaps even on a schedule to this other API. Is storing the credentials to access the API a good idea? We still need authentication to use the host API so maybe we should something like mutual TLS. The basic idea here is that, the client has its own set of certificates that the server verifies using what is hopefully the client certificates' CA.
In the above example, we could have secured traffic between the client and API by just using certificates. And if you wanted more security you could require both. People who use mTLS frequently use it sign in without using a password. But obviously the standard has much more versatility than even the examples I've offered. One project I've done recently was to obtain information from the certificate such as "Who was it issued to?" Accessing the certificate itself is a feature offered by most implementations of mTLS.
So how would a reader implement mTLS? Well, first you'll need keys and certificates that verifies the identity of a server. Then you need a root CA to create the set of keys and certificates the client will use. That means you'll need a least a client key and certificate, a server key and certificate, and a CA. There are many guides on setting these up and a couple of scripts you can take a look at. But why not go over that here?
Obviously, as the title indicates we need to create a CA. I'll show you how to do this with bash and openssl but I'm sure you can translate this to another tool if you want.
openssl genrsa 4096 > ca.key
Now we have a key but we aren't done yet, because we need to make a CA certificate to do stuff with. Running the command below will ask you to fill in some CA info. It's fine just fill it in with what you want.
openssl req -new -x509 -nodes \
-days 365 -key ca.key > ca.crt
At this point we will use the CA to create server certificates. Obviously these will not be trusted because they are self-signed. Nonetheless, if you don't have a pair of Let's Encrypt certificates handy and you don't like paying $300+ dollars a year to other CAs, a self-signed one will do. If you do have a server certificate you can skip this section.
openssl genrsa 4096 > server.key
Now we have a key but we need a certificate because keys aren't good enough. Or something. Anyway, to get a server certificate we first need a certificate signing request. This command is going to ask you some stuff as well.
openssl req -new -key server.key -out server.csr
And then we just need our CA to sign that certificate but seeing as we are our certificate authority, we can just do that.
openssl x509 -req -days 365 -in server.csr \
-CA ca.crt -CAkey ca.key -out server.crt
And now we have some a server key and certificate aren't we special! Honestly, you could replace these with realy keys by somebody like Comodo or Let's Encrypt any day and it wouldn't matter much,
If you've read the above section this one is gonna look pretty similar but with addition of some extra commands at the end. So let's do it again you should be getting good at this by now and if you didn't read the above section then we are going to create a client key.
openssl genrsa 4096 > client.key
Ok, let's keep going we still have to create the certificate signing request so we can sign it. Same great process, totally different key with a different name! This command is still gonna ask you stuff.
openssl req -new -key client.key -out client.csr
Once again, we ask our CA to sign this key as well, but once again we are our CA, so go ahead and ask yourself if signing your own key is okay and then do it.
openssl x509 -req -days 365 -in client.csr \
-CA ca.crt -CAkey ca.key -out client.crt
And now we are still not done. We have to make these keys useable by a browser.
openssl pkcs12 -export -out client.p12 \
-in client.crt -inkey client.key
And now you are done unless you actually intend to use these keys. In that case you probably want to put these in some software you likely haven't written yet. Don't worry this is the easy part. Just look up mTLS for your hosting / authentication framework. I know .NET's Identity, Rust's Actix, and Apache support mTLS connections. Moreover, Apache's implementation doesn't require you to write code so I'll go ahead and show that here. Also, you probably want to import your new client.p12 into your browser so you can actually use it.
You likely already have lines in Apache about using SSL but in case you don't here's what the relevant parts of a typical Apache SSL configuration look like.
You simply have to add a few more lines.
I typically use absolute paths for keys and stuff but for the sake of clarity that has been removed here. Also SSLVerifyDepth isn't too important as long as the number is higher than the number of certificates in the client certificate's signing chain, in this case one would probably suffice but 3 is pretty standard. Also 'SSLVerifyClient require' will not accept connections that don't use mTLS. This may be what you want, however, if you wish to accept all clients and implement a certain functionality for people using mTLS, I suggest changing this to 'optional'.
Hey look, it's a thing and now you can tell people about a thing you learned about. If you liked this video wait hold on this isn't dumb youtube video. I guess I don't need to ask you to: subscribe, click the bell, buy my merch, share the video, click the like button and then signup for some crappy online learning resource because I gave you a discount promo code. Everyone knows there are plenty of free resources like this one. Merry Christmas people! Until next time!Tweet
said on 12-20-2020: