TL;DR 手工建立CA证书链,手写代码打通HTTPs的两端git
HTTPs最近是一个重要的话题,同时也是一个有点难懂的话题。因此网上有大量的HTTPs/TLS/SSL的教程。关于这些的原理,这里不作讲解,有兴趣的能够自行搜索。github
本文介绍一个本身建立证书,并编写 Go 代码实现 client/server 两端的过程。从实践的角度帮助理解。shell
咱们首先要建立 client/server 使用的证书。建立证书的方法有不少种:有不怕麻烦,直接经过 openssl
建立的,有经过 cfssl 建立的。这里要介绍的是我认为最简单的一种:tls-gen
服务器
tls-gen
是一个用 Python 编写的、很是易用的工具。它定义了三种 profile。这里咱们选择最简单的一种:一个根证书和一组证书、私钥对。dom
在 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会用加密的方式来传输。code
ListenAndServeTLS(addr, certFile, keyFile string, handler Handler)
运行服务程序:server
go run server.go
假定咱们的服务程序是运行在本地的。咱们先改一下 /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 语言来实现,其它的语言过程也相似。