实现https双向认证
一、什么是https?
日常开发中大家可能接触最多的都是http协议, 说到http协议也不得不提到TCP/IP协议以及计算机网络中tcp/ip五层与OCI7层架构模型有关, http(超文本传输协议)用户客户端和服务端之间的通信, 位于tcp/ip五层协议中最上层传输层, http传输过程中都是明文传输, 易引起安全问题, 所有诞生了https协议。
HTTPS协议 = HTTP协议 + SSL/TLS协议, 在HTTPS数据传输的过程中, 需要用SSL/TLS对数据进行加密和解密, 需要用HTTP对加密后的数据进行传输, 由此可以看出HTTPS是由HTTP和SSL/TLS一起合作完成的。
SSL的全称是Secure Sockets Layer, 即安全套接层协议, 是为网络通信提供安全及数据完整性的一种安全协议。SSL协议在1994年被Netscape发明, 后来各个浏览器均支持SSL, 其最新的版本是3.0
二、加密算法
2.1 TLS/SSL的功能主要依赖三类算法实现:散列函数 Hash、对称加密和非对称加密, 其利用非对称加密实现身份认证和密钥协商, 对称加密算法采用协商的密钥对数据加密, 基于散列函数验证信息的完整性(对于三种加密算法含义这里就不一一概述), 而在TLS/SSL加密算法中还会涉及一下几个关键概念:
密钥:改变密码行为的数字化参数。
对称密钥加密系统:编 / 解码使用相同密钥的算法。
不对称密钥加密系统:编 / 解码使用不同密钥的算法。
公开密钥加密系统:一种能够使数百万计算机便捷地发送机密报文的系统。
数字签名:用来验证报文未被伪造或篡改的校验和。
数字证书:由一个可信的组织验证和签发的识别信息。
2.2 TLS的基本工作方式是,客户端使用非对称加密与服务器进行通信,实现身份验证并协商对称加密使用的密钥, 然后对称加密算法采用协商密钥对信息以及信息摘要进行加密通信,不同的节点之间采用的对称密钥不同,从而可以保证信息只能通信双方获取。
2.3 https通信过程中数字证书概览:
ca.pem 根证书, 由根证书颁发客户端和服务端证书
client.pem 客户端证书
client-key.pem 客户端秘钥
server.pem 服务端证书
server-key.pem 服务端秘钥
三、如何自签名证书?
3.1 目前自颁发证书工具有两类, 一是使用openssl 插件生成证书, 二是通过cfssl插件生成证书, 本例采用cfssl 工具生成证书
3.2 cfssl是一款由golang编写的证书生成工具, 官网地址:https://github.com/cloudflare/cfssl
安装步骤:
@tips: 该方法需要安装golang环境,如果没有golang请参照二进制包安装方式
> # go get -u github.com/cloudflare/cfssl/cmd/cfssl
> # go get -u github.com/cloudflare/cfssl/cmd/cfssljson
@tips windows环境需要将gopath下bin目录添加path环境中方可执行cfssl命令
查看安装是否成功:
> # cfssl
3.3 创建CA证书
> # mkdir ssl & cd ssl
> # cfssl print-defaults config > ca-config.json
> # cfssl print-defaults csr > ca-csr.json
然后修改ca-config.json文件
> # vim ca-config.json
{
"signing": {
"default": {
"expiry": "43800h"
},
"profiles": {
"server": {
"expiry": "43800h",
"usages": [
"signing",
"key encipherment",
"server auth"
]
},
"client": {
"expiry": "43800h",
"usages": [
"signing",
"key encipherment",
"client auth"
]
},
"peer": {
"expiry": "43800h",
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
]
}
}
}
}
修改 expiry 值为 43800h
## 生成ca 证书
> # mkdir ca
> # cfssl gencert -initca ca-csr.json | cfssljson -bare ca/ca -
3.4 创建serverd端证书
> # mkdir server
> # cfssl print-defaults csr > server.json
## 修改server.json
{
"CN": "Server",
"hosts": [
"localhost",
"127.0.0.1"
],
"key": {
"algo": "ecdsa",
"size": 256
},
"names": [
{
"C": "CN",
"L": "SH",
"ST": "SH"
}
]
}
## 签发server端证书
> # cfssl gencert -ca=ca/ca.pem -ca-key=ca/ca-key.pem -config=ca-config.json -profile=server server.json | cfssljson -bare server/server
注意: 其中 -profile=server 来自于配置文件 ca-config.json profiles 属性值
3.5 创建客户端证书:
> # mkdir client
> # cfssl print-defaults csr > client.json
## 修改client.json
{
"CN": "Client",
"hosts": [],
"key": {
"algo": "ecdsa",
"size": 256
},
"names": [
{
"C": "CN",
"L": "SH",
"ST": "SH"
}
]
}
## 生成客户端证书和私钥
> # cfssl gencert -ca=ca/ca.pem -ca-key=ca/ca-key.pem -config=ca-config.json -profile=client client.json | cfssljson -bare client/client
注意: 其中 -profile=client 来自于配置文件 ca-config.json profiles 属性值
3.6 检验生成的证书是否和配置相符合:
> # cd
> # openssl x509 -in ca.pem -text -noout
> # cd ..
> # cd server
> # openssl x509 -in server.pem -text -noout
> # cd ..
> # cd client
> # openssl x509 -in client.pem -text -noout
四 golang实现https双向认证
实例1: https双向认证
server端代码实现:
server.go
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"log"
"net/http"
)
type Cert struct {
K8sAgentCrtPath string
K8sAgentServerCertPath string
K8sAgentKeyPath string
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w,
"Hi, This is an example of https service in golang!")
}
func main() {
cert := Cert{
K8sAgentCrtPath: "ca/ca.pem",
K8sAgentServerCertPath: "server/server.pem",
K8sAgentKeyPath: "server/server-key.pem",
}
// ssl 双向检验
pool := x509.NewCertPool()
// 根证书
crt, err := ioutil.ReadFile(cert.K8sAgentCrtPath)
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.K8sAgentServerCertPath, cert.K8sAgentKeyPath)) // 服务端证书和服务端秘钥
}
client.go
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"log"
"net/http"
)
type Cert struct {
K8sAgentCrtPath string
ClientCrt string
ClientKey string
}
func main() {
cert := Cert{
K8sAgentCrtPath: "ca/ca.pem",
ClientCrt: "client/client.pem",
ClientKey: "client/client-key.pem",
}
pool := x509.NewCertPool()
caCrt, err := ioutil.ReadFile(cert.K8sAgentCrtPath) // 根证书
if err != nil {
log.Fatal("read ca.crt file error:", err.Error())
}
pool.AppendCertsFromPEM(caCrt)
cliCrt, err := tls.LoadX509KeyPair(cert.ClientCrt, cert.ClientKey) // 客户端证书 和 客户端秘钥
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://127.0.0.1:8080/")
if err != nil {
panic(err.Error())
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
> # go run server.go
> # go run client.go
Hi, This is an example of https service in golang!
实例2: 搭建https服务
> # http.go
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "这是一个https请求")
}
func main() {
http.HandleFunc("/", handler)
// err := http.ListenAndServeTLS(":8081", "server.crt", "server.key", nil)
// 证书的文件名称可能有出入
err := http.ListenAndServeTLS(":8081", "server/server.pem", "server/server-key.pem", nil)
if err != nil {
fmt.Println(err)
}
}
> # curl -k https://112.74.106.92:8081/
这是一个https请求
注意: -k 忽略证书警告
项目目录结构
> # cd /data/ssl
> # tree
.
├── ca
│ ├── ca.csr
│ ├── ca-key.pem
│ └── ca.pem
├── ca-config.json
├── ca-csr.json
├── client
│ ├── client.csr
│ ├── client-key.pem
│ └── client.pem
├── client.go
├── client.json
├── go.mod
├── http.go
├── server
│ ├── server.csr
│ ├── server-key.pem
│ └── server.pem
├── server.go
└── server.json
3 directories, 17 files
五 使用openssl生成server.crt和server.key文件供程序使用
生成秘钥文件
> # openssl genrsa -out server.key 2048
生成证书文件
> # openssl req -new -x509 -key server.key -out server.crt -days 365
......
Common Name (eg, your name or your server's hostname) []:localhost
......
提示: 指定hostname的名称, 其他随便写或留空
实例3: 搭建https服务
http.go
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "这是一个https请求")
}
func main() {
http.HandleFunc("/", handler)
// 证书的文件名称可能有出入
err := http.ListenAndServeTLS(":8081", "server.crt", "server.key", nil)
// err := http.ListenAndServeTLS(":8081", "server/server.pem", "server/server-key.pem", nil)
if err != nil {
fmt.Println(err)
}
}
> # curl -k https://112.74.106.92:8081/
这是一个https请求