TLS 学习笔记

最近在部署 KubeEdge 的时候遇到了一些和 TLS 有关的问题,对这方面不太了解,补习了一下

学习链接


流程

  1. server 生成 public key 和 private key
  2. server 将 server public key 和 id(例如域名)合并为 CSR(Certificate Signing Request)提交给 CA
  3. CA 使用 CA private key 基于 CSR 生成 signature(数字签名),并和 CSR 一起作为 CRT 发还给 server
  4. client 通过 tls 访问 server 时,server 将 server CRT 发给 client
  5. client 使用 [CA CRT].[CA CSR].[CA public key] 解密 [Server CRT].[Server Signature],并将结果与 [Server CRT].[Server CSR] 比较,如果一致,则可以使用 [Server CRT].[Server CSR].[Server Public Key] 对发送的数据进行加密

自签名

公钥私钥对

1
openssl genrsa -out server.key 2048

生成 CSR :申请身份证

1
openssl req -nodes -new -key server.key -subj "/CN=localhost" -out server.csr

生成 CRT :

1
openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt

CA 签名

生成 CA 的 key 和 crt

1
2
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -days 10000 -out ca.crt -subj "/CN=self-ca"

生成 server 的相关信息

1
2
3
openssl genrsa -out server.key 2048
openssl req -nodes -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 365

注意,在执行最后一条命令的时候可能需要加上 server 的ip,否则可能会报 “x509: cannot validate certificate for x.x.x.x because it doesn’t contain any IP SANs”

将最后一条命令替换如下

1
2
3
server_ip=<Your server ip>
echo subjectAltName = IP:$server_ip > extfile.cnf
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 -extfile extfile.cnf

编写服务器

1
2
3
4
5
6
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "hello secure world!\n")
})
_ = http.ListenAndServeTLS(":8080", "server.crt", "server.key", nil)
}

使用 curl 访问:curl --cacert ca.crt https://localhost:8080


构建客户端访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func loadCA(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: loadCA("ca.crt")},
}}

resp, e := c.Get("https://localhost:8080")
if e != nil {
log.Fatal("http.Client.Get: ", e)
}
defer resp.Body.Close()
_, _ = io.Copy(os.Stdout, resp.Body)
}

双向验证

参考链接

server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
s := &http.Server{
Addr: ":8080",
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 := s.ListenAndServeTLS("server.crt", "server.key")
if e != nil {
log.Fatal("ListenAndServeTLS: ", e)
}
}

client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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:8080")
if e != nil {
log.Fatal("http.Client.Get: ", e)
}
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
}

tcp + tls

参考链接


websocket + tls

使用的库为 gorilla/websocket,样例修改自官网给出的样例 echo

server:

1
2
3
4
5
// ...
func main() {
http.HandleFunc("/echo", echo)
log.Fatal(http.ListenAndServeTLS("localhost:8080", "server.crt", "server.key", nil))
}

client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func loadCA(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() {
u := url.URL{Scheme: "wss", Host: "localhost:8080", Path: "/echo"}
log.Printf("connecting to %s", u.String())

websocket.DefaultDialer.TLSClientConfig = &tls.Config{RootCAs: loadCA("ca.crt")}
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
// ...
}