最近工作中有相关项目有安全要求,需要用到 HTTPS 的双向数字证书校验,以增强应用的数据交换安全。
数字签名
数字证书由两部分组成:
- C:证书相关信息(对象名称 + 过期时间 + 证书发布者 + 证书签名算法….)
- S:证书的数字签名
S = F(Digest(C))
Digest 为摘要函数,也就是 md5、sha-1 或 sha256 等单向散列算法,用于将无限输入值转换为一个有限长度的“浓缩”输出值。比如我们常用 md5 值来验证下载的大文件是否完 整。大文件的内容就是一个无限输入。大文件被放在网站上用于下载时,网站会对大文件做一次 md5 计算,得出一个 128bit 的值作为大文件的 摘要一同放在网站上。用户在下载文件后,对下载后的文件再进行一次本地的 md5 计算,用得出的值与网站上的 md5 值进行比较,如果一致,则大 文件下载完好,否则下载过程大文件内容有损坏或源文件被篡改。
SHA-256 ( 1.2.840.113549.1.1.11 )
签名校验
F'(S) ?= Digest(C)
接收端进行两个计算,并将计算结果进行比对:
- 首先通过 Digest(C),接收端计算出证书内容(除签名之外)的摘要。
- 数字证书携带的签名是 CA 通过 CA 密钥加密摘要后的结果,因此接收端通过一个解密函数 F’对 S 进行“解密”。RSA 系统中,接收端使用 CA 公钥对 S 进行“解密”,这恰是 CA 用私钥对 S 进行“加密”的逆过程。
将上述两个运算的结果进行比较,如果一致,说明签名的确属于该 CA,该证书有效,否则要么证书不是该 CA 的,要么就是中途被人篡改了。
Golang 使用 HTTPS 双向证书验证步骤
openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key ca.key -subj "/CN=vsjclub.com" -days 5000 -out ca.pem
或者生成 csr,然后再生成证书
openssl req -new -sha256 -out ca.csr -key ca.key
openssl x509 -req -in ca.csr -signkey ca.key -days 5000 -out ca.crt
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj "/CN=localhost" -out server.csr
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 5000
openssl x509 -in server.crt -pubkey -noout > public.key
openssl genrsa -out client.key 2048
openssl req -new -key client.key -subj "/CN=vsjclub.com" -out client.csr
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 5000
服务端程序
// golnag/https/go
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net/http"
)
type myhandler struct {
}
func (h *myhandler) ServeHTTP(w http.ResponseWriter,
r *http.Request) {
fmt.Fprintf(w,
"Hi, This is an example of http service in golang!\n")
}
func main() {
pool := x509.NewCertPool()
caCertPath := "rsa/ca.crt"
caCrt, err := ioutil.ReadFile(caCertPath)
if err != nil {
fmt.Println("ReadFile err:", err)
return
}
pool.AppendCertsFromPEM(caCrt)
s := &http.Server{
Addr: ":8081",
Handler: &myhandler{},
TLSConfig: &tls.Config{
ClientCAs: pool,
ClientAuth: tls.RequireAndVerifyClientCert,
},
}
err = s.ListenAndServeTLS("rsa/server.crt", "rsa/server.key")
if err != nil {
fmt.Println("ListenAndServeTLS err:", err)
}
}
客户端程序
// golang/https_client.go
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
pool := x509.NewCertPool()
caCertPath := "rsa/ca.crt"
caCrt, err := ioutil.ReadFile(caCertPath)
if err != nil {
fmt.Println("ReadFile err:", err)
return
}
pool.AppendCertsFromPEM(caCrt)
cliCrt, err := tls.LoadX509KeyPair("rsa/client.crt", "rsa/client.key")
if err != nil {
fmt.Println("Loadx509keypair err:", err)
return
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: pool,
Certificates: []tls.Certificate{cliCrt},
},
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://localhost:8081")
if err != nil {
fmt.Println("Get error:", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
结语
通过上面的例子可以看到,Golang 做 HTTPS 相关的开发是非常便利的,Golang 标准库已经实现了 TLS 1.2 版本协议。