将 ACM 导出的证书转换为 Java 中的 p12 文件

Convert ACM exported certificate to p12 file in Java

我正在构建一个 MTLS 身份验证系统,其中注册步骤将使用 AWS ACM 私有 CA 生成证书。

所以在注册步骤中,我使用AWS ACM SDK先生成证书,然后导出。

RequestCertificateResult requestCertificateResult = client.requestCertificate(request);
String certificateArn = requestCertificateResult.getCertificateArn();

ExportCertificateRequest exportRequest = new ExportCertificateRequest();
exportRequest.setCertificateArn(certificateArn);
exportRequest.setPassphrase(ByteBuffer.wrap(password.getBytes()));

ExportCertificateResult exportCertificateResult = client.exportCertificate(exportRequest);

String certificateChain = exportCertificateResult.getCertificateChain();
String certificate = exportCertificateResult.getCertificate();
String privateKey = exportCertificateResult.getPrivateKey();

我想return给客户端的是一个.p12文件,包括证书和私钥。 但是,ACM 的结果包含 certificatecertificateChainprivateKey 作为字符串。 如何使用 Java 将它们转换为 .p12 文件? 我在互联网上找到的所有内容都使用 openssl,但由于这将是自动注册步骤的一部分,因此我需要以编程方式将其转换。

非常感谢任何正确方向的建议或指示。

编辑! 这就是我想要做的,但是在 Java:

openssl pkcs12 -export -inkey private_key.txt -in certificate.txt -certfile certificate_chain.txt -out final_result.p12

并使用字符串而不是 .txt 文件。

如果您使用的是 https://docs.aws.amazon.com/acm/latest/APIReference/API_ExportCertificate.html,它会将这些值描述并显示为 PEM 格式,这与您在 openssl pkcs12 -export 中的使用一致。然而,规范说私钥被标记为 BEGIN/END PRIVATE KEY 这是 PKCS8 未加密的,而示例显示 ENCRYPTED PRIVATE KEY 这是(打赌你猜不到!)PKCS8 加密,尽管该示例有明显的错误让我不信任它。

叶证书和链很简单,只需将它们提供给可以处理 PEM 或 'DER'(二进制)的 CertificateFactory。要将它们放入 Java 密钥库(任何格式),您需要将它们组合在一个数组中,leaf-then-chain:

// note: use java.security.cert.Certificate, not obsoleted java.security.Certificate or javax.security.cert.Certificate 
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate leaf = cf.generateCertificate(new ByteArrayInputStream(certString.getBytes()));
Collection<Certificate> chain = cf.generateCertificates(new ByteArrayInputStream(chainString.getBytes()));
// for general data String.getBytes() omitting/defaulting charset 
// can be dangerous, but PEM data is a strict subset of ASCII and safe
Certificate[] combine = chain.toArray( new Certificate[chain.size()+1] );
System.arraycopy(combine,0,combine,1,combine.length-1);
combine[0] = leaf;

或者组合 输入 可能更容易,但规范表明它们可能不提供证书的最终换行符(在 PEM END 行);如果是这样,您必须添加它以获得有效的 PEM 序列:

String temp = certString.endsWith("\n")? certString: certString + "\n";
Certificate[] combine = CertificateFactory.getInstance("X.509")
    .generateCertificates(new ByteArrayInputStream(temp+chainString))
    .toArray(new Certificate[0]);

如果私钥实际上是 PKCS8 未加密的,则 几乎 一样容易。 KeyFactory 处理该问题,但仅作为 'DER' 而不是 PEM,因此您需要撤消 PEM 'wrapping'。一种流行的方式是

String justb64 = privkeyString.replaceAll("-----(BEGIN|END) PRIVATE KEY-----","").replaceAll("\r?\n","");
byte[] binary = Base64.getDecoder().decode(justb64);
// or can leave the linebreaks in the data and use getMimeDecoder()
// prior to j8 other base64 decoders like Apache commons were popular,
// although in a pinch you can write your own by hand

// Java wants to know the algorithm of the key _before_ parsing it;
// since we have a known-matching cert, we can use that
PrivateKey pkey = KeyFactory.getInstance(combine[0].getPublicKey().getAlgorithm())
   .generatePrivate(new PKCS8EncodedKeySpec(binary));

另一种方法类似于

String[] lines = privkeyString.split("\r?\n"); 
// or if reading from a file use BufferedReader or nio.Files.readAllLines
// may want to check that lines[0] and lines[lines.length-1] are in fact
// the desired BEGIN and END lines, if there is any chance the data is wrong
String justb64 = String.join("",Arrays.copyOfRange(lines,1,lines.length-1));
// continue as above

您现在可以使用

将它们放入 PKCS12 密钥库中
KeyStore p12 = KeyStore.getInstance("PKCS12"); p12.load(null);
p12.setKeyEntry(alias, privkey, password, combine);
p12.store(/*OutputStream to desired file or other writable location*/, password);

如果私钥实际上是加密的,标准 Java 不能轻易读取它 作为 密钥。但是,if 它是使用 PKCS12 存储中(您的)Java 支持的算法之一加密的,作为绕过,您可以使用 'pre-protected' API:

byte[] priv_pkcs8enc = // un-PEM privkeyString as before, but _don't_ pass to KeyFactory
...
p12.setKeyEntry(alias, priv_pkcs8enc, combine);

我说 'your' Java,因为 Java 支持的 PKCS8 加密集随时间(版本)和实现(即提供商)而变化,我怀疑会继续。

如果您的Java使用PKCS12不支持的算法对私钥进行加密,您可能不适合使用标准(Oracle、OpenJDK等) ) Java。但是如果你想追求它,请在你的 Q 中添加测试密钥的详细信息——请记住,如果亚马逊没有记录他们使用的算法,它可能会有所不同(可能跨地区或服务产品)并且可能未来的改变。