Enable Client Authentication on Apache

之前的文章提到过重新开始在更新这边的原因是为了研究怎么玩client certificate。玩Client Authentication嘛肯定是需要SSL证书的,所以我又研究了一下怎么弄一个自己的ACME客户端获取LetsEncrypt的证书。最近客户端写好了才想起来要填自己挖下的坑。

什么是Client Authentication

网上介绍Client Authentication的文章很多,为了避免复制黏贴我简单说说我的理解。
网络安全是现在最重要的议题,作为普通用户,我们可以通过服务器的SSL来辨别服务器的所属公司并用证书来加密传输给服务器的信息,那么作为服务器来说,是否也有办法可以验证客户的信息呢?答案就是Client Authentication。使用Client Authentication可以让服务器识别用户信息,并且可以限定哪些区域只有持特定证书的用户才能打开。我认为仅仅使用用户名和密码在安全性上的强度已经不够,如果有使用2FA的话还可以不过之前也有报道说2FA不太够安全。用Client Authentication是杜绝没有权限的人访问网站并且可以快速识别用户的好方法。

从哪儿获取Client Certificate

现在大多数CA(Certificate Authority)都提供Client证书。按照客户需要包含在证书上信息的多少价格不一样。对于普通用户来说,要么自己搭建一个CA(需要手动把证书的根放到服务器上)或者可以通过Actalis来获取最基础的免费证书。(只验证邮箱,证书上也只有邮箱这个信息)

为了演示Client certificate如何使用,我从Actalis申请了一张Client certificate。

配置需求

安装SSL证书

这个部分我们在前文已经说过,这里就不再赘述,只放上演示用的vitual host文件。

1
2
3
4
5
6
7
8
9
<VirtualHost *:443>
DocumentRoot /var/www/html/
ServerName acme.aufomm.com
SSLEngine on

SSLCertificateFile /etc/ssl/acme/server.crt
SSLCertificateKeyFile /etc/ssl/acme/private.key
SSLCertificateChainFile /etc/ssl/acme/intermediate.crt
</VirtualHost>

配置Client Authentication

制作可以接受的Client Certificate的CA文件

这一步顾名思义就是选择哪个或者哪几个CA签署的证书会弹出让我们选择。这里我选择用一张Letencrypt的SSL作为client certificate来使用,和刚才我从Actalis获取到的client certificate来演示。在这个文件里面,需要包含两张证书的Intermediate和root。如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 这里是Actalis的证书
-----BEGIN CERTIFICATE-----
##
## Encoded Intermediate CA certificate data
##
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
##
## Encoded Root CA certificate data
##
-----END CERTIFICATE-----

# 这里是LetsEncrypt的证书
-----BEGIN CERTIFICATE-----
##
## Encoded Intermediate CA certificate data
##
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
##
## Encoded Root CA certificate data
##
-----END CERTIFICATE-----
了解你的证书信息并考虑好你想要验证的证书信息

为了了解证书里我们可以用来验证的信息,我们可以一方面参考官方文档,另一方面我们可以让PHP把环境变量打出来看看具体有什么变量我们可以用。

修改Virtual Host文件

在Virtual Host里添加下列要求。

1
2
3
4
5
6
7
8
9
<VirtualHost *:443>
...
...

SSLCACertificateFile "/etc/ssl/acme/clientca.crt"
SSLVerifyClient require
SSLVerifyDepth 10
SSLOptions +StdEnvVars
</VirtualHost>
创建一个php文件显示证书信息

我们这里用php文件index.php来读取环境变量,查看证书信息。

1
2
3
4
5
<?php
foreach($_SERVER as $key_name => $key_value) {
echo $key_name.'='.$key_value.'<br />';
}
?>
用浏览器打开网址,第一次Pop Up

打开浏览器,输入网址https://acme.aufomm.com,浏览器会弹出窗口让我们选择使用哪张证书做验证。

chooseCertIE

选择之后网页就会显示所有的环境变量,下面是我截取的我们Client Certificate的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[email protected]
SSL_CLIENT_I_DN_C=IT
SSL_CLIENT_I_DN_ST=Milano
SSL_CLIENT_I_DN_L=Milano
SSL_CLIENT_I_DN_O=Actalis S.p.A./03358520967
SSL_CLIENT_I_DN_CN=Actalis Client Authentication CA G1
[email protected]
SSL_CLIENT_VERIFY=SUCCESS
SSL_CLIENT_M_VERSION=3
SSL_CLIENT_M_SERIAL=058631057C7D03CAB379DE90AA8CA8A6
SSL_CLIENT_V_START=Jun 5 13:31:57 2019 GMT
SSL_CLIENT_V_END=Jun 5 13:31:57 2020 GMT
SSL_CLIENT_V_REMAIN=365
[email protected]
SSL_CLIENT_I_DN=CN=Actalis Client Authentication CA G1,O=Actalis S.p.A./03358520967,L=Milano,ST=Milano,C=IT
SSL_CLIENT_A_KEY=rsaEncryption
SSL_CLIENT_A_SIG=sha256WithRSAEncryption

如果你是自己搭建的CA签自己的证书,那么设定到这里也就够了,因为不会有其他人拥有你的Root签发的证书.因此其他人去到你的网站根本不会有弹窗让它选择使用哪个证书做验证,直接就是ERR_BAD_SSL_CLIENT_AUTH_CERT的错误提示。

配置Apache mod_authz_core

如果你不想搭建自己的PKI,想用现成的SSL证书或者从其他CA获取Client证书,那么我们需要添加一层验证。

我们可以用SSLRequire来做限定,我个人认为这个最简单。但是既然SSLRequire已经deprecated,官方现在推荐使用Require Expr,让我们来看看怎么用。

这个部分有两个用法:

  1. 在需要限定的文件夹下用.htaccess做限定,
  2. 在Virtual Host做限定。

我们这里选择在Virtual Host里面做限定。首先我们假设没有其他人可以获取到我们将要使用的证书以及其私钥,并且我们希望限定只有自己这唯一一张证书可以访问你的网页。根据上文得出的Variable的信息,让我们在同一个Virtual Host文件里添加一条规则。

1
2
3
<Location '/'>
Require expr "%{SSL_CLIENT_M_SERIAL} =~ /058631057C7D03CAB379DE90AA8CA8A6/"
</Location>

这个说的是当有用户访问网站的某一个区域(我这里指定的root folder)的时候验证用户提供的证书。规定用户使用的证书的序列号必须是058631057C7D03CAB379DE90AA8CA8A6才访问。

保存文件,重启Apache之后我们再次访问链接,这次我们用Letsentryp的证书验证就会出现403错误,因为序列号对不上。

1
2
3
4
5
Forbidden
You don't have permission to access /index.php on this server.
Additionally, a 403 Forbidden error was encountered while trying to use an ErrorDocument to handle the request.

Apache/2.4.35 (Win32) OpenSSL/1.1.0i PHP/7.2.11 Server at acme.aufomm.com Port 443

参考Require Expr的官方文档,我们可以混合使用<RequireAll>, <RequireAny> and <RequireNone>来识别特定的用户。比如下面这个写法说的就是present这两张证书任意一张都可以访问网站。

1
2
3
4
5
6
<Location '/'>
<RequireAny>
Require expr "%{SSL_CLIENT_M_SERIAL} =~ /058631057C7D03CAB379DE90AA8CA8A6/"
Require expr "%{SSL_CLIENT_M_SERIAL} =~ /036AA1DDB6379521E6057379DC4207BE260B/"
<RequireAny>
</Location>

以上就是很简单的讲解了一下clilent authenticaiton在Apache上的使用方法,希望对大家有帮助。