Go 1.14 is out and with it come a few nice updates to crypto/tls!

Will this certificate work?

Certificate selection in TLS[1] is a mess. I was going to try and describe it here to make the point, but I kept getting it wrong and it was even too messy for something just meant to make the point that it's a mess. Anyway, something nice that's coming to crypto/tls in Go 1.14 are SupportsCertificate methods on the structs that are passed to callbacks. They "simply" tell you if a peer is going to support a certificate or not based on the dozen interlocked negotiated parameters that come into play.

func (*ClientHelloInfo) SupportsCertificate(c *Certificate) error
func (*CertificateRequestInfo) SupportsCertificate(c *Certificate) error

This functionality usually comes up when a server has both an RSA and an ECDSA certificate. The server prefers the latter because it's smaller and faster to make signatures with, but not all clients support it. autocert has a hacked up version of this heuristic that we can now replace with SupportsCertificate.

Even better, if you just put all your certificates in preference order in Config.Certificates, we'll now pick the first one that the connection supports. Certificate selection is the kind of gnarly stuff that applications will never want us to do wrong, nor want to have to think about, so we can just take the decisions and make it easy.

I'm pretty proud of how "straightforward" the implementation turned out, but that was only possible thanks to a Pepe Silvia-worthy refactor that Adam and Katie heroically reviewed.

Speaking of certificates, identical ones can still behave differently if their private keys are implemented by something like a remote API, an RPC, an HSM or a TPM, rather than by local crypto code. In Go this is particularly easy because the private key just has to implement the crypto.Signer interface. Signer arguably has a flaw in that you are supposed to make your implementation behave like the private key type corresponding to the public key type you expose, but there's nothing checking that you are doing it properly[2]. For example, if your Public method returns an *rsa.PublicKey, then your Sign method is expected to behave like (*rsa.PrivateKey).Sign. But it's just as easy to ignore the opts argument, so that's how we ended up with broken partial crypto.Signer implementations that will return an RSA PKCS#1 v1.5 signature even when asked for an RSA-PSS one. Now consider that TLS 1.3 switched to RSA-PSS and you can imagine what my debugging sessions have been about[3]. Anyway, Certificates now have a SupportedSignatureAlgorithms field to specify which signature algorithms they support, and crypto/tls will consider that in both certificate and algorithm selection. This still won't let those partial Signers be used with TLS 1.3 since it doesn't support PKCS#1 v1.5 at all, but it finally allowed me to turn on RSA-PSS in TLS 1.2 since there is now an opt-out for crippled keys. It only took a year and four reverts!

Cipher suites

By popular demand, crypto/tls has new APIs exposing the cipher suites supported by the package. No more need to maintain maps of cipher suite IDs to names!

type CipherSuite struct {
    ID                uint16
    Name              string
    SupportedVersions []uint16
    Insecure          bool
}
func CipherSuites() []*CipherSuite
func InsecureCipherSuites() []*CipherSuite

They went through a million iterations but mostly thanks to Russ Cox's, the team's and the community's advice they are IMHO pretty nice: one CipherSuiteName(uint16) string helper for the common use case, one CipherSuites function returning a slice of secure cipher suites, and one function loudly named InsecureCipherSuites returning a slice of the insecure ones.

I specifically wanted to make it as hard as possible to just enable all supported cipher suites: there's a reason we disable some by default. Since it requires to type I-n-s-e-c-u-r-e, I am pretty satisfied.

But really, the best solution is what we did with TLS 1.3: not let applications configure cipher suites at all. In TLS 1.3 they are all secure and we just sort them for you by performance based on your hardware. Again, sometimes we can just make the decisions and make it easy. (I stole this policy from BoringSSL, and only got yelled at half a dozen time over it. I think that's pretty good, although I don't get why people really like configuring cipher suites and not, say, signature algorithms.)

In with the new...

Only ever adding features is how you bankrupt your complexity budget, so in Go 1.14 we are also removing support for SSLv3 (-250 lines), the cryptographically broken predecessor of TLS[4], and NPN (-160 lines), the never-standardized extension replaced by ALPN over six years ago. đŸ•ē

I expected the former to cause some breakage in ill-advised applications, and the latter to be inconsequential. Of course, I was wrong. I heard not a single complaint about SSLv3, while NPN turned out to be necessary to speak to gRPC built against ancient versions of OpenSSL. So ancient that they are twice unsupported, though, so I still hope we can mostly blame the application here.

How APIs are made

API changes aren't directly born as CLs (the Gerrit equivalent of PRs), but go through the proposal process, where everyone has the opportunity to comment. For example, here are a few recently accepted or likely to be accepted ones you might see land in Go 1.15.

  • #34105 — crypto/elliptic: handle compressed formats in new MarshalCompressed, UnmarshalCompressed
  • #35428 — crypto/x509: add API to create RFC-compliant CRLs
  • #33430 — crypto: implement Hash.String
  • #36736 — crypto/tls: add VerifyConnection callback
  • #21704 — crypto: add Equal(PublicKey) bool method to PublicKey implementations
  • #33564 — crypto/ecdsa: make PublicKey implement encoding.TextMarshaler/TextUnmarshaler using PEM
  • #20544 — crypto/ecdsa: add SignASN1, VerifyASN1

I could tell you how each of them is amazing, but they are kicking me out now.

The magnificent NYPL Rose Hall at closing time

As usual, for a bunch of other neat changes, including defer now being free, and machine-readable inliner, escape analysis, and bounds-check elimination diagnostics, read the release notes!


  1. It's mostly bad in TLS 1.0 to 1.2. TLS 1.3 did a nice cleanup and made sure that for example cipher suites only affect the symmetric encryption, and not the certificate signatures or the key exchange algorithms, so you can just look at the supported signature algorithms to select certificates. ↩ī¸Ž

  2. It's also annoying because we can never make private keys accept new opts arguments, or all custom Signer implementations will suddenly be incomplete. Anyway, I have a mostly idle CL implementing some interface tests that would help make sure implementations are compliant. Interface tests are great and we should write more of those. ↩ī¸Ž

  3. Fun fact, a community member started telling me a story of a weird issue they had with TLS 1.3 because they store certificate keys on a YubiKey, and I just finished the sentence for them because I knew this is where that was going. TLS implementers need a support group. ↩ī¸Ž

  4. The draft of RFC 7568, which deprecates SSLv3, was called draft-ietf-tls-sslv3-diediedie. I love that. ↩ī¸Ž