有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java如何读取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进行客户端身份验证,这就是我目前拥有的。链条。pem文件具有多个形式证书

-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----

钥匙工厂。generatePublic方法在尝试解析证书链时失败,返回Caused by: java.security.InvalidKeyException: invalid key format。如何为OkhttpClient解析此证书链?我是否必须将链拆分为多个PEM


共 (1) 个答案

  1. # 1 楼答案

    这些是证书,而不是一个或几个公钥。证书包含公钥,但该证书不是公钥,不能作为公钥读取。此外,向您颁发这些证书是有原因的;如果您使用提供的证书链(和私钥),服务器将信任它们,但如果您生成自己的自签名证书,即使是相同的密钥,这也是您的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未加密,按照 -BEGIN PRIVATE KEY -和类似的END标记的RFC7468 section 10和类似的END,在开始/结束和私钥之间没有其他单词,则可以如下转换它:

    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使用,如果BEGINEND行表示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在我看来,它是由不知道自己在做什么的人设计的;将KeyPairCertificate一起使用毫无意义。但从逻辑上讲,这应该起作用:

    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