https双向认证

GO 1.15 以上版本解决GRPC X509 Common Name field, use SANs or temporarily enable Common Name matching

第1步:生成 CA 根证书

1
openssl genrsa -out ca.key 2048
1
2
3
4
Generating RSA private key, 2048 bit long modulus (2 primes)
.............+++++
..................................................................................................................+++++
e is 65537 (0x010001)
1
openssl req -new -x509 -days 3650 -key ca.key -out ca.pem
1
2
3
4
5
6
7
8
9
10
11
12
13
14
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:cn
State or Province Name (full name) [Some-State]:fj
Locality Name (eg, city) []:fz
Organization Name (eg, company) [Internet Widgits Pty Ltd]:wyy
Organizational Unit Name (eg, section) []:wyy
Common Name (e.g. server FQDN or YOUR name) []:localhost
Email Address []:

第2步:用 openssl 生成 ca 和双方 SAN 证书

https://raw.fastgit.org/openssl/openssl/master/apps/openssl.cnf

准备默认 OpenSSL 配置文件于当前目录

linux系统在 : /etc/ssl/openssl.cnf

Mac系统在: /System/Library/OpenSSL/openssl.cnf

window系统(cmder):C:/Users/<user>/Desktop/cmder/vendor/git-for-windows/usr/ssl/openssl.cnf

  • 1:cp 目录到项目目录进行修改设置

    1
    cp C:/Users/<user>/Desktop/cmder/vendor/git-for-windows/usr/ssl/openssl.cnf ./
  • 2:找到 [ CA_default ],打开 copy_extensions = copy

  • 3:找到[ req ],打开 req_extensions = v3_req # The extensions to add to a certificate request

  • 4:找到[ v3_req ],添加 subjectAltName = @alt_names

  • 5:添加新的标签 [ alt_names ] , 和标签字段

    1
    2
    3
    [ alt_names ]
    DNS.1 = localhost
    DNS.2 = *.test.com

这里填入需要加入到 Subject Alternative Names 段落中的域名名称,可以写入多个。

第3步:生成服务端证书

1
openssl genpkey -algorithm RSA -out server.key
1
2
........................................................................................+++++
.......................................+++++
1
openssl req -new -nodes -key server.key -out server.csr -days 3650 -subj "/C=cn/OU=wyy/O=wyy/CN=localhost" -config ./openssl.cnf -extensions v3_req
1
Ignoring -days; not generating a certificate
1
openssl x509 -req -days 3650 -in server.csr -out server.pem -CA ca.pem -CAkey ca.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req
1
2
3
Signature ok
subject=C = cn, OU = custer, O = custer, CN = localhost
Getting CA Private Key

server.csr是上面生成的证书请求文件。ca.pem/ca.key是CA证书文件和key,用来对server.csr进行签名认证。这两个文件在之前生成的。

第4步:生成客户端证书

1
openssl genpkey -algorithm RSA -out client.key
1
2
........+++++
...........+++++
1
openssl req -new -nodes -key client.key -out client.csr -days 3650 -subj "/C=cn/OU=wyy/O=wyy/CN=localhost" -config ./openssl.cnf -extensions v3_req
1
Ignoring -days; not generating a certificate
1
openssl x509 -req -days 3650 -in client.csr -out client.pem -CA ca.pem -CAkey ca.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req
1
2
3
Signature ok
subject=C = cn, OU = custer, O = custer, CN = localhost
Getting CA Private Key

现在 Go 1.15 以上版本的 GRPC 通信,这样就完成了使用自签CA、Server、Client证书和双向认证

示例

rpc

服务端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
cert, _ := tls.LoadX509KeyPair("cert/server.pem", "cert/server.key")
certPool := x509.NewCertPool()
ca, _ := ioutil.ReadFile("cert/ca.pem")
certPool.AppendCertsFromPEM(ca)

creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: certPool,
})
rpcServer := grpc.NewServer(grpc.Creds(creds))

services.RegisterProdServiceServer(rpcServer, new(services.ProdService))

listen, _ := net.Listen("tcp", ":8081")
rpcServer.Serve(listen)
}

客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func main() {
cert, _ := tls.LoadX509KeyPair("cert/client.pem", "cert/client.key")
certPool := x509.NewCertPool()
ca, _ := ioutil.ReadFile("cert/ca.pem")
certPool.AppendCertsFromPEM(ca)

creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{cert},
ServerName: "localhost",
RootCAs: certPool,
})

conn, err := grpc.Dial(":8081", grpc.WithTransportCredentials(creds))

if err != nil {
log.Fatal(err)
}
defer conn.Close()

prodClient := services.NewProdServiceClient(conn)
prodRes, err := prodClient.GetProdStock(context.Background(), &services.ProdRequest{ProdId: 12})
if err != nil {
log.Fatal(err)
}
log.Info(prodRes.ProdStock)
}

https

服务端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w,
"Hi, This is an example of https service in golang!")
}

func main() {
// ssl 双向检验
pool := x509.NewCertPool()
crt, err := ioutil.ReadFile("../cert/ca.pem")
if err != nil {
log.Fatalln("读取证书失败!", err.Error())
}
pool.AppendCertsFromPEM(crt)
http.HandleFunc("/", handler)
s := &http.Server{
Addr: ":8080",
TLSConfig: &tls.Config{
ClientCAs: pool,
ClientAuth: tls.RequireAndVerifyClientCert, // 检验客户端证书

},
}
log.Fatal(s.ListenAndServeTLS("../cert/server.pem", "../cert/server.key"))
}

客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func main() {
pool := x509.NewCertPool()
caCrt, err := ioutil.ReadFile("../cert/ca.pem")
if err != nil {
log.Fatal("read ca.crt file error:", err.Error())
}

pool.AppendCertsFromPEM(caCrt)
cliCrt, err := tls.LoadX509KeyPair("../cert/client.pem", "../cert/client.key")
if err != nil {
log.Fatalln("LoadX509KeyPair error:", err.Error())
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: pool,
Certificates: []tls.Certificate{cliCrt},
},
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://localhost:8080/")
if err != nil {
panic(err.Error())
}

defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}