为你的Web应用提供HTTPS服务

超文本传输协议 HTTP 作为一种无状态无连接的协议,在传输信息时采用的是明文的方式,因此传输过程中的信息存在着被窃听、被篡改和被劫持的风险。因此,当你的引用涉及诸如共享密码、银行卡等私密信息时,单纯的使用 HTTP 显然是无法高效地对信息进行有效的加密和保护的。此时,就需要对涉及隐私的网站启用 HTTPS 了,HTTPS 实际就是将 HTTP 通信放到了具有安全性的 SSL/TSL 上进行加密传输的协议。

SSL、TLS 和 HTTPS

SSL(Secure Socket Layer,安全套接字层),是一种通过公钥基础设施(Public Key Infrastructure,PKI)为通信双方提供数据加密和身份验证的协议,其中通信双方通常是客户端和服务器。SSL 协议于 1994 年被 Netscape 公司发明,后来各个浏览器均逐渐开始支持 SSL 协议。之后由 IETF(Internet Engineering Task Force,互联网工程任务组)接受并在 SSL 3.0 协议规范的基础上,制定了 TLS (Transport Layer Security,安全传输层协议),TLS 与 SSL 之间的加密算法不同,因此两者存在着显著差异,但是在理解 HTTPS 的过程中,可以把 SSL 和 TLS 看成是同一种协议,也就是说 HTTPS 实际上就是在 SSL/TLS 连接的上层进行 HTTP 通信。

要为你的 Web 应用提供 HTTPS 服务,你需要使用 SSL/TLS 证书来实现数据加密以及身份验证。而为了保证证书的可靠性,证书一般由 CA(Certificate Authority,证书分发机构)签发。

HTTPS 为了兼顾效率和安全,当服务器接收到客户端发送的请求之后,会将证书和响应一并返回给客户端,而客户端在确认证书的真实性之后,就会生成一个随机密钥(random key),并使用证书中的公钥对随机密钥进行加密,此次加密产生的对称密钥(symmetric key)就是客户端和服务器进行通信时,负责对通信实施加密的实际密钥。那 SSL 证书举例,它是一种使用 X.509 格式进行格式化的数据,这些数据包含了公钥以及其他一些相关信息。

一个 X.509 证书(简称 SSL 证书)实际上就是一个经过编码的 ASN.1(Abstract Syntax Notation One,抽象语法表示法/1)格式的电子文档。ASN.1 既是一个标准,也是一种表示法,该标准包含了公钥证书的标准格式,同时描述了表示电信以及计算机网络数据的规则和结构。

X.509 有多种格式编码,其中一种是 BER(Basic Encoding Rulues,基本编码规则),指定了一种自解释并且自定义的格式用于对 ASN.1 数据结构进行编码,而 DER 格式则是 BER 的一个子集(后续代码会提到),DER 只提供了一种编码 ASN.1 值的方法。SSL 证书有多种不同的格式保存,其中一种是 PEM(Privacy Enhanced Email,隐私增强邮件)格式,该格式会对 DEM 格式的 X.509 证书实施 Base64编码,这种格式的文件都以 -----BEGIN CERTIFICATE----- 开头,以 -----END CERTIFICATE----- 结尾。

提供 HTTPS 服务

假设你已经获取了 SSL/TLS 证书文件 cert.pem,以及服务器私钥文件 key.pem,那么我们在使用的时候,只需要用一行代码即可启动

1
2
3
4
5
6
server := http.Server{
Addr: config.Address,
Handler: mux,
}
// 启动 HTTPS 服务
server.ListenAndServeTLS("cert.pem", "key.pem")

假如我们是在测试环境中,我们想要测试一下证书和私钥,那么我们完全可以使用自己生成的证书。虽然自行生成的证书和私钥不会用在生产环境中,但是了解 SSL 证书和私钥的生成方法,并学会如何在开发和测试的过程中使用证书和私钥,也是一件非常有意义的事情。

生成个人使用的 SSL 证书以及服务器私钥

生成 SSL 证书和密钥的步骤并不是特别复杂。因为 SSL 证书实际上就是一个将扩展密钥用法(extended key usage)设置成了服务器身份验证操作的 X.509 证书,所以在下面的代码中会使用 crypto/x509 标准库。此外,因为创建证书需要用到私钥,所以程序在使用私钥成功创建证书之后,还需将私钥单独保存在一个存放服务器私钥的文件里面。

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
package ssl

import (
"math/big"
"crypto/rand"
"crypto/x509/pkix"
"crypto/x509"
"time"
"net"
"crypto/rsa"
"os"
"encoding/pem"
)

func Generate() {
// 指定一个大整数 2^128
max := new(big.Int).Lsh(big.NewInt(1), 128)
// 生成范围为 [0, max) 的随机序列号,用于记录 CA 分发的唯一号码
serialNumber, _ := rand.Int(rand.Reader, max)
// 定义一个具有特殊标识的 x.509 专有名称
subject := pkix.Name{
Organization: []string{"Your Organization"},
OrganizationalUnit: []string{"Your Apartment"},
CommonName: "Project Name",
}

// 定义 x.509 证书,设定有效期为 1 年,并将证书设置成只能在 IP 地址 127.0.0.1 至上运行
// KeyUsage 和 ExtKeyUsage 指明该证书用于进行服务器身份验证操作
template := x509.Certificate{
SerialNumber: serialNumber,
Subject:subject,
NotBefore:time.Now(),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
KeyUsage:x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage:[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
IPAddresses:[]net.IP{net.ParseIP("127.0.0.1")},
}

// 生成 rsa 私钥,其结构里还包含一个能够公开访问的公钥
pk, _ := rsa.GenerateKey(rand.Reader, 2048)

// CreateCertificate 函数需要接收 Certificate 函数、公钥和私钥等参数,
// 生成一个经过 DER 编码格式化的字节切片
derBytes, _ := x509.CreateCertificate(rand.Reader, &template, &template, &pk.PublicKey, pk)

// 将证书编码到 cert.pem 文件中
certOut, _ := os.Create("cert.pem")
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
defer certOut.Close()

// 将生成的密钥编码到 key.pem 中
keyOut, _ := os.Create("key.pem")
pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(pk)})
defer keyOut.Close()
}

值得注意的是,若证书是由 CA 签发,那么证书文件中将同时包含服务器签名以及 CA 签名,其中服务器前面在前,CA 签名在后

引用

  • Sau Sheong Chang.Go Web 编程[M].人民邮电出版社:北京,2017:53-56.
文章作者: Inno Fang
文章链接: https://innofang.github.io/2020/03/03/为你的Web应用提供HTTPS服务/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来自 Inno's Blog