HTTPS的基本工作原理想必对于许多开发者来说是非常熟悉的了,我们一起先回忆一下HTTP常见的八股文中的描述:
- 客户端(浏览器)访问 HTTPS 的URL;
- 服务器返回 HTTPS 使用的 CA 证书;
- 客户端(浏览器)验证 CA 证书是否为合法证书;
- 验证通过,证书合法,生成一串随机数并使用公钥(证书中提供的)进行加密;
- 发送公钥加密后的随机数给服务器;
- 服务器拿到密文,通过私钥进行解密,获取到随机数(公钥加密,私钥解密,反之也可以);
- 服务器把要发送给浏览器的内容,使用随机数进行加密后传输给客户端(浏览器);
- 此时客户端(浏览器)可以使用随机数进行解密,获取到服务器的真实传输内容。
大体的过程是这样的,从整个过程中可以看出来,整个流程中涉及到了多个交互过程,主要分为了客户端、浏览器、以及CA之间的交互,还是比较复杂的,尤其是要具体知道每一步骤的其中细节,也是非常多的内容,所以这篇文章不是要去分析每一步的工作机制和原理,这篇文章主要内容是如何使用自签证书完成这一个过程,从而使我们能够进一步的对其有个初步的认识,如果对这其中细节感兴趣的,可以自行学习下,或者可能之后我也会对其进行深入的研究再和大家分享学习。
那么我们回到我们的主题里,首先需要知道CA证书是什么,CA是证书颁发机构(Certificate Authority)的缩写,那么CA证书就是证书机构颁发的一种数字证书,为什么需要这个机构来颁发证书呢?可以理解为就是一个第三方担保机构,这个担保机构保证了在客户端和服务器的通讯阶段的安全,这样客户端只要是看到证书是这个担保机构担保过的,那就会认可这个服务器并和它进行后续交互,反之其他证书都是不合法的,不会进行更进一步的交互了。
其次,这个证书不光是用来证明其合法性,其中还包含了一个公钥,公钥是什么呢?这边就涉及到一些密码学相关知识点了,我们需要先知道非对称加密的一点概念,可以猜出来,有非对称加密那肯定有对称加密,对称加密就是使用同一把密钥进行加密和解密,而非对称加密会用到2个密钥,一把公钥和一把私钥进行加密和解密,公钥会公开出去给客户端,而私钥会保存在本地防止泄漏,所以会更安全,使用这个公钥加密后的数据可以被私钥解开,反过来也是一样的,在上面的HTTPS的连接过程中,1-6步骤所涉及到的就是非对称加解密过程,而后续则会采用对称加密的方式,即使用一个随机数充当了对称加密的密钥进行了通讯,这样一方面是为了提高通讯的效率更重要的是为了保证安全,因为如果还使用原有的公钥和私钥的方式在服务端使用私钥加密,那么只要有公钥的客户端都可以截获服务端发来的信息进行解密,显然这样是不行的。对加密感兴趣的小伙伴可以自行百度看看,这边先不展开了。
知道了概念后,那自签证书的概念就是我们自己充当这个CA机构给自己颁发一个证书,然后想办法让客户端信任这个证书(一般的默认做法是系统会有一个受信任的证书列表,其中就会有存放一些CA机构的根证书),并且证书里面包含了我们的公钥,客户端使用这个公钥进行数据加密,再传输数据,服务端接收并用私钥解开,从而完成整个加解密的通讯过程。那接下来我们就来模拟这样一个通讯过程
首先,我们需要把自己先扮演成CA机构,根据非对称加密的概念,我们需要有一对密钥,对应一把公钥和一把私钥,私钥放在自己身边,公钥提供出去,这边需要注意的是不能把他看成是服务端,CA机构是在客户端和服务端两者之间的一个存在,这边理解了后,我们先来创建一把私钥,可以使用openssl来生成
Administrator@PC-20210427XPHC MINGW64 ~/Desktop/crt/crtDemo
$ openssl genrsa -out rootca.key 4096
Generating RSA private key, 4096 bit long modulus
................................................................++
...........++
e is 65537 (0x10001)
这样我们就生成了rootca.key的私钥文件,这个私钥是必须要保护好的,不能泄漏,然后我们来生成公钥,这边我们不会直接生成一把公钥对外开放,而是用上面提到的证书,为什么不直接用公钥呢?其实上面已经提到了,因为公钥私钥随便谁都可以生成出来,所以更重要的是要让客户端可以确认到这把公钥是否是受信任的,如果是受信任的才会使用这把公钥去和服务端交互,所以现在来生成一个带公钥的证书,我们可以叫他根证书
生成req请求文件,req请求文件用来生成证书的时候使用,其中包含了一些重要的信息,如证书所有者的一些信息等
Administrator@PC-20210427XPHC MINGW64 ~/Desktop/crt/rootCrt
$ openssl req -new -out rootca.csr -key rootca.key
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:cn
State or Province Name (full name) [Some-State]:sz
Locality Name (eg, city) []:sz
Organization Name (eg, company) [Internet Widgits Pty Ltd]:dobefa
Organizational Unit Name (eg, section) []:it
Common Name (e.g. server FQDN or YOUR name) []:karl
Email Address []:a.karl.com
string is too long, it needs to be less than 64 bytes long
Email Address []:a.karl.com
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:82468246
An optional company name []:dd
接着我们通过csr和key来生成根证书,会使用到x509工具,这样生成会比较便捷,不会依赖太多的openssl的知识
Administrator@PC-20210427XPHC MINGW64 ~/Desktop/crt/crtDemo
$ openssl x509 -req -in rootca.csr -out rootca.crt -signkey rootca.key -days 36500
Signature ok
subject=/C=cn/ST=sz/L=sz/O=dobefa/OU=it/CN=karl/emailAddress=a.karl.com
Getting Private key
生成出rootca.crt,这个证书就是CA机构会预先在客户端中的受信任列表中埋下的根证书,其中包括了证书的所有者信息和公钥信息,有了这些信息,客户端就能通过这些信息来验证服务端返回的CA证书的合法性了
然后我们CA机构就可以开张营业了,CA机构会先把自己的证书让客户端放到其受信任的证书列表里,这边的客户端可以是Android系统,可以是Android的APP,也可以是Windows系统,也或者是Windows上的一个浏览器。我们这边先以Windows为例,把我们的CA证书植入进Windows的受信任列表中去,首先打开mmc
添加删除管理单元
选证书,添加
选计算机账户
本地计算机
完成后,就可以在列表中看到受信任的证书列表了
找到刚刚生成的ca根证书
安装证书,加入受信任列表
刷新刚刚的证书列表,就可以看到我们刚刚的证书
完成后,我们的CA机构就可以给别人颁发证书了,当然这边也是给我们自己颁发咯
我们来创建server密钥
Administrator@PC-20210427XPHC MINGW64 ~/Desktop/crt/crtDemo
$ openssl genrsa -out server.key 4096
Generating RSA private key, 4096 bit long modulus
.........................................................................................................................++
.................................................................................................................................................................................++
e is 65537 (0x10001)
创建请求文件
Administrator@PC-20210427XPHC MINGW64 ~/Desktop/crt/crtDemo
$ openssl req -new -key server.key -out server.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:cn
State or Province Name (full name) [Some-State]:js
Locality Name (eg, city) []:sz
Organization Name (eg, company) [Internet Widgits Pty Ltd]:dobefa
Organizational Unit Name (eg, section) []:it
Common Name (e.g. server FQDN or YOUR name) []:karl server
Email Address []:test.server.com
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:82468246
An optional company name []:server
使用根证书签发证书
Administrator@PC-20210427XPHC MINGW64 ~/Desktop/crt/crtDemo
$ openssl x509 -req -in server.csr -CA rootca.crt -CAkey rootca.key -CAcreateserial -out server.crt -days 36500
Signature ok
subject=/C=cn/ST=js/L=sz/O=dobefa/OU=it/CN=karl server/emailAddress=test.server.com
Getting CA Private Key
做完这些步骤我们得到了一个server.crt,这个就是我们服务器的CA证书啦,这个服务端的CA证书是通过CA机构的私钥和CA根证书进行签名得到的,这样做会形成一个证书关联,这样我们就知道了这个服务器证书是出自哪个CA机构的哪张证书的了,我们可以查看下百度的证书会比较清楚,google浏览器为例
这个证书有三层的结构,根证书是GlobalSign的根证书,这是一个知名的CA机构,中间证书是GlobalSign使用根证书所生成出来的二级证书,而子证书就是GlobalSign颁发给baidu的CA证书了,大多数的CA证书都是通过二级CA来签发的,如果要实现这种结构的证书签署,会需要额外的一些配置来实现,所以我们这边暂不考虑这种做法,我们这边直接使用根CA进行签发,效果是一样的,这篇文章主要为了学习如何自签一个证书并完成https的交互过程,所以对证书签发感兴趣的同学可以学习更多关于openssl的知识。
接着,我们现在可以来试一下这个证书了,我们用go写一个非常简单的https服务器
package main
import (
_ "github.com/icattlecoder/godaemon"
"net/http"
"github.com/gin-gonic/gin"
)
func setupRouter() *gin.Engine {
// Disable Console Color
// gin.DisableConsoleColor()
r := gin.Default()
// Ping test
r.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})
return r
}
func main() {
r := setupRouter()
// Listen and Server in 0.0.0.0:8080
r.RunTLS(":8081", "C:\\Users\\Administrator\\Desktop\\crt\\crtDemo\\server.crt",
"C:\\Users\\Administrator\\Desktop\\crt\\crtDemo\\server.key")
}
上面我们监听了本地8081端口,并且配置了/ping的接口,这个go程序启动后,访问https://ip地址:8081/ping就会返回pong的text,就这么简单,另外,为了能让这个程序常驻在后台,使用了"github.com/icattlecoder/godaemon"这个库,我们来启动他,在cmd中执行
go run main.go -d=true
然后在浏览器试试访问https://ip地址:8081/ping接口
啊,报警告了,这说明我们的证书有问题,不过我们已经将根证书埋入我们的系统信任列表中了啊,根据之前的理论,客户端拿着服务端带来的CA证书在本地受信任的CA证书列表中查找,并对比其公钥是否一致,一致则信任,不是吗?我们查看下server证书详细信息,省略部分不关注数据
Administrator@PC-20210427XPHC MINGW64 ~/Desktop/crt/crtDemo
$ openssl x509 -in rootca.crt -noout -text
Certificate:
Data:
Version: 1 (0x0)
Serial Number:
d9:a5:8c:82:6e:f7:a3:a1
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=cn, ST=sz, L=sz, O=dobefa, OU=it, CN=karl/emailAddress=a.karl.com
Validity
Not Before: Jan 28 07:46:36 2022 GMT
Not After : Jan 4 07:46:36 2122 GMT
Subject: C=cn, ST=sz, L=sz, O=dobefa, OU=it, CN=karl/emailAddress=a.karl.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (4096 bit)
Modulus:
.......
Exponent: 65537 (0x10001)
Signature Algorithm: sha256WithRSAEncryption
......
Administrator@PC-20210427XPHC MINGW64 ~/Desktop/crt/crtDemo
$ openssl x509 -in server.crt -noout -text
Certificate:
Data:
Version: 1 (0x0)
Serial Number:
fe:52:f8:e2:bf:78:1d:60
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=cn, ST=sz, L=sz, O=dobefa, OU=it, CN=karl/emailAddress=a.karl.com
Validity
Not Before: Jan 29 09:35:55 2022 GMT
Not After : Jan 5 09:35:55 2122 GMT
Subject: C=cn, ST=js, L=sz, O=dobefa, OU=it, CN=karl server/emailAddress=test.server.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (4096 bit)
Modulus:
.......
Exponent: 65537 (0x10001)
Signature Algorithm: sha256WithRSAEncryption
.......
可以看到上面的server.crt证书的Issuer为rootca,是一致的
再来查看下rootca证书公钥和server证书的公钥
Administrator@PC-20210427XPHC MINGW64 ~/Desktop/crt/rootCrt
$ openssl x509 -in root2.crt -pubkey
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHRS1rCHo/DeXWQHvxHclhAkBT
as6VVbVFzf/tVjNHUsabKVnW6P+/ASQj+AoGgX+cjvX+6V44NcavJw8juokFrJki
MOZvt8wiY/kjsvMAZ8d1xTYYw0huUjMftFjemKJq7y6QFKrz+nqsJrsj1ThfFpfI
S3CAvnFMr9JtF+Uu8QIDAQAB
-----END PUBLIC KEY-----
................
Administrator@PC-20210427XPHC MINGW64 ~/Desktop/crt/rootCrt
$ openssl x509 -in root3.crt -pubkey
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHRS1rCHo/DeXWQHvxHclhAkBT
as6VVbVFzf/tVjNHUsabKVnW6P+/ASQj+AoGgX+cjvX+6V44NcavJw8juokFrJki
MOZvt8wiY/kjsvMAZ8d1xTYYw0huUjMftFjemKJq7y6QFKrz+nqsJrsj1ThfFpfI
S3CAvnFMr9JtF+Uu8QIDAQAB
-----END PUBLIC KEY-----
-----BEGIN CERTIFICATE-----
................
发现也是一样的,那么还有哪边会有问题呢,我们看了google的提示信息,然后不妨看看百度的证书
这边有个重要的信息,即使用者可选名称,里面配置了好多DNS Name,还有通配符,而我们的证书是没有的,这就是关键了,其他的参数没有暂时还不影响我们的证书,所以我们来看看如何添加这个到我们的证书中
这个字段我们查到其对应的是“Subject Alternative Name”这个扩展字段,还记得我们在签发服务器证书的时候,使用的x509模块,该模块支持-extensions和-extfile来配置一个扩展字段,我们先建立一个配置文件ssl.cnf文件,内容如下
[ req_ext ]
subjectAltName = @alt_names
[alt_names]
IP.1 = 10.0.10.22
#DNS.1 = your-website.dev
#DNS.2 = another-website.dev
因为我们是使用的IP访问的,所以我们添加了一个IP.1 = 10.0.10.22的alt name
然后执行签发命令,带上配置文件
Administrator@PC-20210427XPHC MINGW64 ~/Desktop/crt/crtDemo
$ openssl x509 -req -in server.csr -CA rootca.crt -CAkey rootca.key -CAcreateserial -out server.crt -days 36500 -extensions req_ext -extfile ssl.cnf
Signature ok
subject=/C=cn/ST=js/L=sz/O=dobefa/OU=it/CN=karl server/emailAddress=test.server.com
Getting CA Private Key
确认一下我们的新的证书
$ openssl x509 -in server.crt -noout -text Certificate:
Data:
Version: 3 (0x2)
Serial Number:
fe:52:f8:e2:bf:78:1d:61
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=cn, ST=sz, L=sz, O=dobefa, OU=it, CN=karl/emailAddress=a.karl.com
Validity
Not Before: Jan 30 03:27:04 2022 GMT
Not After : Jan 6 03:27:04 2122 GMT
Subject: C=cn, ST=js, L=sz, O=dobefa, OU=it, CN=karl server/emailAddress=test.server.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (4096 bit)
Modulus:
.........
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Alternative Name:
IP Address:10.0.10.22
Signature Algorithm: sha256WithRSAEncryption
....................
可以看到X509v3 extensions: X509v3 Subject Alternative Name: IP Address:10.0.10.22已经配置到证书里面了,我们部署到go服务上,再来访问下浏览器试试吧
使用
taskkill /f /t /im main.exe
来关闭服务器进程,然后重新启动go服务,访问接口
这样就成功了,我们的证书已经被浏览器信任了。