3

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)
utkarsh sharma
  • 149
  • 2
  • 12
  • 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 Oct 18 '19 at 00:31
  • @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 Oct 18 '19 at 04:09
  • @MarkusWMahlberg - I added code, hope it helps. – utkarsh sharma Oct 18 '19 at 04:25
  • 2
    @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](https://tools.ietf.org/html/rfc5246#section-7.2). – Steffen Ullrich Oct 18 '19 at 05:28
  • 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 Oct 18 '19 at 13:00
  • Regarding certificate authorities, make sure the server returns the full cert bundle such that the client can build a full path from its leaf to the CA root. For example, if your CA path looks like leaf+intermediate+root, the server must return intermediate+root in its TLS Certificate Request. If you omit the intermediate, even if you include the concatenated intermediate in the client's cert PEM file, the client will have no way to build a path from leaf to root, and therefore have no way to determine which leaf to send (if it has a choice). – ae6rt May 19 '20 at 13:05

2 Answers2

3

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.

Steffen Ullrich
  • 114,247
  • 10
  • 131
  • 172
  • 1
    In the 2nd last image the TLSv1.2: Record Layer: Handshake Protocol: Certificate section - certificate length is 0, can we conclude from this that there are no certs passed? – utkarsh sharma Oct 18 '19 at 05:55
  • @utkarshsharma: you are right, I did not see this detail. I've adapted the question. – Steffen Ullrich Oct 18 '19 at 06:13
2

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)
}
jqualls
  • 1,483
  • 14
  • 19