本文为演示采用自签名证书
一.生成证书
通过openssl工具生成证书
1.1 安装openssl
macos通过brew安装
brew install openssl
1.2 生成跟证书私钥
openssl genrsa -out ca.key 4096
1.3 准备配置文件
vim ca.conf
内容如下
-
[ req ]
-
default_bits = 4096
-
distinguished_name = req_distinguished_name
-
-
[ req_distinguished_name ]
-
countryName = Country Name (2 letter code)
-
countryName_default = CN
-
stateOrProvinceName = State or Province Name (full name)
-
stateOrProvinceName_default = JiangSu
-
localityName = Locality Name (eg, city)
-
localityName_default = NanJing
-
organizationName = Organization Name (eg, company)
-
organizationName_default = Sheld
-
commonName = Common Name (e.g. server FQDN or YOUR name)
-
commonName_max = 64
-
commonName_default = Ted CA Test
生成根证书签发申请文件(csr文件)
openssl req -new -sha256 -out ca.csr -key ca.key -config ca.conf
该命令含义如下:
req——执行证书签发命令
-new——新证书签发请求
-key——指定私钥路径
-out——输出的csr文件的路径
1.4 自签发根证书(cer文件)
openssl x509 -req -days 3650 -in ca.csr -signkey ca.key -out ca.crt
该命令的含义如下:
x509——生成x509格式证书
-req——输入csr文件
-days——证书的有效期(天)
-signkey——签发证书的私钥
-in——要输入的csr文件
-out——输出的cer证书文件
1.5 生成服务端私钥
openssl genrsa -out server.key 2048
1.6 准备配置文件,得到server.conf
vim server.conf
内容如下
-
[ req ]
-
default_bits = 2048
-
distinguished_name = req_distinguished_name
-
req_extensions = req_ext
-
-
[ req_distinguished_name ]
-
countryName = Country Name (2 letter code)
-
countryName_default = CN
-
stateOrProvinceName = State or Province Name (full name)
-
stateOrProvinceName_default = JiangSu
-
localityName = Locality Name (eg, city)
-
localityName_default = NanJing
-
organizationName = Organization Name (eg, company)
-
organizationName_default = Sheld
-
commonName = Common Name (e.g. server FQDN or YOUR name)
-
commonName_max = 64
-
commonName_default = server.com
-
-
[ req_ext ]
-
subjectAltName = @alt_names
-
-
[alt_names]
-
DNS.1 = server.com
生成服务端证书申请文件
openssl req -new -sha256 -out server.csr -key server.key -config server.conf
参考1.3输入服务端证书信息
1.7 用CA证书签发服务端证书
openssl x509 -req -days 3650 -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.crt -extensions req_ext -extfile server.conf
这里有必要解释一下这几个参数:
-CA——指定CA证书的路径
-CAkey——指定CA证书的私钥路径
-CAserial——指定证书序列号文件的路径
-CAcreateserial——表示创建证书序列号文件(即上方提到的serial文件),创建的序列号文件默认名称为-CA,指定的证书名称后加上.srl后缀
1.8 生成客户端私钥
openssl genrsa -out client.key 2048
1.9 准备配置文件,得到client.conf
vim client.conf
内容如下
-
[ req ]
-
default_bits = 2048
-
distinguished_name = req_distinguished_name
-
req_extensions = req_ext
-
-
[ req_distinguished_name ]
-
countryName = Country Name (2 letter code)
-
countryName_default = CN
-
stateOrProvinceName = State or Province Name (full name)
-
stateOrProvinceName_default = HeNan
-
localityName = Locality Name (eg, city)
-
localityName_default = AnYang
-
organizationName = Organization Name (eg, company)
-
organizationName_default = Sheld_client
-
commonName = Common Name (e.g. server FQDN or YOUR name)
-
commonName_max = 64
-
commonName_default = server.com
-
-
[ req_ext ]
-
subjectAltName = @alt_names
-
-
[alt_names]
-
DNS.1 = server.com
-
DNS.2 = localhost
生成客户端证书申请文件
openssl req -new -sha256 -out client.csr -key client.key -config client.conf
1.10 用跟证书签发客户端证书
openssl x509 -req -days 3650 -CA ca.crt -CAkey ca.key -CAserial ca.srl -in client.csr -out client.crt -extensions req_ext -extfile client.conf
需要注意的是,上方签发服务端证书时已经使用-CAcreateserial生成过ca.srl文件,因此这里不需要带上这个参数了。
二. gin框架实现https,服务端不需要验证客户端证书的示例
2.1 项目引入gin框架
go get -u github.com/gin-gonic/gin
2.2 服务端代码
-
package main
-
-
import (
-
"github.com/gin-gonic/gin"
-
"net/http"
-
)
-
-
func
main() {
-
router := gin.New()
-
-
router.GET(
"/test", func(c *gin.Context) {
-
c.JSON(200, gin.H{
-
"message":
"success",
-
})
-
})
-
-
// 可以直接用
-
//router.RunTLS(
"0.0.0.0:10679",
"./certs/server.cer",
"./certs/server.key")
-
server := &http.Server{Addr:
"0.0.0.0:10679", Handler: router}
-
_ = server.ListenAndServeTLS(
"./certs/server.cer",
"./certs/server.key")
-
}
2.3 安全提示
打开浏览器访问https://localhost:10679/test
因为浏览器并不信任证书的颁发机构,浏览器会有安全提示,当然可以点高级直接强行访问页面,同样可以返回结果,很多文章也是到这里就结束了。强迫症的我还是要解决一下。
2.4 给浏览器添加自己生成的CA证书
本文选择firefox浏览器,因为设置可以轻松添加信任CA证书。chrome则是直接掉起系统的证书,没有找到友好的办法自己添加
我们把CA证书(1.4节生成的ca.csr)添加到firefox证书颁发机构中去,这样浏览器就信任我们自制的CA证书了。路径为:设置->隐私与安全->查看证书->导入
由于我们证书配置的common name是server.com,因此需要修改本地hosts文件,将下面文字添加到hosts文件中
127.0.0.1 server.com
再次访问https://server.com:10679/test不再阻止
三.实现服务器和客户端双端验证
3.1 服务端代码
-
package main
-
-
import (
-
"crypto/tls"
-
"crypto/x509"
-
"fmt"
-
"github.com/gin-gonic/gin"
-
"io/ioutil"
-
"log"
-
"net"
-
"net/http"
-
"os"
-
"os/signal"
-
"syscall"
-
)
-
-
var (
-
caCert string =
"./certs/ca.crt"
-
serverCert string =
"./certs/server.crt"
-
serverKey string =
"./certs/server.key"
-
)
-
-
func
main() {
-
router := gin.New()
-
router.Use(gin.Logger())
-
-
router.GET(
"/test", func(c *gin.Context) {
-
c.JSON(200, gin.H{
-
"message":
"success",
-
})
-
})
-
-
// 客户端CA证书
-
certPool := x509.NewCertPool()
-
ca, err := os.ReadFile(caCert)
-
if err != nil {
-
fmt.Printf(
"load ca err: %s", err)
-
return
-
}
-
-
if ok := certPool.AppendCertsFromPEM(ca); !ok {
-
fmt.Printf(
"certpool append ca fail.")
-
return
-
}
-
-
// 可以直接用注释的代码代替最后两行
-
//router.RunTLS(
"0.0.0.0:10679",
"./cert/server.cer",
"./cert/server.key")
-
server := &http.Server{
-
Addr:
"server.com:10679",
-
Handler: router,
-
TLSConfig: &tls.Config{
-
ClientAuth: tls.RequireAndVerifyClientCert,
-
//这里一定要注意,服务端设置ClientCAs,用于服务端验证客户端证书,客户端设置RootCAs,用户客户端验证服务端证书。设置错误或者设置反了都会造成认证不通过。
-
//RootCAs: certPool,
-
ClientCAs: certPool,
-
},
-
}
-
-
_ = server.ListenAndServeTLS(serverCert, serverKey)
-
}
3.2 客户端代码
-
package main
-
-
import (
-
"crypto/tls"
-
"crypto/x509"
-
"fmt"
-
"io/ioutil"
-
"log"
-
"net/http"
-
"os"
-
)
-
-
func
main() {
-
pool := x509.NewCertPool()
-
caCrt, err := os.ReadFile(
"./certs/ca.crt")
-
if err != nil {
-
log.Fatal(
"read ca.crt file error:", err.Error())
-
}
-
pool.AppendCertsFromPEM(caCrt)
-
cliCrt, err := tls.LoadX509KeyPair(
"./certs/client.crt",
"./certs/client.key")
-
if err != nil {
-
log.Fatalln(
"LoadX509KeyPair error:", err.Error())
-
}
-
tr := &http.Transport{
-
TLSClientConfig: &tls.Config{
-
//这里一定要注意,服务端设置ClientCAs,用于服务端验证客户端证书,客户端设置RootCAs,用户客户端验证服务端证书。设置错误或者设置反了都会造成认证不通过。
-
RootCAs: pool,
-
//ClientCAs: pool,
-
Certificates: []tls.Certificate{cliCrt},
-
},
-
}
-
client := &http.Client{Transport:
tr}
-
resp, err := client.Get(
"https://server.com:10679/test")
-
if err != nil {
-
fmt.Printf(
"get failed. | err: %s\n", err)
-
return
-
}
-
defer resp.Body.Close()
-
body, err := ioutil.ReadAll(resp.Body)
-
fmt.Println(string(body))
-
-
}
四. 整理过程中遇到的bug
4.1 tls: failed to verify certificate: x509: “server.com” certificate is not standards compliant
remote error: tls: bad certificate
这两个报错可能就是客户端或服务端设置参数ClientCAs、RootCAs错误有关。
服务端设置ClientCAs,里面保存客户端的CA证书Pool,用于服务端验证客户端证书。
客户端设置RootCAs,里面保存服务端的CA证书Pool,用户客户端验证服务端证书。
设置错误或者设置反了都会造成认证不通过。
这就属于知道就很简单解决,但找不到错误就很崩溃,本人因为这个小bug竟然熬了一夜,说多了都是泪啊啊啊啊。。。
4.2 use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0
这个报错原因是生成证书没有开启SAN扩展,go 1.15 版本开始废弃 CommonName,因此推荐使用 SAN 证书。 这就是1.6、1.9两节配置文件最下面做的事情。重新把证书生成一下。当然,自己生成的证书可以随便改,如果线上证书出现这种情况,我看有的说设置下环境变量GODEBUG 为 x509ignoreCN=0,不过我测试没有效果
这个问题的解决要感谢下面这个博主,我是看了这个帖子解决的问题。
https://blog.csdn.net/weixin_40280629/article/details/113563351
4.3 安全提示
参考2.3解决办法
4.4 证书commonName
这个字段是比较重要的,不要随便配置。服务端、客户端证书认证都会验证host name是否与其一致,不一致会造成认证失败。这也是为什么要修改hosts并用server.com来访问
睡觉睡觉。。。
转载:https://blog.csdn.net/chen_peng7/article/details/128978575