Restlet javax.net.ssl.SSLHandshakeException: null cert chain

Я тестирую SSL-связь между клиентом и сервером локально. Поэтому я сгенерировал сертификат, используя команды OpenSSL. Добавлен этот сертификат в файл cacert. Также создан файл .p12.

Я использую тот же файл .p12 на сервере и клиенте. Это код сервера

Server server = component.getServers().add(Protocol.HTTPS, port);
Series<Parameter> params = server.getContext().getParameters();

params.add("keystorePath", ".p12 file path");
params.add("keystoreType", "PKCS12");
params.add("needClientAuthentication","true");

component.getDefaultHost().attach("", "/AA"), new AAClass());
component.start();

И это код клиента:

Client client = trustAllCerts();
clientResource = new ClientResource(url);
clientResource.setNext(client);
try{
      clientText = clientResource.post"");
 }
 catch(ResourceException e){
    e.printStackTrace();
 }

public Client trustAllCerts() {
    Client client = null;
    try {
        client = new Client(new Context(), Protocol.HTTPS);
        Context context = client.getContext();


        final SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
        context.getAttributes().put("sslContextFactory", new SslContextFactory() {
            public void init(Series<Parameter> parameters) {

            }

            public SSLContext createSslContext() {
                return sslContext;
            }
        });
        TrustManager tm = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
        context.getAttributes().put("hostnameVerifier", new HostnameVerifier() {                
            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                return true;
            }

        });         

        sslContext.init(null, new TrustManager[] { tm }, null);         

    } catch (KeyManagementException e) {
        LOGGER.error("Exception in Key Management" + e);
    } catch (NoSuchAlgorithmException e) {
        LOGGER.error("Exception in Algorithm Used" + e);
    }
    return client;
}

Я получаю следующее исключение:

Restlet-1299242, fatal error: 42: null cert chain
javax.net.ssl.SSLHandshakeException: null cert chain
%% Invalidated:  [Session-25, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256]
Restlet-1299242, SEND TLSv1.2 ALERT:  fatal, description = bad_certificate
Restlet-1299242, WRITE: TLSv1.2 Alert, length = 2
Restlet-1299242, fatal: engine already closed.  Rethrowing javax.net.ssl.SSLHandshakeException: null cert chain
Restlet-1299242, called closeInbound()
Restlet-1299242, fatal: engine already closed.  Rethrowing javax.net.ssl.SSLException: Inbound closed before receiving peer close_notify: possible truncation attack?
Restlet-1299242, called closeOutbound()
Restlet-1299242, closeOutboundInternal()

Я попытался добавить хранилище ключей и truststore, используя System.setProperty(), но это не сработало.

Пожалуйста, помогите. Спасибо заранее.

Ответ 1

Сначала давайте создадим хранилище ключей в формате JKS. PKCS12 обычно используется в браузере, и по умолчанию java-приложения используют JKS (насколько я знаю). Java также поддерживает PKCS12, но я не знаю точных параметров для него.

Подготовка файла JKS

Давайте посмотрим в нашем файле PKCS12 и получим псевдонимы сертификатов, которые мы хотим извлечь из нашего файла JKS.

keytool -list \
        -keystore [*.p12 file] \
        -storepass [password] \
        -storetype PKCS12 \
        -v

Обратите внимание на псевдонимы, которые вы хотите экспортировать. А теперь создадим файл JKS.

keytool -keystore [*.jks file path] -genkey -alias client

Это задаст кучу вопросов. Вы можете заполнить их, как вам нравится. Теперь вы можете экспортировать свои псевдонимы из файла *.p12 в файл *.jks.

keytool -importkeystore \
        -srckeystore [*.p12 file path] \
        -srcstoretype pkcs12 \
        -srcalias [alias from first command] \
        -destkeystore [*.jks file path] \
        -deststoretype jks \
        -deststorepass [*.jks file password] \
        -destalias [new alias]

Если у вас нет файла PKCS12 или ваши сертификаты находятся в формате CER, DER или PEM, вы можете добавить свои сертификаты в хранилище ключей, используя приведенную ниже команду.

keytool -import \
        -alias [new alias] \
        -keystore [*.jks file path] \
        -file [*.DER file path]

И убедитесь, что вы импортировали, ваш сертификат, сертификат поставщика сертификатов (промежуточный сертификат) и корневой сертификат.

Теперь вы можете проверить, что ваш файл JKS содержит все необходимые вам сертификаты.

keytool -list \
        -keystore [*.jks file path] \
        -storepass [password] \
        -storetype jks \
        -v

Настройка сервера

Вы можете использовать свой файл JKS как на стороне клиента, так и на стороне сервера. В соответствии с документацией о размещении > вы можете использовать JKS файл, подобный этому, для обеспечения HTTPS-соединения.

Server server = component.getServers().add(Protocol.HTTPS, port);  
Series<Parameter> parameters = server.getContext().getParameters();
parameters.add("sslContextFactory","org.restlet.engine.ssl.DefaultSslContextFactory");
parameters.add("keyStorePath", "*.jks file");
parameters.add("keyStorePassword", "password");
parameters.add("keyPassword", "password");
parameters.add("keyStoreType", "JKS");

После этого, если вы проверите свой порт из браузера, вы увидите безопасный знак. Или вы можете использовать онлайн-инструмент (как этот), чтобы проверить свой сертификат.

Настройка клиента

Теперь посмотрим на клиентскую сторону. Поскольку вы разрабатываете обе стороны приложения, вы можете использовать уже созданный файл JKS.

Context con = new Context();
Series<Parameter> clParameters = con.getParameters();
clParameters.add("truststorePath", "*.jks file");
clParameters.add("truststorePassword", "password");
clParameters.add("truststoreType", "JKS");
Client restletClient = new Client(con, Protocol.HTTPS);

Во время тестирования или в других обстоятельствах имя хоста сертификата и ваше фактическое имя хоста могут не совпадать. Чтобы отключить проверки имен хостов, вы можете добавить этот блок в свое приложение.

static{
    javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
    new javax.net.ssl.HostnameVerifier(){

        public boolean verify(String hostname,
                javax.net.ssl.SSLSession sslSession ) {
            return true ;
        }
    });
}

Некоторые мысли

Так как я не могу проверить его на своем языке, я не совсем уверен, что ваш клиентский и серверный файл JKS должен быть одинаковым. Вам может потребоваться добавить свой собственный сертификат на ваш server.jks. SSL и сертификаты всегда сложны для меня. Обычно я получаю работу после проб и ошибок. Надеюсь, это поможет вам.

Кроме того, вы также можете рассмотреть, используя обратный прокси-сервер такого типа, как Apache2 или Nginx. Если вы хотите использовать их, вы должны объединить свои сертификаты в один файл. Если вы посмотрите на свой файл сертификата, вы увидите, что каждый файл (ваш собственный сертификат, промежуточный сертификат и корневой сертификат) выглядит следующим образом

-----BEGIN CERTIFICATE-----
MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUA...
....
-----END CERTIFICATE-----

Вам нужно просто добавить один к другому для создания объединенного сертификата. И чем использовать этот сертификат для завершения SSL на Apache2 или Nginx. Это то, что я обычно делаю. Но на стороне клиента вам все равно нужно создавать файлы JKS.

Ответ 2

Я использую тот же файл .p12 на сервере и клиенте

Это уже ошибка. Клиент и сервер имеют разные идентификаторы и не должны иметь один и тот же закрытый ключ, открытый ключ или сертификат.

Я предлагаю вам выровнять все файлы OpenSSL и начать снова с keytool следующим образом:

  • На сервере создайте пару ключей и запрос сертификата; подписаться; импортировать цепочку сертификатов подписчика с опцией -trustcacerts; и импортировать подписанный сертификат , используя тот же псевдоним, который вы использовали при создании keypair и CSR.
  • На клиенте то же, но используя (конечно) другой файл хранилища ключей.
  • Вы закончили. Забудьте о

    • OpenSSL
    • PKCS # 12
    • самозаверяющие сертификаты
    • все формы trustAllCerts, custom TrustManagers и собственный код любого типа
    • с использованием той же ключевой пары/сертификата для сервера и клиента
    • импорт сертификата сервера клиенту и наоборот
    • любые системные свойства, отличные от тех, которые идентифицируют javax.net.ssl.keyStore и javax.net.ssl.keyStorePassword
    • установка пароля на ключевую пару или импортированный подписанный сертификат.

Шаги (1) и (2) - это то, как это делается. Отправляйся от тех, и у тебя есть проблемы и ссоры.

Ответ 3

Вероятно, вы не добавили полную цепочку сертификатов в хранилище ключей и просто включили в себя пару ключей. В этом случае клиент просто получает открытый ключ, но он не может проверить, можно ли доверять этому ключу. Цепочка сертификатов должна быть в состоянии проверить соответствие подписей открытого ключа и привести к доверенному центру сертификации.

См. например: Добавление цепочки сертификатов в сертификат p12 (pfx)

openssl pkcs12 -in certificate.p12 -out clientcert.pem -nodes -clcerts
openssl x509 -in trusted_ca.cer -inform DER -out trusted_ca.pem
openssl x509 -in root_ca.cer -inform DER -out root_ca.pem
cat clientcert.pem trusted_ca.pem root_ca.pem >> clientcertchain.pem
openssl pkcs12 -export -in clientcertchain.pem -out clientcertchain.pfx

Вы также можете использовать java-способ, используя, например, portecle: http://portecle.sourceforge.net/import-ca-reply.html, но вам также необходимо объединить цепочку сертификатов в одном файле для импорта. Просто скопируйте все сертификаты друг за другом, начиная с вашего собственного и заканчивая корневым ЦС.

Таким образом, полученный файл pfx можно использовать на сервере, чтобы вернуть цепочку сертификатов клиенту.

Ответ 4

Один из вариантов - прочитать файл p12/pfx, получить сертификаты и использовать их для программной разработки KeyStores и TrustStores. Если вход представляет собой один файл pfx, содержащий корневой сертификат CA и соответствующий сертификат клиента, методы, показанные ниже в классе SslUtils, позволят вам это сделать. Однако есть одно предостережение: сервер Restlet по умолчанию (версия 2.3.4) не будет отправлять сертификаты, отправленные клиентом. Мне удалось обойти эту проблему (это не очень нравится), см. Мой ответ на этот вопрос.

Я сконцентрируюсь на настройке защищенных соединений здесь, но весь исходный код и рабочий пример доступны в restlet-clientcert Проект Github. Проект Github является результатом того, что я думаю, что знаю, что я делаю, не повезло и не испытывал опыта с Restlet, но кусать пулю в любом случае, чтобы я мог чувствовать себя немного лучше, зная, что я могу заставить этот базовый материал работать.

На стороне сервера используйте пользовательский ServerSslContextFactory, который программно конфигурирует используемый SSLContext. Зарегистрируйте пользовательский factory с помощью:

ServerSslContextFactory sslCtx = new ServerSslContextFactory();
sslCtx.init(certFileName, certFilePwd);
ConcurrentMap<String, Object> attribs = server.getContext().getAttributes();
attribs.put("sslContextFactory", sslCtx);

и прикрепите "охранник" для извлечения информации о сертификате клиента:

CertificateAuthenticator guard = new CertificateAuthenticator(server.getContext());
guard.setNext(MyRestlet.class);
component.getDefaultHost().attachDefault(guard);

ServerSslContextFactory:

public class ServerSslContextFactory extends DefaultSslContextFactory {

    private static final Logger log = LoggerFactory.getLogger(ServerSslContextFactory.class);

    protected DefaultSslContext wrappedCtx;

    public void init(String certFileName, char[] certFilePwd) throws Exception {

        if (log.isDebugEnabled()) {
            log.debug("Loading certificates from [" + certFileName + "] and using " 
                    + (certFilePwd != null && certFilePwd.length > 0 ? "a" : "no") + " password.");
        }
        Path certFilePath = Paths.get(Thread.currentThread().getContextClassLoader().getResource(certFileName).toURI());
        KeyManagerFactory kmf = SslUtils.loadKeyStore(certFilePath, certFilePwd);
        KeyManager[] kms = kmf.getKeyManagers();
        List<X509Certificate> certs = SslUtils.getClientCaCerts(kms);
        TrustManagerFactory tmf = SslUtils.createTrustStore(Constants.CERT_CA_ALIAS, certs.get(0));
        TrustManager[] tms = tmf.getTrustManagers();

        super.setNeedClientAuthentication(true);

        SSLContext ctx = SSLContext.getInstance(SslUtils.DEFAULT_SSL_PROTOCOL);
        ctx.init(kms, tms, null);
        wrappedCtx = (DefaultSslContext) createWrapper(ctx);
    }

    @Override
    public void init(Series<Parameter> parameters) { 
        log.debug("Not using parameters to initialize server SSL Context factory.");
    }

    @Override
    public SSLContext createSslContext() throws Exception {
        return wrappedCtx;
    }

    @Override
    public boolean isNeedClientAuthentication() {

        if (log.isDebugEnabled()) {
            //log.debug("Needing client auth: " + super.isNeedClientAuthentication(), new RuntimeException("trace"));
            log.debug("Needing client auth: " + super.isNeedClientAuthentication());
        }
        return super.isNeedClientAuthentication();
    }

}

На стороне клиента аналогичная вещь:

ClientSslContextFactory sslCtx = new ClientSslContextFactory();
sslCtx.init(certFileName, certFilePwd);
attribs.put("sslContextFactory", sslCtx);

Также установите hostnameVerifier (как показано в вопросе), чтобы не проверять имена хостов.
ClientSslContextFactory:

public class ClientSslContextFactory extends SslContextFactory {

    private static final Logger log = LoggerFactory.getLogger(ClientSslContextFactory.class);

    protected KeyManager[] kms;
    protected TrustManager[] tms;

    public void init(String certFileName, char[] certFilePwd) throws Exception {

        log.debug("Loading certificates from [" + certFileName + "] and using " 
                + (certFilePwd != null && certFilePwd.length > 0 ? "a" : "no") + " password.");
        Path certFilePath = Paths.get(Thread.currentThread().getContextClassLoader().getResource(certFileName).toURI());
        KeyManagerFactory kmf = SslUtils.loadKeyStore(certFilePath, certFilePwd);
        kms = kmf.getKeyManagers();
        /*
        List<X509Certificate> certs = SslUtils.getClientCaCerts(kms);
        TrustManagerFactory tmf = SslUtils.createTrustStore(Constants.CERT_CA_ALIAS, certs.get(0));
        tms = tmf.getTrustManagers();
        */
        tms = new TrustManager[1];
        tms[0] = new TrustServerCertAlways();
    }

    @Override
    public void init(Series<Parameter> parameters) {
        log.debug("Not using parameters to initialize client SSL Context factory.");
    }

    @Override
    public SSLContext createSslContext() throws Exception {

        SSLContext ctx = SSLContext.getInstance(SslUtils.DEFAULT_SSL_PROTOCOL);
        ctx.init(kms, tms, null);
        return ctx;
    }

    static class TrustServerCertAlways implements X509TrustManager {

        @Override public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
            log.debug("Trusting all client certificates.");
        }

        @Override public void checkServerTrusted(X509Certificate[] arg0, String arg1)   throws CertificateException {
            log.debug("Trusting all server certificates.");
        }

        @Override public X509Certificate[] getAcceptedIssuers() {
            log.debug("No accepted issuers.");
            return null;
        }
    }

}

И, наконец, класс SslUtils, содержащий методы "читать и восстанавливать" (полная версия, включая методы "получить адрес электронной почты из сертификата", доступна в вышеупомянутом проекте Github):

import java.io.InputStream;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore;
import java.security.KeyStore.LoadStoreParameter;
import java.security.cert.X509Certificate;
import java.util.*;

import javax.net.ssl.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SslUtils {

    private static final Logger log = LoggerFactory.getLogger(SslUtils.class);

    /**
     * List of SSL protocols (SSLv3, TLSv1.2, etc.). See also {@link SslUtils#DEFAULT_SSL_PROTOCOL}.
     * <br>Documented at http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext
     */
    public static final String[] SSL_PROTOCOLS = new String[] { "SSL", "SSLv2", "SSLv3", "TLS", "TLSv1", "TLSv1.1", "TLSv1.2" };

    /**
     * Default SSL protocol to use ("TLSv1.2").
     */
    public static final String DEFAULT_SSL_PROTOCOL = "TLSv1.2";

    /**
     * Creates a default SSL context with an empty key-store and the default JRE trust-store.
     */
    public static SSLContext createDefaultSslContext() throws Exception {
        return createSslContext(null, null, null, null);
    }
    /**
     * Creates a default SSL socket factory.
     * <br>All system properties related to trust/key-stores are ignored, eveything is done programmatically.
     * This is because the Sun implementation reads the system-properties once and then caches the values.
     * Among other things, this fails the unit tests.
     * <br>For reference, the system properties (again, NOT USED):
     * <br> - javax.net.ssl.trustStore (default cacerts.jks)
     * <br> - javax.net.ssl.trustStorePassword
     * <br>and for client certificate:
     * <br> - javax.net.ssl.keyStore (set to "agent-cert.p12")
     * <br> - javax.net.ssl.keyStoreType (set to "pkcs12")
     * <br> - javax.net.ssl.keyStorePassword
     * <br>See for a discussion:
     * https://stackoverflow.com/info/6340918/trust-store-vs-key-store-creating-with-keytool
     * <br>See for client certificates in Java:
     * https://stackoverflow.com/info/1666052/java-https-client-certificate-authentication
     * @param keyStoreFileName The name (ending with pfx) of the file with client certificates.
     * @param trustStoreFileName The name (ending with jks) of the Java KeyStore with trusted (root) certificates.
     * @return null or the SSLContext.
     */
    public static SSLContext createSslContext(Path keyStoreFile, String keyStorePwd, 
            Path trustStoreFile, String trustStorePwd) throws Exception {
        return createSslContext(keyStoreFile, keyStorePwd, trustStoreFile, trustStorePwd, DEFAULT_SSL_PROTOCOL);
    }

    /**
     * See {@link #createSslContext(Path, String, Path, String)}.
     * @param sslProtocol a value from {@link #SSL_PROTOCOLS}.
     */
    public static SSLContext createSslContext(Path keyStoreFile, String keyStorePwd, 
            Path trustStoreFile, String trustStorePwd, String sslProtocol) throws Exception {

        KeyManagerFactory kmf = loadKeyStore(keyStoreFile, keyStorePwd == null ? null : keyStorePwd.toCharArray());
        TrustManagerFactory tmf = loadTrustStore(trustStoreFile, trustStorePwd == null ? null : trustStorePwd.toCharArray());
        //set an Authenticator to generate username and password
        SSLContext ctx = SSLContext.getInstance(sslProtocol);
        ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
        return ctx;
    }

    /**
     * Calls {@link #createSslContextFromClientKeyStore(Path, String, Path, String)} with the {@link #DEFAULT_SSL_PROTOCOL}.
     */
    public static SSLContext createSslContextFromClientKeyStore(Path keyStoreFile, String keyStorePwd, 
            String caAlias) throws Exception {
        return createSslContextFromClientKeyStore(keyStoreFile, keyStorePwd, caAlias, DEFAULT_SSL_PROTOCOL);
    }

    /**
     * Creates a SSL context from the given key-store containing a client certificate and a (CA) root certificate.
     * The root certificate is set in the trust-store of the SSL context.  
     * @param keyStoreFileName key-store file name (ending with .pfx).
     * @param keyStorePwd key-store password
     * @param caAlias the alias to use for the CA (root) certificate (e.g. "mycaroot").
     * @param sslProtocol the ssl-protocol (e.g. {@link #DEFAULT_SSL_PROTOCOL}).
     */
    public static SSLContext createSslContextFromClientKeyStore(Path keyStoreFile, String keyStorePwd, 
            String caAlias, String sslProtocol) throws Exception {

        KeyManagerFactory kmf = loadKeyStore(keyStoreFile, keyStorePwd == null ? null : keyStorePwd.toCharArray());
        List<X509Certificate> certs = getClientCaCerts(kmf.getKeyManagers());
        if (certs.size() < 1) {
            throw new Exception("Cannot find CA (root) certificate in key-managers from key store "  + keyStoreFile.getFileName());
        }
        TrustManagerFactory tmf = createTrustStore(caAlias, certs.get(0));
        SSLContext ctx = SSLContext.getInstance(sslProtocol);
        ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
        return ctx;
    }

    public static KeyManagerFactory loadKeyStore(Path storeFile) throws Exception {
        return loadKeyStore(storeFile, null);
    }

    public static KeyManagerFactory loadKeyStore(Path storeFile, char[] storePwd) throws Exception {
        return loadKeyStore(storeFile, storePwd, null, null);
    }

    public static KeyManagerFactory loadKeyStore(Path storeFile, char[] storePwd, 
            String storeType, String algorithm) throws Exception {

        KeyManagerFactory kmf = null;
        if (storeFile == null) {
            kmf = loadKeyStore((InputStream)null, storePwd, storeType, algorithm);
        } else {
            try (InputStream storeIn = Files.newInputStream(storeFile)) {
                kmf = loadKeyStore(storeIn, storePwd, storeType, algorithm);
                log.info("Initialized certificate key-store from ["  + storeFile.getFileName() + "]");
            }
        }
        return kmf;
    }

    public static KeyManagerFactory loadKeyStore(InputStream storeIn, char[] storePwd, 
            String storeType, String algorithm) throws Exception {

        if (storePwd == null && storeIn != null) {
            storePwd = "changeit".toCharArray();
            log.debug("Using default key store password.");
        }
        if (storeType == null) {
            storeType = "pkcs12";
            log.debug("Using default key store type " + storeType);
        }
        if (algorithm == null) {
            algorithm = KeyManagerFactory.getDefaultAlgorithm(); // "SunX509"
            log.debug("Using default key store algorithm " + algorithm);
        }
        KeyManagerFactory kmf = null;
        KeyStore keyStore = loadStore(storeIn, storePwd, storeType);
        kmf = KeyManagerFactory.getInstance(algorithm);
        kmf.init(keyStore, storePwd);
        if (storeIn == null) {
            log.info("Initialized a default certificate key-store");
        }
        return kmf;
    }

    /**
     * Creates a trust-store with the given CA (root) certificate.
     * @param certAlias the alias for the certificate (e.g. "mycaroot")
     * @param caCert the CA (root) certificate
     * @return an initialized trust manager factory.
     */
    public static TrustManagerFactory createTrustStore(String certAlias, X509Certificate caCert) throws Exception {

        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load((LoadStoreParameter)null); // must initialize the key-store
        ks.setCertificateEntry(certAlias, caCert);
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ks);
        return tmf;
    }

    public static TrustManagerFactory loadTrustStore(Path storeFile) throws Exception {
        return loadTrustStore(storeFile, null);
    }

    public static TrustManagerFactory loadTrustStore(Path storeFile, char[] storePwd) throws Exception {
        return loadTrustStore(storeFile, storePwd, null, null);
    }

    public static TrustManagerFactory loadTrustStore(Path storeFile, char[] storePwd, 
            String storeType, String algorithm) throws Exception {

        TrustManagerFactory tmf = null;
        if (storeFile == null) {
            tmf = loadTrustStore((InputStream)null, storePwd, storeType, algorithm);
        } else {
            try (InputStream storeIn = Files.newInputStream(storeFile)) {
                tmf = loadTrustStore(storeIn, storePwd, storeType, algorithm);
            }
            log.info("Initialized certificate trust-store from ["  + storeFile.getFileName() + "]");
        }
        return tmf;
    }

    public static TrustManagerFactory loadTrustStore(InputStream storeIn, char[] storePwd, 
            String storeType, String algorithm) throws Exception {

        if (storePwd == null && storeIn != null) {
            storePwd = "changeit".toCharArray();
            log.debug("Using default trust store password.");
        }
        if (storeType == null) {
            storeType = KeyStore.getDefaultType();
            log.debug("Using default trust store type " + storeType);
        }
        if (algorithm == null) {
            algorithm = TrustManagerFactory.getDefaultAlgorithm();
            log.debug("Using default trust store algorithm " + algorithm);
        }
        TrustManagerFactory tmf = null;
        KeyStore trustStore = loadStore(storeIn, storePwd, storeType);
        tmf = TrustManagerFactory.getInstance(algorithm);
        tmf.init(trustStore);
        if (storeIn == null) {
            log.info("Initialized a default certificate trust-store");
        }
        return tmf;
    }

    /**
     * Creates a default trust store containing the JRE certificates in {@code JAVA_HOME\lib\security\cacerts.jks}
     * <br>To view loaded certificates call 
     * <br>{@code System.setProperty("javax.net.debug", "ssl,trustmanager");}
     * <br>before calling this method.
     */
    public static TrustManagerFactory createDefaultTrustStore() throws Exception {

        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init((KeyStore)null);
        return tmf;
    }

    /**
     * @param in if null, null is returned.
     */
    public static KeyStore loadStore(InputStream in, char[] pwd, String type) throws Exception {

        if (in == null) {
            return null;
        }
        KeyStore ks = KeyStore.getInstance(type);
        ks.load(in, pwd);
        return ks;
    }

    /**
     * Finds any CA (root) certificates present in client certificate chains.
     * <br>Uses {@link #getClientAliases(KeyManager)}
     * @param kms key-managers (from a key-store).
     * @return an empty list or a list containing CA (root) certificates.
     */
    public static List<X509Certificate> getClientCaCerts(KeyManager[] kms) {

        List<X509Certificate> caCerts = new LinkedList<X509Certificate>();
        for (int i = 0; i < kms.length; i++) {
            if (!(kms[i] instanceof X509KeyManager)) {
                continue;
            }
            X509KeyManager km = (X509KeyManager) kms[i];
            List<String> aliases = getClientAliases(km);
            for (String alias: aliases) {
                X509Certificate[] cchain = km.getCertificateChain(alias);
                if (cchain == null || cchain.length < 2) {
                    continue;
                }
                // first certificate in chain is the user certificate
                // last certificate is the CA (root certificate).
                caCerts.add(cchain[cchain.length-1]);
                if (log.isDebugEnabled()) {
                    log.debug("Found 1 root certificate from client certificate alias " + alias);
                }
            }
        }
        return caCerts;
    }

    /**
     * List of key types for client certificate aliases, used in {@link #getAliases(KeyManager)}
     * <br>List is documented at 
     * http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#jssenames
     */
    public static final String[] KEY_TYPES = new String[] {"RSA", "DSA", "DH_RSA", "DH_DSA", "EC", "EC_EC", "EC_RSA" };

    /**
     * Searches for client aliases in the given key-manager. 
     * Does nothing when the given key-manager is not an instance of {@link X509KeyManager}. 
     * @return an empty list or a list containing client aliases found in the key-manager.
     */
    public static List<String> getClientAliases(KeyManager keyManager) {

        List<String> aliases = new LinkedList<String>();
        if (keyManager instanceof X509KeyManager) {
            X509KeyManager km = (X509KeyManager) keyManager;
            for (String keyType: KEY_TYPES) {
                String[] kmAliases = km.getClientAliases(keyType, null);
                if (kmAliases != null) {
                    for (String alias: kmAliases) {
                        if (!isEmpty(alias)) {
                            aliases.add(alias);
                        }
                    }
                }
            } // for keytypes
        }
        return aliases;
    }

    /**
     * Sets the default authenticator which can be used for example with http-request that require basic authoriation.
     * <br>See also {@link Authenticator#setDefault(Authenticator)}.
     */
    public static void setDefaultAuthenticator(final String userName, final char[] pwd) throws Exception {

        Authenticator auth = new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(userName, pwd);
            }
        };
        Authenticator.setDefault(auth);
    }

    /**
     * @return true if s is not null and not empty after trimming, false otherwise.
     */
    public static boolean isEmpty(String s) { return (s == null || s.trim().isEmpty()); }

}

На стороне node: Java переводит тип кэста по умолчанию из JKS в PKCS12 (см. JEP 229).