如何读取 OkHttpClient 的证书链
How to read a certificate chain for OkHttpClient
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] keyBytes = Files.readAllBytes((Paths.get("/path/to/chain.pem")));
X509EncodedKeySpec spec =
new X509EncodedKeySpec(keyBytes);
PublicKey publicKey = keyFactory.generatePublic(spec);
byte[] privateKeyBytes = Files.readAllBytes(Paths.get("/path/to/key.pem"));
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
HeldCertificate cert = new HeldCertificate.Builder().keyPair(publicKey, privateKey).build();
HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder()
.heldCertificate(cert)
.build();
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager())
.build();
我正在尝试使用 Okhttpclient 进行客户端身份验证,这就是我目前拥有的。 chain.pem 文件有多个形式为
的证书
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
keyFactory.generatePublic 方法在尝试解析证书链时失败并返回 Caused by: java.security.InvalidKeyException: invalid key format
。我如何为 OkhttpClient 解析这个证书链?我是否必须将链拆分为多个 pems?
这些是证书,而不是一个甚至几个公钥。证书包含公钥,但证书不是公钥,不能作为公钥读取。此外,这些证书是出于某种原因颁发给您的;如果您使用提供的证书链(和私钥),服务器将信任它们,但如果您生成自己的自签名证书,即使是相同的密钥,这就是您的 HeldCertificate.Builder()
会做的,服务器将不会'不要信任该证书,因为它不是由有效的 CA 颁发的。数字证书有时被类比为护照;如果你有你的政府签发的带有你的名字和照片的护照,其他国家(和国内实体)通常会接受它作为你身份的证明,但如果你在上面写上你的名字和单词 'passport'一张纸贴上你自己的照片,没有人会接受它作为证据——这就是自签名证书的样子。
阅读 Java 中的文件 相当容易。证书最简单——CertificateFactory
可以直接读取该格式:
byte[] certBytes = Files.readAllBytes(Paths.get("/path/to/chain.pem"));
InputStream certstream = new ByteArrayInputStream(certBytes);
X509Certificate[] certs = CertificateFactory.getInstance("X.509")
.generateCertificates(certstream) .toArray(new X509Certificate[0]);
certstream.close(); // or use try-resources if you prefer
钥匙可能更难;如果它是 PEM 格式,如名称 key.pem
建议 KeyFactory
(不同于 CertificateFactory
)不读取 PEM。如果它是 一种特定的 PEM 格式 即 PKCS8 根据 RFC7468 section 10 未加密——由 -----BEGIN PRIVATE KEY-----
和类似的 END
标记(如图所示)在BEGIN/END和PRIVATE KEY之间没有其他词——你可以这样转换它:
byte[] pkeyPEM = Files.readAllBytes(Paths.get("/path/to/key.pem"));
byte[] pkeyDER = Base64.getDecoder().decode( new String(pkeyBytes)
.replaceAll("-----(BEGIN|END) PRIVATE KEY-----","").replaceAll("\r?\n","") );
RSAPrivateKey privateKey = (RSAPrivateKey) KeyFactory.getInstance("RSA")
.generatePrivate(new PKCS8EncodedKeySpec(pkeyDER));
但是,至少有十几种其他 PEM 格式用于 Java 无法直接读取的私钥。 其中大多数 由 OpenSSL 使用,如果 BEGIN
和 END
行显示 ENCRYPTED PRIVATE KEY
或 {RSA|DSA|EC} PRIVATE KEY
,您可以使用 openssl
命令行将其转换为 Java 可以处理的格式:
openssl pkey -in badPEM -out goodPEM # only 1.0.0 up but that is now very common
openssl pkcs8 -topk8 -nocrypt -in badPEM -out goodPEM # even old versions
此外,如果您将 -outform der
添加到其中任何一个(请相应地更改文件名以避免混淆),您不再需要 de-PEM 步骤,您可以将 Files.readAllBytes
结果直接在 PKCS8EncodedKeySpec
。如果您的密钥文件是其他任何东西,那将更加困难或可能是不可能的;您必须提供更多详细信息。
在 OkHttp 中使用 坦率地说 this API 在我看来它是由不知道自己在做什么的人设计的;将 KeyPair
与 Certificate
一起使用完全没有意义。但从逻辑上讲,这个 应该 有效:
RSAPublicKey publicKey = (RSAPublicKey) certs[0].getPublicKey();
if( ! publicKey.getModulus().equals( privateKey.getModulus() ) )
throw new Exception ("key does not match cert"); // or other error handling
HeldCertificate client1 = new HeldCertificate( new KeyPair(publicKey, privateKey), certs[0]);
HandshakeCertificates client2 = new HandshakeCertificates.Builder()
.addPlatformTrustedCertificates() // or more specific if necessary
.heldCertificate(client1,Arrays.copyOfRange(certs,1,certs.length) )
.build();
// use client2 as you do now to set sslSocketFactory and trustManager
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] keyBytes = Files.readAllBytes((Paths.get("/path/to/chain.pem")));
X509EncodedKeySpec spec =
new X509EncodedKeySpec(keyBytes);
PublicKey publicKey = keyFactory.generatePublic(spec);
byte[] privateKeyBytes = Files.readAllBytes(Paths.get("/path/to/key.pem"));
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
HeldCertificate cert = new HeldCertificate.Builder().keyPair(publicKey, privateKey).build();
HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder()
.heldCertificate(cert)
.build();
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager())
.build();
我正在尝试使用 Okhttpclient 进行客户端身份验证,这就是我目前拥有的。 chain.pem 文件有多个形式为
的证书-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
keyFactory.generatePublic 方法在尝试解析证书链时失败并返回 Caused by: java.security.InvalidKeyException: invalid key format
。我如何为 OkhttpClient 解析这个证书链?我是否必须将链拆分为多个 pems?
这些是证书,而不是一个甚至几个公钥。证书包含公钥,但证书不是公钥,不能作为公钥读取。此外,这些证书是出于某种原因颁发给您的;如果您使用提供的证书链(和私钥),服务器将信任它们,但如果您生成自己的自签名证书,即使是相同的密钥,这就是您的 HeldCertificate.Builder()
会做的,服务器将不会'不要信任该证书,因为它不是由有效的 CA 颁发的。数字证书有时被类比为护照;如果你有你的政府签发的带有你的名字和照片的护照,其他国家(和国内实体)通常会接受它作为你身份的证明,但如果你在上面写上你的名字和单词 'passport'一张纸贴上你自己的照片,没有人会接受它作为证据——这就是自签名证书的样子。
阅读 Java 中的文件 相当容易。证书最简单——CertificateFactory
可以直接读取该格式:
byte[] certBytes = Files.readAllBytes(Paths.get("/path/to/chain.pem"));
InputStream certstream = new ByteArrayInputStream(certBytes);
X509Certificate[] certs = CertificateFactory.getInstance("X.509")
.generateCertificates(certstream) .toArray(new X509Certificate[0]);
certstream.close(); // or use try-resources if you prefer
钥匙可能更难;如果它是 PEM 格式,如名称 key.pem
建议 KeyFactory
(不同于 CertificateFactory
)不读取 PEM。如果它是 一种特定的 PEM 格式 即 PKCS8 根据 RFC7468 section 10 未加密——由 -----BEGIN PRIVATE KEY-----
和类似的 END
标记(如图所示)在BEGIN/END和PRIVATE KEY之间没有其他词——你可以这样转换它:
byte[] pkeyPEM = Files.readAllBytes(Paths.get("/path/to/key.pem"));
byte[] pkeyDER = Base64.getDecoder().decode( new String(pkeyBytes)
.replaceAll("-----(BEGIN|END) PRIVATE KEY-----","").replaceAll("\r?\n","") );
RSAPrivateKey privateKey = (RSAPrivateKey) KeyFactory.getInstance("RSA")
.generatePrivate(new PKCS8EncodedKeySpec(pkeyDER));
但是,至少有十几种其他 PEM 格式用于 Java 无法直接读取的私钥。 其中大多数 由 OpenSSL 使用,如果 BEGIN
和 END
行显示 ENCRYPTED PRIVATE KEY
或 {RSA|DSA|EC} PRIVATE KEY
,您可以使用 openssl
命令行将其转换为 Java 可以处理的格式:
openssl pkey -in badPEM -out goodPEM # only 1.0.0 up but that is now very common
openssl pkcs8 -topk8 -nocrypt -in badPEM -out goodPEM # even old versions
此外,如果您将 -outform der
添加到其中任何一个(请相应地更改文件名以避免混淆),您不再需要 de-PEM 步骤,您可以将 Files.readAllBytes
结果直接在 PKCS8EncodedKeySpec
。如果您的密钥文件是其他任何东西,那将更加困难或可能是不可能的;您必须提供更多详细信息。
在 OkHttp 中使用 坦率地说 this API 在我看来它是由不知道自己在做什么的人设计的;将 KeyPair
与 Certificate
一起使用完全没有意义。但从逻辑上讲,这个 应该 有效:
RSAPublicKey publicKey = (RSAPublicKey) certs[0].getPublicKey();
if( ! publicKey.getModulus().equals( privateKey.getModulus() ) )
throw new Exception ("key does not match cert"); // or other error handling
HeldCertificate client1 = new HeldCertificate( new KeyPair(publicKey, privateKey), certs[0]);
HandshakeCertificates client2 = new HandshakeCertificates.Builder()
.addPlatformTrustedCertificates() // or more specific if necessary
.heldCertificate(client1,Arrays.copyOfRange(certs,1,certs.length) )
.build();
// use client2 as you do now to set sslSocketFactory and trustManager