Search code examples
gossltls1.3

GoLang: TLS client error `x509: certificate signed by unknown authority`


I have followed multiple tutorials [1 , 2, 3] to write a client-server in Go that accepts TLS authentication.

I have my server certificates and keys named:

  • server_cert.pem,
  • server_key.pem, and
  • server_cacerts.pem

The server code looks as follows:

    path, err := os.Getwd()
    if err != nil {
        log.Println(err)
    }
    CACert := path + "/server_cacerts.pem"
    log.Println(CACert)

    certs, err := ioutil.ReadFile(CACert)
    if err != nil {
        log.Fatal("Invalid CACert: %s", err)
    }

    rootCAs := x509.NewCertPool()
    ok := rootCAs.AppendCertsFromPEM(certs)
    if !ok {
        log.Println("Taking default certificates")
    }

    certPem := path + "/server_cert.pem"
    keyPem := path + "/server_key.pem"
    cert, err := tls.LoadX509KeyPair(certPem, keyPem)
    if err != nil {
        log.Fatal(err)
    }

    log.Println(certPem)
    log.Println(keyPem)

    config := &tls.Config{Certificates: []tls.Certificate{cert},
                          // ServerName: "elxa3x9nxd3",
                          ClientAuth: tls.RequireAndVerifyClientCert,
                          ClientCAs: rootCAs}

    ln, err := tls.Listen("tcp", ":8888", config)
    if err != nil {
        log.Fatal("listen failed: %s", err.Error())
        return
    }
    defer ln.Close()

    for {
        conn, err := ln.Accept()
        if err != nil {
            log.Fatal("Accept Failed: %s", err.Error())
            continue
        }
        // log.Printf("connection open: %s", conn.RemoteAddr())
        // printConnState(conn.(*tls.Conn))
        go handleConnection(conn)
    }

and the client certificates and keys named:

  • client_cert.pem,
  • client_key.pem, and
  • client_cacerts.pem

Client code follows:

func main() {
    log.SetFlags(log.Lshortfile)
    args := os.Args

    var numberHandshakes int
    var err error

    if len(args) > 1 {
        numberHandshakes, err = strconv.Atoi(args[1])
        if err != nil {
            log.Println("Error in number of arguments")
            panic(err)
        }
    } else {
        numberHandshakes = 1000
    }
    fmt.Println("Number of handshakes requested: " + strconv.Itoa(numberHandshakes))


    path, err := os.Getwd()
    if err != nil {
        log.Println(err)
    }
    CACert := path + "/client_cacerts.pem"
    certs, err := ioutil.ReadFile(CACert)
    if err != nil {
        log.Fatal(err)
    }

    // rootCAs := x509.NewCertPool()
    rootCAs, err := x509.SystemCertPool()
    ok := rootCAs.AppendCertsFromPEM(certs)
    if !ok {
        log.Println("Taking default certificates")
    }


    certPem := path + "/client_cert.pem"
    keyPem := path + "/client_key.pem"
    cert, err := tls.LoadX509KeyPair(certPem, keyPem)
    if err != nil {
        log.Fatal("Error loading KeyPair: %s", err)
    }

    conf := &tls.Config{ Certificates: []tls.Certificate{cert},
                         // InsecureSkipVerify: true,
                         ServerName: "elxa3x9nxd3",
                         RootCAs: rootCAs,
                        }

    ch := make(chan int)
    for i := 1; i <= numberHandshakes; i++ {
        go func(){
            conn, err := tls.Dial("tcp", ":8888", conf)      // ERROR HERE
            if err != nil {
                log.Println("Error in client", err.Error())
                return
            }

            conn.SetWriteDeadline(time.Time{})
            conn.Write([]byte(strconv.Itoa(i)))
        }()
    }
}

I run the server and then call the client to perform some number of queries to the server. The error seems to be in the line marked as ERROR HERE, and the error that I see says:

tls-client.go:98: Error in client tls: failed to verify certificate: x509: certificate signed by unknown authority

I know that I have self-signed a certificate, but I thought that given the client and server options that I have in my code, I do not need to throw out of the window any security (I do not want to enable InsecureSkipVerify).

Any tips?


Solution

  • I assume that server_cacerts.pem contains the root CA and possible intermediate CAs which are used to create server_cert.pem and the same for client_*.pem.

    Since the client needs to verify the server certificate and this is done using server_cacerts.pem the client needs this as RootCA, not client_cacerts.pem as done in your code. Similar with mutual authentication the server needs to verify the client certificate and thus needs client_cacerts.pem as ClientCA, not server_cacerts.pem as done in your code.