进来看看我们如何在本地构建CA并颁发证书吧!

放在前头,本文并不是深入讲解CA鉴权的博文,而是就具体的使用而整理的一篇精简短文。

没错,写这篇文章的时候确实是战战兢兢,生怕露出马脚,毕竟笔者不是从事安全专业的。

好在本文的所有内容均有实践验证,这让笔者在操刀的时候踏实不少。

书归正传,看看如何使用自建CA完成证书的签署和颁发吧。

可复现的系统环境

操作系统套件版本信息:

1
2
prexer $ cat /proc/version
Linux version 4.18.0-25-generic (buildd@lcy01-amd64-025) (gcc version 8.3.0 (Ubuntu 8.3.0-6ubuntu1~18.10.1)) #26-Ubuntu SMP Mon Jun 24 09:32:08 UTC 2019

openssl工具版本信息:

1
2
prexer $ openssl version
OpenSSL 1.1.1 11 Sep 2018

python3版本信息:

1
2
prexer $ python3 --version
Python 3.8.5

docker版本信息:

1
2
prexer $ docker --version
Docker version 18.09.7, build 2d0083d

这里只是笔者的环境,并不是说你必须一样才能通过,只不过在这个版本上,笔者是经过充分验证的。

CA证书涉及的对象有哪些?

一一列举:

  • 假冒的授权中心,其实就是一个私钥/公钥对
    • ca-key.pem < 私钥
    • ca.pem < 公钥
  • 服务端证书私钥/公钥对,这些还不够,如果要颁发证书,必须要有签名请求文件才可以
    • server-key.pem < 私钥
    • server.csr
    • ca.srl < 服务端与客户端通用的
    • server-cert.pem < 公钥
  • 类似的,客户端证书也是同样的一对密钥,请求文件当然不能少,只不过细微的部分和服务端配置不同,详见下文
    • client-key.pem < 私钥
    • client.csr
    • ca.srl
    • extfile.conf < 扩展了SSL属性的文件
    • client-cert.pem < 公钥

后续生成文件树的概览:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
prexer $ tree CA/ CA_demo/
CA/
├── ca-key.pem
├── ca.pem
├── ca.srl
├── client-cert.pem
├── client.csr
├── client-key.pem
├── client.p12
├── extfile.conf
├── server-cert.pem
├── server.csr
└── server-key.pem
CA_demo/
├── https-home
│   └── index.html
└── nginx-conf
└── default.conf

2 directories, 13 files

建立一个证书授权中心玩玩

证书授权中心当然不能随便建立,这里说的建立是一个抽象层面的说法,本质上我们使用openssl工具建立一个本地的虚假证书机构。

虽然它颁发的不是官方的授权证书,但对于实际的应用来说依旧有用。

后文证书的私钥/公钥均采用2048字节,如果有需要可以提升。

简单一句完成建立:

1
2
3
4
5
6
7
prexer $ sudo openssl genrsa -des3 -out ca-key.pem
Generating RSA private key, 2048 bit long modulus (2 primes)
...................................................................+++++
......................+++++
e is 65537 (0x010001)
Enter pass phrase for ca-key.pem:
Verifying - Enter pass phrase for ca-key.pem:

这里的ca-key.pem就是你本地CA授权机构的密钥,你就当他是授权机构。
执行的时候需要你输入密码,这个密码要记住,不仅后续的指令还要依赖它,没准以后其他情况也要用到哦。

颁发认证机构的证书

使用前文创建的证书机构密钥来建立一个代表机构的证书,当然也很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
prexer $ sudo openssl req -new -x509 -days 365 -key ca-key.pem -out ca.pem
Enter pass phrase for ca-key.pem:
Can't load /home/wei/.rnd into RNG
140197562365120:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/home/wei/.rnd
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]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:prexer.gitee.io
Email Address []:

相关参数请自行到搜索引擎查询,讲解的文章成片飘落…
这里还要说明两点:

  1. 在输入FQDN,也就是Common Name的时候给一个服务的名字,正式的时候需要添加详细的信息,这里是一个演示,所以只是例子。
  2. 指令执行的时候需要输入ca-key.pem的密码,你懂的。

创建用于服务端的证书签名请求和密钥

在正式颁发指定服务端证书的时候,需要一个证书签名请求,而这个证书的签名请求还需要你为服务器生成一个服务端的密钥文件。

那么,我们先生成一个服务端的密钥文件:

1
2
3
4
5
6
7
prexer $ sudo openssl genrsa -des3 -out server-key.pem
Generating RSA private key, 2048 bit long modulus (2 primes)
.........+++++
.............................................+++++
e is 65537 (0x010001)
Enter pass phrase for server-key.pem:
Verifying - Enter pass phrase for server-key.pem:

没错,他和生成虚假认证机构的密钥是相似的,过程中需要你为它也设置一个密码,这个密码后续我们会删除,毕竟大部分应用场景都不需要密码完成认证。

接下来,用上面的服务器密钥来生成一个证书签名请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
prexer $ sudo openssl req -new -key server-key.pem -out server.csr
Enter pass phrase for server-key.pem:
Can't load /home/wei/.rnd into RNG
139737719624896:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/home/wei/.rnd
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]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:*
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

这里除了要输入密码,还要重点注意FQDN这个选项,这里设置为*是表示允许该证书在任意的一台服务器上都可以使用,当然,你也可以按照你的需求指定一个有效的服务器,如:prexer.example.org

正式生成服务端的证书

有了请求,向认证机构申请证书还是比较简单的,不过在请求前,要先生成一个srl文件:

1
prexer $ echo 01 > $PWD/ca.srl

开始颁发证书吧:

1
2
3
4
5
prexer $ sudo openssl x509 -req -days 365 -in server.csr -CA ca.pem -CAkey ca-key.pem -out server-cert.pem
Signature ok
subject=C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = *
Getting CA Private Key
Enter pass phrase for ca-key.pem:

密码什么的相信你已经熟练的使用了,接下来完成一个实用的步骤,移除服务端的密钥密码:

1
2
3
prexer $ sudo openssl rsa -in server-key.pem -out server-key.pem
Enter pass phrase for server-key.pem:
writing RSA key

如果你还是没安全感,那就让文件的权限跟窄一些:

1
sudo chmod 0600 *.pem

这一步骤你可以根据需求执行,到现在我们拥有的文件试图应该是:

1
2
3
4
5
6
7
8
prexer $ ls -l
total 24
-rw------- 1 root root 1743 10月 30 23:06 ca-key.pem
-rw------- 1 root root 1318 10月 30 23:12 ca.pem
-rw-r--r-- 1 wei wei 3 10月 30 23:32 ca.srl
-rw------- 1 root root 1151 10月 30 23:32 server-cert.pem
-rw-r--r-- 1 root root 972 10月 30 23:24 server.csr
-rw------- 1 root root 1675 10月 30 23:36 server-key.pem

创建客户端的密钥和证书

操作方法和服务器端相似,先生成客户端密钥:

1
2
3
4
5
6
7
prexer $ sudo openssl genrsa -des3 -out client-key.pem
Generating RSA private key, 2048 bit long modulus (2 primes)
.............+++++
...................................+++++
e is 65537 (0x010001)
Enter pass phrase for client-key.pem:
Verifying - Enter pass phrase for client-key.pem:

这个阶段同样要设置一个临时的密码,后续会删除。

接下来创建一个客户端证书签名请求,和前文一样,是一个CSR文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
prexer $ sudo openssl req -new -key client-key.pem -out client.csr
Enter pass phrase for client-key.pem:
Can't load /home/wei/.rnd into RNG
140471898436800:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/home/wei/.rnd
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]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

这里除了密码没有什么要注意的地方,也是客户端与服务端的第一点差别。

第二点差别是,有时候需要在使用客户端的时候添加SSL的扩展属性,也就类似TSL/SSL这样的需求。
其实也很好办,这里给出一个简单的样例,先生成一个SSL扩展文件:

1
prexer $ echo extendedKeyUsage = clientAuth > extfile.conf

来吧,都准备好了,那就进行签名授书喽:

1
2
3
4
5
prexer $ sudo openssl x509 -req -days 365 -in client.csr -CA ca.pem -CAkey ca-key.pem -out client-cert.pem -extfile extfile.conf
Signature ok
subject=C = AU, ST = Some-State, O = Internet Widgits Pty Ltd
Getting CA Private Key
Enter pass phrase for ca-key.pem:

输入认证机构密钥密码后,客户端的证书也就授权完成了,一鼓作气,让我们把客户端密钥的密码删掉吧:

1
2
3
prexer $ sudo openssl rsa -in client-key.pem -out client-key.pem
Enter pass phrase for client-key.pem:
writing RSA key

好了,到了这里所有的内容都已经准备齐全了,他们应该是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
prexer $ ls -l
total 40
-rw------- 1 root root 1743 10月 30 23:06 ca-key.pem
-rw------- 1 root root 1318 10月 30 23:12 ca.pem
-rw-r--r-- 1 wei wei 3 10月 30 23:53 ca.srl
-rw-r--r-- 1 root root 1176 10月 30 23:53 client-cert.pem
-rw-r--r-- 1 root root 956 10月 30 23:46 client.csr
-rw------- 1 root root 1679 10月 30 23:56 client-key.pem
-rw-r--r-- 1 wei wei 30 10月 30 23:51 extfile.conf
-rw------- 1 root root 1151 10月 30 23:32 server-cert.pem
-rw-r--r-- 1 root root 972 10月 30 23:24 server.csr
-rw------- 1 root root 1675 10月 30 23:36 server-key.pem

那么接下来的部分我们谈谈验证,毕竟你有了这些东东还要用不是?

如何验证授权的证书?

现在,我们已经有了服务端和客户端的双向证书,那么就用下面的案例来验证:

  • 使用Nginx搭建一个反向代理服务器,在配置文件中使用TSL认证,使用授权的服务端证书,当然这部分要以来https/http协议来完成
  • 在访问服务器的客户端,不安装授权的客户端证书来问指定服务器
  • 在访问服务器的客户端,安装授权的客户端证书,然后访问指定服务器

搭建一个最简单的http服务器

利用python的内置模块搭建http服务器是一件很简单的事情,不过它是一个简易的http服务器,用来做验证和开发。

我们创建一个目录,用来存放http服务的主页内容:

1
prexer $ mkdir -p $PWD/CA_demo/http-home

然后写一个简单的索引文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
prexer $ cat > $PWD/CA_demo/http-home/index.html <<EOF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h3>Python SimpleHTTPServer</h3>
<p style="margin-left: 30px; color: rebeccapurple">Python 3 Web Server</p>
</body>
</html>
EOF

最后只要到http-home目录启动python内置的http服务即可:

1
prexer $ (cd $PWD/CA_demo/http-home/ && python3 -m http.server 8008 &)

这里我们绑定了8008端口,后续在配置nginx会重定向到它,看到这里的括号了么,没错它是防止你的操作对父SHELL产生影响而已。

为了模拟一个有用的设计,我们将本机的地址映射为想要的主机名,后续都用prexer.home来完成localhost127.0.0.1的映射。
其实还可以搭建一个DNS服务来完成这件事,其实也很简单,本站已有相关的文章讲解DNS服务其的内网搭建,请使用本站搜索引擎来翻阅吧。

完成映射:

1
prexer $ sudo echo 127.0.0.1 prexer.home >> /etc/hosts

现在你访问http://prexer.home:8008的界面应该是这样的(请保证你的8008端口没有被占用):
理想的显示结果

构建一个Ngix反向代理服务

使用docker构建一Nginx服务简直太简单了,如何安装docker请参考本站的其他文章,依旧直接使用本站的搜索引擎即可。

接下来默认你已经拥有了docker环境,来一起搭建一个Nginx。

不过在搭建之前还有一些要准备的工作,其实也很容易,就是一个nginx的配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
prexer $ mkdir -p $PWD/CA_demo/nginx-conf
prexer $ cat > $PWD/CA_demo/nginx-conf/default.conf <<EOF
server {
listen 8000;
server_name prexer.home;

listen 443 ssl;

ssl_certificate /etc/nginx/CA/server-cert.pem;
ssl_certificate_key /etc/nginx/CA/server-key.pem;
ssl_client_certificate /etc/nginx/CA/ca.pem;
ssl_verify_client on;

ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_protocols SSLv2 SSLv3 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

location / {
proxy_pass http://localhost:8008/;
}
}
EOF

好了,开始构建nginx服务容器:

1
2
3
4
5
6
prexer $ docker run -t --name ca_nginx -d \
--net=host \
--volume `pwd`/CA:/etc/nginx/CA \
--volume `pwd`/CA_demo/nginx-conf/default.conf:/etc/nginx/conf.d/default.conf \
--privileged=true \
nginx:latest

到了这里你的目录结构应该是这样才对:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
prexer $ tree CA_demo/ CA/
CA_demo/
├── https-home
│   └── index.html
└── nginx-conf
└── default.conf
CA/
├── ca-key.pem
├── ca.pem
├── ca.srl
├── client-cert.pem
├── client.csr
├── client-key.pem
├── extfile.conf
├── server-cert.pem
├── server.csr
└── server-key.pem

2 directories, 12 files

查看docker容器的输出可以这样:docker logs -f ca_nginx

到这里nginx也搭建完成了,下面来验证。

通过浏览器来验证自建证书的有效性

首先,浏览器没有安装证书的时候,应该得到这样的结果:
没有证书的时候

很遗憾,浏览器并不能直接使用刚才我们制定的证书格式,它需要用一个转换后的格式,这里我们生成.p12文件,至于格式的知识请自行索引。

1
prexer $ sudo openssl pkcs12 -export -in client-cert.pem -inkey client-key.pem -out client.p12 -name "iot"

这里转换成另一种格式的证书时,需要输入密码,请记住它,后续要在浏览器中使用!

好了现在我们有了client.p12文件,浏览器可以很好的支持它,你肯定已经记住了它的生成位置,现在通过浏览器添加证书:
导入证书步骤

添加完成后再次访问https://prexer.home,应该是这样:
添加证书后

到了这里所有相关证书的生成和验证流程就讲解完成了,真心希望对你有帮助呢 ^_^