原理
参照这里: 双向认证SSL原理,已经说的很详细了
证书生成
根证书生成
openssl genrsa -out ca.key 2048
#这里可以使用 -subj 不用进行交互 当然还可以添加更多的信息
openssl req -x509 -new -nodes -key ca.key -subj "/CN=test" -days 5000 -out ca.crt
服务端证书生成
openssl genrsa -out server.key 2048
#这里的/cn可以是必须添加的 是服务端的域名 或者是etc/hosts中的ip别名
openssl req -new -key server.key -subj "/CN=server" -out server.csr
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 5000
客户端证书生成
openssl genrsa -out client.key 2048
openssl req -new -key client.key -subj "/CN=client" -out client.csr
echo extendedKeyUsage=clientAuth > extfile.cnf
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -extfile extfile.cnf -out client.crt -days 5000
nginx 配置
server {
listen 443 ssl;
ssl_certificate server.crt;
ssl_certificate server.key;
ssl_client_certificate ca.crt;
# 开启客户端认证
ssl_verify_client on;
}
这里已经可以进行访问,但是由于是自己签发的证书,浏览器会提示不安全。需要在浏览器中添加例外(firefox),或者继续访问(chrome)
浏览器访问后,出现:no required SSL certificate was sent,说明浏览器已经认证了服务器,但是没有找到证书发送给服务器,因此服务器对客户端没有认证通过,就返回了400错误。这个时候我们需要在浏览器中导入客户端证书,让双向认证完成。
把客户端证书转为client.p12格式,方便浏览器导入测试
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
浏览器导入 client.p12 后,已经能正常访问服务器了。
客户端代码测试
用客户端代码测试,能正常访问,go实现,其他的可能需要用到其他格式的证书,但只需要转换一下格式即可:
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
pool := x509.NewCertPool()
caCertPath := "certs/cert_server/ca.crt"
caCrt, err := ioutil.ReadFile(caCertPath)
if err != nil {
fmt.Println("ReadFile err:", err)
return
}
pool.AppendCertsFromPEM(caCrt)
cliCrt, err := tls.LoadX509KeyPair("certs/cert_server/client.crt", "certs/cert_server/client.key")
if err != nil {
fmt.Println("Loadx509keypair err:", err)
return
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: pool,
Certificates: []tls.Certificate{cliCrt},
InsecureSkipVerify: true,
},
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://server:8081")
if err != nil {
fmt.Println("Get error:", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}