CA 動手玩

 ·  ☕ 3 

前面說了一些基礎概念,本篇文章就實際來動手做做看用 OpenSSL 、 Nginx 加上 GO 開發一個簡單的 TLS 範例。

建立 CA key

由於我們要自己簽 SSL 所以需要透過 OpenSSL 先建立一個 CA 的私鑰。

1
2
3
4
5
6
7
8
openssl genrsa -out ca.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
........+++++
......+++++
e is 65537 (0x010001)

root@internalTest:/home/ca# ls
ca.key

建立 CA CRT

建立完 CA 的私鑰後,我們還需要建立 CA 的身分證 CA CRT ,這個身分證就代表 CA 公開出去的所有人都看得到。

1
2
3
4
5
openssl req -x509 -new -nodes -key ca.key -days 10000 -out ca.crt -subj "/CN=playwithca"

root@internalTest:/home/ca# ls
ca.key
ca.crt

建立 Server Key

建立 server 的私鑰 , 用來給 nginx server 的 TLS private key。

1
2
3
4
5
6
openssl genrsa -out server.key 2048

Generating RSA private key, 2048 bit long modulus (2 primes)
...................+++++
.............................................................................................+++++
e is 65537 (0x010001)

Server CSR

建立一個 CSR 用來向 CA 說請幫我簽名,簽的 Domain 是 example.com

可以想像你去戶政事務所填寫申請身分證的表單

1
openssl req -new -key server.key -out server.csr -subj "/CN=example.com"

Server CRT

透過剛剛產出的 CSR 給 CA 簽名,這邊一般來說是用買的,你給 CA 商 CSR 他會幫你簽名出一個 CRT , 這個 CRT 就是這個 server 的身分證。

從你填寫表單的內容戶政事務所發給你對應的身分證

1
2
3
4
5
6
7
8
9
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365

-out server.crt -days 365
Signature ok
subject=CN = example.com
Getting CA Private Key

root@internalTest:/home/ca# ls
ca.crt  ca.key  ca.srl  server.crt  server.csr  server.key

這邊簡單設定一下 nginx 的 SSL ,讓 nginx 可以使用指定的證書,並且開啟 443 port 提供其他人連線。

nginx config

...
         server {
                listen 443 ssl default_server;
                listen [::]:443 ssl default_server;
                ssl_certificate /home/ca/server.crt;
                ssl_certificate_key /home/ca/server.key;
        }
...

設定好 nginx 別忘了重新載入 nginx 設定檔(題外話我覺得 nginx graceful reload做得滿好的,有興趣的朋友可以去研究一下)

1
nginx -s reload

由於是測試環境沒有實際的 domain ,就先以修改 hostname 作為替代方案。

1
2
3
4
5
6
7
8
9
cat /etc/hosts
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
52.163.243.191 example.com
...

修改完成後測試一下能不能透過 https 連線到 nginx 。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
curl -k https://example.com                                       

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully i
...

可以順利連線到,那接著可以撰寫一個簡單的 Client 去打打看 Server 囉!

Sample Client

這邊會以一個單向 TLS 認證作為示範,單向 TLS 認證大致上的動作是, Client 向 Server 發起請求。這時候 Client 會拿到 Server 的 證書( crt ) ,Client 會拿著這組 Server crt 跟 CA 的 crt 進行驗證 ,看 Server 是不是被 CA 授權的還是假冒的。
如果是假冒的那麼 Client 會拒絕連線,反之 Server 若是被 CA 認證的那 Client 就會繼續往 Server 發請求。

可以想得簡單一點,今天所有人來向你買東西,看到你身分證都馬上拿去問戶政事務所問這個身分證是不是合法的公民,如果不是合法的身分證就不進行交易。

以下是一個用 go 開發的單向 TLS 請求的 Client 。

1
2
3
4
5
6
7
tree /go/src/dev/
.
├── 2.0
│   ├── client.crt
│   ├── client.key
│   └── root-ca.crt
└── main.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
	"crypto/tls"
	"crypto/x509"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
)

func main() {
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{
			RootCAs:      loadCA("/Users/jason/go/src/dev/example-tls/2.0/root-ca.crt"),
		},
	}
	c := &http.Client{Transport: tr}
	if resp, e := c.Get("https://example.com"); e != nil {
		log.Fatal("http.Client.Get: ", e)
	} else {
		defer resp.Body.Close()
		io.Copy(os.Stdout, resp.Body)
	}
}

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
}

以下是單向 TLS 請求的 Client 向 Server 請求的結果。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
go run main.go
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>

...

結束語

這篇文章分享了如何用 nginx 、 OpenSSL 以及 Go 做一個自簽的 TLS 服務,雖然是一個簡單的範例但我們可以從中瞭解單向 TLS , Client 只要向 CA 驗證 Server 的身份即可,那這樣安全嗎?這個討論我會保留到下一章再來跟大家分享。


Meng Ze Li
Meng Ze Li
Kubernetes / DevOps / Backend