Skip to content

Instantly share code, notes, and snippets.

@denji
Forked from spikebike/client.go
Last active March 13, 2024 05:39
Star You must be signed in to star a gist
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

@c3mb0
Copy link

c3mb0 commented Feb 12, 2016

Instead of skipping insecure certificates which could expose your service to MITM attacks, you can create a client that accepts your self-signed certificate:

func main() {

    rootPEM := `-----BEGIN CERTIFICATE-----
MIIEBDCCAuygAwIBAgIDAjppMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
YWwgQ0EwHhcNMTMwNDA1MTUxNTU1WhcNMTUwNDA0MTUxNTU1WjBJMQswCQYDVQQG
EwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzElMCMGA1UEAxMcR29vZ2xlIEludGVy
bmV0IEF1dGhvcml0eSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AJwqBHdc2FCROgajguDYUEi8iT/xGXAaiEZ+4I/F8YnOIe5a/mENtzJEiaB0C1NP
VaTOgmKV7utZX8bhBYASxF6UP7xbSDj0U/ck5vuR6RXEz/RTDfRK/J9U3n2+oGtv
h8DQUB8oMANA2ghzUWx//zo8pzcGjr1LEQTrfSTe5vn8MXH7lNVg8y5Kr0LSy+rE
ahqyzFPdFUuLH8gZYR/Nnag+YyuENWllhMgZxUYi+FOVvuOAShDGKuy6lyARxzmZ
EASg8GF6lSWMTlJ14rbtCMoU/M4iarNOz0YDl5cDfsCx3nuvRTPPuj5xt970JSXC
DTWJnZ37DhF5iR43xa+OcmkCAwEAAaOB+zCB+DAfBgNVHSMEGDAWgBTAephojYn7
qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1dvWBtrtiGrpagS8wEgYD
VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwOgYDVR0fBDMwMTAvoC2g
K4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9ndGdsb2JhbC5jcmwwPQYI
KwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwOi8vZ3RnbG9iYWwtb2NzcC5n
ZW90cnVzdC5jb20wFwYDVR0gBBAwDjAMBgorBgEEAdZ5AgUBMA0GCSqGSIb3DQEB
BQUAA4IBAQA21waAESetKhSbOHezI6B1WLuxfoNCunLaHtiONgaX4PCVOzf9G0JY
/iLIa704XtE7JW4S615ndkZAkNoUyHgN7ZVm2o6Gb4ChulYylYbc3GrKBIxbf/a/
zG+FA1jDaFETzf3I93k9mTXwVqO94FntT0QJo544evZG0R0SnU++0ED8Vf4GXjza
HFa9llF7b1cq26KqltyMdMKVvvBulRP/F/A8rLIQjcxz++iPAsbw+zOzlTvjwsto
WHPbqCRiOwY1nQ2pM714A5AuTHhdUDqB1O6gyHA43LL5Z/qHQF1hwFGPa4NrzQU6
yuGnBXj8ytqU0CwIPX4WecigUCAkVDNx
-----END CERTIFICATE-----`

    roots := x509.NewCertPool()
    ok := roots.AppendCertsFromPEM([]byte(rootPEM))
    if !ok {
        panic("failed to parse root certificate")
    }
    tlsConf := &tls.Config{RootCAs: roots}
    tr := &http.Transport{TLSClientConfig: tlsConf}
    client := &http.Client{Transport: tr}

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

    ...
}

Kudos for examples!

@andradei
Copy link

andradei commented Mar 4, 2016

@c3mb0 If I'm not mistaken, http.Client doesn't have define a Dial() method.

@crondotnet
Copy link

@andradei https://golang.org/pkg/net/http/#Client the struct Transport does have the Dial method

@seantcanavan
Copy link

seantcanavan commented Dec 2, 2016

@c3mb0 With your code I get:

--- FAIL: TestCheckinHandlerPass (0.00s) rest_test.go:88: Get https://localhost:53024/checkin/1480705188/samplegmail: x509: certificate is valid for SignedName, not localhost

Where "SignedName" represents the name I used when self-signing the certificate with the command denji shared.

EDIT: For posterity, to fix this issue, when generating the certificate for the server and it asks for "FQDN or Name" do not put your name there. FQDN stands for "Fully Qualified Domain Name" so if there's a mismatch then Go will complain (correctly). Instead I've put "localhost" the second time around and it's working beautifully now. Testing it easy and don't have to rely on InsecureSkipVerify.

@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