Coder Social home page Coder Social logo

hakky54 / sslcontext-kickstart Goto Github PK

View Code? Open in Web Editor NEW
481.0 8.0 76.0 3.94 MB

🔐 A lightweight high level library for configuring a http client or server based on SSLContext or other properties such as TrustManager, KeyManager or Trusted Certificates to communicate over SSL TLS for one way authentication or two way authentication provided by the SSLFactory. Support for Java, Scala and Kotlin based clients with examples. Available client examples are: Apache HttpClient, OkHttp, Spring RestTemplate, Spring WebFlux WebClient Jetty and Netty, the old and the new JDK HttpClient, the old and the new Jersey Client, Google HttpClient, Unirest, Retrofit, Feign, Methanol, Vertx, Scala client Finagle, Featherbed, Dispatch Reboot, AsyncHttpClient, Sttp, Akka, Requests Scala, Http4s Blaze, Kotlin client Fuel, http4k Kohttp and Ktor. Also gRPC, WebSocket and ElasticSearch examples are included

Home Page: https://sslcontext-kickstart.com/

License: Apache License 2.0

Java 99.99% Shell 0.01%
tls ssl certificate keystore truststore mutual-authentication java security https scala

sslcontext-kickstart's Introduction

Actions Status Security Rating Known Vulnerabilities Coverage JDK Compatibility Kotlin Compatibility Scala Compatibility Android API Compatibility Apache2 license Maven Central javadoc Dependencies: none GitHub stars chart Join the chat at https://gitter.im/hakky54/sslcontext-kickstart

SonarCloud

SSLContext Kickstart 🔐 Tweet

Hey, hello there 👋 Welcome, I hope you will like this library ❤️ Feel free to drop a message in the 📖 Guestbook, I would love to hear your story and experience in using this library.

Install library with:

Install with Maven

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>sslcontext-kickstart</artifactId>
    <version>8.3.6</version>
</dependency>

Install with Gradle

implementation 'io.github.hakky54:sslcontext-kickstart:8.3.6'

Install with Gradle Kotlin DSL

implementation("io.github.hakky54:sslcontext-kickstart:8.3.6")

Install with Scala SBT

libraryDependencies += "io.github.hakky54" % "sslcontext-kickstart" % "8.3.6"

Install with Apache Ivy

<dependency org="io.github.hakky54" name="sslcontext-kickstart" rev="8.3.6"/>

Table of contents

  1. Introduction
  2. Usage
  3. Additional mappers for specific libraries
  4. Tested HTTP Clients
  5. Contributing
  6. Contributors

Introduction

SSLContext Kickstart is a library which provides a High-Level SSLFactory class for configuring a http client or a server to communicate over SSL/TLS for one way authentication or two-way authentication. It is designed to be as lightweight as possible by having minimized the external dependencies. The core library only depends on the SLF4J logging API.

History

As a Java developer I worked for different kinds of clients. Most of the time the application required to call other microservices within the organization or some other http servers. These requests needed to be secured, and therefore it was required to load the ssl materials into the http client. Each http client may require different input value to enable https requests, and therefore I couldn't just copy-paste my earlier configuration into the new project. The resulting configuration was in my opinion always verbose, not reusable, hard to test and hard to maintain.

As a developer you also need to know how to properly load your file into your application and consume it as a KeyStore instance. Therefore, you also need to understand how to properly create for example a KeyManager and a TrustManager for you SSLContext. The sslcontext-kickstart library is taking the responsibility of creating an instance of SSLContext from the provided arguments, and it will provide you all the ssl materials which are required to configure 40+ different Http Client for Java, Scala and Kotlin. I wanted the library to be as easy as possible to use for all developers to give them a kickstart when configuring their Http Client. So feel free to provide feedback or feature requests. The library also provides other utilities such as:

See the javadoc for all the options.

Acknowledgement

I would like to thank Cody A. Ray for his contribution to the community regarding loading multiple Keystores into the SSLContext. The limitation of the JDK is to only support one keystore for the KeyManagerFactory and only one keystore for the TrustManagerFactory. The code snippets which Cody has shared are now available within this library and can be found here: CompositeX509KeyManager and CompositeX509TrustManager

The original content can be found here:

Advantages:

  • No need for low-level SSLContext configuration anymore
  • No knowledge needed about SSLContext, TrustManager, TrustManagerFactory, KeyManager, KeyManagerFactory and how to create it.
  • Above classes will all be created with just providing an identity and a trust material
  • Load multiple identities/trustStores/keyManagers/trustManagers
  • Hot reload ssl material without need of restarting/recreating Http Client or Server

Definitions

  • Identity material: A KeyStore or KeyManager which holds the key pair also known as private and public key
  • Trust material: A KeyStore or TrustManager containing one or more certificates also known as public key. This KeyStore contains a list of trusted certificates
  • One way authentication (also known as one way tls, one way ssl): Https connection where the client validates the certificate of the counter party
  • Two way authentication (also known as two way tls, two way ssl, mutual authentication): Https connection where the client as well as the counter party validates the certificate, also known as mutual authentication

Compatibility

Java Kotlin Scala Android
8+ 1.5+ 2.11+ 24+

Usage

Basic example configuration

Example configuration with apache http client, or click here to view the other client configurations

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.json.JSONException;
import org.json.JSONObject;

import nl.altindag.ssl.SSLFactory;

public class App {

    public static void main(String[] args) throws IOException, JSONException {
        SSLFactory sslFactory = SSLFactory.builder()
                .withDefaultTrustMaterial()
                .build();

        HttpClient httpClient = HttpClients.custom()
                .setSSLContext(sslFactory.getSslContext())
                .setSSLHostnameVerifier(sslFactory.getHostnameVerifier())
                .build();

        HttpGet request = new HttpGet("https://api.chucknorris.io/jokes/random");

        HttpResponse response = httpClient.execute(request);
        String chuckNorrisJoke = new JSONObject(EntityUtils.toString(response.getEntity())).getString("value");

        System.out.println(String.format("Received the following status code: %d", response.getStatusLine().getStatusCode()));
        System.out.println(String.format("Received the following joke: %s", chuckNorrisJoke));
    }

}

Response:

Received the following status code: 200
Received the following joke: If a black cat crosses your path, you have bad luck. If Chuck Norris crosses your path, it was nice knowing you.

Other possible configurations

Loading keystore and truststore from the classpath
SSLFactory.builder()
          .withIdentityMaterial("identity.jks", "password".toCharArray())
          .withTrustMaterial("truststore.jks", "password".toCharArray())
          .build();
Loading keystore and trust store from anywhere on the filesystem
SSLFactory.builder()
          .withIdentityMaterial(Paths.get("/path/to/your/identity.jks"), "password".toCharArray())
          .withTrustMaterial(Paths.get("/path/to/your/truststore.jks"), "password".toCharArray())
          .build();
Loading keystore and trust store from InputStream
InputStream keyStoreStream = ...
InputStream trustStoreStream = ...

SSLFactory.builder()
          .withIdentityMaterial(keyStoreStream, "password".toCharArray())
          .withTrustMaterial(trustStoreStream, "password".toCharArray())
          .build();
Loading trust material with OCSP options
Loading trust material with TrustStore and OCSP options from the classpath
CertPathBuilder certPathBuilder = CertPathBuilder.getInstance("PKIX");
PKIXRevocationChecker revocationChecker = (PKIXRevocationChecker) certPathBuilder.getRevocationChecker();
revocationChecker.setOptions(EnumSet.of(PKIXRevocationChecker.Option.NO_FALLBACK));

SSLFactory sslFactory = SSLFactory.builder()
        .withTrustMaterial("truststore.jks", "password".toCharArray(), trustStore -> {
            PKIXBuilderParameters pkixBuilderParameters = new PKIXBuilderParameters(trustStore, new X509CertSelector());
            pkixBuilderParameters.addCertPathChecker(revocationChecker);
            return new CertPathTrustManagerParameters(pkixBuilderParameters);
        })
        .build();
Loading trust material with TrustStore and OCSP options from the file system
CertPathBuilder certPathBuilder = CertPathBuilder.getInstance("PKIX");
PKIXRevocationChecker revocationChecker = (PKIXRevocationChecker) certPathBuilder.getRevocationChecker();
revocationChecker.setOptions(EnumSet.of(PKIXRevocationChecker.Option.NO_FALLBACK));

SSLFactory sslFactory = SSLFactory.builder()
        .withTrustMaterial(Paths.get("/path/to/your/truststore.jks"), "password".toCharArray(), trustStore -> {
            PKIXBuilderParameters pkixBuilderParameters = new PKIXBuilderParameters(trustStore, new X509CertSelector());
            pkixBuilderParameters.addCertPathChecker(revocationChecker);
            return new CertPathTrustManagerParameters(pkixBuilderParameters);
        })
        .build();
Loading trust material with TrustManager and OCSP options
X509ExtendedTrustManager trustManager = ...

CertPathBuilder certPathBuilder = CertPathBuilder.getInstance("PKIX");
PKIXRevocationChecker revocationChecker = (PKIXRevocationChecker) certPathBuilder.getRevocationChecker();
revocationChecker.setOptions(EnumSet.of(PKIXRevocationChecker.Option.NO_FALLBACK));

SSLFactory sslFactory = SSLFactory.builder()
        .withTrustMaterial(trustManager, trustStore -> {
            PKIXBuilderParameters pkixBuilderParameters = new PKIXBuilderParameters(trustStore, new X509CertSelector());
            pkixBuilderParameters.addCertPathChecker(revocationChecker);
            return new CertPathTrustManagerParameters(pkixBuilderParameters);
        })
        .build();
Loading trust material with certificates and OCSP options
List<Certificate> certificates = ...

CertPathBuilder certPathBuilder = CertPathBuilder.getInstance("PKIX");
PKIXRevocationChecker revocationChecker = (PKIXRevocationChecker) certPathBuilder.getRevocationChecker();
revocationChecker.setOptions(EnumSet.of(PKIXRevocationChecker.Option.NO_FALLBACK));

SSLFactory sslFactory = SSLFactory.builder()
        .withTrustMaterial(certificates, trustStore -> {
            PKIXBuilderParameters pkixBuilderParameters = new PKIXBuilderParameters(trustStore, new X509CertSelector());
            pkixBuilderParameters.addCertPathChecker(revocationChecker);
            return new CertPathTrustManagerParameters(pkixBuilderParameters);
        })
        .build();
Enhanceable trust validations

By default, the TrustManager ships with default validations to validate if the counterparty is trusted during the SSL Handshake. If needed the default behaviour can be overruled by custom validators. If a custom validator is specified and if the condition evaluates to true, then the certificate of the counterparty will be trusted. If the condition evaluates to false, than it will fall back to the default behaviour of the TrustManager.

SSLFactory.builder()
          .withDefaultTrustMaterial()
          .withTrustEnhancer(trustManagerParameters -> {
              X509Certificate[] chain = trustManagerParameters.getChain();
              return chain[0].getIssuerX500Principal().getName().equals("Foo")
                      && chain[0].getSubjectX500Principal().getName().equals("Bar");
          })
          .build();
Hide trusted certificate names of a server

By default, a server exposes the list of trusted certificate names if requested by the client. The list of trusted certificate names can be requested with:

openssl s_client -showcerts -servername 127.0.0.1 -connect 127.0.0.1:8443

The output will be under Acceptable client certificate CA names: alt text

For some end-user this might lead into information leaks and security risks. This information can be hidden away so the client cannot request it anymore with an additional option within the SSLFactory#withConcealedTrustMaterial(). An example usage would be:

SSLFactory updatedSslFactory = SSLFactory.builder()
      .withIdentityMaterial(Paths.get("/path/to/your/identity.jks"), "password".toCharArray())
      .withTrustMaterial(Paths.get("/path/to/your/truststore.jks"), "password".toCharArray())
      .withConcealedTrustMaterial()
      .build();

Which will result into the following output: alt text

Trusting all certificates without validation, not recommended to use at production!
SSLFactory.builder()
          .withUnsafeTrustMaterial()
          .build();
Skip hostname validation
SSLFactory.builder()
          .withDefaultTrustMaterial()
          .withUnsafeHostnameVerifier()
          .build();
Loading JDK and OS trusted certificates
SSLFactory.builder()
          .withDefaultTrustMaterial()
          .withSystemTrustMaterial()
          .build();
Using specific protocols, ciphers with custom secure random and hostname verifier

If you are using java 11 or newer, than you are also able to use TLSv1.3 as encryption protocol by default.

SSLFactory.builder()
          .withDefaultTrustMaterial()
          .withProtocols("TLSv1.3", "TLSv1.2")
          .withCiphers("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384")
          .withHostnameVerifier(hostnameVerifier)
          .withSecureRandom(secureRandom)
          .build();

Enhanceable hostname verifier

If you want to improve or whitelist certain hostnames from the default hostname verifier, you can apply the snippet bellow. If the method body returns true the hostname will be trusted by default

SSLFactory sslFactory = SSLFactory.builder()
          .withDefaultTrustMaterial()
          .withHostnameVerifierEnhancer(parameters -> "localhost".equals(parameters.getHostname()))
          .build();
Support for using multiple identity materials and trust materials
SSLFactory.builder()
          .withIdentityMaterial("identity-1.jks", password)
          .withIdentityMaterial("identity-2.jks", password)
          .withIdentityMaterial("identity-3.jks", password)
          .withIdentityMaterial("identity-4.jks", password)
          .withTrustMaterial("truststore-1.jks", password)
          .withTrustMaterial("truststore-2.jks", password)
          .withTrustMaterial("truststore-3.jks", password)
          .withTrustMaterial("truststore-4.jks", password)
          .build();

In some use cases multiple identities can fail to work. If that happens please try to add the additional SSLFactory option of identity route. See here for more: Routing identity to specific host

Support for using X509ExtendedKeyManager and X509ExtendedTrustManager
X509ExtendedKeyManager keyManager = ...
X509ExtendedTrustManager trustManager = ...

SSLFactory.builder()
          .withIdentityMaterial(keyManager)
          .withTrustMaterial(trustManager)
          .build();
Using dummy identity and trust material

In some use cases it may be useful to use a dummy identity or trust material. An example use case would be to create a base SSLFactory with the dummies which can be swapped afterwords. See below for a refactored version of Support for swapping KeyManager and TrustManager at runtime.

SSLFactory baseSslFactory = SSLFactory.builder()
          .withDummyIdentityMaterial()
          .withDummyTrustMaterial()
          .withSwappableIdentityMaterial()
          .withSwappableTrustMaterial()
          .build();
          
HttpClient httpClient = HttpClient.newBuilder()
          .sslParameters(sslFactory.getSslParameters())
          .sslContext(sslFactory.getSslContext())
          .build()
          
Runnable sslUpdater = () -> {
    SSLFactory updatedSslFactory = SSLFactory.builder()
          .withIdentityMaterial(Paths.get("/path/to/your/identity.jks"), "password".toCharArray())
          .withTrustMaterial(Paths.get("/path/to/your/truststore.jks"), "password".toCharArray())
          .build();
    
    SSLFactoryUtils.reload(baseSslFactory, updatedSslFactory);
};

// initial update of ssl material to replace the dummies
sslUpdater.run();
   
// update ssl material every hour    
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(sslUpdater, 1, 1, TimeUnit.HOURS);

HttpResponse<String> response = httpClient.send(aRequest, HttpResponse.BodyHandlers.ofString());
Support for using a single KeyStore which contains multiple keys with different passwords
KeyStore keyStore = ...
X509ExtendedKeyManager keyManager = KeyManagerUtils.createKeyManager(keyStore, Map.of(
        "foo","foo-password".toCharArray(),
        "bar","bar-password".toCharArray(),
        "lorum-ipsum","lorum-ipsum-password".toCharArray()
));

SSLFactory.builder()
          .withIdentityMaterial(keyManager)
          .withDefaultTrustMaterial()
          .build();
Support for using PrivateKey and Certificates
PrivateKey privateKey = ...
char[] privateKeyPassword = ...
Certificate[] certificateChain = ...

Certificate trustedCertificate = ...

SSLFactory.builder()
          .withIdentityMaterial(privateKey, privateKeyPassword, certificateChain)
          .withTrustMaterial(trustedCertificate)
          .build();
Support for reloading ssl at runtime
Reload identity and trust material

It is possible to reload or update the ssl configuration while already using it with your client or server without the need of restarting your application or recreating it with SSLFactory. The identity and trust material may expire at some point in time and needs to be replaced to be still functional. Restart of the application with a traditional setup is unavoidable and can result into a downtime for x amount of time. A restart is not needed when using the setup below. The below example is a high-level method of reloading the ssl configuration, if you prefer to use a low-level setup please have a look at the following example displayed here: Hot swap KeyManager and TrustManager at runtime.

SSLFactory baseSslFactory = SSLFactory.builder()
          .withDummyIdentityMaterial()
          .withDummyTrustMaterial()
          .withSwappableIdentityMaterial()
          .withSwappableTrustMaterial()
          .build();
          
HttpClient httpClient = HttpClient.newBuilder()
          .sslParameters(sslFactory.getSslParameters())
          .sslContext(sslFactory.getSslContext())
          .build()
          
Runnable sslUpdater = () -> {
    SSLFactory updatedSslFactory = SSLFactory.builder()
          .withIdentityMaterial(Paths.get("/path/to/your/identity.jks"), "password".toCharArray())
          .withTrustMaterial(Paths.get("/path/to/your/truststore.jks"), "password".toCharArray())
          .build();
    
    SSLFactoryUtils.reload(baseSslFactory, updatedSslFactory);
};

// initial update of ssl material to replace the dummies
sslUpdater.run();
   
// update ssl material every hour    
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(sslUpdater, 1, 1, TimeUnit.HOURS);

HttpResponse<String> response = httpClient.send(aRequest, HttpResponse.BodyHandlers.ofString());

See here for a basic reference implementation for a server: GitHub - Instant SSL Reloading The code example above cleans the cache instantly which forces any client or server to create a new ssl session and so it requires a new ssl handshake. If you prefer to use existing ssl session for existing connection, but want to use a new ssl session for new clients or servers, then you can use the following snippet below. In that way existing connections which already have done the ssl handshake won't require to do another handshake till the ssl session expires with the default timeout.

SSLFactoryUtils.reload(baseSslFactory, updatedSslFactory, false);

See also here for other examples:

Reload ssl parameters

Additionally the SSL parameters can also be reloaded such as ciphers. A basic example is demonstrated below:

SSLFactory sslFactory = SSLFactory.builder()
          .withIdentityMaterial(Paths.get("/path/to/your/identity.jks"), "password".toCharArray())
          .withTrustMaterial(Paths.get("/path/to/your/truststore.jks"), "password".toCharArray())
          .withSwappableSslParameters()
          .build();
          
sslFactory.getSslParameters().setCipherSuites(new String[]{"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256"})

Please note that this might not work for all http clients and servers. It works out of the box with Jetty, but for Netty it needs some additional configuration see the code snippet below. It basically depends on how the http client or server uses the ciphers or other ssl properties during the ssl handshake. Please be aware that this option has some limitations/drawbacks. It might cause other options of a server not to work, so it is advised to test this option in dept. If it breaks your server configuration such as ALPN I would not recommend to use reloadable ssl parameters. Updating your server properties and running a rolling update/restarting your server would be a better option.

The option below might be needed for some servers/clients to reload ssl parameters, for example Netty Server.

SSLFactory sslFactory = ... // your initialized SSLFactory similar to the above one with SwappableSslParameters 
Provider provider = ProviderUtils.create(sslFactory);
Security.insertProviderAt(provider, 1);

// Initialize your server at this point
Support for swapping KeyManager and TrustManager at runtime

It is possible to swap a KeyManager and TrustManager from a SSLContext, SSLSocketFactory and SSLServerSocketFactory while already using it within your client or server at runtime. This option will enable to refresh the identity and trust material of a server or client without the need of restarting your application or recreating it with SSLFactory. The identity and trust material may expire at some point in time and needs to be replaced to be still functional. Restart of the application with a traditional setup is unavoidable and can result into a downtime for x amount of time. A restart is not needed when using the setup below.

SSLFactory baseSslFactory = SSLFactory.builder()
          .withIdentityMaterial(Paths.get("/path/to/your/identity.jks"), "password".toCharArray())
          .withTrustMaterial(Paths.get("/path/to/your/truststore.jks"), "password".toCharArray())
          .withSwappableIdentityMaterial()
          .withSwappableTrustMaterial()
          .build();
          
HttpClient httpClient = HttpClient.newBuilder()
          .sslParameters(sslFactory.getSslParameters())
          .sslContext(sslFactory.getSslContext())
          .build()

// execute https request
HttpResponse<String> response = httpClient.send(aRequest, HttpResponse.BodyHandlers.ofString());

SSLFactory updatedSslFactory = SSLFactory.builder()
          .withIdentityMaterial(Paths.get("/path/to/your/identity.jks"), "password".toCharArray())
          .withTrustMaterial(Paths.get("/path/to/your/truststore.jks"), "password".toCharArray())
          .build();
          
// swap identity and trust materials and reuse existing http client
KeyManagerUtils.swapKeyManager(baseSslFactory.getKeyManager().get(), updatedSslFactory.getKeyManager().get());
TrustManagerUtils.swapTrustManager(baseSslFactory.getTrustManager().get(), updatedSslFactory.getTrustManager().get());

// Cleanup old ssl sessions by invalidating them all. Forces to use new ssl sessions which will be created by the swapped KeyManager/TrustManager
SSLSessionUtils.invalidateCaches(baseSslFactory.getSslContext());

HttpResponse<String> response = httpClient.send(aRequest, HttpResponse.BodyHandlers.ofString());

See here for a basic reference implementation for a server: GitHub - Instant SSL Reloading

Trust additional new certificates at runtime

Although it is possible to reload the complete trust material as shown before in Reloading SSL at runtime and Hot swap KeyManager and TrustManager at runtime, in some occasions you might want the trust additional new certificates without reloading all the trust material as it might be redundant. Especially if you want to keep other trust material intact which is already loaded to your SSLFactory and you don't want it to be reloaded. An example use case would be using the JDK and OS trusted Certificates Authorities and your custom truststore which can grow over time. See below for two examples:

Option 1
SSLFactory sslFactory = SSLFactory.builder()
        .withDefaultTrustMaterial()
        .withSystemTrustMaterial()
        .withInflatableTrustMaterial()
        .build();

List<X509Certificate> certificates = ... ; // after some point in time you have a couple of new CA which you want to trust

TrustManagerUtils.addCertificate(sslFactory.getTrustManager().get(), certificates);

With the option below your newly trusted certificates will be also stored on the file-system. If the file exists then it will first read and append to it. The predicate is thread-safe and can be used for example prompting the user to trust the certificate if integrated in a GUI.

Option 2
SSLFactory sslFactory = SSLFactory.builder()
        .withDefaultTrustMaterial()
        .withSystemTrustMaterial()
        .withInflatableTrustMaterial(Paths.get("/path/to/truststore.p12"), "password".toCharArray(), "PKCS12", trustManagerParameters -> {
            // do some validation to decide whether to trust this certificate
            return true;
        })
        .build();

A proof-of-concept is available here: GitHub - Trust Me which demonstrates how it can be integrated in a GUI and prompting the end-user to either trust or reject the server certificate. It will be applied instantly and the ssl configuration will be reloaded.

Routing identity material to specific host

It may occur that the client is sending the wrong certificate to the server when using multiple identities. This will happen when the client certificate has insufficient information for the underlying ssl engine (the KeyManager) and therefore it cannot select the right certificate. Recreating the certificates can resolve this issue. However, if that is not possible you can provide an option to the engine to use a specific certificate for a given server. Below is an example setup for correctly routing the client identity based on the alias which can be found within the KeyStore file.

SSLFactory.builder()
          .withIdentityMaterial("identity-1.jks", password)
          .withIdentityMaterial("identity-2.jks", password)
          .withTrustMaterial("truststore.jks", password)
          .withIdentityRoute("client-alias-one", "https://localhost:8443/", "https://localhost:8453/")
          .withIdentityRoute("client-alias-two", "https://localhost:8463/", "https://localhost:8473/")
          .build();
Updating identity routes at runtime
SSLFactory sslFactory = SSLFactory.builder()
          .withIdentityMaterial("identity-1.jks", password)
          .withIdentityMaterial("identity-2.jks", password)
          .withTrustMaterial("truststore.jks", password)
          .withIdentityRoute("client-alias-one", "https://localhost:8443/", "https://localhost:8453/")
          .withIdentityRoute("client-alias-two", "https://localhost:8463/", "https://localhost:8473/")
          .build();

X509ExtendedKeyManager keyManager = sslFactory.getKeyManager().get()

// Add additional routes next to the existing ones
KeyManagerUtils.addIdentityRoute(keyManager, "client-alias-one", "https://localhost:8463/", "https://localhost:8473/")

// Override existing routes
KeyManagerUtils.overrideIdentityRoute(keyManager, "client-alias-two", "https://localhost:9463/", "https://localhost:9473/")
Managing ssl session
SSLFactory sslFactory = SSLFactory.builder()
          .withIdentityMaterial("identity.jks", "password".toCharArray())
          .withTrustMaterial("truststore.jks", "password".toCharArray())
          .withSessionTimeout(3600) // Amount of seconds until it will be invalidated
          .withSessionCacheSize(1024) // Amount of bytes until it will be invalidated
          .build();
          
SSLContext sslContext = sslFactory.getSslContext();
          
// Caches can be invalidated with the snippet below
SSLSessionUtils.invalidateCaches(sslContext);

// or any other option:
SSLSessionUtils.invalidateCachesBefore(
        sslContext,
        ZonedDateTime.of(LocalDateTime.of(2021, JANUARY, 1, 15, 55), ZoneOffset.UTC)
);

SSLSessionUtils.invalidateCachesAfter(
        sslContext,
        ZonedDateTime.of(LocalDateTime.of(2021, FEBRUARY, 10, 8, 14), ZoneOffset.UTC)
);

SSLSessionUtils.invalidateCachesBetween(
        sslContext,
        ZonedDateTime.now().minusHours(2),    // from
        ZonedDateTime.now()                   // up till
);
Extracting server certificates
Single server
List<X509Certificate> certificates = CertificateUtils.getCertificatesFromExternalSource("https://github.com/");
Bulk extraction from multiple servers
Map<String, List<X509Certificate>> certificates = CertificateUtils.getCertificatesFromExternalSources(
            "https://github.com/", 
            "https://stackoverflow.com/", 
            "https://www.reddit.com/",
            "https://www.youtube.com/");
Extracting certificates behind proxy
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("my-custom-host", 1234));
List<X509Certificate> certificates = CertificateUtils.getCertificatesFromExternalSource(proxy, "https://github.com/");
Extracting certificates behind proxy with authentication
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("my-custom-host", 1234));
PasswordAuthentication passwordAuthentication = new PasswordAuthentication("foo", "bar".toCharArray());
List<X509Certificate> certificates = CertificateUtils.getCertificatesFromExternalSource(proxy, passwordAuthentication, "https://github.com/");
Extracting certificates as pem

All previous examples are also available for extracting the server certificates as pem. The method has an additional asPem suffix. See below for all of the examples:

// single
List<String> certificates = CertificateUtils.getCertificatesFromExternalSourceAsPem("https://github.com/");

// bulk
Map<String, List<X509Certificate>> urlsToCertificates = CertificateUtils.getCertificatesFromExternalSourcesAsPem(
            "https://github.com/", 
            "https://stackoverflow.com/", 
            "https://www.reddit.com/",
            "https://www.youtube.com/");
    
// proxy        
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("my-custom-host", 1234));
certificates = CertificateUtils.getCertificatesFromExternalSource(proxy, "https://github.com/");

// proxy + authentication
PasswordAuthentication passwordAuthentication = new PasswordAuthentication("foo", "bar".toCharArray());
certificates = CertificateUtils.getCertificatesFromExternalSource(proxy, passwordAuthentication, "https://github.com/");

See here also for a demo application for the CLI: GitHub - Certificate Ripper

Using P7B or PKCS#7 Files

Support for using p7b formatted certificates and certificate-chain from classpath, any directory or as an InputStream. P7b file is a text file containing a -----BEGIN PKCS7----- as header, -----END PKCS7----- as footer and has a Base64 encoded data between it.

List<Certificate> certificates = CertificateUtils.loadCertificate("certificate.p7b");

SSLFactory.builder()
          .withTrustMaterial(certificates)
          .build();

Using DER Files

Support for using der formatted certificates and certificate-chain from classpath, any directory or as an InputStream. Der file is a binary form of a certificate. Commonly used extensions are .cer and crt.

List<Certificate> certificates = CertificateUtils.loadCertificate("certificate.cer");

SSLFactory.builder()
          .withTrustMaterial(certificates)
          .build();

Using PFX, P12 or PKCS#12 Files

PFX and p12 are both PKCS#12 type keystores which are supported.

SSLFactory.builder()
          .withIdentityMaterial("identity.p12", "password".toCharArray())
          .withTrustMaterial("truststore.p12", "password".toCharArray())
          .build();

Using PEM Files

Support for using pem formatted private key and certificates from classpath, any directory or as an InputStream. See PemUtilsShould for detailed usages. Add the dependency below to use this feature, it also includes the core features from the library such as SSLFactory.

<dependency>
    <groupId>io.github.hakky54</groupId>
  <artifactId>sslcontext-kickstart-for-pem</artifactId>
  <version>8.3.6</version>
</dependency>
Loading pem files from the classpath
X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial("certificate.pem", "private-key.pem");
X509ExtendedTrustManager trustManager = PemUtils.loadTrustMaterial("some-trusted-certificate.pem");

SSLFactory.builder()
          .withIdentityMaterial(keyManager)
          .withTrustMaterial(trustManager)
          .build();
Loading pem files from anywhere on the filesystem
X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial(Paths.get("/path/to/your/certificate.pem"), Paths.get("/path/to/your/private-key.pem"));
X509ExtendedTrustManager trustManager = PemUtils.loadTrustMaterial(Paths.get("/path/to/your/some-trusted-certificate.pem"));
Loading pem files from InputStream
InputStream privateKey = ...
InputStream certificate = ...
InputStream trustedCertificates = ...

X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial(certificate, privateKey);
X509ExtendedTrustManager trustManager = PemUtils.loadTrustMaterial(trustedCertificates);
Loading pem files from string content
String privateKey =
        "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" +
        "MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIy3Fposf+2ccCAggA\n" +
        "-----END ENCRYPTED PRIVATE KEY-----\n";

String certificate =
        "-----BEGIN CERTIFICATE-----\n" +
        "g0Y2YBH5v0xmi8sYU7weOcwynkjZARpUltBUQ0pWCF5uJsEB8uE8PPDD3c4=\n" +
        "-----END CERTIFICATE-----\n";

String trustedCertificates =
        "-----BEGIN CERTIFICATE-----\n" +
        "CC01zojqS10nGowxzOiqyB4m6wytmzf0QwjpMw==\n" +
        "-----END CERTIFICATE-----\n";

X509ExtendedKeyManager keyManager = PemUtils.parseIdentityMaterial(certificate, privateKey, "secret".toCharArray());
X509ExtendedTrustManager trustManager = PemUtils.parseTrustMaterial(trustedCertificates);
Loading encrypted pem files
X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial("certificate.pem", "private-key.pem", "secret".toCharArray());
Migrating from classic configuration

Below is an example of the classic configuration for enabling ssl for your application.

-Djavax.net.ssl.trustStore=/path/to/truststore.jks
-Djavax.net.ssl.trustStoreType=jks
-Djavax.net.ssl.trustStorePassword=changeit
-Djavax.net.ssl.trustStoreProvider=SunJSSE

-Djavax.net.ssl.keyStore=/path/to/keystore.jks
-Djavax.net.ssl.keyStoreType=jks
-Djavax.net.ssl.keyStorePassword=changeit
-Djavax.net.ssl.keyStoreProvider=SunJSSE

-Dhttps.protocols=TLSv1.3
-Dhttps.cipherSuites=TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384

SSLFactory can be used with these properties together with the existing properties with the following snippet:

SSLFactory sslFactory = SSLFactory.builder()
        .withSystemPropertyDerivedIdentityMaterial()
        .withSystemPropertyDerivedTrustMaterial()
        .withSystemPropertyDerivedProtocols()
        .withSystemPropertyDerivedCiphers()
        .build();

SSLContext.setDefault(sslFactory.getSslContext());

The SSLFactory returnable values can be supplied to the http client as shown here

Global SSL configuration

If it is not possible to adjust the ssl configuration of a server or client because it is using the default ssl configuration or using a pre-configured, then you can give the snippet below a try. The snippet below will ensure the default SSLContext will be the one which is constructed by SSLFactory and the Security utility of Java will also use the SSLContext of SSLFactory if it is initialized with SSLContext.getInstance("TLS") or any of the following protocols: SSL, SSLv2, SSLv3, TLSv1, TLSv1.1, TLSv1.2, TLSv1.3 See also a working demo here: Bypassing and overruling SSL configuration of libraries

// The SSLFactory below is just an example, use your own custom initialized one here
SSLFactory sslFactory = SSLFactory.builder()
        .withDefaultTrustMaterial()
        .withSystemTrustMaterial()
        .build();

Provider provider = ProviderUtils.create(sslFactory);
Security.insertProviderAt(provider, 1);
SSLContext.setDefault(sslFactory.getSslContext());
Logging detailed certificate validation
SSLFactory sslFactory = SSLFactory.builder()
        .withTrustMaterial(Paths.get("/path/to/your/truststore.jks"), "password".toCharArray())
        .withLoggingTrustMaterial()
        .build();
        
// run your server or client and analyse the logs

You will get a log message which is similar to the following one:

Validating the certificate chain of the server[google.com:443] with authentication type RSA, while also using the SSLEngine. See below for the full chain of the server:
[[
[
  Version: V3
  Subject: CN=*.google.com, O=Google LLC, L=Mountain View, ST=California, C=US
  Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11

  Key:  Sun EC public key, 256 bits
  public x coord: 54347275970077566368513898626765286548687250565786921039060191273785455056345
  public y coord: 88043846562958291988419087726639688241289668084120802006631053348150453315748
  parameters: secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)
  Validity: [From: Wed Oct 16 14:36:57 CEST 2019,
               To: Wed Jan 08 13:36:57 CET 2020]
  Issuer: CN=GTS CA 1O1, O=Google Trust Services, C=US
  SerialNumber: [    a2b1428b 94a636b2 08000000 0019fa68]

Certificate Extensions: 10
[1]: ObjectId: 1.3.6.1.4.1.11129.2.4.2 Criticality=false
Extension unknown: DER encoded OCTET string =
0000: 04 81 F5 04 81 F2 00 F0   00 76 00 B2 1E 05 CC 8B  .........v......
0010: A2 CD 8A 20 4E 87 66 F9   2B B9 8A 25 20 67 6B DA  ... N.f.+..% gk.
0020: FA 70 E7 B2 49 53 2D EF   8B 90 5E 00 00 01 6D D4  .p..IS-...^...m.
0030: C9 39 F6 00 00 04 03 00   47 30 45 02 20 3B E9 89  .9......G0E. ;..
0040: 83 7B 8C F6 11 AC C5 2C   2E 8C 21 E9 DE 24 3F E2  .......,..!..$?.
0050: 3B 46 6C 20 86 36 38 A3   E2 39 89 80 13 02 21 00  ;Fl .68..9....!.
0060: C0 B8 0E AC C3 71 A9 66   B3 49 AE 46 2F FF CE 35  .....q.f.I.F/..5
0070: CE C0 CD 5B 3E AA 3B 33   1B CC A4 7E E2 62 98 78  ...[>.;3.....b.x
0080: 00 76 00 5E A7 73 F9 DF   56 C0 E7 B5 36 48 7D D0  .v.^.s..V...6H..
0090: 49 E0 32 7A 91 9A 0C 84   A1 12 12 84 18 75 96 81  I.2z.........u..
00A0: 71 45 58 00 00 01 6D D4   C9 39 99 00 00 04 03 00  qEX...m..9......
00B0: 47 30 45 02 20 1B 76 BF   FD 79 76 D9 A0 A1 6D F7  G0E. .v..yv...m.
00C0: F2 33 67 55 DD 38 7A F5   98 E0 28 05 25 DD 3D 8B  .3gU.8z...(.%.=.
00D0: A5 91 BC DF 2E 02 21 00   87 81 AD 92 A6 1D 6B A0  ......!.......k.
00E0: 32 75 B8 68 FF 5C D2 F6   FA 11 0E FF 44 2D 7D DB  2u.h.\......D-..
00F0: 9C 1A 27 3A D3 32 CB B7                            ..':.2..


[2]: ObjectId: 1.3.6.1.5.5.7.1.1 Criticality=false
AuthorityInfoAccess [
  [
   accessMethod: ocsp
   accessLocation: URIName: http://ocsp.pki.goog/gts1o1
, 
   accessMethod: caIssuers
   accessLocation: URIName: http://pki.goog/gsr2/GTS1O1.crt
]
]

[3]: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: 98 D1 F8 6E 10 EB CF 9B   EC 60 9F 18 90 1B A0 EB  ...n.....`......
0010: 7D 09 FD 2B                                        ...+
]
]

[4]: ObjectId: 2.5.29.19 Criticality=true
BasicConstraints:[
  CA:false
  PathLen: undefined
]

[5]: ObjectId: 2.5.29.31 Criticality=false
CRLDistributionPoints [
  [DistributionPoint:
     [URIName: http://crl.pki.goog/GTS1O1.crl]
]]

[6]: ObjectId: 2.5.29.32 Criticality=false
CertificatePolicies [
  [CertificatePolicyId: [2.23.140.1.2.2]
[]  ]
  [CertificatePolicyId: [1.3.6.1.4.1.11129.2.5.3]
[]  ]
]

[7]: ObjectId: 2.5.29.37 Criticality=false
ExtendedKeyUsages [
  serverAuth
]

[8]: ObjectId: 2.5.29.15 Criticality=true
KeyUsage [
  DigitalSignature
]

[9]: ObjectId: 2.5.29.17 Criticality=false
SubjectAlternativeName [
  DNSName: *.google.com
]

[10]: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: BA 16 19 65 61 DB B1 32   D3 8E E7 C6 A6 A5 CC A4  ...ea..2........
0010: 3F 19 20 73                                        ?. s
]
]

]
  Algorithm: [SHA256withRSA]
  Signature:
0000: 52 3B 09 75 6D 73 2C 57   CE F5 6B F3 1F A8 5C FD  R;.ums,W..k...\.
0010: 0F F7 78 6D 02 9F DB 19   99 B1 9B A2 A5 42 7A 3B  ..xm.........Bz;
0020: 0C 92 2C 65 F6 36 B8 15   28 5B 63 D2 7A 9D 34 94  ..,e.6..([c.z.4.
0030: 6E 2E 40 82 E0 90 95 BE   B7 27 85 01 8F D7 25 6A  n.@......'....%j
0040: 74 11 06 92 2C 6B 2F E7   D7 D3 AD BD 89 B3 C5 1F  t...,k/.........
0050: 57 9B BB C6 43 79 8B 34   42 41 1C 80 A8 01 77 03  W...Cy.4BA....w.
0060: 10 34 95 C4 B2 67 31 9D   2B 3B 5A 77 9D 96 7C 14  .4...g1.+;Zw....
0070: F4 9A F3 E3 1C 18 08 60   CB 63 E1 17 EB 5C C2 B9  .......`.c...\..
0080: 21 4D 22 05 D7 63 E1 5B   D7 DD A6 E1 46 48 17 7D  !M"..c.[....FH..
0090: 10 54 FA 08 E3 43 DD F2   C7 41 A1 42 F7 EC D2 70  .T...C...A.B...p
00A0: 5E 4A FB 8B 85 2E F4 A1   D1 3E AD 4E 39 72 21 AF  ^J.......>.N9r!.
00B0: B7 5B 9E 7D EB C0 29 91   7C 75 9F F7 7A 94 8C 46  .[....)..u..z..F
00C0: FA 0B F7 A3 E9 49 6D B7   5D FE 68 49 E1 9F 18 B2  .....Im.].hI....
00D0: A0 50 EB 93 8D 71 53 84   A2 34 C4 F8 C9 08 9D 5F  .P...qS..4....._
00E0: 9B 2A 37 5E E0 F8 5D F5   7A 7D BC EB 3D 78 5C 23  .*7^..].z...=x\#
00F0: 84 DD CC 32 97 6C 77 92   7C 06 E4 5D 52 A0 5A 39  ...2.lw....]R.Z9

]]
Logging detailed KeyManager flow, input and output
SSLFactory sslFactory = SSLFactory.builder()
        .withIdentityMaterial(Paths.get("/path/to/your/identity.jks"), "password".toCharArray())
        .withLoggingIdentityMaterial()
        .withDefaultTrustMaterial()
        .build();
        
// run your server or client and analyse the logs

You will get a log message which is similar to the following one:

Attempting to find a client alias for key types [EC], while also using the Socket. See below for list of the issuers:
[CN=some-cn, OU=java-business-unit, O=thunderberry, C=NL]
Attempting to find a client alias for key types [RSA], while also using the Socket. See below for list of the issuers:
[CN=some-cn, OU=java-business-unit, O=thunderberry, C=NL]
Found the following client aliases [my-client-alias] for key types [RSA], while also using the Socket. See below for list of the issuers:
[CN=some-cn, OU=java-business-unit, O=thunderberry, C=NL]
Attempting to get the private key for the alias: my-client-alias
Found a private key for the alias: my-client-alias
Attempting to get the certificate chain for the alias: my-client-alias
Found the certificate chain with a size of 1 for the alias: my-client-alias. See below for the full chain:
[[
[
  Version: V3
  Subject: CN=some-cn, OU=java-business-unit, O=thunderberry, C=NL
  Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11

  Key:  Sun RSA public key, 2048 bits
  params: null
  modulus: 24358361148173123789972454702359337497482540111137434929916055417657354571697209833398713022918665517266658129513432713825681637659966415899913132315999013865220594646161546243646863695313013179456071195691453898185614193141245291456731398570603932104743113343898797041713131938343069988939700047591424592896073860712253945927117061051481828014230668012078029149888844657841672769678941972627103264098329661131121121108364416406527046714029325801099459715576059589001573317998720822010338410175438085716969314224320362271384261147189938038370804394737540861857893390249061609350687279289599644929221019981684263046077
  public exponent: 65537
  Validity: [From: Mon Feb 08 18:14:16 CET 2021,
               To: Thu Feb 06 18:14:16 CET 2031]
  Issuer: CN=some-cn, OU=java-business-unit, O=thunderberry, C=NL
  SerialNumber: [    3a03c719]

Certificate Extensions: 3
[1]: ObjectId: 2.5.29.37 Criticality=false
ExtendedKeyUsages [
  serverAuth
  clientAuth
]

[2]: ObjectId: 2.5.29.15 Criticality=false
KeyUsage [
  DigitalSignature
  Key_Encipherment
  Data_Encipherment
  Key_Agreement
]

[3]: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 6C D2 C6 3D 90 94 1F C9   43 9A A8 A3 41 3E BC 93  l..=....C...A>..
0010: FF E9 00 9E                                        ....
]
]

]
  Algorithm: [SHA256withRSA]
  Signature:
0000: 5F CD B8 0D 27 23 46 81   80 96 A0 E3 4D 79 82 F3  _...'#F.....My..
0010: AC E4 FC 53 B6 8B 17 FD   88 E7 03 DF B5 A6 DC 78  ...S...........x
0020: 75 D7 57 BE 14 C6 12 44   A3 25 E2 9B 2B E1 F1 FA  u.W....D.%..+...
0030: 68 19 19 F3 1B E7 67 17   8F 12 F6 C7 82 CA B7 E2  h.....g.........
0040: F9 66 44 09 3C D7 0F E1   0B FB CF 4B 58 37 79 32  .fD.<......KX7y2
0050: DC E1 E1 CD 97 9B 99 C8   95 DA F3 0E 74 0D 36 7E  ............t.6.
0060: A4 E0 DA BC 66 A0 CD AD   0C BE 6D C5 12 7E F2 6E  ....f.....m....n
0070: AC 89 00 55 1B 1A 23 CA   26 0D B3 B8 E5 52 8C F6  ...U..#.&....R..
0080: 20 D3 ED A3 D7 CD 55 2F   2D EB 07 12 1E 70 C6 0E   .....U/-....p..
0090: 1F 3C AB 8C 23 2F 15 19   A4 F6 4E B0 0E F5 2A D9  .<..#/....N...*.
00A0: E1 F2 50 A9 BC 6D 7A 24   CA CA 07 69 61 0E 55 C5  ..P..mz$...ia.U.
00B0: C3 36 72 2D B8 4A 93 2E   19 45 F9 49 C1 C8 14 15  .6r-.J...E.I....
00C0: 99 C7 06 8D 2A 93 08 87   0B 89 BE 3D 72 01 A5 E7  ....*......=r...
00D0: 97 2A B3 EA 63 92 45 32   D3 58 55 BE BB 69 B8 21  .*..c.E2.XU..i.!
00E0: 5A 98 D2 7D 0B 8D BD 23   A2 3B C3 53 94 5A 54 BA  Z......#.;.S.ZT.
00F0: F2 FD 48 AD 59 F6 E1 CB   86 BF EF 12 0E BD 69 1E  ..H.Y.........i.

]]

Returnable values from the SSLFactory

The SSLFactory provides different kinds of returnable values, see below for all the options:

import nl.altindag.ssl.SSLFactory;
import nl.altindag.ssl.model.KeyStoreHolder;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Optional;

public class App {

    public static void main(String[] args) {
        SSLFactory sslFactory = SSLFactory.builder()
                .withIdentityMaterial("keystore.p12", "secret".toCharArray(), "PKCS12")
                .withTrustMaterial("truststore.p12", "secret".toCharArray(), "PKCS12")
                .build();

        SSLContext sslContext = sslFactory.getSslContext();
        HostnameVerifier hostnameVerifier = sslFactory.getHostnameVerifier();
        Optional<X509ExtendedKeyManager> keyManager = sslFactory.getKeyManager();
        Optional<X509ExtendedTrustManager> trustManager = sslFactory.getTrustManager();
        Optional<KeyManagerFactory> keyManagerFactory = sslFactory.getKeyManagerFactory();
        Optional<TrustManagerFactory> trustManagerFactory = sslFactory.getTrustManagerFactory();
        List<X509Certificate> trustedCertificates = sslFactory.getTrustedCertificates();
        SSLSocketFactory sslSocketFactory = sslFactory.getSslSocketFactory();
        SSLServerSocketFactory sslServerSocketFactory = sslFactory.getSslServerSocketFactory();
        SSLEngine sslEngine = sslFactory.getSslEngine(host, port);
        SSLParameters sslParameters = sslFactory.getSslParameters();
        List<String> ciphers = sslFactory.getCiphers();
        List<String> protocols = sslFactory.getProtocols();
    }

}

Additional mappers for specific libraries

Some http clients relay on different ssl classes from third parties and require mapping from SSLFactory to those libraries. Below you will find the maven dependency which will provide the mapping and also the SSLFactory library. When using one of the below libraries, it is not required to also explicitly include sslcontext-kickstart into your project. The additional mappers for specific libraries below won't provide transitive dependencies on Netty, Jetty or Apache. This has been decided to prevent dependency hell on your side.

Netty

Some know http clients which relay on netty libraries are: Spring WebFlux WebClient Netty, Async Http Client and Dispatch Reboot Http Client.

<dependency>
    <groupId>io.github.hakky54</groupId>
  <artifactId>sslcontext-kickstart-for-netty</artifactId>
  <version>8.3.6</version>
</dependency>

Example setup for Spring WebClient with Netty:

import io.netty.handler.ssl.SslContext;
import nl.altindag.ssl.SSLFactory;
import nl.altindag.ssl.netty.util.NettySslUtils;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;

import javax.net.ssl.SSLException;

public class App {
    
    public static void main(String[] args) throws SSLException {
        SSLFactory sslFactory = SSLFactory.builder()
                .withDefaultTrustMaterial()
                .build();

        SslContext sslContext = NettySslUtils.forClient(sslFactory).build();
        HttpClient httpClient = HttpClient.create()
                .secure(sslSpec -> sslSpec.sslContext(sslContext));

        WebClient webClient = WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .build();
    }

}

Jetty

<dependency>
    <groupId>io.github.hakky54</groupId>
  <artifactId>sslcontext-kickstart-for-jetty</artifactId>
  <version>8.3.6</version>
</dependency>

Example setup for Spring WebFlux WebClient Jetty:

import nl.altindag.ssl.SSLFactory;
import nl.altindag.ssl.jetty.util.JettySslUtils;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.springframework.http.client.reactive.JettyClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;

public class App {

    public static void main(String[] args) {
        SSLFactory sslFactory = SSLFactory.builder()
                .withDefaultTrustMaterial()
                .build();
        
        SslContextFactory.Client sslContextFactory = JettySslUtils.forClient(sslFactory);
        HttpClient httpClient = new HttpClient(sslContextFactory);

        WebClient webClient = WebClient.builder()
                .clientConnector(new JettyClientHttpConnector(httpClient))
                .build();
    }

}

Apache

Apache 4

Apache Http Client works with javax.net.ssl.SSLContext, so an additional mapping to their library is not required, see here. However it is still possible to configure the http client with their custom configuration class. you can find below an example configuration for that use case:

<dependency>
    <groupId>io.github.hakky54</groupId>
  <artifactId>sslcontext-kickstart-for-apache4</artifactId>
  <version>8.3.6</version>
</dependency>
import nl.altindag.ssl.SSLFactory;
import nl.altindag.ssl.apache4.util.Apache4SslUtils;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.impl.client.HttpClients;

public class App {

    public static void main(String[] args) {
        SSLFactory sslFactory = SSLFactory.builder()
                .withDefaultTrustMaterial()
                .build();

        LayeredConnectionSocketFactory socketFactory = Apache4SslUtils.toSocketFactory(sslFactory);

        HttpClient httpClient = HttpClients.custom()
                .setSSLSocketFactory(socketFactory)
                .build();
    }

}
Apache 5
<dependency>
    <groupId>io.github.hakky54</groupId>
  <artifactId>sslcontext-kickstart-for-apache5</artifactId>
  <version>8.3.6</version>
</dependency>
import nl.altindag.ssl.SSLFactory;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory;
import nl.altindag.ssl.apache5.util.Apache5SslUtils;

class App {

    public static void main(String[] args) {
        SSLFactory sslFactory = SSLFactory.builder()
                .withDefaultTrustMaterial()
                .build();

        LayeredConnectionSocketFactory socketFactory = Apache5SslUtils.toSocketFactory(sslFactory);
        PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
                .setSSLSocketFactory(socketFactory)
                .build();

        HttpClient httpClient = HttpClients.custom()
                .setConnectionManager(connectionManager)
                .build();

        PoolingAsyncClientConnectionManager asyncConnectionManager = PoolingAsyncClientConnectionManagerBuilder.create()
                .setTlsStrategy(Apache5SslUtils.toTlsStrategy(sslFactory))
                .build();

        CloseableHttpAsyncClient httpAsyncClient = HttpAsyncClients.custom()
                .setConnectionManager(asyncConnectionManager)
                .build();
    }
    
}

Tested HTTP Clients

Below is a list of clients which have already been tested with examples, see in the ClientConfig class and the service directory for detailed configuration

Java

Kotlin

Scala

There is a github project available named Mutual-tls-ssl which provides a tutorial containing steps for setting up these four scenarios:

  • No security
  • One way authentication
  • Two way authentication
  • Two way authentication with trusting the Certificate Authority

It will also explain how to create KeyStores, Certificates, Certificate Signing Requests and how to implement it.

Contributing

There are plenty of ways to contribute to this project:

  • Give it a star
  • Share it with a Tweet
  • Join the Gitter room and leave a feedback or help with answering users questions
  • Submit a PR

Contributors ✨

Thanks goes to these wonderful people (emoji key):


Laurynas

💻 🚧

Philippe Charles

🤔

Tadhg Pearson

🐛

winster

🤔

Pablo Lalloni

🤔

Luis Miguel Ospina

🐛

Jérémie Panzer

🤔

patpatpat123

🤔 🐛

Cody A. Ray

💻

Benoit Tellier

💻

sal0max

🐛

lhstack

🐛

dasteg

🤔

rymsha

🤔

manbucy

🐛 💻

swankjesse

🐛

ivenhov

🐛

ecki

🐛 💻

mbenson

🤔 💻

EugenMayer

🤔

bjorndarri

🤔 💻

henryju

🤔 💻

nquinquenel

🐛 💻

woj-tek

🐛

thahnen

🐛

gerardnorton

🤔

austinarbor

🐛 🤔

ssmoss

🤔

maxxedev

🤔

This project follows the all-contributors specification. Contributions of any kind welcome!

sslcontext-kickstart's People

Contributors

allcontributors[bot] avatar chibenwa avatar dependabot-preview[bot] avatar dependabot[bot] avatar ecki avatar fossabot avatar gitter-badger avatar hakky54 avatar henryju avatar mbenson avatar nquinquenel avatar snyk-bot avatar sullis avatar tadhgpearson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sslcontext-kickstart's Issues

Method names withDefaultTrustMaterial vs withSystemTrustMaterial

Currently (at least for me suprisingly) those methods mean this semantically (under linux at least)

withDefaultTrustMaterial adds the default for the system ca store, under linux e.g. ca-certificates which lets you validate officially signed certificates

withSystemTrustMaterial adds the support for OS bases keystores of the user to expand the validation with custom imported certificates. For linux, there is no standard keystore (unless you use android).

Iam not sure what withDefaultTrustMaterial does under macos/windows, i assume that withSystemTrustMaterial under macos actually adds the ability to validate official certificates AND custom certificates.

My suggestion would be to fill up what we know about those methods for each os and then reconsider, if the naming still fits or even if it is very much OS specific.

  • MacOS: IMHO withDefaultTrustMaterial does nothing under macos, withSystemTrustMaterial adds support for official and custom cert validation
  • Linux: withDefaultTrustMaterial adds support for official certificates, while withSystemTrustMaterial does nothing
  • Android: IMHO withDefaultTrustMaterial adds support for official certificates, while withSystemTrustMaterial does add special android certificates (not sure about both).
  • Windows: TBA

The inverse behavior for Linux/MacOS is what concerns me. It's a tripwire i would say

BasicHostnameVerifier is broken?

It appears that BasicHostnameVerifier doesn't actually check that the hostname in the URL matches the hostname in the SAN part of the peer’s certificate.

As a consequence, a server that had a certificate for say attacker.com could be verified against a client request to victim.com.

public methods for getting trustStore, keyStore, KeyManager, TrustManager out of the SSLFactory

Hi, nice project ... I like it.

My request would be:

once I have created and "loaded" everything I want into the val sslFactory: SSLFactory

it would be nice if I could pass this around and still be able to also get the finer building blocks of it.

I'd love to have public(!) methods on it (or somewhere else) to get its

  • trustStore (KeyStore)
  • keyStore (KeyStore)
  • X509ExtendedKeyManager
  • X509ExtendedTrustManager
  • etc.

maybe it is already possible, and I just didn't find on where to find it "in the deeper API"

thanx for considering!

Set Enabled Protocols

Hello,

I could not find a way to set enabled protocols in the sslcontext. This would be useful, if the client want to reject server which supports only old version (say TLSv1.0).

for example:


public SSLFactory.Builder withAllowedProtocols(String[] allowedProtocols) {
      this.allowedProtocols = allowedProtocols;
      return this;
}
private void createSSLContext(KeyManager[] keyManagers, TrustManager[] trustManagers) {
        try {
            this.sslContext = SSLContext.getInstance(this.protocol);
            this.sslContext.init(keyManagers, trustManagers, this.secureRandom);
            //Add following line
            this.sslContext.getDefaultSSLParameters().setProtocols(this.allowedProtocols);
        } catch (KeyManagementException | NoSuchAlgorithmException var4) {
            throw new GenericSSLContextException(var4);
        }
    }

Regards
Winster

org.bouncycastle.pkcs.PKCSException

Describe the bug

i use permUtil to load the perm certificate, a Exception was thrown

To Reproduce

X509ExtendedKeyManager keyManager = PemUtils.parseIdentityMaterial(certificate.getCertificate(), certificate.getPrivateKey(), certificate.getPassphrase().toCharArray());

the error is

Caused by: org.bouncycastle.pkcs.PKCSException: unable to read encrypted data: 1.2.840.113549.1.5.13 not available: Wrong algorithm: AES or Rijndael required
	at org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo.decryptPrivateKeyInfo(Unknown Source)
	at nl.altindag.ssl.decryptor.BouncyFunction.lambda$andThen$0(BouncyFunction.java:22)
	at nl.altindag.ssl.util.PemUtils.extractPrivateKeyInfo(PemUtils.java:493)
	... 55 common frames omitted
Caused by: org.bouncycastle.operator.OperatorCreationException: 1.2.840.113549.1.5.13 not available: Wrong algorithm: AES or Rijndael required
	at org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder$1.get(Unknown Source)
	... 58 common frames omitted
Caused by: java.security.InvalidKeyException: Wrong algorithm: AES or Rijndael required
	at com.sun.crypto.provider.AESCrypt.init(AESCrypt.java:83)
	at com.sun.crypto.provider.CipherBlockChaining.init(CipherBlockChaining.java:93)
	at com.sun.crypto.provider.CipherCore.init(CipherCore.java:591)
	at com.sun.crypto.provider.CipherCore.init(CipherCore.java:619)
	at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:355)
	at javax.crypto.Cipher.implInit(Cipher.java:810)
	at javax.crypto.Cipher.chooseProvider(Cipher.java:864)
	at javax.crypto.Cipher.init(Cipher.java:1539)
	at javax.crypto.Cipher.init(Cipher.java:1470)
	... 59 common frames omitted

JDK version: Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
System: Ubuntu SMP Fri Aug 10 11:14:32 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

however this is ok when using jdk1.8.0_291.jdk

what should i do when using jdk 1.8.0_131-b11?

android 6.0 Caused by: java.lang.NoClassDefFoundError

Describe the bug
I use android 12、13 platform is work.But use android 6 is crash.

code:

SSLFactory sslFactory = SSLFactory.builder().withTrustMaterial(bks file, password).build
crash log

Caused by: java.lang.NoClassDefFoundError: nl.altindag.ssl.util.KeyStoreUtils$$ExternalSyntheticLambda0 at nl.altindag.ssl.util.KeyStoreUtils.<clinit>(KeyStoreUtils.java:56) at nl.altindag.ssl.SSLFactory$Builder.withTrustMaterial(SSLFactory.java:366)

Expected behavior
I generate SSLSocketFactory crash

Screenshots
image

Environmental Data:

  • android targetsdkversion 32
  • android compilesdkversion 32
  • java 8
  • Gradle: io.github.hakky54:sslcontext-kickstart:7.4.9

New System Keystores in Windows

With Java 11.0.18 and 17.0.5 a long standing issue (https://bugs.openjdk.org/browse/JDK-6782021) with Windows-MY was solved. This includes the introduction of new keystore types in SunMSCAPI

From: https://bugs.openjdk.org/browse/JDK-8286790

The Windows KeyStore support in the SunMSCAPI provider has been expanded to include access to the local machine location. The new keystore types are:

  • "Windows-MY-LOCALMACHINE"
  • "Windows-ROOT-LOCALMACHINE"

The following keystore types were also added, allowing developers to make it clear they map to the current user:

  • "Windows-MY-CURRENTUSER" (same as "Windows-MY")
  • "Windows-ROOT-CURRENTUSER" (same as "Windows-ROOT")

Perhaps it could be generated automatically from something like:

    try {
        final Provider p = Security.getProvider("SunMSCAPI");
        for(final Enumeration e = p.keys(); e.hasMoreElements();) {
            final String name = e.nextElement().toString();
            if (name.startsWith("KeyStore.")) {
                System.out.println(name.substring(9));
            }

            }
    } catch (final Exception e) {
        System.out.println(e);
    }

Which returns in Java 11/17

Windows-MY-CURRENTUSER
Windows-MY-LOCALMACHINE
Windows-ROOT-LOCALMACHINE
Windows-ROOT-CURRENTUSER
Windows-ROOT
Windows-MY

Could this be added in the KeyStoreUtils.loadSystemKeyStores() ? The new Windows-MY-LOCALMACHINE is very useful for services that run on ACME certificates published automatically to the store (e.g. with win.acme)

If multiple certificates are configured, the browser cannot obtain the correct certificate after the host address is entered

Hello, I have configured two certificates through your library, the domain name of one certificate is www.gateway.com.cn, and the domain name of the other certificate is www.ingress.com.cn. My code is written like this

@Bean
    public WebServerFactoryCustomizer<NettyReactiveWebServerFactory> sslServerConsumer() {
        return factory -> {
            factory.addServerCustomizers(httpServer -> httpServer.secure(sslContextSpec -> {
                try {
                    X509ExtendedKeyManager x509ExtendedKeyManager = PemUtils.loadIdentityMaterial(
                            getResource("classpath:ssl/default/server.pem"),
                            getResource("classpath:ssl/default/server-key.pem"));
                    X509ExtendedTrustManager x509ExtendedTrustManager = PemUtils.loadTrustMaterial(getResource("classpath:ssl/default/ca.pem"));
                    SSLFactory.Builder builder = SSLFactory.builder()
                            .withIdentityMaterial(x509ExtendedKeyManager)
                            .withTrustMaterial(x509ExtendedTrustManager);
                    loadOtherCertificate(builder);
                    builder.withProtocols("TLSv1.2");
                    SSLFactory sslFactory = builder.build();
                    System.out.println("ssl init success");
                    X509ExtendedKeyManager keyManager = sslFactory.getKeyManager()
                            .orElseThrow(NullPointerException::new);
                    SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(composeKeyManager((CompositeX509ExtendedKeyManager) keyManager))
                            .ciphers(sslFactory.getCiphers(), SupportedCipherSuiteFilter.INSTANCE)
                            .protocols(sslFactory.getProtocols())
                            .clientAuth(getClientAuth(sslFactory.getSslParameters()));
                    sslFactory.getTrustManager().ifPresent(sslContextBuilder::trustManager);
                    sslContextSpec.sslContext(sslContextBuilder);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }));
        };
    }

    private KeyManager composeKeyManager(CompositeX509ExtendedKeyManager keyManager) {
        return new CustomKeyManager(keyManager.getKeyManagers());
    }

    private void loadOtherCertificate(SSLFactory.Builder builder) throws IOException {
        X509ExtendedKeyManager x509ExtendedKeyManager = PemUtils.loadIdentityMaterial(
                getResource("classpath:ssl/ingress/server.pem"),
                getResource("classpath:ssl/ingress/server-key.pem"));
        X509ExtendedTrustManager x509ExtendedTrustManager = PemUtils.loadTrustMaterial(
                getResource("classpath:ssl/ingress/ca.pem"));
        builder.withIdentityMaterial(x509ExtendedKeyManager)
                .withTrustMaterial(x509ExtendedTrustManager);
    }

    private static ClientAuth getClientAuth(SSLParameters sslParameters) {
        if (sslParameters.getNeedClientAuth()) {
            return ClientAuth.REQUIRE;
        } else if (sslParameters.getWantClientAuth()) {
            return ClientAuth.OPTIONAL;
        } else {
            return ClientAuth.NONE;
        }
    }

    private InputStream getResource(String path) throws IOException {
        Resource resource = ResourceUtils.getResource(path);
        return resource.getInputStream();
    }

When I input www.ingress.com.cn on the browser, the console printed www.gateway.com.cn, there is no other way to match the correct certificate, and the certificate displayed on my browser is www.gateway.com.cn, can you help me

image

Provide method to set default Truststore

Is your feature request related to a problem? Please describe.
I was trying to add Truststore to all SSLContext that will be used in the app
Currently this lib requires manual injection of SSLContext into all connection libraries to achieve it.
It's cumbersome when there are few higher level libraries that are using different connection libraries under the hood (eg Apache and OkHttp).

Describe the solution you'd like
It would be good to set single TruststoreFactory (or SSLContext factory) that will generate desired objects.

Additional context
I created a simple TrustManagerFactory that provides TrustManagers.
I would be good to have this functionality out of the box in this lib.

public class SingletonTrustManagerFactorySpi extends TrustManagerFactorySpi {

    private static TrustManager[] trustManagers;

    public static void setTrustManager(TrustManager aTrustManager) {
        trustManagers = new TrustManager[]{aTrustManager};
    }

    @Override
    protected void engineInit(KeyStore keyStore) {
        log.info("Ignoring provided keystore to SingletonTrustManager");
    }

    @Override
    protected void engineInit(ManagerFactoryParameters managerFactoryParameters) {
        log.info("Ignoring provided ManagerFactoryParameters");
    }

    @Override
    protected TrustManager[] engineGetTrustManagers() {
        return trustManagers;
    }
}
public class SingletonTrustManagerFactoryProvider extends Provider {

    public SingletonTrustManagerFactoryProvider() {
        super("TrustManagerFactoryProvider", 1.0, "Provides TrustManagerFactory");
        Service service = new Service(this,
                "TrustManagerFactory",
                "PKIX",
                SingletonTrustManagerFactorySpi.class.getName(),
                ImmutableList.of("SunPKIX", "X509", "X.509"),
                Collections.emptyMap());
        putService(service);
    }
}

This method should be available in TrustManagerUtils

    public void setDefaultTrustManager(TrustManager trustManager) {
        SingletonTrustManagerFactorySpi.setTrustManager(trustManager);

        ProviderList providerList = ProviderList.insertAt(Providers.getProviderList(),
                new SingletonTrustManagerFactoryProvider(), 0);
        Providers.setProviderList(providerList);
    }

Support for using multiple identity materials and trust materials not working

Hello,

First of all, a big thanks for this project.
It is well written, well documented, and very helpful!

I am opening an issue regarding the "Support for using multiple identity materials and trust materials" as I believe it might not be behaving correctly.

In order to illustrate my example, I am going to simplify to two different services (instead of a large number)

First service I need to talk to: OrderService. It is a service where one will place orders. (I order a video game, I order a bicycle)

Second service I need to take to, DeliveryService. It is a service where one will deliver something to someone. It can work a standalone, I deliver this contract to this person, often used in conjunction of OrderService, I deliver the video game I just ordered to someone.

The two companies owning the services are competing company, they will enforce client to have it their own keystores. No way possible to go to one company and say: If I bring you the certificate I use to talk to the other service, can you trust it as well? No no.

In order to perform such, I am using this library, where I beforehand verified.

 SSLFactory sslFactory = SSLFactory.builder()
                    .withIdentityMaterial(Paths.get(/path/to/orderservice/client-to-talk-to-orderservice-keystore.p12), "talkToOrderService".toCharArray())
                    .withTrustMaterial(Paths.get(/path/to/truststore.p12), "trustStorePassPhrase".toCharArray())
                    .build();
            return NettySslUtils.forClient(sslFactory).build();

This is working fine to talk to order service

But also, I verified,

 SSLFactory sslFactory = SSLFactory.builder()
                    .withIdentityMaterial(Paths.get(/path/to/deliveryservice/client-to-talk-to-deliveryservice-keystore.p12), "talkToDeliveryService".toCharArray())
                    .withTrustMaterial(Paths.get(/path/to/truststore.p12), "trustStorePassPhrase".toCharArray())
                    .build();
            return NettySslUtils.forClient(sslFactory).build();

This is working fine to talk to delivery service (mock the step to talk to order service)

Now, chaining the two calls, first talk to order service, then to delivery service.

 SSLFactory sslFactory = SSLFactory.builder()
                    .withIdentityMaterial(Paths.get(/path/to/orderservice/client-to-talk-to-orderservice-keystore.p12), "talkToOrderService".toCharArray())
                    .withIdentityMaterial(Paths.get(/path/to/deliveryservice/client-to-talk-to-deliveryservice-keystore.p12), "talkToDeliveryService".toCharArray())
                    .withTrustMaterial(Paths.get(/path/to/truststore.p12), "trustStorePassPhrase".toCharArray())
                    .build();
            return NettySslUtils.forClient(sslFactory).build();

This still yields javax.net.ssl.SSLHandshakeException: Received fatal alert: unknown_ca; nested exception is io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: Received fatal alert: unknown_ca

Could you please help?

Thank you

KeychainStore Ignored Exception on macOS

Describe the bug
On macOS, a KeychainStore exception is printed on the error stream instead of being logged or thrown. This exception happens when a certificate provided by the system has multiple "X509v3 Extended Key Usage" sections.

To Reproduce

  1. On macOS, load any system certificate that has duplicate extensions
  2. The following text should be printed on the err stream of the command line :
    KeychainStore Ignored Exception: java.security.cert.CertificateParsingException: java.io.IOException: Duplicate extensions not allowed

Expected behavior
The system should not print the error instead of logging it, expecially for command line apps.

Environmental Data:

  • Any Java Version
  • Any Maven Version
  • MacOS

Additional context
The faulty code is available on openjdk repository at
https://github.com/openjdk/jdk16/blob/020ec8485251698d1187204ac13321f4726e45ea/src/java.base/macosx/classes/apple/security/KeychainStore.java#L810
https://github.com/openjdk/jdk16/blob/37043b05576c8b81b43ac41a8f06de0d0bbb3f5b/src/java.base/share/classes/sun/security/x509/CertificateExtensions.java#L105

I've written a quick and dirty workaround that decorate the err stream to log the exception instead of printing it.

My question is: is there a better solution?

support keystores on the filesystem instead of only on the classpath

My usecase:
I deploy my application in a docker container in different environments. The appropriate keystore and truststore is mounted in the containers filesystem. In my application I just want to reference "/mnt/certs/truststore.jks", but the current code uses KeyStoreUtils.class.getClassLoader().getResourceAsStream(keystorePath) which only searches in the classpath.
I'd like support for absolute paths on the filesystem.

To not break backwards compatibility, maybe first search in the classpath and if nothing is found try reading from the filesystem.

Easy way to specify KeyManagerFactory type (PKIX instead of SunX509)

Is your feature request related to a problem? Please describe.
I want to use the New (PKIX) Key Manager (instead of SunX509) for some client auth Keystores. Currently I use factory.withIdendityMaterial(keystore) which internally uses the default KeyStoreManager (which defaults to the SunX509 legacy manager).

Describe the solution you'd like
Adding a withIdendityMaterial(KeyStore ks, String keyManagerType) or allowing to configure the default would help.

Describe alternatives you've considered
There are alternatives using the withIdendityMaterial(KeyManager p), but that requires two utility calls to get a Keystore and a KeyManager first. Is that the intended way?

Version 7.4.3 fails to accept all certificates

Describe the bug
It seems that changes in version 7.4.3 skips accept-all truststore configured using
builder.withUnsafeTrustMaterial() or builder.withTrustingAllCertificatesWithoutValidation()

This is because UnsafeX509ExtendedTrustManager has 0 accepted X509Certificate
therefore it is skipped during checking in CombinableX509TrustManager

To Reproduce

SSLFactory.Builder builder = SSLFactory.builder().withDefaultTrustMaterial();
builder.withUnsafeTrustMaterial();
SSLFactory factory = builder.build();

SSLContext sslContext = factory.getSslContext();
SSLContext.setDefault(sslContext);

Expected behavior
Validation of the TLS certificate should pass and connection should be established

Environmental Data:

  • Java Version 11.0
  • Gradle
  • OS MacOS

Additional context
The test passes with 7.3.0 and 7.4.2

How do I use this library with JDBC?

Hello,

Thanks for creating this library. I followed the example to load PEM files to build an SSLFactory. What's next? How do I use it to connect to a MySQL server using JDBC securely, with the server CA certificate and the client's private/public key pairs? I tried a few things but couldn't get anywhere. Thank you!

No warning for non existent system keystore

Currently, we see that if there is no system keystore (which basically will be the case for all linux based os) a warning is logged

        if (keyStores.isEmpty()) {
            LOGGER.warn("No system KeyStores available for [{}]", operatingSystem);
            return Collections.emptyList();
        }

Considering that this would be the case for all linux systems except android, i would suggest either of this

  • Do log info for linux, warn for all other os
  • log info for all other os

What is your intention / idea with this, could you share it? Thanks!

the alias of CertificateEntry

Describe the bug
Hello, I am going to create an SSLContext with the following code, but the resulting SSLContext does not appear to be complete and its trustedCerts is empty.

    SSLFactory sslFactory = SSLFactory.builder()
            .withTrustMaterial(CertificateUtils.loadCertificate("ca.crt"))
            .build();
    SSLContext sslContext = sslFactory.getSslContext();

image

the content of ca.crt

-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIUW4b6bPPPyRAm0DrDKKJJ8YlSqOkwDQYJKoZIhvcNAQEL
BQAwPDE6MDgGA1UEAxMxRWxhc3RpY3NlYXJjaCBzZWN1cml0eSBhdXRvLWNvbmZp
Z3VyYXRpb24gSFRUUCBDQTAeFw0yMjA0MDUxMjQ1MzVaFw0yNTA0MDQxMjQ1MzVa
MDwxOjA4BgNVBAMTMUVsYXN0aWNzZWFyY2ggc2VjdXJpdHkgYXV0by1jb25maWd1
cmF0aW9uIEhUVFAgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDB
q3aR//NaqXBUqI0AVHuVWJmFMLYwpi/DQLUYifwOlGx4iAb6ePuiA8b7tXAGPn0z
TWFQ82t0DZf/1nXoRmNJO8ardAVWcL7z+VDUY7Hab08GJzRRP/V5b7VL+J+WBQOG
auN0cal3jM14k3FeZApyoL+XqmJ36MSY3WtAPfF3ySH1ltcMguXqN79k3Bxw0mGq
AJt+z4q8Lq2e8vsMKKpSO1vZ0grvffj6MBni2stfZ4ifA6Kubh/yePShKsG/N8nY
K6iJYjwLuVUQ1Eaw6X3s78c+eESTlzZiM6I7qTR1JzW5Fuyz/ZPbDcI1zg+p9H4g
NRaX76Fv9XG/XehLeYxNoTBLytY2d9kdEmW9MIGCqaROabDdxygxcJ5l3aqkBTiA
tq42vguuiQvpLndfGIEA4qh5AFyo+iqP1226+1onHfeXtbtyqjpHIV6RZa8RqNLg
ynmf96NzzHQq1CfKp5CgQB/l3yaAtFxguNyhKftHia518iTjcUpn9f3gmSzDlZy2
KgzZMaw8GwdtT+qac3XCVI7vwjY21uEHbCEklh8ZycAt28Dc0h747MqG9A3xdMDV
lf8iBtGjuxQNJSsOBRY6Up8ajEWeYvEqpKDHVHYxc78qdjGCzltgGIvjzah389mH
UC458l4Ey4Lns4C7NVAteHva9L71CE/zDTwJ3nECqwIDAQABo1MwUTAdBgNVHQ4E
FgQUphpsq/WD1QUsRkr9EQaGinawo9owHwYDVR0jBBgwFoAUphpsq/WD1QUsRkr9
EQaGinawo9owDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAlg8X
PnpSKIkt+a9imOFcddUgoNSCgwAyBuGdnUKTjuDnV2630O7cRky4Ly8gI3hxuV3j
I0JHatPA4Xw8m/8rAkgoega4zCQ89L7w8g1b54NnvnMOQIKs4aQ7TsYQUgyGxj6j
hhs1uLEBgF/uCJR1INZbiw4tjGTJssRSGMUsn7Mto0+3UL3AHqbmQY4IavRDEd2s
zpyGN1acwh9jl50pcKjgM/UYhNgWvGQgOF7MP8+4BWXBn9O7ufdUt4n08yPFP5hn
sOKrScnTCPIVn3uExcYDLDEuRDsQXfDvD03Bm6aFPC+qwr+W7k8WZPc7UW3vLzTg
TPtvnFwTunD3Bzv14b+2BOQH+caOKVyjBn73HzXQ6Xp8KM6ef3+6RZTeomHhqAwr
TG2vVsLzDhZiNjOE1Le3UeT4eAz7psgg+piouaXkY5FnVmMlNqWGkXfmvtMC8JzG
uWGUtSV2plImhQMgfrF4wMhntiNQcHa0Fge0k4I4ajt/HD5Al4yMYCMzx7ocbZLg
bTSDn+PuRt1NBZYC/Icz6L3CaSAVCMIEw145G/ytyu9annHs+hXSx+1ji3MHkF/g
yE65FKuMXoHLhCdN9MoKFEDr6eLlY7l9HWbcfQGpePoX4L/g1nGMVQmssCChkH5r
h5BvtLZEjAtAP6q1Al0phYV6eYQvLE8Dzbw0RQ0=
-----END CERTIFICATE-----

Environmental Data:

  • Java Version 1.8.0_202
  • sslcontext-kickstart Version 7.3.0
  • OS: Windows

Additional context
I found out the cause of the problem, when create X509TrustManagerImpl, its trustedCerts is already empty.
sun.security.ssl.X509TrustManagerImpl
image

sun.security.validator.KeyStores.getTrustedCerts(KeyStore var0)
image

java.security.KeyStore.isCertificateEntry(String alias)
image

sun.security.pkcs12.PKCS12KeyStore.engineIsCertificateEntry(String var1)
the entries keys has the capital letters, but the parameter var1 is lowercase letters
image

Can you consider changing alias to lowercase in the nl.altindag.ssl.util.KeyStoreUtils.createTrustStore(List<T> certificates)
image
or nl.altindag.ssl.util.CertificateUtils.generateAlias(Certificate certificate)
image

Getting All Certificates

Thanks for creating should a great library!

I have a question about getting all the certificates associated with the SSLFactory but I am coming up blank on how exactly to get access to all of them.

I create a factory using the following code:

var builder = SSLFactory.builder()
    .withSwappableTrustMaterial()
    .withSwappableIdentityMaterial()

Followed by either of the following two flows depending on if I am processing a JKS or PEM

//JKS
builder
    .withTrustMaterial(get(<PATH>), <PASSWORD>.toCharArray())
    .withIdentityMaterial(get(<PATH>), <PASSWORD>.toCharArray())

//PEM
builder
    .withTrustMaterial(loadTrustMaterial(get(<PATH>))))
    .withIdentityMaterial(loadIdentityMaterial(get(<PATH>), get(<PATH>)))

var factory = builder.build();

I would like to be able to get all the certificates in use by this factory which I naively thought would be returned by the following call:

factory.getTrustedCertificates()

This seems to only return the certificates loaded using withTrustMaterial which in retrospect makes sense. Is there any way to get the certificates loaded via the withIdentityMaterial calls?

I did some poking around and it seemed that the following gets close:

var key = factory.getKeyManager().get();

Inside the manager there is a credentialsMap which chooses an alias based on the certificates used as the second parameter of withIdentityMaterial but the manager doesn't return a list of the all the aliases.

Any help or suggestions you can provide would be greatly appreciated. Thanks!

InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty on Windows

Hi @Hakky54

With the latest version 8.1.0, our QA pipeline failed on Windows (sorry, I made all my previous tests on Linux) with:

javax.net.ssl.SSLException: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
	at org.apache.hc.core5.reactor.ssl.SSLIOSession.convert(SSLIOSession.java:317)
	at org.apache.hc.core5.reactor.ssl.SSLIOSession.doWrap(SSLIOSession.java:324)
	at org.apache.hc.core5.reactor.ssl.SSLIOSession.doHandshake(SSLIOSession.java:367)
	at org.apache.hc.core5.reactor.ssl.SSLIOSession.access$100(SSLIOSession.java:74)
	at org.apache.hc.core5.reactor.ssl.SSLIOSession$1.inputReady(SSLIOSession.java:201)
	at org.apache.hc.core5.reactor.InternalDataChannel.onIOEvent(InternalDataChannel.java:142)
	at org.apache.hc.core5.reactor.InternalChannel.handleIOEvent(InternalChannel.java:51)
	... 5 more
Caused by: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
	at java.base/java.security.cert.PKIXParameters.setTrustAnchors(PKIXParameters.java:200)
	at java.base/java.security.cert.PKIXParameters.<init>(PKIXParameters.java:120)
	at java.base/java.security.cert.PKIXBuilderParameters.<init>(PKIXBuilderParameters.java:104)
	at java.base/sun.security.validator.PKIXValidator.<init>(PKIXValidator.java:99)
	at java.base/sun.security.validator.Validator.getInstance(Validator.java:181)
	at java.base/sun.security.ssl.X509TrustManagerImpl.getValidator(X509TrustManagerImpl.java:300)
	at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrustedInit(X509TrustManagerImpl.java:176)
	at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:246)
	at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:141)
	at nl.altindag.ssl.trustmanager.CompositeX509ExtendedTrustManager.lambda$checkServerTrusted$5(CompositeX509ExtendedTrustManager.java:91)
	at nl.altindag.ssl.trustmanager.CombinableX509TrustManager.checkTrusted(CombinableX509TrustManager.java:40)
	at nl.altindag.ssl.trustmanager.CompositeX509ExtendedTrustManager.checkServerTrusted(CompositeX509ExtendedTrustManager.java:91)
	at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1335)

Not sure what is the problem...

The failing test is here:
https://github.com/SonarSource/sonarlint-core/blob/130de06eb43f48452138145a05bd4f4647dcdd7d/core/src/test/java/mediumtest/SslMediumTests.java#L110

And this is how we setup the SSLFactory:
https://github.com/SonarSource/sonarlint-core/blob/130de06eb43f48452138145a05bd4f4647dcdd7d/http/src/main/java/org/sonarsource/sonarlint/core/http/HttpClientProvider.java#L57

"The BC provider no longer provides an implementation for KeyFactory.RSA."

Describe the bug
On Android >= Android 9/P (API 28) I get the following error in logcat:

FATAL EXCEPTION: DefaultDispatcher-worker-1
    Process: myapp, PID: 28762
    nl.altindag.ssl.exception.PrivateKeyParseException: org.bouncycastle.openssl.PEMException: unable to convert key pair: The BC provider no longer provides an implementation for KeyFactory.RSA.  Please see https://android-developers.googleblog.com/2018/03/cryptography-changes-in-android-p.html for more details.
        at nl.altindag.ssl.util.PemUtils.extractPrivateKey(PemUtils.java:454)
        at nl.altindag.ssl.util.PemUtils.$r8$lambda$s6ElOTWwPljNhjlxXLkUrjqZZdY(Unknown Source:0)
        at nl.altindag.ssl.util.PemUtils$$ExternalSyntheticLambda10.apply(Unknown Source:2)
        at java.util.Optional.map(Optional.java:211)
        at nl.altindag.ssl.util.PemUtils.parsePrivateKey(PemUtils.java:421)
        at nl.altindag.ssl.util.PemUtils.parseIdentityMaterial(PemUtils.java:334)
        at nl.altindag.ssl.util.PemUtils.loadIdentityMaterial(PemUtils.java:306)
        at nl.altindag.ssl.util.PemUtils.loadIdentityMaterial(PemUtils.java:232)
        at nl.altindag.ssl.util.PemUtils.loadIdentityMaterial(PemUtils.java:224)
        ...
     Caused by: org.bouncycastle.openssl.PEMException: unable to convert key pair: The BC provider no longer provides an implementation for KeyFactory.RSA.  Please see https://android-developers.googleblog.com/2018/03/cryptography-changes-in-android-p.html for more details.
        at org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter.getPrivateKey(Unknown Source:48)
        at nl.altindag.ssl.util.PemUtils.extractPrivateKey(PemUtils.java:452)
        at nl.altindag.ssl.util.PemUtils.$r8$lambda$s6ElOTWwPljNhjlxXLkUrjqZZdY(Unknown Source:0) 
        at nl.altindag.ssl.util.PemUtils$$ExternalSyntheticLambda10.apply(Unknown Source:2) 
        at java.util.Optional.map(Optional.java:211) 
        at nl.altindag.ssl.util.PemUtils.parsePrivateKey(PemUtils.java:421) 
        at nl.altindag.ssl.util.PemUtils.parseIdentityMaterial(PemUtils.java:334) 
        at nl.altindag.ssl.util.PemUtils.loadIdentityMaterial(PemUtils.java:306) 
        at nl.altindag.ssl.util.PemUtils.loadIdentityMaterial(PemUtils.java:232) 
        at nl.altindag.ssl.util.PemUtils.loadIdentityMaterial(PemUtils.java:224) 
        ...
     Caused by: java.security.NoSuchAlgorithmException: The BC provider no longer provides an implementation for KeyFactory.RSA.  Please see https://android-developers.googleblog.com/2018/03/cryptography-changes-in-android-p.html for more details.
        at sun.security.jca.Providers.checkBouncyCastleDeprecation(Providers.java:386)
        at sun.security.jca.Providers.checkBouncyCastleDeprecation(Providers.java:336)
        at java.security.KeyFactory.getInstance(KeyFactory.java:235)
        at org.bouncycastle.jcajce.util.NamedJcaJceHelper.createKeyFactory(Unknown Source:2)
        at org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter.getKeyFactory(Unknown Source:20)
        at org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter.getPrivateKey(Unknown Source:4)

The error links to this site: https://android-developers.googleblog.com/2018/03/cryptography-changes-in-android-p.html

This is my code, how I use this library:

// load private client cert
val cert = javaClass.getResourceAsStream("/credentials/myCert.pem")
val key = javaClass.getResourceAsStream("/credentials/myKey.pem")
if (cert == null || key == null)
    throw Exception("Private certificate or key missing.")
val keyManager = PemUtils.loadIdentityMaterial(cert, key)

// activate client ssl in fuel
val sslFactory = SSLFactory.builder()
    .withIdentityMaterial(keyManager)
    .build()
FuelManager.instance.socketFactory = sslFactory.sslSocketFactory

Environmental Data:

  • Android >= Android 9/P (API 28)
  • io.github.hakky54:sslcontext-kickstart-for-pem:7.2.0
  • I tested several emulators with different Android versions, and it works on devices < API 28, and doesn't work on devices >= API 28

PemUtils parses can't handle single line keys or certs

Describe the bug
A clear and concise description of what the bug is.
PemUtils parsers (parseIdentityMaterial and parseTrustMaterial, etc) require that the -----BEGIN PRIVATE KEY----- sections ends with newline. This is not required and makes it so that it breaks one

-----BEGIN PRIVATE KEY-----KEYSTUFHEREASDASDASD-----END PRIVATE KEY-----

and

-----BEGIN CERTIFICATE-----KEYSTUFHEREASDASDASD-----END CERTIFICATE-----

To Reproduce

  1. Provide as much of a code sample as possible.
  2. provide full stack traces if possible

use any of the PemUtils parses with a single line key info and watch it crash

Expected behavior
A clear and concise description of what you expected to happen.
Handle single line keys and certs with ease.

Screenshots
If applicable, add screenshots to help explain your problem.

Environmental Data:

  • Java Version 11
  • Maven Version -
  • OS Windows

Using BC fails with Java 1.8.292+

stacktrace:

Error:  nl.altindag.ssl.util.PemUtilsShould.loadEncryptedIdentityMaterialFromDirectory  Time elapsed: 0.031 s  <<< ERROR!
nl.altindag.ssl.exception.GenericIOException: nl.altindag.ssl.exception.GenericKeyStoreException: java.security.KeyStoreException: Key protection  algorithm not found: java.security.UnrecoverableKeyException: Encrypt Private Key failed: unrecognized algorithm name: PBEWithSHA1AndDESede
	at nl.altindag.ssl.util.PemUtilsShould.loadEncryptedIdentityMaterialFromDirectory(PemUtilsShould.java:310)
Caused by: nl.altindag.ssl.exception.GenericKeyStoreException: java.security.KeyStoreException: Key protection  algorithm not found: java.security.UnrecoverableKeyException: Encrypt Private Key failed: unrecognized algorithm name: PBEWithSHA1AndDESede
	at nl.altindag.ssl.util.PemUtilsShould.loadEncryptedIdentityMaterialFromDirectory(PemUtilsShould.java:310)
Caused by: java.security.KeyStoreException: Key protection  algorithm not found: java.security.UnrecoverableKeyException: Encrypt Private Key failed: unrecognized algorithm name: PBEWithSHA1AndDESede
	at nl.altindag.ssl.util.PemUtilsShould.loadEncryptedIdentityMaterialFromDirectory(PemUtilsShould.java:310)
Caused by: java.security.UnrecoverableKeyException: Encrypt Private Key failed: unrecognized algorithm name: PBEWithSHA1AndDESede
	at nl.altindag.ssl.util.PemUtilsShould.loadEncryptedIdentityMaterialFromDirectory(PemUtilsShould.java:310)
Caused by: java.security.NoSuchAlgorithmException: unrecognized algorithm name: PBEWithSHA1AndDESede
	at nl.altindag.ssl.util.PemUtilsShould.loadEncryptedIdentityMaterialFromDirectory(PemUtilsShould.java:310)

See bug report at oracle to get the latest update: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8266261

Request support check general key with tools PemUtils

//X509ExtendedTrustManager trustManager = PemUtils.loadTrustMaterial("<path_key>");
X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial("path_key_cer", "path_client_key", "scretKeyPass");
SSLFactory sslFactory = SSLFactory.builder()
.withTrustMaterial(trustManager)
.withIdentityMaterial(keyManager)
.build();

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

URL wsURL = new URL(urlServer);
HttpsURLConnection httpsConn = (HttpsURLConnection) wsURL.openConnection();
httpsConn.setSSLSocketFactory(sslFactory.getSslSocketFactory());

Hi [sslcontext-kickstart] I using your tools to load key for client site call request, Now I want config a nginx proxy to handler redirect request other server, I can change config urlServer in olds server, and key path_key, path_key_cer, path_client_key, but now I cannot change code server old. How any solution config nginx with key tool PemUtils.loadIdentityMaterial;

2023/01/31 13:44:32 [info] 32#32: *4 SSL_do_handshake() failed (SSL: error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown:SSL alert number 46) while SSL handshaking, client: 172.17.0.1, server: 0.0.0.0:443

I have try some solution but cannot https://egkatzioura.com/2021/09/24/add-ssl-to-nginx/;
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Simplify use of system truststore

Under Windows and MacOS, it is possible to use the system truststore in addition to regular keystores.
With the current library, it can be achieved by a code like this:

SSLFactory.Builder result = SSLFactory
                .builder()
                .withDefaultTrustMaterial();
if (isWindows()) {
  KeyStore root = KeyStore.getInstance("Windows-ROOT");
  root.load(null, null);
  result.withTrustMaterial(root, new char[0]);
  KeyStore my = KeyStore.getInstance("Windows-MY");
  my.load(null, null);
  result.withTrustMaterial(my, new char[0]);
} else if (isMacos()) {
  KeyStore keychain = KeyStore.getInstance("KeychainStore");
  keychain.load(null, null);
  result.withTrustMaterial(keychain, new char[0]);
}

I would prefer to have something like:

SSLFactory.Builder result = SSLFactory
                .builder()
                .withDefaultTrustMaterial()
                .withSystemTrustMaterial();

Rename sslcontext package to a generic name

Initially SSLFactory was named SSLContextHelper and it was the only class within the package. Therefor it made sense to name the package nl.altindag.sslcontext.* As the library grows and contains other functionalities such as KeyStoreUtils, TrustManagerUtils, KeyManagerUtils, CompositeX509ExtendedKeyManager, CompositeX509ExtendedTrustManager, PemUtils, JettySslContextUtils, NettySslContextUtils, ApacheSslContextUtils it would make more sense to group them in a more generic name such as nl.altindag.ssl.*

No end-to-end tests?

Do you have test cases for your various sample configurations that confirm they're secure? Without that it's difficult to trust this project with such a critical security responsibility.

Add support for Java Platform Module System

End users should still be able to use this library with java 8 while it is also compatible with java modules (Java 11+)
Every maven module should contain module-info.java
Internal libraries should be encapsulated and if required only exposed to sub maven modules.

To be expected:

  • New major release
  • Breaking changes (package name changes in submodules as it is conflicting with the core module)
  • Internal api with public access modifiers will be moved to a sub package internal and will be locked with modules

Trusting a pem certificate

Describe the bug
Thanks for this library, it looks exactly like what I'm looking for: adding a root certificate as trusted so that HTTPS works for all certificates issued by that root certificate.
I however am not sure how to use it, all examples load both a trust material and an identity material, and I only have a public certificate in the pem format. I thought I'd be able to just call SSLFactory.builder().withTrustMaterial(buildTrustManager()).build(); but I'm getting an exception.

To Reproduce

git clone https://github.com/Athou/ssl-test
cd ssl-test
mvn test

I then get the following exception

javax.net.ssl.SSLException: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
        at sun.security.ssl.Alerts.getSSLException(Alerts.java:208)
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1964)
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1921)
        at sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1904)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1420)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1397)
        at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
        at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1564)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
        at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:347)
        at test.SslTest.test(SslTest.java:35)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
        at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
        at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
        at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
        at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
        at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
Caused by: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
        at sun.security.validator.PKIXValidator.<init>(PKIXValidator.java:91)
        at sun.security.validator.Validator.getInstance(Validator.java:181)
        at sun.security.ssl.X509TrustManagerImpl.getValidator(X509TrustManagerImpl.java:312)
        at sun.security.ssl.X509TrustManagerImpl.checkTrustedInit(X509TrustManagerImpl.java:171)
        at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:184)
        at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1596)
        at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
        at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1052)
        at sun.security.ssl.Handshaker.process_record(Handshaker.java:987)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1072)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1385)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1413)
        ... 37 more
Caused by: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
        at java.security.cert.PKIXParameters.setTrustAnchors(PKIXParameters.java:200)
        at java.security.cert.PKIXParameters.<init>(PKIXParameters.java:120)
        at java.security.cert.PKIXBuilderParameters.<init>(PKIXBuilderParameters.java:104)
        at sun.security.validator.PKIXValidator.<init>(PKIXValidator.java:89)

Expected behavior
The test passes since mockserver issues certificates that have the CertificateAuthorityCertificate.pem as root, as described here https://www.mock-server.com/mock_server/HTTPS_TLS.html (in the "Ensure MockServer Certificates Are Trusted" section)

Environmental Data:

  • Java Version: 8
  • Maven Version: 3.5
  • OS: Windows

Any help would be greatly appreciated :)

Example SSL / TLS Configuration for Http Clients

Every http client will have a slightly different configuration for encryption/https and therefor as a developer we need to dive into their documentation to find examples, this will be unfortunately time-consuming. Some clients just only require SSLContext, others will need a SocketFactory, SSLSocketFactory, TrustManager, X509TrustManager, X509ExtendedTrustManager, KeyManager, X509KeyManager, X509ExtendedKeyManager, DefaultSSLParameters or a list of trusted certificates. I wanted to provide an overview of http clients configuration as a cheat-sheet to make our lives easier.

Below is an overview of client configuration examples with and without TLS/SSL enabled and with basic http requests. The examples are from the github project mutual-tls-ssl, which is a practical tutorial for configuring a client and a server for four scenarios:

  • No security
  • One way authentication
  • Two way authentication
  • Two way authentication with trusting the Certificate Authority

Example client configuration and example requests / Cheatsheet

All client examples use the same base ssl configuration created within the SSLConfig class

Java

Kotlin

Scala

Feel free to ask for other client examples here

SSLFactory withTrustMaterial with null (empty password)

Hello.

I'm trying to load my own TrustMaterial which i have in my own JKS keystore.
But I see you have a validations in all the overloaded methods withTrustMaterial that the password can not be NULL or empty char[].
Do i really need to add a password to my keystore? Right now my JKS has no password at all.

SSLFactory sslFactory = SSLFactory.builder()
        .withDefaultTrustMaterial()
        .withTrustMaterial("keystore/custom-keystore.jks", null)
        .build();

Misleading NullPointerException when reading an encrypted PEM key and none supplied

Describe the bug

When an encrypted PEM private key is used without supplying a password, a null pointer exception is being returned.

There is not way for the library user (an probably my end users too) to know the reason of this failure is that a password is required.

To Reproduce

            X509ExtendedKeyManager[] x509ExtendedKeyManager = {PemUtils.loadIdentityMaterial(
                        "certs.crt",
                        "encrypted.private.key",
                        null)};
                        
                     
            SSLContext context = SSLContext.getInstance("TLS");
            context.init(kmf.getKeyManagers(), null, null);

Results in :

java.lang.NullPointerException: null
        at java.base/java.util.Objects.requireNonNull(Objects.java:221)
        at nl.altindag.ssl.util.PemUtils.lambda$static$1(PemUtils.java:92)
        at nl.altindag.ssl.util.PemUtils.parsePrivateKey(PemUtils.java:416)
        at nl.altindag.ssl.util.PemUtils.parseIdentityMaterial(PemUtils.java:329)
        at nl.altindag.ssl.util.PemUtils.loadIdentityMaterial(PemUtils.java:301)
        at nl.altindag.ssl.util.PemUtils.loadIdentityMaterial(PemUtils.java:227)

Expected behavior

I expect a dedicated exception with an easy to understand message...

Environmental Data:

  • Java 11 (OpenJDK 11.0.11)
  • Maven 3.6.3
  • Ubuntu

Additional context

I could end up using this library in a contribution to Apache James to not just support keystores but also PEM files.

The ease of use is appealing though I need users to understand by themselves their mistakes before reaching the community (including I) and says stuff like "your soft is not working there is a NPE" without understanding the mistake is theirs...

BTW I could fire a patch if my above proposal is deemed acceptable.

[HELP] Use of .PEM and self signed

Hi,
I'm working with .pem files that out supplier give us.
In DEV environment, it provided us 3 files:

  • certificate
  • private key
  • ca root (self-signed)
X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial(
   //certificate
   Paths.get("certificate.pem"),

   //private key
   Paths.get("private-key.pem"));

//trusted certificate
X509ExtendedTrustManager trustManager = PemUtils.loadTrustMaterial(
   Paths.get("caroot.pem"));


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

SSLContext contestoSSL = sslFactory.getSslContext();

I pass the context to Spring Integration.
When I call the service, I got:

org.apache.cxf.binding.soap.SoapFault: Policy Falsified

To be honest, I got the same error without using your library but using directly trustore and keystore.
The problem arise some weeks ago only in DEV enviroment.
To avoid the "problem" to update and/or re-create trustore and keystore from .pem files every time the supplier give us a new version of .pem files, we tried to move to a smarter way and your library is the perfect choice for that.
But we got the same error.

Using CURL it works!

curl --location 'https://webserver/service.ws' \
--key "./private-key.pem" \
-E "./certificate.pem" \
--cacert "./caroot.pem" \
--header 'SoapAction: http://....' \
--header 'Content-Type: text/xml' \
--data '<?xml version="1.0" encoding="UTF-8"?><xml>.....</xml>'

If anyone has any suggestions we greatly appreciate it.

Thanks!
Daniele

CVE-2019-20444 io.netty:netty-tcnative-classes:jar:2.0.48.Final:compile (version managed from 2.0.48.Final)

Hello Hakan,

Just wanted to report something, hope you are well!

I upgraded to the latest 7.3.0 version of this project, but after analysis, this is highlighted:

[INFO] +- io.github.hakky54:sslcontext-kickstart-for-netty:jar:7.3.0:compile
[INFO] |  +- io.github.hakky54:sslcontext-kickstart:jar:7.3.0:compile
[INFO] |  |  \- (org.slf4j:slf4j-api:jar:1.7.36:compile - version managed from 1.7.36; omitted for duplicate)
[INFO] |  \- io.netty:netty-handler:jar:4.1.74.Final:compile (version managed from 4.1.74.Final)
[INFO] |     +- io.netty:netty-common:jar:4.1.74.Final:compile (version managed from 4.1.74.Final)
[INFO] |     |  \- (org.graalvm.nativeimage:svm:jar:19.3.6:provided - omitted for conflict with 21.0.0.2)
[INFO] |     +- io.netty:netty-resolver:jar:4.1.74.Final:compile (version managed from 4.1.74.Final)
[INFO] |     |  \- (io.netty:netty-common:jar:4.1.74.Final:compile - version managed from 4.1.74.Final; omitted for duplicate)
[INFO] |     +- io.netty:netty-buffer:jar:4.1.74.Final:compile (version managed from 4.1.74.Final)
[INFO] |     |  \- (io.netty:netty-common:jar:4.1.74.Final:compile - version managed from 4.1.74.Final; omitted for duplicate)
[INFO] |     +- io.netty:netty-transport:jar:4.1.74.Final:compile (version managed from 4.1.74.Final)
[INFO] |     |  +- (io.netty:netty-common:jar:4.1.74.Final:compile - version managed from 4.1.74.Final; omitted for duplicate)
[INFO] |     |  +- (io.netty:netty-buffer:jar:4.1.74.Final:compile - version managed from 4.1.74.Final; omitted for duplicate)
[INFO] |     |  \- (io.netty:netty-resolver:jar:4.1.74.Final:compile - version managed from 4.1.74.Final; omitted for duplicate)
[INFO] |     +- io.netty:netty-codec:jar:4.1.74.Final:compile (version managed from 4.1.74.Final)
[INFO] |     |  +- (io.netty:netty-common:jar:4.1.74.Final:compile - version managed from 4.1.74.Final; omitted for duplicate)
[INFO] |     |  +- (io.netty:netty-buffer:jar:4.1.74.Final:compile - version managed from 4.1.74.Final; omitted for duplicate)
[INFO] |     |  \- (io.netty:netty-transport:jar:4.1.74.Final:compile - version managed from 4.1.74.Final; omitted for duplicate)
[INFO] |     \- io.netty:netty-tcnative-classes:jar:2.0.48.Final:compile (version managed from 2.0.48.Final)

The thing is, io.netty:netty-tcnative-classes:jar:2.0.48.Final is known to have the vulnerability:
CVE-2019-20444

Is there a way to bump this to a version that is safe?

Thank you!

android platform I don't want to use user imported self-signed CA?

Hello, I hava a question about load system cert .

    fun OkHttpClient.Builder.setSSLCertificate(
        trustManager: X509TrustManager,
        bksFile: InputStream? = null,
        password: String? = null,
    ) = apply {
        try {
            val trustManagerFinal: X509TrustManager = trustManager
            val keyManagers = prepareKeyManager(bksFile, password)

            var factory = SSLFactory.builder()
                .withDefaultTrustMaterial()
                .withSystemTrustMaterial()
                .build()

            var combine = TrustManagerUtils.combine(
                trustManagerFinal,
                factory.trustManager.get(),
                factory.trustManager.get()
            )
            factory.sslContext.init(keyManagers, arrayOf<TrustManager?>(combine), null)
            // 通过sslContext获取SSLSocketFactory对象
            sslSocketFactory(factory.sslContext.socketFactory,trustManagerFinal)
        } catch (e: NoSuchAlgorithmException) {
            throw AssertionError(e)
        } catch (e: KeyManagementException) {
            throw AssertionError(e)
        }
    }

When I used the sslContext above for my OKHTTP, I found that the CA manually imported by the user in the settings was also added. How to separate CA certificates manually imported by users? I don't want to use the user imported system CA, I just want to use the system pre installed CA certificate

            withDefaultTrustMaterial(), withSystemTrustMaterial()  

The two lines of code load the pre-installed certificate of the Android system and the CA installed by the user. I don't want this. I only need the pre-installed CA of the Android system and the CA certificate I use to load the code, which will be much safer.

Heads up, thank you for this gem!

Just wanted to thank you for making and maintaining this little gem here.

Very flexible with that build and solves basically all scenarios i could think of. Please take my credits :)

FenixHostnameVerifier issues

Should this be an endsWith()?

can the !empty be removed if you check for size=2 anyway?

.filter(subjectAlternativeName -> !subjectAlternativeName.isEmpty())

Is this intentionally not checking CN?

Collection<List<?>> subjectAlternativeNames = Optional.ofNullable(certificate.getSubjectAlternativeNames())

i can provide a PR for those issues if youmagree it needs to be fixed.

this verifier should probably also carry a warning that it does not know/forbid patterns in TLDs (*.co.uk instead of *.dom.co.uk or even *.com)?

PEM helpers to generate easiy a key pair programatically?

Is your feature request related to a problem? Please describe.

As discussed in this email on the James mailing list https://www.mail-archive.com/[email protected]/msg70783.html we are considering adding on-the-fly generation for SSL cryptographic materials as an option.

The goal being to conciliate two requirements:

  • Not shipping default keys - administrators NOT updating the default configuration would be trivially exposed to man-in-the-middle attacks...
  • But at the same time have one signle command startup (docker run ...)

One solution being on the fly key generation.

Describe the solution you'd like

I would enjoy having methods in PEM util (or another one) to create such keys / KeyManager...

Describe alternatives you've considered

Specifying a startup script in the dockerfile creating the keys if missing. Could work too.

Suitable for my demo image where I already have a bash wrapper in place ( apache/james-project#628 contributes it) yet making it more generic to the other production grade docker image can not rely on this approach but might benefit from this 'feature' - for instance people could try the Distributed James docker compose in a single docker-compose up command.

Additional context

James ticket: https://issues.apache.org/jira/browse/JAMES-3640

Remove/Disable logging in unsafe HostnameVerifier and TrustManager

the logging in the unsafe variants of HostnameVerifier and TrustManager spam logs. There should be a way to disable this or at least log it to DEBUG.

I see no reason why i would like to know that a self signed certificate is trusted. that's exactly what i want to do else i wouldn't use this specific verifier.

`SSLFactory.Builder.withDefaultIdentityMaterial` method

When custom sslContext is created there is no easy way to load "default" keyManager
, although withDefaultTrustMaterial allows it do so for trustManager.

It would be nice to have withDefaultIdentityMaterial method that would utilize default keyManager (javax.net.ssl.keyStore)

Documentation suggests to use

.withIdentityMaterial(Paths.get(System.getProperty("javax.net.ssl.keyStore")), System.getProperty("javax.net.ssl.keyStorePassword").toCharArray(), 
System.getProperty("javax.net.ssl.keyStoreType"))

which probably does the trick, but does not handle the cases when properties are not set. Helper method would do null checks and remove boilerplate code from users of SSLFactory.

And Maybe there is a way to get a reference to internal keyManage. I haven't found one.

Client Certificate not presented by Apache HTTP Client when loaded in SSL Context

First, awesome idea for a project 👍

Describe the bug
When using the PEM Utils and SSL Factory to create an SSL Context for Apache HTTP Client with a client cert, Apache then does not present the client cert.

I'm following your tutorials and have checked the Apache manual too, so I think I'm doing this right

To Reproduce

  1. git clone https://github.com/tadhgpearson/sslfactory-client-cert-test.git
  2. mvn verify

Expected behavior
The SSLContextFactory in this repository uses kickstart to read the cert, CA chain and key file for the client cert, and should build an SSL Context with both the default trust material and this key and trust material.

Then the SSLContextFactoryTest uses Apache HTTP Client and should present the loaded client certificate to the test site... but it doesn't.

Environmental Data:

  • Java Version 11
  • Maven Version 3.6.3
  • Mac OS Catalina

Keystore password is unnecessary

Hello,

First of all, thanks for the handy library. I have a suggestion to drop the keystore password as it is redundant (client would have already a Keystore at hand) in the api that takes KeyStore as the first argument.

For example, this is how I use your library.

SSLFactory.Builder sslFactoryBuilder = SSLFactory.builder()
                                                         .withDefaultTrustMaterial();
        if ((trustStoreResource != null) && (trustStorePassword != null)) {
            KeyStore trustStore = KeyStore.getInstance("PKCS12");
            trustStore.load(trustStoreResource.getInputStream(),
                            trustStorePassword.toCharArray());
            sslFactoryBuilder.withTrustMaterial(
                trustStore, trustStorePassword.toCharArray());
        }

And as you can see, sslFactoryBuilder.withTrustMaterial(trustStore, trustStorePassword.toCharArray());, password is not required there.

Certificate Revocation List?

Today the library do not allow to easily turn on Certificate Revocation List checks.

I would like to have an easy option to turn on OCSP checks using eg PKIXRevocationChecker

EG:

SSLContext context = sslFactoryBuilder.enableCRLOCSPChecks(true)
    .build()
    .getSslContext();

Context: As an email server writter I receive many connections from third parties and checking their certificate validity is rather important,

CombinableX509TrustManager can't completed all trustManager's check

Describe the bug
Hi, i found that CombinableX509TrustManager can't completed all trustManager's check, when some trustManager's trustdCerts is empty.

To Reproduce

public class ElasticsearchTest {
    @Test
    public void test_connect_elasticsearch_server() throws IOException {
        SSLFactory sslFactory = SSLFactory.builder()
                .withSystemTrustMaterial()
                .withTrustMaterial(CertificateUtils.loadCertificate("http_ca.crt"))
                .build();
        BasicCredentialsProvider basicCredentialsProvider = new BasicCredentialsProvider();
        basicCredentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "es123456"));

        RestClient restClient = RestClient.builder(new HttpHost("192.168.169.2", 39200, "https"))
                .setHttpClientConfigCallback(httpAsyncClientBuilder -> httpAsyncClientBuilder
                        .setDefaultCredentialsProvider(basicCredentialsProvider)
                        .setSSLContext(sslFactory.getSslContext())
                        .setSSLHostnameVerifier(sslFactory.getHostnameVerifier())
                )
                .build();
        RestClientTransport restClientTransport = new RestClientTransport(restClient, new JacksonJsonpMapper());
        ElasticsearchClient elasticsearchClient = new ElasticsearchClient(restClientTransport);
        IndicesResponse indices = elasticsearchClient.cat().indices();
    }
}

in withSystemTrustMaterial(), it would load Windows-ROOT keystore and Windows-MY keystore, but now my Windows-MY keystore certicate is empty.
in withTrustMaterial(CertificateUtils.loadCertificate("http_ca.crt")), it would load the elasticsearch's certicate.
trustmanagers

the error is:

Caused by: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
	at java.security.cert.PKIXParameters.setTrustAnchors(PKIXParameters.java:200)
	at java.security.cert.PKIXParameters.<init>(PKIXParameters.java:120)
	at java.security.cert.PKIXBuilderParameters.<init>(PKIXBuilderParameters.java:104)
	at sun.security.validator.PKIXValidator.<init>(PKIXValidator.java:99)
	at sun.security.validator.Validator.getInstance(Validator.java:181)
	at sun.security.ssl.X509TrustManagerImpl.getValidator(X509TrustManagerImpl.java:299)
	at sun.security.ssl.X509TrustManagerImpl.checkTrustedInit(X509TrustManagerImpl.java:175)
	at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:245)
	at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:140)
	at nl.altindag.ssl.trustmanager.CompositeX509ExtendedTrustManager.lambda$checkServerTrusted$6(CompositeX509ExtendedTrustManager.java:110)
	at nl.altindag.ssl.trustmanager.CombinableX509TrustManager.checkTrusted(CombinableX509TrustManager.java:40)
	at nl.altindag.ssl.trustmanager.CompositeX509ExtendedTrustManager.checkServerTrusted(CompositeX509ExtendedTrustManager.java:110)
	at sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:630)
	at sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:471)
	at sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:367)
	at sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:376)
	at sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:479)
	at sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:990)
	at sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:977)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.security.ssl.SSLEngineImpl$DelegatedTask.run(SSLEngineImpl.java:924)
	at org.apache.http.nio.reactor.ssl.SSLIOSession.doRunTask(SSLIOSession.java:288)
	at org.apache.http.nio.reactor.ssl.SSLIOSession.doHandshake(SSLIOSession.java:356)
	... 9 more

I found in CombinableX509TrustManager will check all the TrustManager whether to support the current certificate chain. As a result of my second TrustManage from Windows-MY trustCerts is empty, so when executing callBackConsumer.checkTrusted(trustManager); method throws InvalidAlgorithmParameterException, but my third TrustManager actually can trust the current certificate.
image

Whether all exceptions can be caught in CombinableX509TrustManager.checkTrusted so that all TrustManagers have a chance to be executed, or whether callBackConsumer.checkTrusted(trustManager); is not executed when the trustCerts of the trustManager is empty.

Although I could have avoided this problem in my example by removing .withSystemTrustMaterial(), I think it's still possible that someone could load multiple TrustManagers at the same time, and the trustCerts of one of them might be empty.

Environmental Data:

  • Java Version 1.8.0_321
  • sslcontext-kickstart Version 7.4.2
  • OS: Windows

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.