1

Is there an easy way to determine if the server I communicate with from the http.Client accepted and validated my client certificate in mTLS?

In my code I want to know if the remote server accepted the client certificate I put in the transport for the client when I perform a http.Client.Get call.

The http.Response struct has a TLS field that contains the ConnectionState but this is also set when I do not provide a client cert in the transport. I do see that I get more elements in VerifiedChains when I perform a Get with client certificate than without the client certificate.

2
  • 2
    You wouldn't get a response at all if the server rejected your certificate; the TLS handshake wouldn't succeed. Commented Apr 16, 2020 at 16:35
  • 1
    Why do you want to test/know on the client side? It is the server that should protect itself from not validating the client certificate. If you want to ensure that the server accepts only clients with a certificate, you could attempt to connect with a client without certificate and expect the TLS handshake to fail. We need more context to better understand. Commented Apr 16, 2020 at 20:02

1 Answer 1

1

Thanks for all the suggestions and comments. I will comment on these and share the solution I came up with to get the information I sought.

I am in the process of testing the server and needed to see if the server asked for a client certificate and also which CAs it presents to the client for signing. I want a report similar to what this command produces:

openssl s_client -state -connect example.org:443

Thing is that our server only presents a client certificate for a specific path and not for the servername alone.

The code I came up with looks like this:


...
// Client wraps http.Client and has a callback for flagging a certificate request.
type client struct {
    http.Client
    clientAutenticated bool
}

// SetClientAuthenticated 
func (c *client) SetClientAutenticated(auth bool) {
    c.clientAutenticated = auth
}

...
// This prepares a tls config with customized GetClientCertificate

func prepareTLSConfig(setAuth func(bool)) *tls.Config {
    certPool := x509.NewCertPool()
    err := loadCertFiles(certPool, config.cacerts)
    if err != nil {
        log.Fatalf("%s", err)
    }
    cert, err := tls.LoadX509KeyPair(config.cert, config.key)
    if err != nil {
        log.Fatalf("error loading key pair: %s", err)
    }

    tlsConfig := &tls.Config{
        // Certificates:  []tls.Certificate{cert},
        RootCAs:              certPool,
        MinVersion:           config.tlsver,
        Renegotiation:        tls.RenegotiateOnceAsClient,
        GetClientCertificate: buildGetClientCertificate([]tls.Certificate{cert}, setAuth),
    }
    return tlsConfig
}

// buildGetClientCertificate returns a closure that returns an verified client cert
// or an error
func buildGetClientCertificate(certs []tls.Certificate, setAuth func(bool)) 
    func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
    // return as closure
    return func(requestInfo *tls.CertificateRequestInfo) (*tls.Certificate, error) {
        logCertificates(requestInfo.AcceptableCAs)
        setAuth(false)
        log.Printf("Client cert requested by server")
        var err error
        for _, c := range certs {
            if err = requestInfo.SupportsCertificate(&c); err == nil {
                var cert *x509.Certificate
                if c.Leaf != nil {
                    cert = c.Leaf
                } else {
                    cert, _ = x509.ParseCertificate(c.Certificate[0])
                }
                subject := cert.Subject
                issuer := cert.Issuer
                log.Printf("Client cert accepted")
                log.Printf("   s:/C=%v/ST=%v/L=%v/O=%v/OU=%v/CN=%s", subject.Country, 
                    subject.Province, subject.Locality, subject.Organization, 
                    subject.OrganizationalUnit, subject.CommonName)
                log.Printf("   i:/C=%v/ST=%v/L=%v/O=%v/OU=%v/CN=%s", issuer.Country,
                    issuer.Province, issuer.Locality, issuer.Organization, 
                    issuer.OrganizationalUnit, issuer.CommonName)
                // Signal that a suitable CA has been found and therefore 
                // the client has been authenticated.
                setAuth(true)
                return &c, nil
            }
            err = fmt.Errorf("cert not supported: %w", err)
        }
        log.Print("Could not find suitable client cert for authentication")
        return nil, err
    }
}

...
// logCertificates logs the acceptableCAs for client certification
func logCertificates(acceptableCAs [][]byte) {
    log.Printf("CA Names offered by server")
    for _, ca := range acceptableCAs {
        var name pkix.RDNSequence
        if _, err := asn1.Unmarshal(ca, &name); err == nil {
            log.Printf("   %s", name.String()           )
        }else {
            log.Printf("error unmarshalling name: %s", err)
        }
    }
}

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.