内容简介:在公网环境下,设备接入要保证安全性,server端既要验证设备的身份,设备也要验证server端的身份,这时就需要做双端互相认证。我们先用网页的https单向认证举例,来说明证书是如何验证的。一般的HTTPS服务都是只需要客户端验证服务器的身份就好了。比如我们想访问某个网站,我们得确认那个网站真是我们要访问的网站,而不是一个界 面类似的用来诱骗我们帐号的钓鱼网站。而网站并不需要通过TLS验证我们的身份。
背景
在公网环境下,设备接入要保证安全性,server端既要验证设备的身份,设备也要验证server端的身份,这时就需要做双端互相认证。
我们先用网页的https单向认证举例,来说明证书是如何验证的。
1. 单向身份认证
一般的HTTPS服务都是只需要客户端验证服务器的身份就好了。比如我们想访问某个网站,我们得确认那个网站真是我们要访问的网站,而不是一个界 面类似的用来诱骗我们帐号的钓鱼网站。而网站并不需要通过TLS验证我们的身份。
https服务端程序
package main import ( "io" "log" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { io.WriteString(w, "hello, world!\n") }) if e := http.ListenAndServeTLS(":443", "server.crt", "server.key", nil); e != nil { log.Fatal("ListenAndServe: ", e) } }
通过openssl创建自签名证书
openssl genrsa -out server.key 2048 openssl req -nodes -new -key server.key -subj "/CN=localhost" -out server.csr openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt
客户端程序
让客户端用服务器自己的证书证验证它自己。类似于curl --cacert server.crt
package main import ( "crypto/tls" "io" "log" "net/http" "os" ) func loadCrt(caFile string) *x509.CertPool { pool := x509.NewCertPool() if ca, e := ioutil.ReadFile(caFile); e != nil { log.Fatal("ReadFile: ", e) } else { pool.AppendCertsFromPEM(ca) } return pool } func main() { c := &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{RootCAs: loadCrt("server.crt")}, }} if resp, e := c.Get("https://localhost"); e != nil { log.Fatal("http.Client.Get: ", e) } else { defer resp.Body.Close() io.Copy(os.Stdout, resp.Body) } }
2. 双方认证对方身份
设备作为后台接入需要验证的时候,我们希望双方都利用一个身份证(certificate)通过TLS协议向对方展示自己的身份,而不是像人一下输入帐号、密码。
创建CA并签署server以及client的身份证
我们可以按照上文中例子展示的:让通信双方互相交换身份证,这样既可互相验证。但是如果一个系统里有多方, 任意两方都要交换身份太麻烦。我们通常创建一个自签署的根身份证,然后用它来签署系 统中各方的身份。这样每一方都只要有这个根身份即可验证所有其他通信方。
这里解释了用OpenSSL生成根身份证和签署其他身 份证的过程。针对我们的例子,具体过程如下:
创建我们自己CA的私钥:
openssl genrsa -out ca.key 2048
创建我们自己CA的CSR,并且用自己的私钥自签署之,得到CA的身份证:
openssl req -x509 -new -nodes -key ca.key -days 365 -out ca.crt -subj "/CN=me"
创建server的私钥,CSR,并且用CA的私钥自签署server的身份证:
openssl genrsa -out server.key 2048 openssl req -new -key server.key -out server.csr -subj "/CN=localhost" openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365
创建client的私钥,CSR,以及用ca.key签署client的身份证:
openssl genrsa -out client.key 2048 openssl req -new -key client.key -out client.csr -subj "/CN=localhost" openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365
Server
相对于上面的例子,server需要做一些修改: 增加了一个 http.Server 变量serv,并且调用serv.ListenAndServeTLS,而不 是像之前那样直接调用http.ListenAndServeTLS了:
func main() { serv := &http.Server{ Addr: ":443", Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!\n") }), TLSConfig: &tls.Config{ ClientCAs: loadCA("ca.crt"), ClientAuth: tls.RequireAndVerifyClientCert, }, } e := serv.ListenAndServeTLS("server.crt", "server.key") if e != nil { log.Fatal("ListenAndServeTLS: ", e) } }
Client
客户端程序相对于上面的变化主要在于
调用tls.LoadX509KeyPair读取client.key和client.crt,并返回一个 tls.Certificate变量,
把这个变量传递给http.Client变量,然后调用其Get函数。
func main() { pair, e := tls.LoadX509KeyPair("client.crt", "client.key") if e != nil { log.Fatal("LoadX509KeyPair:", e) } client := &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ RootCAs: loadCA("ca.crt"), Certificates: []tls.Certificate{pair}, }, }} resp, e := client.Get("https://localhost") if e != nil { log.Fatal("http.Client.Get: ", e) } defer resp.Body.Close() io.Copy(os.Stdout, resp.Body) }
运行和测试
setsid go run ./server.go go run ./client.go
屏幕上打印出Hello World!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。