Skip to content

Instantly share code, notes, and snippets.

@snail007
Forked from xjdrew/client.go
Last active August 18, 2017 03:09
Show Gist options
  • Select an option

  • Save snail007/0e5b3d72c08230fe61fa03262595dd45 to your computer and use it in GitHub Desktop.

Select an option

Save snail007/0e5b3d72c08230fe61fa03262595dd45 to your computer and use it in GitHub Desktop.
golang tls client and server, require and verify certificate in double direction,golang tls 双向验证
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},
ServerName: "server",
RootCAs: roots,
InsecureSkipVerify: false,
}, 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=server
# 签名服务器证书: 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=client
# 签名客户端证书: client.crt
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt
验证逻辑:
1.服务端会使用ClientCAs这里配置的证书里面公钥解密客户端的证书签名,然后验证客户端证书是否有效.
注意的地方就是服务端配置的ServerName就是生成客户端证书的时候客户端的CN=client这里对应.
如果服务端不配置ServerName,那么服务端验证的时候就不会比较ServerName,只是解密看看客户端证书是否有效.
总结:
a.服务端如果没有配置ServerName,那么客户端证书只要是ClientCAs签发的即可,或者客户端的证书直接加入ClientCAs中,都是可以的.
b.服务端配置了ServerName,那么客户端证书不仅要要是ClientCAs签发的或者客户端的证书直接加入ClientCAs中,
而且客户端证书生成的时候CN=xxx,xxx必须和服务端配置的ServerName一样.
2.客户端会使用RootCAs这里配置的证书里面公钥解密服务端的证书签名,然后验证客户端证书是否有效.
如果要求客户端验证服务端的证书InsecureSkipVerify必须是false,客户端必须配置ServerName,
ServerName就是生成服务端证书的时候服务端的CN=server这里对应.
总结:
服务端证书必须是客户端配置的RootCAs签发的或者服务端证书在RootCAs中,客户端会用RootCAs解密服务端的证书,
并比较解密的"server name"是否和服务端证书里面的一致,来确定服务端证书的有效性.
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,
ServerName: "client",
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)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment