基本概念:
证书
证书是一个数据结构,其中包含一个 public key 和一个 name;
权威机构对证书进行签名,签名的大概意思是:public key xxx 关联到了 name xx;
对证书进行签名的 entity 称为 issuer(或 certificate authority, CA), 证书中的 entity 称为 subject。
x509:
X.509 标准主要内容:证书的作用、证书文件的结构、证书管理方式、证书校验方式、证书的撤销等。
X.509 构建在 ASN.1 (Abstract Syntax Notation,抽象语法标注)之上,后者是另一个 ITU-T 标准 (X.208 and X.680)。
ASN.1 定义数据类型,
可以将 ASN.1 理解成 X.509 的 JSON,
但实际上更像 protobuf、thrift 或 SQL DDL。
RFC 5280 用 ASN.1 来定义 X.509 证书,其中包括名字、秘钥、签名等信息。
ASN.1 有很多种编码规则, 但用于 X.509 和其他加密相关的,只有一种常见格式:DER —— 虽然有时也会用到 non-canonical 的 basic encoding rules (BER,基础编码规则) 。
PEM (privacy enhanced email):文本格式
DER 是二进制格式,不便复制粘贴。因此大部分证书都是以 PEM 格式打包的
1
2
3
4
5
6
7
8
| -----BEGIN CERTIFICATE-----
MIIBwzCCAWqgAwIBAgIRAIi5QRl9kz1wb+SUP20gB1kwCgYIKoZIzj0EAwIwGzEZ
MBcGA1UEAxMQTDVkIFRlc3QgUm9vdCBDQTAeFw0xODExMDYyMjA0MDNaFw0yODEx
BgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRc+LHppFk8sflIpm/XKpbNMwx3
SDAfBgNVHSMEGDAWgBTirEpzC7/gexnnz7ozjWKd71lz5DAKBggqhkjOPQQDAgNH
ADBEAiAejDEfua7dud78lxWe9eYxYcM93mlUMFIzbWlOJzg+rgIgcdtU9wIKmn5q
FU3iOiRP5VyLNmrsQD3/ItjUN1f1ouY=
-----END CERTIFICATE-----
|
PEM 编码的证书通常以 .pem、.crt 或 .cer 为后缀.
PKCS #7:Java 中常用
你可能会遇到的是一个称为 PKCS(Public Key Cryptography Standards,公钥加密标准)的标准的一部分, 它由 RSA labs 发布。
其中的第一个标准是 PKCS#7,后面被 IETF 重新冠名为 Cryptographic Message Syntax (CMS) ,其中可以包含多个证书(以 full certificate chain 方式编码)。
PKCS#7 在 Java 中使用广泛。常见扩展名是 .p7b and .p7c。
PKCS #12:微软常用
另一个常见的打包格式 PKCS#12, 它能将一个证书链(这一点与 PKCS#7 类似)连同一个(加密之后的)私钥打包到一起。
微软的产品多用这种格式,常见后缀.pfx and .p12。
PKCS#7 和 PKCS#12 envelopes 仍然使用 ASN.1,这意味着 它们都能以原始 DER、BER 或 PEM 的格式编码
公钥、私钥常见扩展名
公钥:.pub or .pem
私钥:.prv, .key, or .pem
DN (distinguished names)
历史上,X.509 使用 X.500 distinguished names (DN) 来命名证书的使用者(name the subject of a certificate),即 subscriber。
SAN (subject alternative name)
常用的 SAN 有四种类型,绑定的都是广泛使用的名字:
- domain names (DNS)
- email addresse
- IP addresse
- URI
Certificate signing requests(证书签名请求,PKCS#10)
Subscriber 请求一个证书时,会向 CA 会提交一个 certificate signing request (CSR)。
CSR 也是一个 ASN.1 结构,定义在 PKCS#10。
与证书类似,CSR 数据结构包括一个公钥、一个名字和一个签名。
CSR 是自签名的,用与 CRS 中公钥对应的私钥自签名。
这个签名用于证明该 subscriber 有对应的私钥,能对任何用其公钥加密的东西进行解密。
还使即使 CSR 被复制或转发,都没有能篡改其中的内容(篡改无效)。
CSR 中包括了很多证书细节配置项。但在实际中,大部分配置项都会被 CA 忽略。大部分 CA 都使用自己的固定模板, 或提供一个 administrative 接口来收集这些信息。
TLS身份认证原理
以单向的客户端认证服务端身份为例
首先我们需要生成一个用于签发数字证书的CA私钥和CA数字证书
1
2
| openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -days 5000 -out ca.crt -subj "/CN=fridayhub.cn"
|
然后需要生成服务端私钥和数字证书,并用CA私钥给服务端数字证书做签名。
1
2
3
4
5
| openssl genrsa -out server.key 2048
# 生成服务端证书的 CSR
openssl req -new -key server.key -subj "/CN=localhost" -out server.csr
# 通过 CSR 向 CA 签发服务端证书
openssl x509 -req -in server.csr -CA ../root/ca.crt -CAkey ../root/ca.key -CAcreateserial -out server.crt -days 5000
|
单向认证流程如下:
- 客户端发送连接请求
- 服务将自己的数字证书(证书中包含服务端公钥和签名等信息)发给客户端
- 客户端利用本地的CA数字证书验证服务端下发的数字证书,以验证服务端身份
- 客户端随机生成一个session秘钥,并用服务端下发的数字证书中的公钥对这个秘钥加密,并发给服务端
- 服务端用自身私钥解密,然后服务端和客户端以后就用这个session秘钥进行加密通信
以上流程中忽略了双方协商加密算法之类的步骤。如果是双向认证,则还有服务端要求客户端发送证书的过程[1]。
实现TLS双向认证
要实现双向认证,还需要生成客户端的私钥和数字证书,并用CA私钥给服务端数字证书做签名。
1
2
3
4
5
6
| # 生成客户端秘钥
openssl genrsa -out client.key 2048
生成客户端 CSR
openssl req -new -key client.key -subj "/CN=localhost" -out client.csr
# 通过 CSR 向 CA 签发客户端证书
openssl x509 -req -in client.csr -CA ../root/ca.crt -CAkey ../root/ca.key -CAcreateserial -out client.crt -days 5000
|
证书与秘钥管理
要以TLS方式接入我们系统,设备端需要三个文件,CA数字证书,设备私钥,设备数字证书。
CA数字证书必须由我们颁发,没有保密要求。
设备私钥由厂商自己生成,设备私钥不能泄露。
设备数字证书须由我们颁发,需要厂商根据设备私钥先生成一个数字证书,再将该证书传给我们,由我们的CA私钥和CA证书对其签发生成真正的设备数字证书,在保证设备私钥不泄露的情况下,设备数字证书没有保密要求。
后端需要四个文件: CA私钥,CA数字证书,服务端私钥,服务端数字证书
CA私钥用来和CA数字证书一起签发服务端数字证书和设备端数字证书,必须保密。
CA数字证书还被服务端用来验证设备数字证书的真假,没有保密要求。
服务端私钥必须保密
服务端数字证书没有保密要求
双向认证流程:
- 浏览器发送一个连接请求给安全服务器。
- 服务器将自己的证书,以及同证书相关的信息发送给客户浏览器。
- 客户浏览器检查服务器送过来的证书是否是由自己信赖的CA中心所签发的。如果是,就继续执行协议;如果不是,客户浏览器就给客户一个警告消息:警告客户这个证书不是可以信赖的,询问客户是否需要继续。
- 接着客户浏览器比较证书里的消息,例如域名和公钥,与服务器刚刚发送的相关消息是否一致,如果是一致的,客户浏览器认可这个服务器的合法身份。
- 服务器要求客户发送客户自己的证书。收到后,服务器验证客户的证书,如果没有通过验证,拒绝连接;如果通过验证,服务器获得用户的公钥。
- 客户浏览器告诉服务器自己所能够支持的通讯对称密码方案。
- 服务器从客户发送过来的密码方案中,选择一种加密程度最高的密码方案,用客户的公钥加过密后通知浏览器。
- 浏览器针对这个密码方案,选择一个通话密钥,接着用服务器的公钥加过密后发送给服务器。
- 服务器接收到浏览器送过来的消息,用自己的私钥解密,获得通话密钥。
- 服务器、浏览器接下来的通讯都是用对称密码方案,对称密钥是加过密的。
服务端代码:
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
| // 加载服务端证书
srvCert, err := tls.LoadX509KeyPair("server.crt", "server.key")
if err != nil {
log.Panic("try to load key & crt got error", err)
}
// 加载 CA,并且添加进 caCertPool,这个 caCertPool 就是用来校验客户端证书的根证书列表
log.Info("load ca ca.crt")
caCrt, err := ioutil.ReadFile("ca.crt")
if err != nil {
log.Panic("try to load ca got error:",err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCrt)
// https tls config
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{srvCert},
ClientCAs: caCertPool, // 专用于校验客户端证书的 CA 池, 如果是ca机构,此项可以不填,通过加载操作系统的ca 证书。
InsecureSkipVerify: false, // 不接受不安全的连接
ClientAuth: tls.RequireAndVerifyClientCert, // 校验客户端证书
}
tlsConfig.BuildNameToCertificate()
// 监听连接
ln, err := tls.Listen("tcp", ":443", tlsConfig)
if err != nil {
log.Errorf("try to listen tcp got error:%v", err)
time.Sleep(3 * time.Second)
continue LISTEN_LOOP
}
log.Info("listening on 443...")
|
客户端代码:
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
| // 加载客户端证书
cliCert, err := tls.LoadX509KeyPair("client.crt", "client.key")
if err != nil {
log.Panic("try to load key & crt got error", err)
}
// 加载 CA 根证书
log.Info("load ca ca.crt")
caCrt, err := ioutil.ReadFile("ca.crt") // 这里server和client使用同一个ca
if err != nil {
log.Panic("try to load ca got error", err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCrt)
// https tls config
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cliCert},
RootCAs: caCertPool, // 校验服务端证书的 CA 池
InsecureSkipVerify: false,
}
tlsConfig.BuildNameToCertificate()
// 发起连接
conn, err := tls.Dial("tcp", "127.0.0.1:443", tlsConfig)
if err != nil {
log.Panic("try to dial tcp got error", err)
}
defer conn.Close()
log.Info("connected to remote:%s", conn.RemoteAddr().String())
|
使用openssl查看和验证证书:
查看证书详细信息:
openssl x509 -noout -text -in server.crt
校验证书:
使用本地ca证书校验由自己颁发的服务器证书server.crt和客户端证书client.crt
1
2
| openssl verify -CAfile ca.crt server.crt
openssl verify -CAfile ca.crt client.crt
|
kubeedge证书交换过程分析
流程梳理
- 云端生成ca 公私钥,保存在 kubeedge namespace的 casecret secret中
- 使用生成云端公私钥,并用ca生成服务端证书,都保存在kubeedge namespace的 cloudcoresecret secret中
- 在云端获取tokensecret configmap中的token,这个token默认每12小时更新一次
- edgecore 通过 serverip:10002/edge.crt 获取ca证书,需要带上token
- edgecore 生成 私钥 server.key
- edgecore 生成csr,请求cloucore serverip:10002/edge.crt 获取edgecore证书
- cloudcore 接收到crs请求,使用同一套ca签发证书
- 云端保存证书为 server.crt
cloudcore:
证书功能在clodhub中 kubeedge/cloud/pkg/cloudhub/cloudhub.go
http server 配置: CloudHubHTTPS, 默认端口10002
1
2
3
4
5
6
7
8
9
10
11
| type CloudHubHTTPS struct {
// Enable indicates whether enable Https protocol
// default true
Enable bool `json:"enable"`
// Address indicates server ip address
// default 0.0.0.0
Address string `json:"address,omitempty"`
// Port indicates the open port for HTTPS server
// default 10002
Port uint32 `json:"port,omitempty"`
}
|
http handler
1
2
| ws.Route(ws.GET(constants.DefaultCertURL).To(edgeCoreClientCert)) // /edge.crt 用于颁发边缘的证书
ws.Route(ws.GET(constants.DefaultCAURL).To(getCA)) // /ca.crt 请求ca证书
|
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
| // kubeedge/cloud/pkg/cloudhub/servers/httpserver/signcerts.go
// 边缘证书颁发流程
func edgeCoreClientCert(request *restful.Request, response *restful.Response) {
signEdgeCert(response, request.Request)
}
func signEdgeCert(w http.ResponseWriter, r *http.Request) {
// 获取csr
r.Body = http.MaxBytesReader(w, r.Body, constants.MaxRespBodyLength)
csrContent, err := io.ReadAll(r.Body)
csr, err := x509.ParseCertificateRequest(csrContent)
// 签发
clientCertDER, err := signCerts(csr.Subject, csr.PublicKey, usages)
}
func signCerts(subInfo pkix.Name, pbKey crypto.PublicKey, usages []x509.ExtKeyUsage) ([]byte, error) {
// 准备cert config
cfgs := &certutil.Config{
CommonName: subInfo.CommonName,
Organization: subInfo.Organization,
Usages: usages,
}
// 读取ca证书
caCert, err := x509.ParseCertificate(ca)
// 读取ca私钥
caKey, err := x509.ParseECPrivateKey(caKeyDER)
// 签发
certDER, err := NewCertFromCa(cfgs, caCert, clientKey, caKey, edgeCertSigningDuration)
}
// 生成证书
func NewCertFromCa(cfg *certutil.Config, caCert *x509.Certificate, serverKey crypto.PublicKey, caKey crypto.Signer, validalityPeriod time.Duration) ([]byte, error) {
// 拼装模板
certTmpl := x509.Certificate{
Subject: pkix.Name{
CommonName: cfg.CommonName,
Organization: cfg.Organization,
},
DNSNames: cfg.AltNames.DNSNames,
IPAddresses: cfg.AltNames.IPs,
SerialNumber: serial,
NotBefore: time.Now().UTC(),
NotAfter: time.Now().Add(time.Hour * 24 * validalityPeriod),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: cfg.Usages,
}
// 创建证书
certDERBytes, err := x509.CreateCertificate(rand.Reader, &certTmpl, caCert, serverKey, caKey)
}
|
edgecore:
edgecore主要在edgehub中 kubeedge/edge/pkg/edgehub/certificate/certmanager.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
| // 如果本地没有,则请求ca
func (cm *CertManager) applyCerts() error {
cacert, err := GetCACert(cm.caURL) // http 请求不要验证
// 使用token校验ca
ok, hash, newHash := ValidateCACerts(cacert, tokenParts[0]) // sha256校验hash值
// 保存ca为文件 ca.crt
ca, err := x509.ParseCertificate(cacert)
certutil.WriteCert(cm.caFile, ca)
// 获取证书 server.crt
pk, edgeCert, err := cm.GetEdgeCert(cm.certURL, caPem, tls.Certificate{}, strings.Join(tokenParts[1:], "."))
// 保存 edge.crt server.crt server.key
crt, _ := x509.ParseCertificate(edgeCert)
certutil.WriteKeyAndCert(cm.keyFile, cm.certFile, pk, crt)
}
// 生成私钥,并生成crs请求cloudcore签发证书
func (cm *CertManager) GetEdgeCert(url string, capem []byte, cert tls.Certificate, token string) (*ecdsa.PrivateKey, []byte, error) {
// 生成私钥和crs
pk, csr, err := cm.getCSR()
// 请求并返回证书
req, err := http.BuildRequest(nethttp.MethodGet, url, bytes.NewReader(csr), token, cm.NodeName)
res, err := http.SendRequest(req, client)
content, err := io.ReadAll(io.LimitReader(res.Body, constants.MaxRespBodyLength))
return pk, content, nil
}
|
服务连接过程:
cloudcore
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
38
39
40
41
42
43
44
45
46
47
48
| //kubeedge/cloud/pkg/cloudhub/servers/server.go
// 服务启动
func StartCloudHub(messageq *channelq.ChannelMessageQueue) {
go startWebsocketServer()
// go startQuicServer() 根据配置启动websocket或者 quic
}
// 启动websocket服务
func startWebsocketServer() {
// 读取证书相关
tlsConfig := createTLSConfig(hubconfig.Config.Ca, hubconfig.Config.Cert, hubconfig.Config.Key)
svc := server.Server{
Type: api.ProtocolTypeWS,
TLSConfig: &tlsConfig,
AutoRoute: true,
ConnNotify: handler.CloudhubHandler.OnRegister, // 连接会掉函数
Addr: fmt.Sprintf("%s:%d", hubconfig.Config.WebSocket.Address, hubconfig.Config.WebSocket.Port), // 默认10000端口
ExOpts: api.WSServerOption{Path: "/"},
}
klog.Exit(svc.ListenAndServeTLS("", "")) // 监听端口开始服务
}
// 准备ca证书,服务端证书
func createTLSConfig(ca, cert, key []byte) tls.Config {
// init certificate
pool := x509.NewCertPool()
// 指定ca
ok := pool.AppendCertsFromPEM(pem.EncodeToMemory(&pem.Block{Type: certutil.CertificateBlockType, Bytes: ca}))
if !ok {
panic(fmt.Errorf("fail to load ca content"))
}
// 服务端证书和私钥
certificate, err := tls.X509KeyPair(pem.EncodeToMemory(&pem.Block{Type: certutil.CertificateBlockType, Bytes: cert}), pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: key}))
if err != nil {
panic(err)
}
return tls.Config{
ClientCAs: pool,
ClientAuth: tls.RequireAndVerifyClientCert,
Certificates: []tls.Certificate{certificate},
MinVersion: tls.VersionTLS12, // tls版本
// has to match cipher used by NewPrivateKey method, currently is ECDSA
CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, // 加密算法
}
}
|
edgecore:
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
| // /kubeedge/edge/pkg/edgehub/clients/factory.go
func GetClient() (Adapter, error) {
websocketConf := wsclient.WebSocketConfig{
URL: config.WebSocketURL,
CertFilePath: config.TLSCertFile,
KeyFilePath: config.TLSPrivateKeyFile,
HandshakeTimeout: time.Duration(config.WebSocket.HandshakeTimeout) * time.Second,
ReadDeadline: time.Duration(config.WebSocket.ReadDeadline) * time.Second,
WriteDeadline: time.Duration(config.WebSocket.WriteDeadline) * time.Second,
ProjectID: config.ProjectID,
NodeID: config.NodeName,
}
return wsclient.NewWebSocketClient(&websocketConf), nil
}
// /kubeedge/edge/pkg/edgehub/clients/wsclient/websocket.go
// websocket 初始化 双向tls 证书相关的都在这
func (wsc *WebSocketClient) Init() error {
// 证书和私钥
cert, err := tls.LoadX509KeyPair(wsc.config.CertFilePath, wsc.config.KeyFilePath)
// ca 证书
caCert, err := os.ReadFile(config.Config.TLSCAFile)
// 将ca加入到rootCAs pool,之后验证证书就能找到私钥了
pool := x509.NewCertPool()
if ok := pool.AppendCertsFromPEM(caCert); !ok {
return fmt.Errorf("cannot parse the certificates")
}
tlsConfig := &tls.Config{
RootCAs: pool,
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: false,
}
option := wsclient.Options{
HandshakeTimeout: wsc.config.HandshakeTimeout,
TLSConfig: tlsConfig,
Type: api.ProtocolTypeWS,
Addr: wsc.config.URL,
AutoRoute: false,
ConnUse: api.UseTypeMessage,
}
// new client
client := &wsclient.Client{Options: option, ExOpts: exOpts}
// 向服务端发起连接
connection, err := client.Connect()
}
// kubeedge/vendor/github.com/kubeedge/viaduct/pkg/client/client.go
// 连接处理
func (c *Client) Connect() (conn.Connection, error) {
protoClient = NewWSClient(c.Options, c.ExOpts) // 选用websocket
protoConn, err := protoClient.Connect() // 发起ws连接
}
// kubeedge/staging/src/github.com/kubeedge/viaduct/pkg/client/ws.go
func (c *WSClient) Connect() (conn.Connection, error) {
wsConn, resp, err := c.dialer.Dial(c.options.Addr, header)
}
|
问题 x509: certificate is valid for 2.2.2.2, not for 1.1.1.1
这个是因为客户端请求到服务端的证书后,会做校验,其中有一项就是校验dns或者ip,这个是在证书生成的时候配置的。
以kubeedge为例:
生成服务端证书:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // /kubeedge/cloud/pkg/cloudhub/servers/httpserver/signcerts.go
// SignCerts creates server's certificate and key
func SignCerts() ([]byte, []byte, error) {
cfg := &certutil.Config{
CommonName: constants.ProjectName,
Organization: []string{constants.ProjectName},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
AltNames: certutil.AltNames{
DNSNames: hubconfig.Config.DNSNames, //以上报错验证的项
IPs: getIps(hubconfig.Config.AdvertiseAddress), //以上报错验证的项
},
}
certDER, keyDER, err := NewCloudCoreCertDERandKey(cfg)
if err != nil {
return nil, nil, err
}
return certDER, keyDER, nil
}
|
就是当证书是在2.2.2.2生成的,然后把服务放在1.1.1.1运行,当客户端请求到证书以后,校验就会出错。
具体校验流程如下,逻辑比较长。
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
| func (wsc *WebSocketClient) Init() error // edge/pkg/edgehub/clients/wsclient/websocket.go
=> option := wsclient.Options{
HandshakeTimeout: wsc.config.HandshakeTimeout,
TLSConfig: tlsConfig,
Type: api.ProtocolTypeWS,
Addr: wsc.config.URL,
AutoRoute: false,
ConnUse: api.UseTypeMessage,
}
=> client := &wsclient.Client{Options: option, ExOpts: exOpts} //
=> connection, err := client.Connect()
func (c *Client) Connect() (conn.Connection, error) // staging/src/github.com/kubeedge/viaduct/pkg/client/client.go
=> protoClient = NewWSClient(c.Options, c.ExOpts)
func NewWSClient(options Options, exOpts interface{}) *WSClient // staging/src/github.com/kubeedge/viaduct/pkg/client/ws.go
=> TLSClientConfig: options.TLSConfig
=> protoConn, err := protoClient.Connect()
func (c *WSClient) Connect() (conn.Connection, error) { // staging/src/github.com/kubeedge/viaduct/pkg/client/ws.go
=> wsConn, resp, err := c.dialer.Dial(c.options.Addr, header)
=> func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) // github.com/gorilla/websocket@v1.4.2/client.go
=> func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader http.Header) (*Conn, *http.Response, error)
=> u, err := url.Parse(urlStr)
=> hostPort, hostNoPort := hostPortNoPort(u)
=> func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string)
=> hostNoPort = u.Host
=> if u.Scheme == "https"
=> cfg.ServerName = hostNoPort
=> cfg := cloneTLSConfig(d.TLSClientConfig)
=> tlsConn := tls.Client(netConn, cfg)
=> doHandshake(tlsConn, cfg)
func doHandshake(tlsConn *tls.Conn, cfg *tls.Config) error // github.com/gorilla/websocket@v1.4.2/client.go
=> tlsConn.Handshake()
=> if !cfg.InsecureSkipVerify
=> tlsConn.VerifyHostname(cfg.ServerName)
=> func (c *Conn) VerifyHostname(host string) error // /opt/go/src/crypto/tls/conn.go
=> return c.peerCertificates[0].VerifyHostname(host)
=> func (c *Certificate) VerifyHostname(h string) error // /opt/go/src/crypto/x509/verify.go
=> candidateIP := h
=> if ip := net.ParseIP(candidateIP); // 一般是内网情况 用ip
=> return HostnameError{c, candidateIP}
=> func (h HostnameError) Error() string
=> return "x509: certificate is valid for " + valid + ", not " + h.Host
=> for _, match := range c.DNSNames // 一般是外网情况 用dns
=> return HostnameError{c, h}
func doHandshake(tlsConn *tls.Conn, cfg *tls.Config) // /opt/go/src/crypto/tls/conn.go
=> tlsConn.Handshake()
=> c.HandshakeContext(context.Background())
=> c.handshakeContext(ctx)
=> func (c *Conn) handshakeContext(ctx context.Context) (ret error)
=> c.handshakeFn(handshakeCtx)
=> func (c *Conn) clientHandshake(ctx context.Context) (err error)
=> func (hs *clientHandshakeState) handshake() error
=> hs.doFullHandshake()
=> func (hs *clientHandshakeState) doFullHandshake() error
=> c.verifyServerCertificate(certMsg.certificates)
=> func (c *Conn) verifyServerCertificate(certificates [][]byte) error
=> c.peerCertificates = certs
|
参考:
http://arthurchiao.art/blog/everything-about-pki-zh/