如何使用Java中来自Azure KeyVault的证书设置SSLContext

时间:2018-07-03 14:16:09

标签: java ssl azure-keyvault ssl-client-authentication

我正在研究在Azure应用服务实例上部署的Java Web应用程序。而且我需要调用一个REST API,该API需要通过SSL进行相互身份验证来确保安全。由于这是一项应用程序服务,因此我无法将证书和公钥分别添加到密钥库和信任库中,而必须全部通过代码来完成。尽管使用JCE和SSL,但我设法编写了以下控制台应用程序,该应用程序成功地访问了安全API(在其他StackOverflow问答的帮助下):

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);

但是当我在HTTPS连接中使用结果SSLSocketFactory时,出现以下错误:

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

2 个答案:

答案 0 :(得分:1)

  

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

是的。步骤是

1)将Azure证书加载到Cert对象(可能是X509Certificate)中
2)创建一个新的KeyStore实例(无论格式为JKS还是PKCS12
3)通过调用KeyStore with a null input stream来初始化新的load,这将创建一个新的空存储。
4)手动将Azure证书添加为KeyStore中的受信任条目,并调用setCertificateEntry
5)使用此密钥库作为TrustManagerFactory

的基础

答案 1 :(得分:1)

,您可以根据证书创建一个KeyStore,但不能,您不能将其用于客户端身份验证或相互身份验证。

Java使用KeyStore类和相关文件来存储(通常)三种不同但相关的事物as detailed in Javadoc for the class。要对自己进行身份验证,您必须具有一个PrivateKeyEntry,如其名称所暗示的那样,其中包含一个私钥,再加上至少一个证书,通常是一连串的 multiple 证书。 TrustedCertEntry用于认证 other 各方,尤其是SSL / TLS连接的 other 端点(对等方);当您是SSL / TLS客户端时,如此处所示,TrustedCertEntry用于通过验证服务器的证书来对服务器进行身份验证。 (第三个可能性,SecretKeyEntry,不用于SSL / TLS,甚至不被Java实现且常用的PKCS12密钥库类型所支持。)

使用X509Certificate对象,您可以创建TrustedCertEntry,然后从GPI获得的代码即可。 TrustedCertEntry(在KeyStore中)仅可用于认证另一方,在这种情况下为服务器。要验证自己(客户端到服务器)的身份,您不仅需要简单的证书,还需要打包成PrivateKeyEntry的私钥和证书链,从中您可以创建一个KeyManager SSLContext,并由JSSE按照您的第一个代码使用。

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