Java 无法加载"BEGIN TRUSTED CERTIFICATE"格式证书



我有一个CA证书,它是由openssl用"trustout"生成的,所以它以"-----BEGIN TRUSTED CERTIFICATE-----"开头,当我尝试用Java读取它时,异常抛出。Java 是否支持这种格式的证书?如果是这样,如何阅读?

public class TestReadCerts {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
         String sslrootcertfile = "F:\javaworkspace\opensource\certs\ca.pem";
            FileInputStream fis=null;
            try {
              fis = new FileInputStream(sslrootcertfile); // NOSONAR
            } catch (FileNotFoundException ex) {
              ex.printStackTrace();
            }
            try {
              CertificateFactory cf = CertificateFactory.getInstance("X.509");
              Object[] certs = cf.generateCertificates(fis).toArray(new Certificate[]{});
            } catch (Exception e) {
              e.printStackTrace();
            } 
    }
}

例外情况:java.security.cert.CertificateException: 无法初始化,

java.io.IOException: extra data given to DerValue constructor
    at java.base/sun.security.x509.X509CertImpl.<init>(X509CertImpl.java:191)
    at java.base/sun.security.provider.X509Factory.parseX509orPKCS7Cert(X509Factory.java:476)
    at java.base/sun.security.provider.X509Factory.engineGenerateCertificates(X509Factory.java:361)
    at java.base/java.security.cert.CertificateFactory.generateCertificates(CertificateFactory.java:478)
    at TestReadCerts.main(TestReadCerts.java:21)
Caused by: java.io.IOException: extra data given to DerValue constructor
    at java.base/sun.security.util.DerValue.init(DerValue.java:409)
    at java.base/sun.security.util.DerValue.<init>(DerValue.java:294)
    at java.base/sun.security.util.DerValue.<init>(DerValue.java:305)
    at java.base/sun.security.x509.X509CertImpl.<init>(X509CertImpl.java:188)
    ... 4 more

证书如下:

-----BEGIN TRUSTED CERTIFICATE-----
MIICATCCAWoCCQDjKSwZBsrQwTANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMB4XDTE5MDQwMzE0NDcwMVoXDTIwMDQwMjE0NDcwMVowRTELMAkG
A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyRCB
rbB/FqN6e9IAJ86WUUGxM+8vEyfQ7cn2HWca220NB/Ns3Q+QtvztSe48PUzn9w6s
MNOwsDW4+8lenPLd78J32lG59x1P1R1jpjL3GcjNTwuewW1jIsex8jALzfU9hJzO
prraO/6X+UbKbazXt6GiB7mOlUvneKsWuoGpF5MCAwEAATANBgkqhkiG9w0BAQsF
AAOBgQCN1UJF/FdT84bzEn1kmg77b+LCCrU11DsFg/s/ABvo5TKV+OmilBPj1vML
dbZ4GDQSaXKZAOyJiAp0S5BzHXlXz5YfX9sM4mfhaqZt736WAnKVSnzd55CjMlEk
GxW3TkRFL5cVm5my1UQs3Mfg4MC5QPaoer5kc+0UhMHmTlgyvTAMMAoGCCsGAQUF
BwMB
-----END TRUSTED CERTIFICATE-----

PEM类型"受信任证书"是一种OpenSSL特定的非标准格式,Java无法开箱即用。它实际上包含标准 X.509 证书作为一个 DER 块加上另一个 OpenSSL 定义的信任信息 DER 块。

如果您有OpenSSL,最简单的方法是使用openssl x509 <in >out转换为标准的"证书"格式。您可以像 Misantorp 那样添加-outform DER,但这不是必需的; CertificateFactory可以读取DER或PEM中的标准格式,因为OpenSSL不准确地调用它们。

如果您拥有或可以从 https://www.BouncyCastle.org 获取和使用bcpkix和bcprov,它们包括处理此OpenSSL PEM格式(以及许多其他格式(的例程:

        // assumes filename in args[0], adjust as needed
        Object both = new PEMParser(new FileReader(args[0])).readObject();
        // should close the FileReader, maybe using try-resources
        byte[] cert = ((X509TrustedCertificateBlock)both).getCertificateHolder().getEncoded();
        X509Certificate good = (X509Certificate) CertificateFactory.getInstance("X.509")
                .generateCertificate(new ByteArrayInputStream(cert));
        System.out.println (good.getSubjectX500Principal().getName());

否则,可以手动分解 DER,但笨拙且不坚固:

        String in1 = new String(Files.readAllBytes(new File(args[0]).toPath()));
        byte[] both = Base64.getMimeDecoder().decode(in1.replaceAll("-----[A-Z ]*-----\r?\n",""));
        if( both[0]!=0x30 || both[1]!=(byte)0x82 ) throw new Exception("wrong!"); // or other handling
        byte[] cert = Arrays.copyOf(both, (both[2]<<8 | both[3]&0xFF) + 4);
        X509Certificate good = (X509Certificate) CertificateFactory.getInstance("X.509")
                .generateCertificate(new ByteArrayInputStream(cert));
        System.out.println (good.getSubjectX500Principal().getName());

也请查看dave_thompson_085的答案,因为它指出了我的理解中的一些不准确之处,因此这个答案


您的问题是尝试读取PEM编码的证书,该证书需要CertificateFactory 文档中所述的DER编码

对于 X.509 证书的证书工厂, 在 InStream 中提供的证书必须是 DER 编码的,并且可以是 以二进制或可打印 (Base64( 编码提供。

我认为读取证书的最快方法是将证书转换为适当的编码。由于您已经提到使用 openssl 生成证书,因此您可以将当前证书编码为DER

$ openssl x509 -in /F/javaworkspace/opensource/certs/ca.pem -outform DER -out /F/javaworkspace/opensource/certs/ca.der

(根据需要调整路径ca.pemca.der(

最后,不要忘记更新sslrootcertfile变量

String sslrootcertfile = "F:\javaworkspace\opensource\certs\ca.der";

最新更新