如何生成 VPN 客户端密钥并提交 CSR 进行签名
把私钥留在客户端,只发送请求。验证 CSR,批准身份,用显式扩展签名,然后确认密钥对匹配。
迟早会有 VPN 用户主动提出把私钥用邮件发过来,好让管理员去签一张证书,而这样的提议正是要拒绝的。可行的流程从第一条命令起就把密钥留在客户端:客户端生成密钥,发出证书签名请求,签发方验证并授权,签好的证书回到客户端,与当初生成它的私钥放在一起。下面的清单在每一步都采取失败即拒绝的做法,而这正是重点,因为五个步骤中有两步恰恰是人们在赶时间时最常跳过的。
第 1 步:在密钥将要长期存放的地方生成它
在将要持有密钥的那台机器上生成,并把它留在那里。软件密钥会落到权限受限的文件里;由硬件承载或存放在受管存储中的密钥根本不会变成文件,其登记路径通常是单独的工具,而不是 OpenSSL 命令。无论哪种方式,都不需要任何人看到私钥那一半。
软件流程大致如下,密钥和请求在同一个会话里生成:
umask 077
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out client.key
openssl req -new -key client.key -out client.csr -subj "/CN=alice-vpn/O=Example"umask 这一行比看上去更重要。没有它,在默认宽松的权限设置下,密钥会被写成所有人可读,之后把文件拷来拷去就会把这个问题扩散出去。复用已有密钥是合理的,前提是它已经受到保护并符合你的策略;为每台设备新生成一把,理由更好讲清楚。
第 2 步:把身份写进请求,而不是写进邮件
在生成请求之前,先确定 VPN 究竟在认证什么,因为这个答案要放进 CSR 里。OpenVPN 传统上把客户端身份绑定到证书的通用名上,strongSwan 之类的工具则把身份字符串与主题或主题备用名称做匹配。一封写着“我叫 alice”的邮件不是证据,而主题里也写着同样内容的请求,只是稍微好一点。
如果你的验证方会检查主题备用名称,就把这一项加上,因为现代匹配往往忽略旧的 CN:
openssl req -new -key client.key -out client.csr \
-subj "/CN=alice-vpn/O=Example" \
-addext "subjectAltName=DNS:alice-vpn"CN 要保持简短、稳定,并绑定到账号,而不是绑定到某个笔记本型号。名字里带上用户设备序列号,等于给第一次更换硬件埋了一个支持问题。
第 3 步:在签名之前先验证请求
签发方要验证两件不同的事,把两者混为一谈就是经典错误。第一,请求在内部自洽:签名能用它所携带的公钥验证通过。第二,申请者确实有权得到请求所要求的东西,而这一点任何密码学检查都回答不了。
机械的那部分是单条命令,它应当连同主题一起打印验证结果:
openssl req -in client.csr -noout -verify -subject
openssl req -in client.csr -noout -pubkey | openssl pkey -pubin -noout -text注意这能证明什么。验证通过的请求只说明发送方在提交时持有匹配的私钥;它不能证明发送方就是主题里写的那个人,也没有授权请求里所要求的组或路由访问。登记审核是另一道关卡,对于没人批准过的组的请求,应当直接拒绝,而不是悄悄改掉。
第 4 步:授权,然后用显式扩展签名
签名是 CA 决定策略的地方,而不是照单全收请求。有效期、密钥用途和角色扩展应当写进扩展文件,而不是写进半年后你已经记不起来的默认值。用于 VPN 认证的客户端证书,需要把客户端认证作为用途。
cat > client.ext <<'EOF'
basicConstraints = CA:FALSE
keyUsage = digitalSignature
extendedKeyUsage = clientAuth
EOF
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -days 365 -extfile client.ext -out client.crt如果已经有一个在用,就配合现有的序列号文件使用 -CAserial;CA 密钥要放在离线环境,或者你策略所指定的受保护存储里。证书序列号必须对每个签发方唯一;把序列号文件从零重建,会有撞号的风险,从而让吊销变得含糊。
记录签发了什么:序列号、主题、所请求的身份、批准人以及到期时间。日后是否吊销,靠的就是这份记录;如果登记痕迹只活在一个邮箱里,光凭证书是重建不出来的。
第 5 步:把它装在匹配的密钥旁边,并证明二者确实配对
客户端收到的是证书和签发方证书链,永远不会收到密钥。连接之前,先确认证书与磁盘上已有的密钥确实配对,因为不匹配的一对会在很久之后产出一个令人困惑的握手失败:
openssl x509 -in client.crt -noout -pubkey | openssl pkey -pubin -outform DER | openssl sha256
openssl pkey -in client.key -pubout -outform DER | openssl sha256两个摘要必须一致。然后连接,并确认服务器记录的身份正是你授权的那一个,而不只是隧道起来了。如果密钥丢失或设备更换,答案是吊销,再加上从新设备发来的新请求;让管理员从别处把密钥发回来,既找不回原来的密钥,也会悄悄破坏让这个请求变安全的前提。