在公网环境下,设备接入要保证安全性,server端既要验证设备的身份,设备也要验证server端的身份,这时就需要做双端互相认证。
我们先用网页的https单向认证举例,来说明证书是如何验证的。
一般的HTTPS服务都是只需要客户端验证服务器的身份就好了。比如我们想访问某个网站,我们得确认那个网站真是我们要访问的网站,而不是一个界 面类似的用来诱骗我们帐号的钓鱼网站。而网站并不需要通过TLS验证我们的身份。
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)
}
}
设备作为后台接入需要验证的时候,我们希望双方都利用一个身份证(certificate)通过TLS协议向对方展示自己的身份,而不是像人一下输入帐号、密码。
我们可以按照上文中例子展示的:让通信双方互相交换身份证,这样既可互相验证。但是如果一个系统里有多方, 任意两方都要交换身份太麻烦。我们通常创建一个自签署的根身份证,然后用它来签署系 统中各方的身份。这样每一方都只要有这个根身份即可验证所有其他通信方。
这里解释了用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需要做一些修改: 增加了一个 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)
}
}
客户端程序相对于上面的变化主要在于
调用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!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。