原理
參照這里: 雙向認(rèn)證SSL原理,已經(jīng)說的很詳細(xì)了
證書生成
根證書生成
openssl genrsa -out ca.key 2048
#這里可以使用 -subj 不用進(jìn)行交互 當(dāng)然還可以添加更多的信息
openssl req -x509 -new -nodes -key ca.key -subj "/CN=test" -days 5000 -out ca.crt
服務(wù)端證書生成
openssl genrsa -out server.key 2048
#這里的/cn可以是必須添加的 是服務(wù)端的域名 或者是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;
# 開啟客戶端認(rèn)證
ssl_verify_client on;
}
這里已經(jīng)可以進(jìn)行訪問,但是由于是自己簽發(fā)的證書,瀏覽器會提示不安全。需要在瀏覽器中添加例外(firefox),或者繼續(xù)訪問(chrome)
瀏覽器訪問后,出現(xiàn):no required SSL certificate was sent,說明瀏覽器已經(jīng)認(rèn)證了服務(wù)器,但是沒有找到證書發(fā)送給服務(wù)器,因此服務(wù)器對客戶端沒有認(rèn)證通過,就返回了400錯(cuò)誤。這個(gè)時(shí)候我們需要在瀏覽器中導(dǎo)入客戶端證書,讓雙向認(rèn)證完成。
把客戶端證書轉(zhuǎn)為client.p12格式,方便瀏覽器導(dǎo)入測試
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
瀏覽器導(dǎo)入 client.p12 后,已經(jīng)能正常訪問服務(wù)器了。
客戶端代碼測試
用客戶端代碼測試,能正常訪問,go實(shí)現(xiàn),其他的可能需要用到其他格式的證書,但只需要轉(zhuǎn)換一下格式即可:
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))
}