Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Jeremy Mill
Jeremy Mill

Posted on

     

Validating Client Certificate SANs in Go

Go has one of the best TLS libraries available in any programming language, for it's my language of choice for doing networking tasks. So I was a bit surprised to learn that, by default, when you settls.RequireAndVerifyClientCert on atls.Config object, it doesn't verify the SAN/CN field on that client cert. The only thing it will verify is that it is signed by the configured root CA.

Setting go up to perform this validation was not quite as intuitive as I first believed it would be and I hope that I can help you with it if you have the same need as myself.

Starting Point

Lets say you have a starting point of a generic tls config like this:

serverConf:=&tls.Config{Certificates:[]tls.Certificate{cer},MinVersion:tls.VersionTLS12,ClientAuth:tls.RequireAndVerifyClientCert,ClientCAs:rootCAs,}
Enter fullscreen modeExit fullscreen mode

Here we've defined a server certificate, a minimum TLS version, the root CA's to use and that we need to verify client certificates. Right now, client certificates would be validated as signed by a CA in the rootCA's CertPool, but nothing else.

Custom Client Cert Validation

Thetls.Config object has a callback that looks very promising calledVerifyPeerCertificate that takes in a method with this signature:

func(rawCerts[][]byte,verifiedChains[][]*x509.Certificate)error
Enter fullscreen modeExit fullscreen mode

The problem is that this doesn't have any of the client connection information passed it it for us to validate the connecting host! Luckily for us, there's another callback calledGetConfigForClient.GetConfigForClient is another callback on thetls.Config object which gives us thetls.ClientHelloInfo as an argument. and returns a per-clienttls.Config (ornil for no-change) object.

The answer is to useGetConfigForClient to call a function which returns a closure that matches theVerifyPeerCertificate signature but makes theClientHelloInfo available to it.

Our server tls.Config now looks like:

serverConf:=&tls.Config{Certificates:[]tls.Certificate{cer},GetConfigForClient:func(hi*tls.ClientHelloInfo)(*tls.Config,error){serverConf:=&tls.Config{Certificates:[]tls.Certificate{cer},MinVersion:tls.VersionTLS12,ClientAuth:tls.RequireAndVerifyClientCert,ClientCAs:rootCAs,VerifyPeerCertificate:getClientValidator(hi),}returnserverConf,nil},}
Enter fullscreen modeExit fullscreen mode

and our stubbed outgetClientValidator function looks like:

funcgetClientValidator(helloInfo*tls.ClientHelloInfo)func([][]byte,[][]*x509.Certificate)error{returnfunc(rawCerts[][]byte,verifiedChains[][]*x509.Certificate)error{returnnil}}
Enter fullscreen modeExit fullscreen mode

At this point there has been essentially no change to the functionality from where we started. Clients still connect, and their certificates are validated, but their SAN's are not.

Validating SANs

To validate the SAN's on the client certificate we need to modify thegetClientValidatormethod. In order to avoid writing our own validation methods, we can utilize the same validator that's used by default when we specifytls.RequireAndVerifyClientCert on our config object. All we need to do is add some additional options to its configuration object.

funcgetClientValidator(helloInfo*tls.ClientHelloInfo)func([][]byte,[][]*x509.Certificate)error{returnfunc(rawCerts[][]byte,verifiedChains[][]*x509.Certificate)error{//copied from the default options in src/crypto/tls/handshake_server.go, 680 (go 1.11)//but added DNSNameopts:=x509.VerifyOptions{Roots:rootCAs,CurrentTime:time.Now(),Intermediates:x509.NewCertPool(),KeyUsages:[]x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},DNSName:strings.Split(helloInfo.Conn.RemoteAddr().String(),":")[0],}_,err:=verifiedChains[0][0].Verify(opts)returnerr}}
Enter fullscreen modeExit fullscreen mode

This now validates the client's certificate against the root CA's we specified at our starting point, the client's certificate key usage, and the DNSName of the client connecting. Thestrings.Split saves us from including theRemoteAddrport number in the DNSName field, and won't screw anything up if it's an actual DNS name.

There it is, I hope you find this useful! You can find all of the code in a single, small server.go examplehere

Top comments(5)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
living_syn profile image
Jeremy Mill
Veteran, Security Engineer, prior dev
  • Education
    UConn
  • Work
    Cyber Security Engineer
  • Joined

Note: Huge thanks to Filippo Valsorda (github.com/FiloSottile) for his help pointing me in the right direction on how to do thishere

CollapseExpand
 
marcmagnin profile image
Marc Magnin
Living in France and working most of the time as a software engineer.
  • Location
    France
  • Work
    Principal Engineer at Digitalis.io
  • Joined
• Edited on• Edited

I was wondering if we couldn't use tls.VerifyHostname for that check also:golang.org/src/crypto/tls/conn.go?...

Edit: By looking into go code it looks that tls.Verify is broader than tls.VerifyHostname (it actually can call VerifyHostname).
Thanks a lot for such great post!

CollapseExpand
 
kaushaldokania profile image
Kaushal Dokania
Software Developer
  • Location
    Bengaluru, India
  • Education
    B. Tech. in Computer Science & Engineering
  • Joined

Great article.

CollapseExpand
 
cueo profile image
Mohit Mayank
Likes to code
  • Work
    Software Engineer
  • Joined

Would this work if the client is behind a NAT?

CollapseExpand
 
living_syn profile image
Jeremy Mill
Veteran, Security Engineer, prior dev
  • Education
    UConn
  • Work
    Cyber Security Engineer
  • Joined

Sorry this reply issuper late, but unless the public IP is what is on the cert, no, it won't.

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Veteran, Security Engineer, prior dev
  • Education
    UConn
  • Work
    Cyber Security Engineer
  • Joined

Trending onDEV CommunityHot

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp