【问题标题】:How to build a SSLSocketFactory from PEM certificate and key without converting to keystore?如何从 PEM 证书和密钥构建 SSLSocketFactory 而无需转换为密钥库?
【发布时间】:2017-07-29 05:44:36
【问题描述】:

我获得了一个自签名客户端证书工具包,用于通过 HTTPS 访问服务器。该套件包含以下 PEM 文件:

  1. client.crt(客户端证书)
  2. client.key(客户端私钥)
  3. ca.crt(CA 证书)

解决该任务的一种方法是生成 Java 密钥库:

  1. 使用 openssl 将客户端证书和密钥转换为 PKCS12 密钥库
  2. 使用 keytool 将 CA 证书导入到存储中

... 然后使用如下代码构建 SSLSocketFactory 实例:

InputStream stream = new ByteArrayInputStream(pksData);         
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(stream, password);

KeyManagerFactory kmf = KeyManagerFactory.getInstance(
    KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, password.toCharArray());
KeyManager[] keyManagers = kmf.getKeyManagers();

TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
    TrustManagerFactory.getDefaultAlgorithm());
tmfactory.init(keyStore);
TrustManager[] trustManagers = tmfactory.getTrustManagers();

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
sslSocketFactory = sslContext.getSocketFactory();

...后面用来初始化http库。

所以我们得到一个 KeyStore,然后在它的帮助下初始化 KeyManagers 和 TrustManagers,最后我们用它们构建 SSLSocketFactory 实例。

问题是:有没有办法避免创建密钥库文件并以某种方式从 PublicKey 和 Certificate 实例开始构建 SSLSocketFactory(例如,可以使用 bouncycastle 的 PemReader 从 PEM 文件中获取) ?

【问题讨论】:

  • 没有。您必须构建一个 PKCS#12 或 JKS KeyStore。但是你应该得到一个私钥。你应该自己生成它。这里存在严重的安全问题。你的私钥不是私人的,所以无论谁给你,都可以在法律意义上冒充你。不要这样做。
  • 是的,谢谢,我们知道我们应该自己生成私钥。但是我们在这里工作的机构规定了它的规则并且不听任何人的意见:他们只是自己生成所有密钥。不过,这不是技术问题。

标签: java ssl keystore pem sslsocketfactory


【解决方案1】:

事实证明,仍然需要构建 KeyStore 实例,但可以在内存中完成(以 PEM 文件作为输入开始),而无需使用 keytool 构建中间密钥库文件。

要构建内存中的 KeyStore,可以使用如下代码:

private static final String TEMPORARY_KEY_PASSWORD = "changeit";

private KeyStore getKeyStore() throws ConfigurationException {
    try {
        Certificate clientCertificate = loadCertificate(certificatePem);
        PrivateKey privateKey = loadPrivateKey(privateKeyPem);
        Certificate caCertificate = loadCertificate(caPem);

        KeyStore keyStore = KeyStore.getInstance("JKS");
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca-cert", caCertificate);
        keyStore.setCertificateEntry("client-cert", clientCertificate);
        keyStore.setKeyEntry("client-key", privateKey, TEMPORARY_KEY_PASSWORD.toCharArray(), new Certificate[]{clientCertificate});
        return keyStore;
    } catch (GeneralSecurityException | IOException e) {
        throw new ConfigurationException("Cannot build keystore", e);
    }
}

private Certificate loadCertificate(String certificatePem) throws IOException, GeneralSecurityException {
    CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
    final byte[] content = readPemContent(certificatePem);
    return certificateFactory.generateCertificate(new ByteArrayInputStream(content));
}

private PrivateKey loadPrivateKey(String privateKeyPem) throws IOException, GeneralSecurityException {
    return pemLoadPrivateKeyPkcs1OrPkcs8Encoded(privateKeyPem);
}

private byte[] readPemContent(String pem) throws IOException {
    final byte[] content;
    try (PemReader pemReader = new PemReader(new StringReader(pem))) {
        final PemObject pemObject = pemReader.readPemObject();
        content = pemObject.getContent();
    }
    return content;
}

private static PrivateKey pemLoadPrivateKeyPkcs1OrPkcs8Encoded(
        String privateKeyPem) throws GeneralSecurityException, IOException {
    // PKCS#8 format
    final String PEM_PRIVATE_START = "-----BEGIN PRIVATE KEY-----";
    final String PEM_PRIVATE_END = "-----END PRIVATE KEY-----";

    // PKCS#1 format
    final String PEM_RSA_PRIVATE_START = "-----BEGIN RSA PRIVATE KEY-----";
    final String PEM_RSA_PRIVATE_END = "-----END RSA PRIVATE KEY-----";

    if (privateKeyPem.contains(PEM_PRIVATE_START)) { // PKCS#8 format
        privateKeyPem = privateKeyPem.replace(PEM_PRIVATE_START, "").replace(PEM_PRIVATE_END, "");
        privateKeyPem = privateKeyPem.replaceAll("\\s", "");

        byte[] pkcs8EncodedKey = Base64.getDecoder().decode(privateKeyPem);

        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8EncodedKey));

    } else if (privateKeyPem.contains(PEM_RSA_PRIVATE_START)) {  // PKCS#1 format

        privateKeyPem = privateKeyPem.replace(PEM_RSA_PRIVATE_START, "").replace(PEM_RSA_PRIVATE_END, "");
        privateKeyPem = privateKeyPem.replaceAll("\\s", "");

        DerInputStream derReader = new DerInputStream(Base64.getDecoder().decode(privateKeyPem));

        DerValue[] seq = derReader.getSequence(0);

        if (seq.length < 9) {
            throw new GeneralSecurityException("Could not parse a PKCS1 private key.");
        }

        // skip version seq[0];
        BigInteger modulus = seq[1].getBigInteger();
        BigInteger publicExp = seq[2].getBigInteger();
        BigInteger privateExp = seq[3].getBigInteger();
        BigInteger prime1 = seq[4].getBigInteger();
        BigInteger prime2 = seq[5].getBigInteger();
        BigInteger exp1 = seq[6].getBigInteger();
        BigInteger exp2 = seq[7].getBigInteger();
        BigInteger crtCoef = seq[8].getBigInteger();

        RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, prime1, prime2,
                exp1, exp2, crtCoef);

        KeyFactory factory = KeyFactory.getInstance("RSA");

        return factory.generatePrivate(keySpec);
    }

    throw new GeneralSecurityException("Not supported format of a private key");
}

想法取自Programmatically Obtain KeyStore from PEM

【讨论】:

  • 谢谢,这个解决方案适用于 java 8 但是 DerInputStream 和 DerValue 在 java 11 中不再可用。知道什么是解析 -----BEGIN RSA PRIVATE KEY----- * -----END RSA PRIVATE KEY----- 内容的合适替代方案
  • @Hakan54 从法律的角度来看,我不确定这是否可行,但从技术上讲,您可以将整个 sun.security.util 包从 Java 8 复制到您自己的包中。还有 github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.base/… ,你可以从那里复制代码(或者使用 AdoptOpenJDK,可能?)
【解决方案2】:

我之前在面临类似挑战时对您的回答发表了评论,现在我回来提供加载 pem 文件的替代方法。我用它创建了一个库,以方便我自己和其他人使用,请参见此处:GitHub - SSLContext Kickstart我希望你喜欢它:)

添加如下依赖:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>sslcontext-kickstart-for-pem</artifactId>
    <version>7.0.0</version>
</dependency>

pem 文件可以使用以下 sn-p 加载:

var keyManager = PemUtils.loadIdentityMaterial("certificate-chain.pem", "private-key.pem");
var trustManager = PemUtils.loadTrustMaterial("some-trusted-certificate.pem");

var sslFactory = SSLFactory.builder()
          .withIdentityMaterial(keyManager)
          .withTrustMaterial(trustManager)
          .build();

var sslContext = sslFactory.getSslContext();
var sslSocketFactory = sslFactory.getSslSocketFactory();

回到您的主要问题,我还发现没有 KeyStore 就无法创建 SSLSocketFactory。并且内存中的 KeyStore 可以完美地按照您对此用例的建议工作。

【讨论】:

    猜你喜欢
    • 2014-09-27
    • 2022-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-04
    • 2012-07-16
    • 1970-01-01
    相关资源
    最近更新 更多