通过自签名证书进行TLS连接失败

This following reduced test case code works when run locally on my laptop using my own 'developer' certs for accessing internal services

If I run on a remote machine with dynamically generated certs (all of which is handled by a separate team in my organisation) it fails with a 400 and "No required SSL certificate was sent" error

But if I use curl on the remote machine, and specify the same certs as referenced in my Go code, it will work

So seems the certs aren't the issue but the Go code, but that itself doesn't seem to be the issue as it works with my own certs locally

package main

import (
  "crypto/tls"
  "crypto/x509"
  "fmt"
  "io/ioutil"
  "net/http"
  "os"
  "time"
)

func main() {
  transport, transErr := configureTLS()
  if transErr != nil {
    fmt.Printf("trans error: %s", transErr.Error())
    return
  }

  timeout := time.Duration(1 * time.Second)
  client := http.Client{
    Transport: transport,
    Timeout:   timeout,
  }
  resp, clientErr := client.Get("https://my-service-with-nginx/")

  if clientErr != nil {
    fmt.Printf("client error: %s", clientErr.Error())
  } else {
    defer resp.Body.Close()

    contents, contErr := ioutil.ReadAll(resp.Body)
    if contErr != nil {
      fmt.Printf("contents error: %s", contErr.Error())
    }

    fmt.Printf("

contents:

%+v", string(contents))
  }
}

func configureTLS() (*http.Transport, error) {
  certPath := "/path/to/client.crt"
  keyPath := "/path/to/client.key"
  caPath := "/path/to/ca.crt"

  // Load client cert
  cert, err := tls.LoadX509KeyPair(certPath, keyPath)
  if err != nil {
    return nil, err
  }

  // Load CA cert
  caCert, err := ioutil.ReadFile(caPath)
  if err != nil {
    return nil, err
  }
  caCertPool := x509.NewCertPool()
  caCertPool.AppendCertsFromPEM(caCert)

  // Setup HTTPS client
  tlsConfig := &tls.Config{
    Certificates:       []tls.Certificate{cert},
    RootCAs:            caCertPool,
    InsecureSkipVerify: true,
  }
  tlsConfig.BuildNameToCertificate()

  return &http.Transport{TLSClientConfig: tlsConfig}, nil
}

Does anyone know why this would be happening?

I thought it might be the renegotiation bug that Go has (as of 1.6) but I don't think that's the case here as otherwise it would fail for me when running the app locally (but it doesn't, using my own dev certs and running locally works fine - the problem only occurs when run on a remote instance with different certs; and those certs aren't the problem as they work fine when used by curl)

So the actual problem here is part related to my organisations infrastructure and part related to how nginx uses ssl_client_certificate.

We have int, test and live environments

I was led to believe that the environments could communicate between each other.

So I had my service setup on int, and I was able to use curl from there to communicate with another service setup in a live environment.

The problem occurred when using Go to communicate across environments.

The quick solution for me was to ensure when I made a GET to this other service, that instead of using:

https://service.live.me.com

I would use:

https://service.int.me.com

Or:

https://service.test.me.com

Depending on the environment my code was running within.

Obviously this isn't a solution for other people who have a similar issue but don't have the same setup.


So for those of you who still need a solution...


What I was also going to try (and apparently worked for this guy) was to get the service that I was trying to communicate with to modify their nginx conf so that they set ssl_client_certificate to not point to just a client cert, but a combined cert (one that includes the entire CA chain).

This was because apparently Go doesn't send back any certs with its response unless the server has passed a CA along with its response.

I had originally suspected this was the classic renegotiation bug in Go 1.6 and below, but that wasn't the case in this scenario.

Hope this helps anyone else in the same boat.