Last active
February 9, 2025 16:52
-
-
Save xjdrew/97be3811966c8300b724deabc10e38e2 to your computer and use it in GitHub Desktop.
golang tls client and server, require and verify certificate in double direction
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# >>>>>>>>>>>>>>>>>> 根证书 <<<<<<<<<<<<<<<<<<<<<< | |
# 生成根证书私钥: 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
Very good document!
Many thanks
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
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