如何使用Go TLS手动验证客户端证书?

We are building a server in Golang that opens a TCP port over SSL.

We would like to enable mutual-authentication between the client and the server. But also detect when a client attempts to connect to our server without a valid client certificate and return them a error message over SSL - such as "invalid client certificate detected, please contact company ABC for assistance".

Just to be clear: we are adamant about our requirement to return data over SSL to clients that fail to mutually authenticate with the server. We do not want to disconnect them.

The approach we have taken is to use the 'VerifyClientCertIfGiven' configuration setting for TLS.

Whereby, we verify the client certificate if it is given, but we still allow an SSL connection to be established if it isn't.

How can we find out:

  1. Was a client certificate provided?
  2. If so, did it pass the mutual authentication checks performed by TLS?

Below is the code of our server:

package main

import(
  "fmt"
  "io/ioutil"
  "crypto/tls"
  "crypto/x509"
)

func main(){
  // Configure SSL
  cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
  caCert, _ := ioutil.ReadFile("client.crt")
  caCertPool := x509.NewCertPool()
  caCertPool.AppendCertsFromPEM(caCert)
  config := &tls.Config{
    Certificates: []tls.Certificate{cert},
    ClientCAs: caCertPool,
    ClientAuth: tls.VerifyClientCertIfGiven,
  }
  config.BuildNameToCertificate()
  // Listen on port 443
  listener, _ := tls.Listen("tcp", ":443", config)
  defer listener.Close()
  // Accept incoming connection
  conn, _ := listener.Accept()
  defer conn.Close()
  // Print ConnectionState
  tlscon, _ := conn.(*tls.Conn)
  fmt.Println(tlscon.ConnectionState())
}

And below is the code of our client:

package main
import (
  "io/ioutil"
  "crypto/tls"
  "crypto/x509"
)

func main(){
  //Configure SSL
  cert, _ := tls.LoadX509KeyPair("client.crt", "client.key"
  caCert, _ := io.util.ReadFile("server.crt")
  caCertPool := x509.NewCertPool()
  caCertPool.AppendCertsFromPEM(caCert)
  config := &tls.Config{
    Certificates: []tls.Certificate{cert},
    RootCAs:      caCertPool,
  }
  config.BuildNameToCertificate()
  // Connect to server
  conn, _ := tls.Dial("tcp", "127.0.0.1:443", config)
  defer conn.Close()
}

The output:

{0 false false 0  false  [] [] [] [] []}

We've tried implementing conn.ConnectionState().PeerCertificates on the server but in all our attempts, it was an empty byte array.

Thank you in advance. We appreciate your time trying to help us.

Kind Regards,

Julian

I spent the best of my afternoon on this one too.

Once the server accepts the connection you will need to explicitely call tlscon.Handshake() to get the client's certificate, otherwise the tls package will do it automatically after the first i/o. https://golang.org/pkg/crypto/tls/#Conn.Handshake

You can see a example here.