How to set up an https server with a self-signed certificate in golang

Viewed 8531

I'm trying to set up an https server (http.ListenAndServeTLS()) with a self-signed certificate to listen for a webhook updates from a service (I'm sure this service has no problem working with such certificates), but it fails and constantly prints

<date> <time> http: TLS handshake error from <IP>: remote error: tls: bad certificate

I've looked for solutions and tried several (setting InsecureSkipVerify to true in TLSConfig, adding my certificate to the Certificates field of the config or signing another certificate with self-signed one and appending the latter to the RootCAs), but nothing solves the issue (the last solution unsurprisingly changes the error to unknown certificate authority).

As I've mentioned, I'm pretty sure the problem isn't related to the service I'm trying to listen to, so there must be something really simple that I'm missing about setting up a server in Go. My code looks something like this:

    rootCAs, _ := x509.SystemCertPool()
    if rootCAs == nil {
        rootCAs = x509.NewCertPool()
    }
    certs, _ := ioutil.ReadFile(CA_PATH)
    _ = rootCAs.AppendCertsFromPEM(certs);

    server := http.Server{
        Addr: "",
        Handler: nil,
        TLSConfig: &tls.Config{
            InsecureSkipVerify: true,
            Certificates: []tls.Certificate{ my_cert },
            RootCAs: rootCAs,
        },
    }
    fmt.Println(server.ListenAnsServeTLS(PEM_PATH, KEY_PATH))
2 Answers

As mentioned in RFC2246, the handshake protocol establishes a TLS session and negotiates the security permissions between the client and the server. By default, your machine already trusts a set of certificates from many Certificate Authorities like: GoDaddy, IdenTrust, DigiCert, etc. When you are the Certificate Authority (self-signed certificate way), you need to provide the certificate to the client, and the client needs to trust that certificate. So what you need to do is:

after openssl tool installation, run the command below:

openssl req -new -subj "/C=US/ST=Utah/CN=localhost" -newkey rsa:2048 -nodes -keyout localhost.key -out localhost.csr

that command generates a localhost.key file which is the private key and localhost.csr which is the certificate signing request (CSR) that contains the public key. The CN passed in -subj is the most important field because some browsers like chrome require that information. CN means Common Name and it is the domain name that you would like to have SSL secured. Then, you need to generate the certificate file:

openssl x509 -req -days 365 -in localhost.csr -signkey localhost.key -out localhost.crt

The localhost.crt file is generated by the command above and it's the self-signed certificate signed by your own localhost.key private key. The x509 flag states the standard format of an SSL/TLS certificate which is X.509.

After that you need to use the cert and key to serve the https self signed server:

log.Fatal(http.ListenAndServeTLS("8081", "localhost.crt","localhost.key", router))

In the client side, is very important that it trusts the server certificate, instead of using curl's -k parameter, that parameter will ignore TLS protocol. If you're using a linux OS, install the ca-certificates package and then copy the localhost.crt to ca-certificates folder:

cp localhost.crt /usr/local/share/ca-certificates/localhost.crt

finally trust that certificate with the command below:

update-ca-certificates

If you're using a different OS than linux you can follow one of these steps: Mac Os, Windows, Linux. You'll find a complete example in this github repo.

Create a directory golangssl and enter it

mkdir golangssl
cd golangssl

Generate a selfsigned key and cert (install openssl if not yet installed)

openssl genrsa -out server.key 2048
openssl ecparam -genkey -name secp384r1 -out server.key
openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650

Create a file server.go with the following content

package main

import (
    "log"
    "net/http"
)

func SecureServer(w http.ResponseWriter, req *http.Request) {
    w.Header().Set("Content-Type", "text/plain")
    w.Write([]byte("Secure Hello World.\n"))
}

func main() {
    http.HandleFunc("/secure", SecureServer)
    err := http.ListenAndServeTLS(":443", "server.crt", "server.key", nil)
    if err != nil {
            log.Fatal("ListenAndServe: ", err)
    }
 }

Run the server (needs root rights, because of binding of port 443)

sudo go run server.go

Execute a request (-k for ignoring the self signed certificate)

 curl -k https://localhost/secure

Output should be

 Secure Hello World.
Related