nginx自签名证书与client auth机制

于 2023-06-22 发布

在之前的 nextcloud-fpm + frp + nginx + ssl 一文中,公网服务器的Nginx与家中服务器的Nginx通信是是使用自签名证书的。这样做主要的原因是家中的服务器没有公网IP,无法直接获取公共CA签名的证书;而定时从服务器复制证书下来的操作非常不方便,不值得。但是自签名证书容易被中间人攻击是广为人知的,而为了解决这个问题,今天无意间看到client authentication。大概意思是,传统的HTTPS是客户端验证服务器证书是否有效,而client authentication顾名思义就是加多了服务器验证客户端证书是否有效这一方向,所以也称为双向验证。理论上来说这可以提高安全性,不管怎么说,折腾一下总没错的。

本文会介绍:

  • 自签名CA及自签名证书
  • 双向证书验证(Nginx)

1. 自签名CA及自签名证书

生成CA及自签名证书需要分开来做。

1.1 CA

生成自己的CA的Key和Cert,会提示设置私钥密码:

1
2
# 生成10年有效的rsa 2048位CA证书(rootCA.crt)与私钥(rootCA.key)
openssl req -x509 -sha256 -days 3650 -newkey rsa:2048 -keyout rootCA.key -out rootCA.crt

1.2 Server证书与Client证书

先生成证书私钥和CSR。生成CSR的过程中会提示输入相关信息,其中的Common Name通常是填完整域名——比如买了 somedomain.com ,如果是给子域名 blog.somedomain.com 发证书,Common Name写的就是 blog.somedomain.com 而不是 somedomain.com

1
2
3
4
5
6
7
# 把example.domain.com替换成你自己的域名
# 生成私钥
openssl genrsa -out example.domain.com.server.key 2048
openssl genrsa -out example.domain.com.client.key 2048
# 生成CSR
openssl req -new -key example.domain.com.server.key -out example.domain.com.server.csr
openssl req -new -key example.domain.com.client.key -out example.domain.com.client.csr

然后要创建一个域名附加信息文件domain.ext,内容如下:

1
2
3
4
5
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
subjectAltName = @alt_names
[alt_names]
DNS.1 = example.domain.com

然后就可以用CA签发证书了:

1
2
openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in example.domain.com.server.csr -out example.domain.com.server.crt -days 3650 -CAcreateserial -extfile domain.ext
openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in example.domain.com.client.csr -out example.domain.com.client.crt -days 3650 -CAcreateserial -extfile domain.ext

此时手上应有3对共6个文件:

  1. Server的Cert和Key
  2. Client的Cert和Key
  3. CA的Cert和Key

接下来就要分发了。

2. 设置Nginx

2.1 公网服务器用Client套

把Client的一套( example.domain.com.client.crtexample.domain.com.client.key )以及CA的Cert( rootCA.crt )上传到公网服务器的Nginx配置文件夹里,比如 /etc/nginx/ssl 。然后修改nextcloud的站点配置文件,主要是把 location / 的proxy指令们增改一下:

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
server {
    # 其他配置......
    location / {
            # 因为使用了相同的域名,所以这里把server_name配置成相同的值
            proxy_ssl_server_name on;
            proxy_ssl_name $host;
            # 转发到frp的监听端口
            proxy_pass https://127.0.0.1:8002/;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            # 把nextcloud官方建议的secure header的add_header改成这里的proxy_set_header
            proxy_set_header Referrer-Policy "no-referrer";
            proxy_set_header X-Content-Type-Options "nosniff";
            proxy_set_header X-Download-Options "noopen";
            proxy_set_header X-Frame-Options "SAMEORIGIN";
            proxy_set_header X-Permitted-Cross-Domain-Policies "none";
            proxy_set_header X-Robots-Tag "noindex, nofollow";
            proxy_set_header X-XSS-Protection "1; mode=block";
            proxy_set_header Host $http_host;
            proxy_set_header cookie $http_cookie;
            proxy_set_header Proxy-Connection "";
            # 设置client证书
            proxy_ssl_certificate /etc/nginx/ssl/example.domain.com.client.crt;
            proxy_ssl_certificate_key /etc/nginx/ssl/example.domain.com.client.key;
            # 这个指令不再是trust Server证书了,而是指定ca证书
            #proxy_ssl_trusted_certificate /etc/nginx/ssl/nextcloud.crt;
            proxy_ssl_trusted_certificate /etc/nginx/ssl/rootCA.crt;

            proxy_http_version 1.1;
        }
}

2.2 家里服务器用Server套

把Server的一套( example.domain.com.server.crtexample.domain.com.server.key )以及Client的Cert( example.domain.com.client.crt)、CA的Cert( rootCA.crt )上传到家里服务器的Nginx配置文件夹里,比如 /etc/nginx/ssl。然后修改nextcloud的站点配置文件,主要是把server blog的ssl指令们增改一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
        #其他配置...

        ssl_certificate     /etc/nginx/ssl/example.domain.com.server.crt;
        ssl_certificate_key /etc/nginx/ssl/example.domain.com.server.key;
        ssl_client_certificate /etc/nginx/ssl/example.domain.com.client.key;
        # 指定用于验证客户端证书的CA证书
        ssl_trusted_certificate /etc/nginx/ssl/rootCA.crt;
        # 打开客户端验证
        ssl_verify_client on;
        

        # 其他配置...
}

注意 ssl_client_certificate 指令是指定client的证书的,就是前面放到公网服务器上用于proxy_ssl_certificate指令的证书。

此时对公网服务器和家里服务器都reload一下Nginx,就应该可以正常访问的了。

参考文章

  1. CA: 自签名ssl证书生成Creating a Self-Signed Certificate With OpenSSL
  2. Nginx配置文档: proxy_ssl_certificatessl_client_certificate

目录