Skip to content

Instantly share code, notes, and snippets.

@denji
Forked from spikebike/client.go
Last active May 29, 2024 10:16
Show Gist options
  • Save denji/12b3a568f092ab951456 to your computer and use it in GitHub Desktop.
Save denji/12b3a568f092ab951456 to your computer and use it in GitHub Desktop.
Simple Golang HTTPS/TLS Examples

Moved to git repository: https://github.com/denji/golang-tls

Generate private key (.key)
# Key considerations for algorithm "RSA" ≥ 2048-bit
openssl genrsa -out server.key 2048

# Key considerations for algorithm "ECDSA" ≥ secp384r1
# List ECDSA the supported curves (openssl ecparam -list_curves)
openssl ecparam -genkey -name secp384r1 -out server.key
Generation of self-signed(x509) public key (PEM-encodings .pem|.crt) based on the private (.key)
openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650

Simple Golang HTTPS/TLS Server

package main

import (
    // "fmt"
    // "io"
    "net/http"
    "log"
)

func HelloServer(w http.ResponseWriter, req *http.Request) {
    w.Header().Set("Content-Type", "text/plain")
    w.Write([]byte("This is an example server.\n"))
    // fmt.Fprintf(w, "This is an example server.\n")
    // io.WriteString(w, "This is an example server.\n")
}

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

Hint: visit, please do not forget to use https begins,otherwise chrome will download a file as follows:

$ curl -sL https://localhost:443 | xxd
0000000: 1503 0100 0202 0a                        .......

TLS (transport layer security) — Server

package main

import (
    "log"
    "crypto/tls"
    "net"
    "bufio"
)

func main() {
    log.SetFlags(log.Lshortfile)

    cer, err := tls.LoadX509KeyPair("server.crt", "server.key")
    if err != nil {
        log.Println(err)
        return
    }

    config := &tls.Config{Certificates: []tls.Certificate{cer}}
    ln, err := tls.Listen("tcp", ":443", config) 
    if err != nil {
        log.Println(err)
        return
    }
    defer ln.Close()

    for {
        conn, err := ln.Accept()
        if err != nil {
            log.Println(err)
            continue
        }
        go handleConnection(conn)
    }
}

func handleConnection(conn net.Conn) {
    defer conn.Close()
    r := bufio.NewReader(conn)
    for {
        msg, err := r.ReadString('\n')
        if err != nil {
            log.Println(err)
            return
        }

        println(msg)

        n, err := conn.Write([]byte("world\n"))
        if err != nil {
            log.Println(n, err)
            return
        }
    }
}

TLS (transport layer security) — Client

package main

import (
    "log"
    "crypto/tls"
)

func main() {
    log.SetFlags(log.Lshortfile)

    conf := &tls.Config{
         //InsecureSkipVerify: true,
    }

    conn, err := tls.Dial("tcp", "127.0.0.1:443", conf)
    if err != nil {
        log.Println(err)
        return
    }
    defer conn.Close()

    n, err := conn.Write([]byte("hello\n"))
    if err != nil {
        log.Println(n, err)
        return
    }

    buf := make([]byte, 100)
    n, err = conn.Read(buf)
    if err != nil {
        log.Println(n, err)
        return
    }

    println(string(buf[:n]))
}
package main

import (
    "crypto/tls"
    "log"
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
        w.Write([]byte("This is an example server.\n"))
    })
    cfg := &tls.Config{
        MinVersion:               tls.VersionTLS12,
        CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
        PreferServerCipherSuites: true,
        CipherSuites: []uint16{
            tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
            tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
            tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
            tls.TLS_RSA_WITH_AES_256_CBC_SHA,
        },
    }
    srv := &http.Server{
        Addr:         ":443",
        Handler:      mux,
        TLSConfig:    cfg,
        TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
    }
    log.Fatal(srv.ListenAndServeTLS("tls.crt", "tls.key"))
}

Generation of self-sign a certificate with a private (.key) and public key (PEM-encodings .pem|.crt) in one command:

# ECDSA recommendation key ≥ secp384r1
# List ECDSA the supported curves (openssl ecparam -list_curves)
openssl req -x509 -nodes -newkey ec:secp384r1 -keyout server.ecdsa.key -out server.ecdsa.crt -days 3650
# openssl req -x509 -nodes -newkey ec:<(openssl ecparam -name secp384r1) -keyout server.ecdsa.key -out server.ecdsa.crt -days 3650
# -pkeyopt ec_paramgen_curve:… / ec:<(openssl ecparam -name …) / -newkey ec:…
ln -sf server.ecdsa.key server.key
ln -sf server.ecdsa.crt server.crt

# RSA recommendation key ≥ 2048-bit
openssl req -x509 -nodes -newkey rsa:2048 -keyout server.rsa.key -out server.rsa.crt -days 3650
ln -sf server.rsa.key server.key
ln -sf server.rsa.crt server.crt
  • .crt — Alternate synonymous most common among *nix systems .pem (pubkey).
  • .csr — Certficate Signing Requests (synonymous most common among *nix systems).
  • .cer — Microsoft alternate form of .crt, you can use MS to convert .crt to .cer (DER encoded .cer, or base64[PEM] encoded .cer).
  • .pem = The PEM extension is used for different types of X.509v3 files which contain ASCII (Base64) armored data prefixed with a «—– BEGIN …» line. These files may also bear the cer or the crt extension.
  • .der — The DER extension is used for binary DER encoded certificates.

Generating the Certficate Signing Request

openssl req -new -sha256 -key server.key -out server.csr
openssl x509 -req -sha256 -in server.csr -signkey server.key -out server.crt -days 3650

ECDSA & RSA — FAQ

  • Validate the elliptic curve parameters -check
  • List "ECDSA" the supported curves openssl ecparam -list_curves
  • Encoding to explicit "ECDSA" -param_enc explicit
  • Conversion form to compressed "ECDSA" -conv_form compressed
  • "EC" parameters and a private key -genkey

CA Bundle Path

Distro Package Path to CA
Fedora, RHEL, CentOS ca-certificates /etc/pki/tls/certs/ca-bundle.crt
Debian, Ubuntu, Gentoo, Arch Linux ca-certificates /etc/ssl/certs/ca-certificates.crt
SUSE, openSUSE ca-certificates /etc/ssl/ca-bundle.pem
FreeBSD ca_root_nss /usr/local/share/certs/ca-root-nss.crt
Cygwin - /usr/ssl/certs/ca-bundle.crt
macOS (MacPorts) curl-ca-bundle /opt/local/share/curl/curl-ca-bundle.crt
Default cURL CA bunde path (without --with-ca-bundle option) /usr/local/share/curl/curl-ca-bundle.crt
Really old RedHat? /usr/share/ssl/certs/ca-bundle.crt

Reference Link

@YuvalJoseph
Copy link

YuvalJoseph commented Mar 28, 2017

Thanks @seantcanavan after changing the FQDN to localhost it worked
In order to clarify I am attaching the modified client code I used to connect with the server example

package main

import (
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"
	"log"
)

func main() {
	log.SetFlags(log.Lshortfile)

	cert, err := ioutil.ReadFile("server.crt")
	if err != nil {
		log.Fatalf("Couldn't load file", err)
	}
	certPool := x509.NewCertPool()
	certPool.AppendCertsFromPEM(cert)

	conf := &tls.Config{
		RootCAs: certPool,
	}

	conn, err := tls.Dial("tcp", "localhost:443", conf)
	if err != nil {
		log.Println(err)
		return
	}
	defer conn.Close()

	n, err := conn.Write([]byte("hello\n"))
	if err != nil {
		log.Println(n, err)
		return
	}

	buf := make([]byte, 100)
	n, err = conn.Read(buf)
	if err != nil {
		log.Println(n, err)
		return
	}

	println(string(buf[:n]))
}

@cirocosta
Copy link

Thanks for wrapping it up @YuvalJoseph! Works like a charm.

@tankmr
Copy link

tankmr commented Oct 22, 2018

If your server was behind a load balancer and the clients didn't know which server they were being routed to, how would they know which key/cert to pass

@andykillen
Copy link

@tankrm, in my experience the HTTPS stops at the load balancer and its http within the DMZ (for want of a better term). Do you have different? perhaps as I have varnish in-between on many of my servers I am forced this way.

@hongzimao
Copy link

Maybe a dumb question - the rsa private key is the very first line of the code is overwritten by the ecparam (both outputs are server.key) - why do we generate rsa key in the first place?

@valdemarpavesi
Copy link

Thanks

@dviljoen
Copy link

dviljoen commented May 10, 2021

I'm converting existing code that uses net.Dialer.DialContext() with a timeout to use TLS. However, there doesn't seem to be an equivalent DialContext() in the TLS Dialer. I'm on 1.14. Does anyone know if there's any support for that somewhere?

I guess what I'm looking for is an implementation of a TLS Context.

@pplmx
Copy link

pplmx commented Sep 17, 2021

TLS demo doesn't work

Client will throw the following error:

x509: certificate signed by unknown authority

Here is my CA creation steps:

openssl req -x509 -nodes -sha256 \
	-newkey rsa:4096 \
	-days 10240 \
	-subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg, Inc./OU=Software Dept/CN=localhost" \
	-keyout root.key.pem \
	-out root.crt.pem

# OPENSSL_CONF="/etc/ssl/openssl.cnf"
OPENSSL_CONF="/System/Library/OpenSSL/openssl.cnf"
openssl req -new -nodes \
    -newkey rsa:4096 \
    -subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg, Inc./OU=Software Dept/CN=localhost" \
	-reqexts SAN \
    -config <(cat "${OPENSSL_CONF}" \
        <(printf "\n[SAN]\nsubjectAltName=DNS:localhost")) \
    -keyout localhost.key.pem \
    -out localhost.csr

openssl x509 -req -sha256 -CAcreateserial -days 365 \
	-CA root.crt.pem \
	-CAkey root.key.pem \
	-extfile <(printf "subjectAltName=DNS:localhost") \
	-in localhost.csr \
	-out localhost.crt.pem

@pplmx
Copy link

pplmx commented Sep 17, 2021

@shuklalok
Copy link

Code works with

But, there is an error nowadays and that is

client.go:26: x509: certificate relies on legacy Common Name field, use SANs instead

This error is because the Common Name has been deprecated.
Reference: rancher/rke2#775
RFC: https://tools.ietf.org/html/rfc2818#section-3.1

If a subjectAltName extension of type dNSName is present, that MUST be used as the identity. Otherwise, the (most specific) Common Name field in the Subject field of the certificate MUST be used. Although the use of the Common Name is existing practice, it is deprecated and Certification Authorities are encouraged to use the Name instead.

This can be fixed by generating the certificate with the procedure given below:
Reference: https://stackoverflow.com/questions/64814173/how-do-i-use-sans-with-openssl-instead-of-common-name

  • You must have to have a CA (local CA works too)
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 365 -key ca.key -subj "/C=IN/ST=KA/L=BL/O=MyOrg, Inc./CN=MyOrg Root CA" -out ca.crt
  • Create a server CSR with 'localhost' in CN
openssl req -newkey rsa:2048 -nodes -keyout server.key -subj "/C=IN/ST=KA/L=BL/O=MyOrg, Inc./CN=localhost" -out server.csr
  • Finally, sign the server cert by CA and pass the subjectAltName when you signing the server certificate.
openssl x509 -req -extfile <(printf "subjectAltName=DNS:localhost") -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt

Everything is summarized here: https://github.com/shuklalok/Mywork/tree/master/tls

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment