Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Client side certificates with go
This demonstrates how to make client side certificates with go
First generate the certificates with
./makecert.sh test@test.com
Run the server in one terminal
go run server.go
Run the client in the other
go run client.go
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"log"
)
func main() {
cert, err := tls.LoadX509KeyPair("certs/client.pem", "certs/client.key")
if err != nil {
log.Fatalf("server: loadkeys: %s", err)
}
config := tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true}
conn, err := tls.Dial("tcp", "127.0.0.1:8000", &config)
if err != nil {
log.Fatalf("client: dial: %s", err)
}
defer conn.Close()
log.Println("client: connected to: ", conn.RemoteAddr())
state := conn.ConnectionState()
for _, v := range state.PeerCertificates {
fmt.Println("Client: Server public key is:")
fmt.Println(x509.MarshalPKIXPublicKey(v.PublicKey))
}
log.Println("client: handshake: ", state.HandshakeComplete)
log.Println("client: mutual: ", state.NegotiatedProtocolIsMutual)
message := "Hello\n"
n, err := io.WriteString(conn, message)
if err != nil {
log.Fatalf("client: write: %s", err)
}
log.Printf("client: wrote %q (%d bytes)", message, n)
reply := make([]byte, 256)
n, err = conn.Read(reply)
log.Printf("client: read %q (%d bytes)", string(reply[:n]), n)
log.Print("client: exiting")
}
#!/bin/bash
# call this script with an email address (valid or not).
# like:
# ./makecert.sh foo@foo.com
if [ "$1" == "" ]; then
echo "Need email as argument"
exit 1
fi
EMAIL=$1
rm -rf certs
mkdir certs
cd certs
echo "make CA"
PRIVKEY="test"
openssl req -new -x509 -days 365 -keyout ca.key -out ca.pem -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=www.random.com/emailAddress=KryptoKings@random.com" -passout pass:$PRIVKEY
echo "make server cert"
openssl req -new -nodes -x509 -out server.pem -keyout server.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=www.random.com/emailAddress=${EMAIL}"
echo "make client cert"
#openssl req -new -nodes -x509 -out client.pem -keyout client.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=www.random.com/emailAddress=${EMAIL}"
openssl genrsa -out client.key 2048
echo "00" > ca.srl
openssl req -sha1 -key client.key -new -out client.req -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=client.com/emailAddress=${EMAIL}"
# Adding -addtrust clientAuth makes certificates Go can't read
openssl x509 -req -days 365 -in client.req -CA ca.pem -CAkey ca.key -passin pass:$PRIVKEY -out client.pem # -addtrust clientAuth
openssl x509 -extfile ../openssl.conf -extensions ssl_client -req -days 365 -in client.req -CA ca.pem -CAkey ca.key -passin pass:$PRIVKEY -out client.pem
[ ssl_client ]
extendedKeyUsage = clientAuth
package main
import (
"crypto/rand"
"crypto/tls"
"crypto/x509"
"io/ioutil"
"log"
"net"
)
func main() {
cert, err := tls.LoadX509KeyPair("certs/server.pem", "certs/server.key")
if err != nil {
log.Fatalf("server: loadkeys: %s", err)
}
certpool := x509.NewCertPool()
pem, err := ioutil.ReadFile("certs/ca.pem")
if err != nil {
log.Fatalf("Failed to read client certificate authority: %v", err)
}
if !certpool.AppendCertsFromPEM(pem) {
log.Fatalf("Can't parse client certificate authority")
}
config := tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: certpool,
}
config.Rand = rand.Reader
service := "0.0.0.0:8000"
listener, err := tls.Listen("tcp", service, &config)
if err != nil {
log.Fatalf("server: listen: %s", err)
}
log.Print("server: listening")
for {
conn, err := listener.Accept()
if err != nil {
log.Printf("server: accept: %s", err)
break
}
log.Printf("server: accepted from %s", conn.RemoteAddr())
go handleClient(conn)
}
}
func handleClient(conn net.Conn) {
defer conn.Close()
tlscon, ok := conn.(*tls.Conn)
if ok {
log.Print("server: conn: type assert to TLS succeedded")
err := tlscon.Handshake()
if err != nil {
log.Fatalf("server: handshake failed: %s", err)
} else {
log.Print("server: conn: Handshake completed")
}
state := tlscon.ConnectionState()
log.Println("Server: client public key is:")
for _, v := range state.PeerCertificates {
log.Print(x509.MarshalPKIXPublicKey(v.PublicKey))
}
buf := make([]byte, 512)
for {
log.Print("server: conn: waiting")
n, err := conn.Read(buf)
if err != nil {
if err != nil {
log.Printf("server: conn: read: %s", err)
}
break
}
log.Printf("server: conn: echo %q\n", string(buf[:n]))
n, err = conn.Write(buf[:n])
log.Printf("server: conn: wrote %d bytes", n)
if err != nil {
log.Printf("server: write: %s", err)
break
}
}
}
log.Println("server: conn: closed")
}
@jpgoldberg
Copy link

jpgoldberg commented Mar 1, 2019

When trying this on macOS, you may encounter system firewall rules when trying to launch a service listening on 0.0.0.0. I found that everything worked as expected, if I changed the service in server.go to

service := "127.0.0.1:8000"

@NickyMateev
Copy link

NickyMateev commented Jun 29, 2020

Nice Golang demonstration of the client certificates flow!
I was just wondering, since I saw that you put InsecureSkipVerify to true on the client side - are there use-cases with client certs where the client would not be validating the server certificate (I haven't stumbled on such, tbh) or did you leave it like that just for demonstration purposes?

And in the case where the client would have to validate the server cert I guess the client would also have to have a cert pool of its own which contains the server certificate. Another option would be for the CA generated first in the script to sign the server cert as well and have its certificate in the client's pool, right?

@Tower450
Copy link

Tower450 commented Sep 15, 2020

very nice thank you.

@tisoportes
Copy link

tisoportes commented Dec 3, 2021

Thanks you! Your sample help me a lot to resolve Goddy's issue CA. I used to load crt and key files from Let's Encrypt in golang server https , but my boss bought Godaddy's certificates. And troubles started with app mobile. It was missing the 3rd one to be loaded in the server.

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