My name is Philipp C. Heckel and I write about nerdy things.

Go: Calculating public key hashes for public key pinning in curl

Scripting, Security

Go: Calculating public key hashes for public key pinning in curl

Something occurred to me the other day. This is my blog, and that means I can write about whatever I want. Now you may think that’s totally obvious, but it’s not. For the longest time I wouldn’t blog about anything that I didn’t deem blog-worthy. Small things, like “this is a cool function I found” or “I learned this thing today”, were not blog-worthy in my mind for some reason.

Well today I am changing that. I like writing, but not necessarily so much that I always want to write a super long post. Sometimes, things should be short. Like this one.

So in this super short post I’m gonna show you a cool thing I figured out: How to calculate the the value that curls --pinnedpubkey option needs in Go.

curl’s --insecure flag is insecure — shocker!

For my tiny side project pcopy, I wanted to be able to be able to allow people to easily install/join a remote clipboard using a curl | sh-type install, even when the shared clipboard is internal to a company network and doesn’t have a proper SSL certificate, i.e. when the cert is self-signed.

The only way to make curl work with self-signed certs is with the -k flag (--insecure):

And that’s obviously not cool, because that flag allows replacing the cert entirely in man-in-the-middle attacks (see my post on mitmproxy for a practical example; wow that post is from 7 years ago — I’m telling you time flies …).

--pinnedpubkey to the rescue

So what to do, what to do? Should we just not care about that? No of course not! I care deeply about security, so let’s figure out if we can use public key pinning in curl (note that this link points to “HTTP public key pinning” not the concept of public key pinning in general; there is strangely no Wikipedia article about that).

Public key pinning allows us to pin a specific public key for a given request, so that even if a certificate is self-signed it can’t be replaced without raising an exception. This technique is pretty useful if you want to support self-signed certs, but still be secure.

And surely enough, curl has a --pinnedpubkey option. From the man page:

Now this description wasn’t really helpful when I was trying to figure out what exactly curl was expecting here. Public keys can be encoded as PEM or DER, and then there’s also PKIX and PKCS1. Plus somehow ASN.1 is involved in the whole thing. It’s all pretty confusing, and of course, none of this is mentioned in the tiny man page entry. So it took a while to figure it out.

So with the help of the curl docs on CURLOPT_PINNEDPUBLICKEY and the lovely #security channel on the Go Slack, I figured out that curl is expecting the base64-encoded SHA-256 checksum of the PKIX, ASN.1 DER encoded public key. That’s a mouthful, isn’t it?

In bash (using openssl), that looks like this:

In Go, it looks like this:

It took me a while to figure out that I needed to use x509.MarshalPKIXPublicKey (PKIX, ASN.1 DER form), and not x509.MarshalPKCS1PublicKey (PKCS#1, ASN.1 DER form). Apparently, the PKIX format also includes the public key algorithm (RSA, EC), and not just the raw bytes, and curl expects this form. There is a good explanation of Stack Overflow.

Long story short, now pcopy invite (see source code) can output a secure curl command, even for self-signed certs:

Note that despite the -k flag still being there, the command cannot be intercepted without curl erroring, because the pinned public key hash won’t match. However, removing the flag will make curl complain with curl: (60) SSL certificate problem: self signed certificate. All that means is that the --pinnedpubkey verification happens after the self-signed cert verification.

Leave a comment

I'd very much like to hear what you think of this post. Feel free to leave a comment. I usually respond within a day or two, sometimes even faster. I will not share or publish your e-mail address anywhere.