tls: Optional client certificate authentication with VerifyClientCertIfGiven

1,329 views
Skip to first unread message

sven.g...@gmail.com

unread,
Feb 6, 2017, 11:48:17 AM2/6/17
to golang-nuts
Hi, 

I've a legacy application at hand that has a nginx as TLS offloader in front of it. Besides a simple frontend the application offers an API including a PKI infrastructure (CSRs are pushed to server, signed CRTs are returned). The nginx is configured to optionally request a client certificate ("ssl_verify_client optional") from the requesting party (browsers, IoT devices, ...). 

This approach is working for most browsers except OS X's Safari. nginx offers a list acceptable client certificate CA names but Safari tends to ignore this list and prompts the user to chose a certificate (including iCloud certificates and stuff). 

Browsers are not necessarily required to provide a client certificate. IoT devices are configured to always provide a client certificate. 

Differing from nginx's and Apache's SSL configuration, the TLS implementation of golang seems to offer another client authentication mechanism: VerifyClientCertIfGiven (see https://golang.org/pkg/crypto/tls/#ClientAuthType). So I decided to give golang a try (as a potential TLS offloader replacing nginx) and came up with the following simple TLS secured HTTP server: 

package main


import (
   
"log"
   
"net/http"
   
"crypto/tls"
   
"crypto/x509"
   
"encoding/pem"
   
"io/ioutil"
   
"fmt"
)


func handler
(w http.ResponseWriter, req *http.Request) {
   w
.Header().Set("Content-Type", "text/plain")
   w
.Write([]byte("Test.\n"))
}


func main
() {
   http
.HandleFunc("/", handler)

   pemByte
, _ := ioutil.ReadFile("ssl/ca.pem")
   block
, pemByte := pem.Decode(pemByte)

   cert
, err := x509.ParseCertificate(block.Bytes)

   
if err != nil {
       fmt
.Println(err)
   
}

   certPool
:= x509.NewCertPool()
   certPool
.AddCert(cert)

   server
:= &http.Server{
       
TLSConfig: &tls.Config{
           
ClientAuth: tls.VerifyClientCertIfGiven,
           
ClientCAs: certPool,
       
},
       
Addr: "0.0.0.0:10443",
   
}


    server
.TLSConfig.BuildNameToCertificate()

    err
= server.ListenAndServeTLS("ssl/server.crt", "ssl/server.key")
 
   
if err != nil {
        log
.Fatal(err)
   
}
}

My initial thought when reading the const "VerifyClientCertIfGiven" was that a list of acceptable client certificate CA names will NOT be sent to the connecting client, but certificates that are provided will be validated against the configured ClientCAs. 

I had a look into the server TLS implementation and I'm wondering about the conditional statement "if config.ClientAuth > RequestClientCert" around the certificate request message (see https://github.com/golang/go/blob/release-branch.go1.7/src/crypto/tls/handshake_server.go#L409). 

I'm new to golang and my knowledge of the TLS spec isn't good either but I'm wondering if this conditional would be better changed to "c.config.ClientAuth == RequireAnyClientCert || c.config.ClientAuth == RequireAndVerifyClientCert || c.config.ClientAuth == RequestClientCert"? From my point of view "VerifyClientCertIfGiven" has no special meaning and acts exactly as RequireAndVerifyClientCert. 

Am I missing something?

BR, Sven

t.sch...@gmail.com

unread,
Feb 9, 2017, 11:09:52 AM2/9/17
to golang-nuts, sven.g...@gmail.com
+1
Reply all
Reply to author
Forward
0 new messages