I have been unable to find a solution to this problem elsewhere so I am hoping someone here can provide some insight. My setup below:
keystore, myKeys.jks:
mine-private, 3/6/2014, PrivateKeyEntry
mine-trusted, 3/6/2014, trustedCertEntry
trust store, myTrust.jks:
trusted-cert-1, 3/6/2014, trusterCertEntry
trusted-cert-2, 3/6/2014, trusterCertEntry
mine-trusted, 3/6/2014, trustedCertEntry <-- this is mine
What ends up happening is I get a message stating that my client has not been authenticated. Let me know if there is more information necessary
Responses to questions:
First off: what classes/library are you using? Simply the default https in java?
Apache HTTP Client, code below:
HttpClient client = new HttpClient();
GetMethod method = new GetMethod("https://foo.bar.baz/rest");
client.executeMethod(method);
Secondly: how exactly are you registering the keystore/truststore? You need a custom SSLContext for this.
Don't think so, but could be wrong
-Djavax.net.ssl.trustStore="path/to/myTrust.jks"
-Djavax.net.ssl.trustStorePassword="password"
-Djavax.net.ssl.keyStore="path/to/myKeys.jks"
-Djavax.net.ssl.keyStorePassword="password"
First off: what classes/library are you using? Simply the default https in java?
Secondly: how exactly are you registering the keystore/truststore? You need a custom SSLContext for this.
Initial example:
SSLContext context = SSLContext.getInstance();
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyFactory.init(keyStore, password);
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(trustStore);
context.init(keyFactory.getKeyManagers(), trustFactory.getTrustManagers(), null);
Most libraries that I know support setting a custom SSLContext or SSLSocketFactory which can be obtained from the context.
I have written a sample that does the exact same thing. You can find the particular code in [1].
[1] https://github.com/wso2/carbon-identity/blob/v5.0.7/components/authentication-framework/org.wso2.carbon.identity.application.authentication.endpoint.util/src/main/java/org/wso2/carbon/identity/application/authentication/endpoint/util/TenantMgtAdminServiceClient.java#L155
Related
Small question regarding Netty, Spring Webflux, and how to send http requests to multiples downstream systems, when each of the downstream require mTLS and a different client certificate is required to send requests to each please?
What I have so far in my Java 11 Spring Webflux 2.4.2 app for sending request is:
#Bean
#Primary
public WebClient getWebClient() {
return WebClient.create().mutate().defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).clientConnector(new ReactorClientHttpConnector(HttpClient.create().wiretap(true).secure(sslContextSpec -> sslContextSpec.sslContext(getSslContext())))).build();
}
And for the Netty SslContext (it is not an apache SSLContext btw)
public SslContext getSslContext() {
try {
final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
try (InputStream file = new FileInputStream(keyStorePath)) {
final KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(file, keyStorePassPhrase.toCharArray());
keyManagerFactory.init(keyStore, keyPassPhrase.toCharArray());
}
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
try (InputStream trustStoreFile = new FileInputStream(trustStorePath)) {
final KeyStore trustStore = KeyStore.getInstance(trustStoreType);
trustStore.load(trustStoreFile, trustStorePassPhrase.toCharArray());
trustManagerFactory.init(trustStore);
}
return SslContextBuilder.forClient().keyManager(keyManagerFactory).trustManager(trustManagerFactory).build();
} catch (CertificateException | NoSuchAlgorithmException | IOException | KeyStoreException | UnrecoverableKeyException e) {
return null;
}
}
This is even working perfectly fine when we only need to send request to only one downstream.
This is even working if there are multiple downstream, and they accept the same client certificate!
But problem arise when each downstream requires me to use their respective client certificate.
May I ask how to achieve this please?
Thank you
Option 1
The most straightforward solution would be using a specific client for each downstream api. And having each client configured with their specific client key and trust material.
Option 2
But your question is: how to use SslContext with multiple client certificates please?
So I want to give you some code examples to have a working setup. But the short answer is: yes it is possible!
The long answer is that you need some additional configuration to make it working. Basically what you need to do is create a keymanagerfactory from your keystore-1 and get the keymanager from the keymanagerfactory and repeat that for the other two keystores. Afterwords you will have 3 keymanagers. The next step is to have a special kind of keymanager which can be supplied to the Netty SslContext. This special kind of keymanager has the ability to iterate through the 3 keymanagers which you have created earlier and it will select the correct key material to communicate with the server. What you need is a CompositeKeyManager and CompositeTrustManager which is mentioned at the following stackoverflow answer here: Registering multiple keystores in JVM
The actual code snippet will be the below. I disregarded the loading file with inputstream and creating the keystore file and creating the keymanagerfactory as you already know how to do that.
KeyManager keyManagerOne = keyManagerFactoryOne.getKeyManagers()[0]
KeyManager keyManagerTwo = keyManagerFactoryTwo.getKeyManagers()[0]
KeyManager keyManagerThree = keyManagerFactoryThree.getKeyManagers()[0]
List<KeyManager> keyManagers = new ArrayList<>();
keyManagers.add(keyManagerOne);
keyManagers.add(keyManagerTwo);
keyManagers.add(keyManagerThree);
CompositeX509KeyManager baseKeyManager = new CompositeX509KeyManager(keyManagers);
//repeat the same for the trust material
TrustManager trustManagerOne = trustManagerFactoryOne.getTrustManagers()[0]
TrustManager trustManagerTwo = trustManagerFactoryTwo.getTrustManagers()[0]
TrustManager trustManagerThree = trustManagerFactoryThree.getTrustManagers()[0]
List<TrustManager> trustManagers = new ArrayList<>();
trustManagers.add(trustManagerOne);
trustManagers.add(trustManagerTwo);
trustManagers.add(trustManagerThree);
CompositeX509TrustManager baseTrustManager = new CompositeX509TrustManager(trustManagers);
SslContext sslContext = SslContextBuilder.forClient()
.keyManager(baseKeyManager)
.trustManager(baseTrustManager)
.build();
And the above code should give you the capability of using multiple key and trust for a single client. This client will be able to communicate with the different downstream api's with the different key and trust material.
The downside of this setup is that you require to copy and paste the CompositeKeyManager and CompositeTrustManager into your code base and that the setup is a bit verbose. Java does not provide something out of the box for this use-case.
Option 3
If you want a a bit simpeler setup I would suggest you the code snippet below:
import io.netty.handler.ssl.SslContext;
import nl.altindag.ssl.SSLFactory;
import nl.altindag.ssl.util.NettySslUtils;
public class App {
public static void main(String[] args) {
SSLFactory sslFactory = SSLFactory.builder()
.withIdentityMaterial(keyStorePathOne, password)
.withIdentityMaterial(keyStorePathTwo, password)
.withIdentityMaterial(keyStorePathThree, password)
.withTrustMaterial(trustStorePathOne, password)
.withTrustMaterial(trustStorePathTwo, password)
.withTrustMaterial(trustStorePathThree, password)
.build();
SslContext sslContext = NettySslUtils.forClient(sslFactory).build();
}
}
I need to provide some disclaimer, I am the maintainer of the library of the code snippet above. The library is available here: GitHub - SSLContext Kickstart and it uses the same CompositeKeyManager and CompositeTrustManager under the covers which I mentioned earlier for option 2.
And you can add it to your pom with the following snippet:
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>sslcontext-kickstart-for-netty</artifactId>
<version>7.4.9</version>
</dependency>
I'm using Java implementation for revocation checking using CRL which is like the following code (I tailored the code to be short and clear)
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX");
PKIXRevocationChecker rc = (PKIXRevocationChecker)cpb.getRevocationChecker();
rc.setOptions(EnumSet.of(
PKIXRevocationChecker.Option.PREFER_CRLS,
PKIXRevocationChecker.Option.ONLY_END_ENTITY,
PKIXRevocationChecker.Option.NO_FALLBACK,
PKIXRevocationChecker.Option.SOFT_FAIL));
PKIXBuilderParameters pkixParams =
new PKIXBuilderParameters(getTrustStore(), new X509CertSelector());
pkixParams.setRevocationEnabled(false);
pkixParams.addCertPathChecker(rc);
tmf.init(new CertPathTrustManagerParameters(pkixParams));
SSLContext contect = SSLContext.getInstance("TLSv1.2");
context.init(null, getTrustManagers(), null);
defaultFactory = context.getSocketFactory();
The above code is in the constructor of a custom SSLSocketFactory, and there is a custom TrustManager (returned by getTrustManagers()) which overrides checkServerTrusted() to perform a few certificate checks and executes default checkServerTrusted() at the end.
Everything works fine except that I need that validation of CRL to be failed when it is signed by a CA that does not have the cRLsign key usage
bit set, but it doesn't.
I know that this is a requirement by RFC3280, and somewhere on the web I read that Java implementation is compliant with the RFC and I even saw the methods in the Java base code to do that, But it seems it does not come into play when I run the application.
I did all the revocation checking with a custom code to achieve that but I think Java implementation is way more complete that my custom code and I strongly prefer to use Java implementation.
Any solution?
Oh wise and noble Oracle,
I'm adding SSL to a TCP client I've written on my Android phone. I can
successfully connect to servers with properly signed certificates, and I can
connect to self-certifying hosts by cooking up a TrustManager implementation
that always thinks everything is fine.
I now have a decorator TrustManager capturing the certificates (before
delegating to its decoratee) for self-certifying hosts and presenting them for
my breathless perusal, but what I can't work out is how to implement ssh's
behaviour of warning that a host is unknown and offering to remember it for
next time - and doing so.
I presumed all I needed was to store the public key - as ssh does with
known_hosts - and re-represent it, but with this code and 'sslTrust' holding
the public key:
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null); // initialise!
ks.setKeyEntry("dbentry", Base64.decode(sslTrust, Base64.NO_WRAP), null);
tmf.init(ks);
tms = tmf.getTrustManagers();
ss.stm = new SnoopyTrustManager((X509TrustManager) tms[0]);
// ...
SLContext context = SSLContext.getInstance("SSL");
context.init(null, new TrustManager[] { ss.stm } , null);
ss.factory = context.getSocketFactory();
// ...
SocketFactory factory = ss.getFactory();
mSocket = factory.createSocket(host, port);
attempting to establish a connection results in
SSLHandshakeException: InvalidAlgorithmParameterException: trustAnchors.isEmpty()
which is fair enough: I don't know how to cook things up from the certificate
offered by the remote server. I'm also fairly sure this isn't how I tell a
TrustManager about a remote server's public key anyway.
Since the site is self-certifying, I imagine could probably just verify that
the public keys match in a trivial TrustManager, but I'd like to understand
how this 'should' be done - adding a CA on a per-connection basis, since
I won't trust that CA for anything else.
You need to use your own trust store on pre-ICS version, and add the serer's certificates to it on first error. Subsequent connections will load it from the trust store and thus trust the remote certificate. This is not a complete solution, but here's one way to do it (code on Github), along with some discussion:
http://nelenkov.blogspot.jp/2011/12/using-custom-certificate-trust-store-on.html
I'm currently having a self signed certificate for my HTTPS webserver.
In my java program there is a SSLSocketFactory that will create a socket to the webserver. The default implementation of sun blocks the self signed certificate. With an own implementation of a X509TrustManager I can only check whether the date of the certificate is valid.
Is there any possibility to let the default implementation check the validity (date and hostname, ...), and if it fails to show a dialog to let the user accept this certificate?
Each code I found until now only disabled the ssl check and accepted every invalid certificate.
I haven't actually tried this, but why can't you implement your own trust manager, which first delegates to the default trust manager to check if the certificate is valid and if not, asks the user if he still wants to accept the certificate?
You can initialize most of the security classes with null arguments to use default values. To obtain the default trust manager, you must get the available trust managers and choose the first one in the mgrs arrays to implement the X509TrustManager interface. Usually, the array will only contain one elment anyway.
TrustManagerFactory trustmanagerfactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustmanagerfactory.init((KeyStore)null);
TrustManager[] mgrs = trustmanagerfactory.getTrustManagers();
After you've wrapped the default trust manager with your own extension, you have to initialize an SSL context and get a socket factory from it:
SSLContext sslContext=SSLContext.getInstance("SSL","SunJSSE");
sslContext.init(null, new TrustManager[] {myTm}, null);
SSLSocketFactory sf = sslContext.getSocketFactory();
Then use this socket factory to create new client sockets or pass it to HttpsURLConnection.setDefaultSSLSocketFactory to use the https protocol in URLs with your own trust manager.
Ideally, I only need a simple SSLSocketChannel.
I already have a component that reads and writes message over ordinary SocketChannel, but for some of these connections, I have to use SSL over the wire; the operations over these connections, however, are the same.
Does anyone knows a free SSLSocketChannel implementation (with the appropriate selector) or something similar? I've found this, but the selector doesn't accept it since its vendor isn't SUN.
I'm decoupling the reading_from/writing_to net logic from the insertion and retrieval of network data via a simple object, in order to use a SSLEngine without getting mad, but it's really tricky to implement that correctly, given the fact that I don't know the internals of SSL protocol...
Jetty has an NIO SSL implementation for their server: SslSelectorChannelConnector. You might want to peek at it for details on what its doing.
There is also an old (but decent) article from O'Reilly that explains the details about NIO + SSL along with example code.
TLS Channel is a simple library that does exactly that: wrapping a SSLContext (or SSLEngine) and exposing a ByteChannel interface, doing the heavy lifting internally.
(Disclaimer: I am the library's main author).
Check out Restlet's implementation it may do what you need, and it's all about NIO.
Restlet Engine Javadoc
Specifically the HttpClientCall. SetProtocol(HTTPS) - getResponseEntityChannel returns a ReadableByteChannel (getEntityChannel returns a WriteableByteChannel)
Not sure if this is what you're looking for, but may help... To create SSL/TLS enabled server sockets, I'm currently using code like the following (keystore.jks contains a self signed private/public key pair used for securing confirmation) - clients have a similar trust store which contains the signed certificate with the public key of that pair.
A bit of googling around getting that configured should get you underway.
String keyStorePath = "keystore.jks";
String keyStorePassword = "password";
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = new KeyStore();
keyStore.load(new FileInputStream(keyStorePath), keyStorePassword);
keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
SSLContext sslContext = getServerSSLContext(namespace.getUuid());
SSLServerSocketFactory serverSocketFactory = sslContext.getServerSocketFactory();
// Create sockets as necessary