Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issues with TLS connection in Golang

I have the following certificate hierarchy:

Root-->CA-->3 leaf certificates

The entire chain has both serverAuth and clientAuth as extended key usages explicitly defined.

In my go code, I create a tls.Config object like so:

func parseCert(certFile, keyFile string) (cert tls.Certificate, err error) {
    certPEMBlock , err := ioutil.ReadFile(certFile)
    if err != nil {
        return
    }
    var certDERBlock *pem.Block
    for {
        certDERBlock, certPEMBlock = pem.Decode(certPEMBlock)
        if certDERBlock == nil {
            break
        }
        if certDERBlock.Type == "CERTIFICATE" {
            cert.Certificate = append(cert.Certificate, certDERBlock.Bytes)
        }
    }

    // Need to flip the array because openssl gives it to us in the opposite format than golang tls expects.
    cpy := make([][]byte, len(cert.Certificate))
    copy(cpy, cert.Certificate)

    var j = 0
    for i := len(cpy)-1; i >=0; i-- {
        cert.Certificate[j] = cert.Certificate[i]
        j++
    }

    keyData, err := ioutil.ReadFile(keyFile)
    if err != nil {
        return
    }

    block, _ := pem.Decode(keyData)
    if err != nil {
        return
    }

    ecdsaKey, err := x509.ParseECPrivateKey(block.Bytes)
    if err != nil {
        return
    }

    cert.PrivateKey = ecdsaKey

    return
}

// configure and create a tls.Config instance using the provided cert, key, and ca cert files.
func configureTLS(certFile, keyFile, caCertFile string) (tlsConfig *tls.Config, err error) {

    c, err := parseCert(certFile, keyFile)
    if err != nil {
        return
    }

    ciphers := []uint16 {
        tls.TLS_RSA_WITH_AES_256_CBC_SHA,
        tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
    }

    certPool := x509.NewCertPool()
    buf, err := ioutil.ReadFile(caCertFile)
    if nil != err {
        log.Println("failed to load ca cert")
        log.Fatal(seelog.Errorf("failed to load ca cert.\n%s", err))
    }

    if !certPool.AppendCertsFromPEM(buf) {
        log.Fatalln("Failed to parse truststore")
    }


    tlsConfig = &tls.Config {
        CipherSuites: ciphers,
        ClientAuth: tls.RequireAndVerifyClientCert,
        PreferServerCipherSuites: true,
        RootCAs: certPool,
        ClientCAs: certPool,
        Certificates: []tls.Certificate{c},
    }

    return
}

certFile is the certificate chain file and keyFile is the private key file. caCertFile is the truststore and consists of just the root certificate

So basically, here is what I expect to have inside of my tls.Config object that comes out of this function:

RootCAs: Just the root certificate from caCertFile ClientCAs: Again, just the root certificate from caCertFile, same as RootCAs Certificates: A single certificate chain, containing all of the certificates in certFile, ordered to be leaf first.

Now, I have 3 pieces here. A server, a relay, and a client. The client connects directly to the relay, which in turn forwards the request to the server. All three pieces use the same configuration code, of course using different certs/keys. The caCertFile is the same between all 3 pieces.

Now, if I stand up the server and the relay and connect to the relay from my browser, all goes well, so I can assume that the connection between relay and server is fine. The issue comes about when I try to connect my client to the relay. When I do so, the TLS handshake fails and the following error is returned:

x509: certificate signed by unknown authority

On the relay side of things, I get the following error: http: TLS handshake error from : remote error: bad certificate

I am really at a loss here. I obviously have something setup incorrectly, but I am not sure what. It's really weird that it works from the browser (meaning that the config is correct from relay to server), but it doesn't work with the same config from my client.

Update:

So if I add InsecureSkipVerify: true to my tls.Config object on both the relay and the client, the errors change to:

on the client: remote error: bad certificate

and on the relay: http: TLS handshake error from : tls: client didn't provide a certificate

So it looks like the client is rejecting the certificate on from the server (the relay) due to it being invalid for some reason and thus never sending its certificate to the server (the relay).

I really wish go had better logging. I can't even hook into this process to see what, exactly, is going on.

like image 291
jonbonazza Avatar asked Nov 03 '14 17:11

jonbonazza


1 Answers

When you say

Need to flip the array because openssl gives it to us in the opposite format than golang tls expects.

I have used certificates generated by openssl and had no problem opening them with:

tls.LoadX509KeyPair(cert, key)

Anyway, the error message bad certificate is due to the server not managing to match the client-provided certificate against its RootCAs. I have also had this problem in Go using self-signed certificats and the only work-around I've found is to install the caCertFile into the machines system certs, and use x509.SystemCertPool() instead of x509.NewCertPool(). Maybe someone else will have another solution?

like image 151
beldin0 Avatar answered Oct 28 '22 17:10

beldin0