2
votes

I'm trying to connect to a server over SSL/TLS using golang http/tsl client which is resulting in 'Handshake Faliure(40)' error, but for some reason, this same endpoint works with CURL command. After some debugging, I have collected the following data.

Openssl command outputWireshark packets

client helloclient hello 2server hello Change Cipher Spec Handshake Failure

func PrepCerts(certMap map[string]string) (*http.Transport, bool) {
         ok := false
         tlsConfig := &tls.Config{}

         if len(certMap["ca"]) > 0 {
             caCert, err := ioutil.ReadFile(certMap["ca"])
             fmt.Println("caCert : ", caCert)

             if err != nil {
             log.Fatal(err)
          } else {
             caCertPool := x509.NewCertPool()
             caCertPool.AppendCertsFromPEM(caCert)
             (*tlsConfig).RootCAs = caCertPool
             ok = true
          }
     }

     if len(certMap["cert"]) > 0 && len(certMap["key"]) > 0 {
         cert, err := tls.LoadX509KeyPair(certMap["cert"], certMap["key"])
         fmt.Println("cert : ", cert)

         if err != nil {
            log.Fatal(err)
         } else {
            (*tlsConfig).Certificates = []tls.Certificate{cert}
            ok = true
         }
     }

     tlsConfig.BuildNameToCertificate()
     return &http.Transport{TLSClientConfig: tlsConfig}, ok
  }

Code that uses above function

  client := &http.Client{
      Timeout: timeout,
  }

  //certMap = map[string]string{
  // ca : "filelocation",
  // cert : "filelocation",
  // key " "filelocation",
  //}

  if transport, ok := PrepCerts(certMap); ok {
      (*client).Transport = transport
  }
  resp, err := client.Do(req)
2
"Da codez,plz!" How should we even start to verify and validate if we cannot see what is going on. Handshake failure 40 seems to indicate that server and client can not agree on a cipher suite. Strange...Markus W Mahlberg
@MarkusWMahlberg - 'server hello' packet(5th image) says they agree on TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384. I can still add code, if you need it?utkarsh sharma
@MarkusWMahlberg - I added code, hope it helps.utkarsh sharma
@MarkusWMahlberg: "Handshake failure 40 seems to indicate that server and client can not agree on a cipher suite" - Alert 40 is just a generic "handshake failure". It is often send from the server to the client when no shared cipher can be found since there is no specific alert type for this but it is not restricted to this case. See RFC 5246 section 7.2.Steffen Ullrich
If the client sent 0 certs from a certificate request, it either means that there were no compatible signature schemes, or the certs were not issued by one of the acceptable CAs. The easiest way to inspect the cert request info is to use GetClientCertificate to load the certificate.JimB

2 Answers

3
votes

From the captured packets it can be seen that the server is requesting a certificate from the client (Certificate Request). From the the detailed images included in the question it can also be seen that no certificates are sent by the client (Certificate record with Certificate Length 0).

What can also be seen is that the server complains with an Alert after the client has send the (possible empty) certificate so it likely does not like what the client has sent. So it is for sure not a problem of agreeing to a cipher (server agreed on one already) and not a problem that the client does not like the servers certificate (alert is send by server not client).

Based on your code you are trying to do something with client certificates but based on the pcap it looks like you don't succeed in using one. So somewhere there is the problem.

2
votes

As stated by FiloSottile on Github.

What I think is happening here is that the Certificate Request applies constraints (RSA vs ECDSA, or a specific issuer) which are not satisfied by your certificate.

Using his suggestions you can override the client transports tls.Config.GetClientCertificate()) method.

After following this advice I came to the conclusion that Go will not present tls.Config.RootCAs along with tls.Config.Certificates in response to a certificate request packet.

To solve this issue combine the client certificate and its CA bundle into a single file before calling x509.LoadX509KeyPair(). Taking note that the order of certificates in the file matters. If the client certificate isn't the first one in the bundle you will get a tls: private key does not match public key error.

Once you have combined the client certificate with its CA bundle you can load them into your client like so.

package main

import (
    "crypto/tls"
    "io/ioutil"
    "net/http"
    "time"

    log "github.com/sirupsen/logrus"
)

const (
    certFile = "/location/of/client_cert.bundle.pem"
    keyFile  = "/location/of/client_cert.key.pem"
    testURL  = "https://mtls-site"
)

func main() {
    clientCert, err := tls.LoadX509KeyPair(certFile, keyFile)
    if err != nil {
        panic(err)
    }

    client := http.Client{
        Transport: &http.Transport{
            TLSClientConfig: &tls.Config{
                Certificates: []tls.Certificate{clientCert},
            },
        },
    }

    resp, err := client.Get(testURL)
    if err != nil {
        panic(err)
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }
    log.Infof("%s %s", resp.Status, body)
}