TL;DR 手工创建 CA 证书链, 手写代码打通 HTTPs 的两端
HTTPs 最近是一个重要的话题, 同时也是一个有点难懂的话题所以网上有大量的 HTTPs/TLS/SSL 的教程关于这些的原理, 这里不做讲解, 有兴趣的可以自行搜索
本文介绍一个自己创建证书, 并编写 Go 代码实现 client/server 两端的过程从实践的角度帮助理解
构建 CA 证书链
我们首先要创建 client/server 使用的证书创建证书的方法有很多种: 有不怕麻烦, 直接通过 openssl
创建的, 有通过 cfssl 创建的这里要介绍的是我认为最简单的一种: tls-gen
tls-gen 是一个用 Python 编写的非常易用的工具它定义了三种 profile 这里我们选择最简单的一种: 一个根证书和一组证书私钥对
在 shell 里面执行一下的命令:
- git clone https://github.com/michaelklishin/tls-gen
- cd tls-gen/basic
- make CN = www.mytestdomain.io
就这样, 我们就为域名 www.mytestdomain.io 创建了一套证书观察一下当前路径的内容, 我们会发现两个新的目录: testca 和 server 前者里面存放了刚刚创建的根证书 (root CA), 后者里面存放了我们之后的服务程序要用的的证书和私钥
- testca/
- cacert.pem
- server/
- cert.pem
- key.pem
编写服务
接下来开始写代码 Go 对 TLS 的支持还是比较完备的, 也比较简单以下是服务器端的代码 (server.go):
- func HelloServer(w http.ResponseWriter, req *http.Request) {
- w.Header().Set("Content-Type", "text/plain")
- w.Write([]byte("This is an example server.\n"))
- }
- func main() {
- http.HandleFunc("/hello", HelloServer)
- err := http.ListenAndServeTLS(":1443", "server/cert.pem", "server/key.pem", nil)
- if err != nil {
- log.Fatal("ListenAndServe:", err)
- }
- }
可以看到我们创建了一个 HTTP 服务, 这个服务监听 1443 端口并且只处理一个路径 /hello 然后调用了下面这个函数来监听 1443 端口注意我们给出了之前创建的服务的证书和私钥 - 这样就保证了 HTTP 会用加密的方式来传输
ListenAndServeTLS(addr, certFile, keyFile string, handler Handler)
运行服务程序:
go run server.go
访问 HTTPs 服务
假定我们的服务程序是运行在本地的我们先改一下 /etc/hosts 来配置域名解析:
#echo 127.0.0.1 www.mytestdomain.io >> /etc/hosts
我们用以下的代码 (client.go) 来访问服务:
- func main() {
- client := &http.Client{}
- resp, err := client.Get("https://www.mytestdomain.io:1443/hello")
- if err != nil {
- panic("failed to connect:" + err.Error())
- }
- content, _ := ioutil.ReadAll(resp.Body)
- s := strings.TrimSpace(string(content))
- fmt.Println(s)
- }
运行 go run client.go, 只能得到这样的错误:
panic: failed to connect: Get https://www.mytestdomain.io:1443/hello: x509: certificate signed by unknown authorit
这是因为系统不知道如何来处理这个 self signed 证书
各个 OS 添加根证书的方法是不同的对于 Linux 系统 (以 Ubuntu 为例) 来说, 把证书文件放到相应的目录即可:
#sudo cp testca / cacert.pem / etc / ssl / certs
如果是 macOS, 可以用一下的命令:
#sudo security add - trusted - cert - d - r trustRoot - k / Library / Keychains / System.keychain testca / cacert.pem
上面的方法会把我们手工创建的 root CA
添加到系统所已知的列表里面这样一来, 所有用该 root CA
创建的证书都可以被认证了
现在我们再次运行刚才那个程就会成功的获得服务端的响应了:
This is an example server.
另一种访问方法
假如只是一个普通的用户, 没有 root/sudo 权限, 不就无法做上面的操作了吗? 这种情况下还有另外一种做法: 把 root CA 放置在代码里面
在上面的 client.go 里面添加这么几行代码:
- func main() {
- roots := x509.NewCertPool()
- ok := roots.AppendCertsFromPEM([]byte(rootPEM))
- if !ok {
- panic("failed to parse root certificate")
- }
- tr := &http.Transport{
- TLSClientConfig: &tls.Config{RootCAs: roots},
- }
- client := &http.Client{Transport: tr}
- // ...
其中的 rootPEM 就是 testca/cacert.pem 的内容
- var rootPEM = `
- -----BEGIN CERTIFICATE-----
- MIIDAjCCAeqgAwIBAgIJAL2faqa73yLvMA0GCSqGSIb3DQEBCwUAMDExIDAeBgNV
- BAMMF1RMU0dlblNlbGZTaWduZWR0Um9vdENBMQ0wCwYDVQQHDAQkJCQkMB4XDTE4
- MDIwNTA5Mzc0NVoXDTI4MDIwMzA5Mzc0NVowMTEgMB4GA1UEAwwXVExTR2VuU2Vs
- ZlNpZ25lZHRSb290Q0ExDTALBgNVBAcMBCQkJCQwggEiMA0GCSqGSIb3DQEBAQUA
- A4IBDwAwggEKAoIBAQC9eO6Tam4XFDUbK9FAStAg29teYeKtt8WEJvKGB50xMfXO
- 2pD0StsXhKrspXBYck0FwKIBsTLr97w7dSqa64z3U2V2BorogFzoEE4JH2sydYGA
- QqNAqezGx8VZnQVRyZEBifRPebR4WVD5GtXYe+MnSkHPIgsG0QG0SaiSfMl05dSJ
- HoE9T9Kly9fH6yED88++OYjZZRGKOf2THpQlXJjF3iwCDLkwz9Z/kjmpK/rR0SEh
- tanf7bOgGs3OoFmX4DvmFJXoriVUC9jcj0Z4oX3Ld81XXyd4FJkpKvdKDhYkqcug
- FgERqdBeRDM+MA38YooKHZh0klL2EThNXJxM0r1vAgMBAAGjHTAbMAwGA1UdEwQF
- MAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQBEqp0ON1A/pCKF
- ztfKuzdW+9pauE8dl6Ij3++dt6AqW5QYFLOEFQwoMBOkGChGQDxHkakyaA0DfGe5
- JntMH0yYyZnr4kfs+AcY6P+2PfgrgVBqadhR6uAGOBaXDW7dlllqIJJ8NRInA/fT
- DYXMxBJbFrcj2cGIYVPvAbrosZ5L/YdAdVM76V8uuk8Hmmy5zRQj+gWt/jDkYWFr
- p0b6k3FBXvM7+nhqAIdyMjLioAdYwFpPglGj3xHXS5neWjyUDlAYISNe+PKMERSe
- DrptyDE+ljzl77hvvfZD9OPhXbDkAeVU/NaDwHG/G5HDVdNbg/FZ6ueevF34Xuze
- jm3lrdJm
- -----END CERTIFICATE-----`
也就是说, 我们用准备好的 root CA 的内容产生了一个新的 http transport
运行一下 go run client.go 成功!
This is an example server.
总结
一对 HTTPs client/server 程序中需要一个共同的 root CA 服务器端需要该 root CA
创建的 CA / 私钥对
这里用的是 Go 语言来实现, 其它的语言过程也类似
来源: https://segmentfault.com/a/1190000013287122