HAProxy SSL终止+客户端证书验证+curl / java客户端

3
我希望在HAProxy上进行SSL终止,使用我自己的自签名证书,并使用我创建的客户端证书验证客户端访问。
我按照以下方式创建服务器(也是CA)证书:
openssl genrsa -out ca.key 1024
openssl req -new -key ca.key -out ca.csr
openssl x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt

并且:

cat ca.crt ca.key > haproxy.pem

在HAProxy中,我配置: bind *:443 ssl crt /path/server.pem ca-file /path/ca.crt verify required crt-ignore-err all
我以类似的方式创建客户端证书:
openssl req -new -key client.key -out client.csr
openssl x509 -req -days 365 -in client.csr -signkey ca.key -out client.crt
cat client.crt client.key > client.pem

我的逻辑是:我创建了一个客户端密钥,为其创建了证书签名请求,然后使用CA对其进行签名(该CA也是服务器证书,因此有一个简单的链路,服务器会识别它)。
为测试目的,我首先尝试将服务器证书作为客户端证书。
curl https://my.service:443/ping -E ./haproxy.pem -k
pong

好的,它已经起作用了。现在我尝试使用客户端证书作为客户端证书:

curl https://my.service:443/ping -E ./client.pem -k
curl: (58) unable to set private key file: './client.pem' type PEM

我的问题是: 1)我希望创建一个客户端证书,使该服务器接受,并使用curl进行测试。 2)我想使用keytool将此证书和CA导入到一个新的java keystore / truststore中,以便Java(Jersey client)代码可以访问相同的内容。
我已经花了2天时间来完成第一步。我相信有人之前已经做过这个,他们应该可以在5分钟内回答这个问题。希望如此 :)
谢谢!
1个回答

2

1. 创建客户端证书

错误方式:使用openssl x509 -req -signkey命令创建自签名证书,这意味着证书中的密钥(主题密钥)与用于签署证书的私有密钥的公共半部分相同。证书(而不是 req)的文档清楚地说明,它将原来在证书中的密钥替换为签名密钥。虽然-req的说明不太清楚,但它也会完成同样的操作;它将CSR中的主题名称以及作为颁发者和-signkey中的密钥放入证书中。您使用一个包含客户端名称的CSR,但是使用包含CA密钥的-signkey生成了一个无法使用的合成体。

正确方式:要使用x509签署“子”(非自签名)证书,请按照文档https://www.openssl.org/docs/apps/x509.html#SIGNING-OPTIONS中描述的方式使用-CA和可能的-CAkey选项(或在安装了openssl文档的任何Unix上使用man [where] x509)。如果给定CA(由其DN定义)有一个或多个子证书,则使用序列号文件方案自动和方便地分配顺序编号,或者使用-set_serial手动分配唯一序列号(顺序是实现唯一的最简单方式,但如果您喜欢其他方式也可以)。

备注:对于自签名CA(以及服务器?!)证书,您无需单独使用req -newx509 -req -signkey步骤,可以在req命令中一次性完成:req -new -x509。请参阅req的文档/手册。实际上,您也不需要单独使用genrsa步骤,req -newkey [-nodes] -x509也可以执行此操作。请注意:在OpenSSL 1.0.0+中,这将生成通用PKCS#8格式密钥文件,而不是genrsa(和rsa)使用的“传统”PKCS#1格式。所有OpenSSL函数都可以接受这两种格式,但某些其他事情可能不行。特别是,在我上次检查(一段时间前),Wireshark使用服务器密钥进行解密SSL/TLS的选项仅接受PKCS#1而不是PKCS#8。

2. 在Java中使用(Jersey)。请注意,任何进行客户端身份验证的SSL/TLS客户端,包括Java,都需要证书和私钥,并且在大多数情况下,证书使用“链”或“中间”证书,您也需要这些证书。一些人(咳咳)微软(咳咳)鼓励您误解并忽略这个重要的区别,但如果您尝试仅使用证书,则根本无法工作。另一方面,信任库条目仅需要证书,几乎始终只需要根(CA)证书,并且通常必须只有证书。您的情况有点不同寻常,因为同一个人操作CA、服务器和客户端。

2a. 可能只需转换为pkcs12。Java不直接支持openssl格式的密钥,但是Java和openssl都支持PKCS#12(Microsoft、Mozilla、Apple和可能其他公司也支持)。由于您将客户端密钥和(叶子)证书合并到了client.pem中,请执行转换操作。

openssl pkcs12 -export <client.pem -CA ca.crt [-name whatever] >client.p12
# if you use separate key,cert files see the doc about -in and -inkey

Java加密(JCE和JSSE)可以使用此PKCS#12作为密钥库,如果您可以配置密钥库的“类型”(如pkcs12)。默认的SSLSocketFactory支持此功能,其他我使用过的应用程序也支持,但我不使用Jersey也不知道它在这里做了什么。通常不支持使用PKCS#12携带“单独”的证书(无私钥),但在您的情况下,客户端的CA证书也是服务器的证书,因此它将同时作为您的信任库工作;否则,您需要将服务器CA或服务器自签名证书(只有证书而没有私钥)导入到JKS信任库中(这可能是JRE/lib/security/[jsse]cacerts中的默认信任库)。

2b. 可能进一步转换为JKS。 如果Jersey不能直接使用PKCS#12,则Java可以将其转换为JKS,任何合理的Java代码都可以使用,例如:

keytool -importkeystore -srckeystore client.p12 -srcstoretype pkcs12 -destkeystore client.jks 

UPDATE 2018: 在本回答撰写之后,Java对PKCS12的支持增强了,因此不再需要经常转换为JKS。 2017年秋季发布的8u60及更高版本仍然默认使用密钥库类型JKS,但是作为一种特殊功能(?),JKS实际上可以读取(但无法写入)PKCS12;请参阅发行说明和文件JRE/lib/security/java.security中的项目keystore.type.compat。 Java9在2017年发布,将默认密钥库类型设置为PKCS12,它(如预期)可以读取和写入PKCS12,尽管显式的JKS不再读取PKCS12。但是,如果您出于某种原因确实需要使用Java9进行转换,现在需要指定-deststoretype jks,但不再需要指定-srcstoretype pkcs12

谢谢!让我们重新开始,从头开始。我想创建一个CA、服务器证书和客户端证书。如果haproxy的服务器证书是CA证书,我不介意,但既然我要深入研究这个问题,我想做最合乎逻辑的事情。我希望curl和Java客户端使用客户端证书,并由haproxy进行验证。实现这一目标的最佳方法是什么?您能否指定正确的步骤顺序? - JRun
顺便提一下:我找到了 https://access.redhat.com/documentation/en-US/Red_Hat_JBoss_Fuse/6.0/html/Web_Services_Security_Guide/files/i305191.html 和 https://access.redhat.com/documentation/en-US/Red_Hat_JBoss_Fuse/6.0/html/Web_Services_Security_Guide/files/i382664.html,我能够创建证书和使用curl。我甚至能够将证书导入Java密钥库,但仍然无法在SSL握手中通过Java。 - JRun
@JRun,这是一个实质性的问题变化;这是一个判断性的决定,但我认为这比SE在已回答的问题中所喜欢的更改要多,因为目标是让任何人都能找到问题和答案。因此,我建议您提出一个新的问题,说明您现在正在做什么以及问题的具体内容;“仍然失败”对解决问题没有用处。如果服务器证书不是CA证书(更易管理)并由同一CA颁发(客户端需要作为验证者),则*I可以说更好。 - dave_thompson_085

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接