Issues with TLS connection in Golang

12,128

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?

Share:
12,128
jonbonazza
Author by

jonbonazza

Updated on June 18, 2022

Comments

  • jonbonazza
    jonbonazza almost 2 years

    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.