Skip to content

Instantly share code, notes, and snippets.

@xjdrew
Last active January 22, 2024 05:49
Show Gist options
  • Star 62 You must be signed in to star a gist
  • Fork 28 You must be signed in to fork a gist
  • Save xjdrew/97be3811966c8300b724deabc10e38e2 to your computer and use it in GitHub Desktop.
Save xjdrew/97be3811966c8300b724deabc10e38e2 to your computer and use it in GitHub Desktop.
golang tls client and server, require and verify certificate in double direction
package main
import (
"crypto/tls"
"crypto/x509"
"flag"
"io"
"io/ioutil"
"log"
"os"
"strings"
"sync"
)
func createClientConfig(ca, crt, key string) (*tls.Config, error) {
caCertPEM, err := ioutil.ReadFile(ca)
if err != nil {
return nil, err
}
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM(caCertPEM)
if !ok {
panic("failed to parse root certificate")
}
cert, err := tls.LoadX509KeyPair(crt, key)
if err != nil {
return nil, err
}
return &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: roots,
}, nil
}
func printConnState(conn *tls.Conn) {
log.Print(">>>>>>>>>>>>>>>> State <<<<<<<<<<<<<<<<")
state := conn.ConnectionState()
log.Printf("Version: %x", state.Version)
log.Printf("HandshakeComplete: %t", state.HandshakeComplete)
log.Printf("DidResume: %t", state.DidResume)
log.Printf("CipherSuite: %x", state.CipherSuite)
log.Printf("NegotiatedProtocol: %s", state.NegotiatedProtocol)
log.Printf("NegotiatedProtocolIsMutual: %t", state.NegotiatedProtocolIsMutual)
log.Print("Certificate chain:")
for i, cert := range state.PeerCertificates {
subject := cert.Subject
issuer := cert.Issuer
log.Printf(" %d s:/C=%v/ST=%v/L=%v/O=%v/OU=%v/CN=%s", i, subject.Country, subject.Province, subject.Locality, subject.Organization, subject.OrganizationalUnit, subject.CommonName)
log.Printf(" i:/C=%v/ST=%v/L=%v/O=%v/OU=%v/CN=%s", issuer.Country, issuer.Province, issuer.Locality, issuer.Organization, issuer.OrganizationalUnit, issuer.CommonName)
}
log.Print(">>>>>>>>>>>>>>>> State End <<<<<<<<<<<<<<<<")
}
func main() {
connect := flag.String("connect", "localhost:4433", "who to connect to")
ca := flag.String("ca", "./ca.crt", "root certificate")
crt := flag.String("crt", "./client.crt", "certificate")
key := flag.String("key", "./client.key", "key")
flag.Parse()
addr := *connect
if !strings.Contains(addr, ":") {
addr += ":443"
}
config, err := createClientConfig(*ca, *crt, *key)
if err != nil {
log.Fatal("config failed: %s", err.Error())
}
conn, err := tls.Dial("tcp", addr, config)
if err != nil {
log.Fatalf("failed to connect: %s", err.Error())
}
defer conn.Close()
log.Printf("connect to %s succeed", addr)
printConnState(conn)
var wg sync.WaitGroup
wg.Add(1)
go func() {
io.Copy(conn, os.Stdin)
wg.Done()
}()
wg.Add(1)
go func() {
io.Copy(os.Stdout, conn)
wg.Done()
}()
wg.Wait()
}
# >>>>>>>>>>>>>>>>>> 根证书 <<<<<<<<<<<<<<<<<<<<<<
# 生成根证书私钥: ca.key
openssl genrsa -out ca.key 2048
# 生成自签名根证书: ca.crt
openssl req -new -key ca.key -x509 -days 3650 -out ca.crt -subj /C=CN/ST=GuangDong/O="Localhost Ltd"/CN="Localhost Root"
# >>>>>>>>>>>>>>>>>> 服务器证书 <<<<<<<<<<<<<<<<<<<<<<
# 生成服务器证书私钥: ca.key
openssl genrsa -out server.key 2048
# 生成服务器证书请求: server.csr
openssl req -new -nodes -key server.key -out server.csr -subj /C=CN/ST=GuangDong/L=Guangzhou/O="Localhost Server"/CN=localhost
# 签名服务器证书: server.crt
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
# >>>>>>>>>>>>>>>>>> 客户端证书 <<<<<<<<<<<<<<<<<<<<<<
# 生成客户端证书私钥: ca.key
openssl genrsa -out client.key 2048
# 生成客户端证书请求: client.csr
openssl req -new -nodes -key client.key -out client.csr -subj /C=CN/ST=GuangDong/L=Guangzhou/O="Localhost Client"/CN=localhost
# 签名客户端证书: client.crt
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt
package main
import (
"crypto/tls"
"crypto/x509"
"flag"
"io"
"io/ioutil"
"log"
"net"
)
func createServerConfig(ca, crt, key string) (*tls.Config, error) {
caCertPEM, err := ioutil.ReadFile(ca)
if err != nil {
return nil, err
}
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM(caCertPEM)
if !ok {
panic("failed to parse root certificate")
}
cert, err := tls.LoadX509KeyPair(crt, key)
if err != nil {
return nil, err
}
return &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: roots,
}, nil
}
func printConnState(conn *tls.Conn) {
log.Print(">>>>>>>>>>>>>>>> State <<<<<<<<<<<<<<<<")
state := conn.ConnectionState()
log.Printf("Version: %x", state.Version)
log.Printf("HandshakeComplete: %t", state.HandshakeComplete)
log.Printf("DidResume: %t", state.DidResume)
log.Printf("CipherSuite: %x", state.CipherSuite)
log.Printf("NegotiatedProtocol: %s", state.NegotiatedProtocol)
log.Printf("NegotiatedProtocolIsMutual: %t", state.NegotiatedProtocolIsMutual)
log.Print("Certificate chain:")
for i, cert := range state.PeerCertificates {
subject := cert.Subject
issuer := cert.Issuer
log.Printf(" %d s:/C=%v/ST=%v/L=%v/O=%v/OU=%v/CN=%s", i, subject.Country, subject.Province, subject.Locality, subject.Organization, subject.OrganizationalUnit, subject.CommonName)
log.Printf(" i:/C=%v/ST=%v/L=%v/O=%v/OU=%v/CN=%s", issuer.Country, issuer.Province, issuer.Locality, issuer.Organization, issuer.OrganizationalUnit, issuer.CommonName)
}
log.Print(">>>>>>>>>>>>>>>> State End <<<<<<<<<<<<<<<<")
}
func main() {
listen := flag.String("listen", "localhost:4433", "which port to listen")
ca := flag.String("ca", "./ca.crt", "root certificate")
crt := flag.String("crt", "./server.crt", "certificate")
key := flag.String("key", "./server.key", "key")
flag.Parse()
config, err := createServerConfig(*ca, *crt, *key)
if err != nil {
log.Fatal("config failed: %s", err.Error())
}
ln, err := tls.Listen("tcp", *listen, config)
if err != nil {
log.Fatal("listen failed: %s", err.Error())
}
log.Printf("listen on %s", *listen)
for {
conn, err := ln.Accept()
if err != nil {
log.Fatal("accept failed: %s", err.Error())
break
}
log.Printf("connection open: %s", conn.RemoteAddr())
printConnState(conn.(*tls.Conn))
go func(c net.Conn) {
wr, _ := io.Copy(c, c)
c.Close()
log.Printf("connection close: %s, written: %d", conn.RemoteAddr(), wr)
}(conn)
}
}
@pradt2
Copy link

pradt2 commented Jun 25, 2018

tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: roots,

}

Exactly what I was looking for. Thanks a lot.

@dfense
Copy link

dfense commented Aug 23, 2018

Great reference, and really like the ability to print out details of cert after connection is made.
However, the printconnstate() method works great on the client side
but the server prints what seems to be zero filled information about client after connect.

CLIENT
2018/08/23 11:27:15 connect to server.cree.com:4433 succeed
2018/08/23 11:27:15 >>>>>>>>>>>>>>>> State <<<<<<<<<<<<<<<<
2018/08/23 11:27:15 Version: 303
2018/08/23 11:27:15 HandshakeComplete: true
2018/08/23 11:27:15 DidResume: false
2018/08/23 11:27:15 CipherSuite: c02f
2018/08/23 11:27:15 NegotiatedProtocol:
2018/08/23 11:27:15 NegotiatedProtocolIsMutual: true
2018/08/23 11:27:15 Certificate chain:
2018/08/23 11:27:15  0 s:/C=[US]/ST=[NC]/L=[Durham]/O=[Cree Inc]/OU=[Lighting]/CN=server.cree.com
2018/08/23 11:27:15    i:/C=[US]/ST=[NC]/L=[]/O=[Cree Lighting Inc]/OU=[Cree Lighting Inc Certificate Authority]/CN=Cree Lighting Inc Intermediate CA
2018/08/23 11:27:15 >>>>>>>>>>>>>>>> State End <<<<<<<<<<<<<<<<
SERVER
2018/08/23 11:26:30 connection open: 127.0.0.1:54458
2018/08/23 11:26:30 >>>>>>>>>>>>>>>> State <<<<<<<<<<<<<<<<
2018/08/23 11:26:30 Version: 0
2018/08/23 11:26:30 HandshakeComplete: false
2018/08/23 11:26:30 DidResume: false
2018/08/23 11:26:30 CipherSuite: 0
2018/08/23 11:26:30 NegotiatedProtocol:
2018/08/23 11:26:30 NegotiatedProtocolIsMutual: false
2018/08/23 11:26:30 Certificate chain:
2018/08/23 11:26:30 >>>>>>>>>>>>>>>> State End <<<<<<<<<<<<<<<<

@Lonenso
Copy link

Lonenso commented Sep 12, 2019

@dfense , I met this problem too, did you figure that out ?

Great reference, and really like the ability to print out details of cert after connection is made.
However, the printconnstate() method works great on the client side
but the server prints what seems to be zero filled information about client after connect.

CLIENT
2018/08/23 11:27:15 connect to server.cree.com:4433 succeed
2018/08/23 11:27:15 >>>>>>>>>>>>>>>> State <<<<<<<<<<<<<<<<
2018/08/23 11:27:15 Version: 303
2018/08/23 11:27:15 HandshakeComplete: true
2018/08/23 11:27:15 DidResume: false
2018/08/23 11:27:15 CipherSuite: c02f
2018/08/23 11:27:15 NegotiatedProtocol:
2018/08/23 11:27:15 NegotiatedProtocolIsMutual: true
2018/08/23 11:27:15 Certificate chain:
2018/08/23 11:27:15  0 s:/C=[US]/ST=[NC]/L=[Durham]/O=[Cree Inc]/OU=[Lighting]/CN=server.cree.com
2018/08/23 11:27:15    i:/C=[US]/ST=[NC]/L=[]/O=[Cree Lighting Inc]/OU=[Cree Lighting Inc Certificate Authority]/CN=Cree Lighting Inc Intermediate CA
2018/08/23 11:27:15 >>>>>>>>>>>>>>>> State End <<<<<<<<<<<<<<<<
SERVER
2018/08/23 11:26:30 connection open: 127.0.0.1:54458
2018/08/23 11:26:30 >>>>>>>>>>>>>>>> State <<<<<<<<<<<<<<<<
2018/08/23 11:26:30 Version: 0
2018/08/23 11:26:30 HandshakeComplete: false
2018/08/23 11:26:30 DidResume: false
2018/08/23 11:26:30 CipherSuite: 0
2018/08/23 11:26:30 NegotiatedProtocol:
2018/08/23 11:26:30 NegotiatedProtocolIsMutual: false
2018/08/23 11:26:30 Certificate chain:
2018/08/23 11:26:30 >>>>>>>>>>>>>>>> State End <<<<<<<<<<<<<<<<

@sanguohot
Copy link

@Lonenso It is good to me by sleeping some time before printing on both the client and server side.
go func() {
time.Sleep(time.Nanosecond * 1)
printConnState(conn.(*tls.Conn))
}()

@bjorngylling
Copy link

Super useful example!

And for the last commenters; rather than sleeping you could ensure the tls handshake finishes before printing the connection state by calling conn.Handshake().

@vcheruk2
Copy link

vcheruk2 commented Feb 3, 2021

When I ran the same using go v1.15.

2021/02/02 17:49:15 failed to connect: x509: certificate relies on legacy Common Name field, use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0

In order to avoid this the server.crt needs to be generated using a different command as below.
openssl x509 -req -extfile <(printf "subjectAltName=DNS:localhost,DNS:www.example.com") -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt

@offerm
Copy link

offerm commented Feb 3, 2021

Very good document!
Many thanks

@ReaganCn
Copy link

ReaganCn commented Feb 1, 2022

Very simple example. Thank you.

A question, is there a problem if I dont pass rootCAs from the client side?

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