2

我正在开发部署在 Azure App Service 实例上的 Java Web 应用程序。我需要调用一个 REST API,该 API 需要通过 SSL 进行相互身份验证来保护该 API。由于这是一个应用程序服务,我没有将证书和公钥分别添加到密钥库和信任库的奢侈,而这一切都必须通过代码完成。尽管使用 JCE 和 SSL,我还是设法编写了以下控制台应用程序,成功访问了安全 API(在其他 StackOverflow Q&A 的帮助下):

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;

public class TestPFOM {

    public static void main(String[] args) throws KeyStoreException, NoSuchAlgorithmException, CertificateException,
            IOException, UnrecoverableKeyException, KeyManagementException {

        System.out.println("Start test for mutual authentication");
        KeyStore ks = KeyStore.getInstance("PKCS12");
        File file = new File(System.getProperty("user.dir") + "/client.company.com.pfx");
        System.out.println("Loaded PKCS12 from file");
        try (FileInputStream fis = new FileInputStream(file)) {

            ks.load(fis, "password".toCharArray());
            System.out.println("Loaded keys into keystore");
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
            kmf.init(ks, "password".toCharArray());
            System.out.println("Initialized KeyStoreManager");
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(kmf.getKeyManagers(), null, new SecureRandom());
            System.out.println("initialized SSLContext");
            SSLSocketFactory factory = sc.getSocketFactory();
            System.out.println("Obtained SSLSocketFactory");

            URL url = new URL("https://services.company.com/api/company_data");
            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
            System.out.println("Opened secure HTTPS connection");
            connection.setSSLSocketFactory(factory);
            connection.setRequestMethod("GET");
            connection.setRequestProperty("Accept", "application/json");
            StringBuilder stringBuilder = new StringBuilder();
            int responseCode = connection.getResponseCode();
            System.out.println("HTTP response code = " + responseCode);
            try (BufferedReader reader = responseCode == 200
                    ? new BufferedReader(new InputStreamReader(connection.getInputStream()))
                    : new BufferedReader(new InputStreamReader(connection.getErrorStream()))) {
                String line = null;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                    stringBuilder.append(line);
                }
            } catch (Exception e) {
                System.err.println("Error: " + e.getMessage());
            }
            System.out.println(stringBuilder.toString());
        } catch (Exception ex) {
            System.err.println("Error: " + ex.getMessage());
        }
    }
}

我需要从已存储证书的 Azure Keyvault 获取证书,而不是将 PFX 文件加载到 KeyStore 中。KeyVaultClient(来自 Azure 的 Java 客户端库)为我提供了一种获取 X509Certificate 对象的机制。是否可以使用 X509Certificate 对象而不是从 PFX 文件启动 KeyStore?

我的目标是为请求处理机制提供一个可重用的 SSLContext 对象,这样当我的 Web 应用程序接收到请求时,我可以使用它来调用外部的安全 API。而且我需要在不依赖文件系统中的任何文件和外部 JVM 密钥/信任存储的情况下执行此操作。

2018 年 7 月 5 日:跟进 GPI 的富有洞察力的建议,我手动构建了SSLContext

KeyStore keyStore = KeyStore.getInstance("PKCS12");
// Initiate and load empty key store
keyStore.load(null, null);
// clientCert is an X509Certificate object
keyStore.setCertificateEntry("clientCert", clientCert);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); // PKIX
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

但是当我使用生成SSLSocketFactory的 HTTPS 连接时,我收到以下错误:

sun.security.validator.ValidatorException:
  PKIX path building failed: 
    sun.security.provider.certpath.SunCertPathBuilderException:
      unable to find valid certification path to requested target
4

2 回答 2

2

KeyVaultClient(来自 Azure 的 Java 客户端库)为我提供了一种获取 X509Certificate 对象的机制。是否可以使用 X509Certificate 对象而不是从 PFX 文件启动 KeyStore?

是的。步骤是

1)在对象中加载 Azure 证书Cert(可能是 a X509Certificate
2)创建一个新KeyStore实例(无论格式如何,JKSPKCS12) 3)通过使用 null 输入流调用来
初始化这个新实例,这将创建一个新的空存储。 4) 手动将 Azure 证书添加为 KeyStore 中的受信任条目,并调用 5) 将此密钥库用作您的基础KeyStoreload
setCertificateEntry
TrustManagerFactory

于 2018-07-04T09:39:33.230 回答
1

KeyStore的,您可以从证书创建一个,但不,您不能将其用于客户端身份验证,也就是相互身份验证。

Java 使用KeyStore类和相关文件来存储(通常)三种不同但相关的事物,如类的 Javadoc 中所述。要对自己进行身份验证,您必须拥有一个 PrivateKeyEntry,正如其名称所暗示的那样,它包含一个私钥,加上至少一个证书,通常是多个证书链。TrustedCertEntry 用于验证其他方,尤其是其他方SSL/TLS 连接的端点(对等点);当您是 SSL/TLS 客户端时,就像这里一样,TrustedCertEntry 用于通过验证服务器的证书来验证服务器。(第三种可能性,SecretKeyEntry,不用于 SSL/TLS,甚至不支持由 Java 实现且常用的 PKCS12 密钥库类型。)

使用一个X509Certificate对象,您可以创建一个 TrustedCertEntry,而您从 GPI 获得的代码就是这样做的。TrustedCertEntry(在 a 中KeyStore)仅可用于验证另一方,在这种情况下是服务器。要向服务器验证您自己(客户端)的身份,您不仅需要证书,还需要打包为 PrivateKeyEntry 的私钥和证书链,根据您的第一个代码,您可以从中创建一个KeyManagerput inSSLContext并供 JSSE 使用。

AFAICS Azure vault 将密钥表示为com.microsoft.azure.keyvault.webkey.JSONWebKey,这似乎仅限于 RSA 或 AES。如果是 RSA,则描述中有两种toRSA方法(重载)应该产生一个 KeyPair,我认为这意味着java.security.KeyPair包含您需要的私钥,除非在我查看的地方没有说明限制。我看不到任何直接获取证书链的方法,但似乎证书条目具有颁发者链接,这应该足以让您构建链,尽管我自己无法测试/验证这一点。

于 2018-07-05T11:39:23.067 回答