-
-
Save ncw/9253562 to your computer and use it in GitHub Desktop.
This demonstrates how to make client side certificates with go | |
First generate the certificates with | |
./makecert.sh [email protected] | |
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 [email protected] | |
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/[email protected]" -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") | |
} |
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?
very nice thank you.
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.
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
inserver.go
to