Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added trust manager wrapper with dynamic callback for accepting serve…
…r certificates on the fly.
- Loading branch information
bharbulot
committed
Jul 20, 2011
1 parent
bd83c90
commit e1a0fd2
Showing
2 changed files
with
386 additions
and
0 deletions.
There are no files selected for viewing
204 changes: 204 additions & 0 deletions
204
.../main/java/org/jsslutils/sslcontext/trustmanagers/ServerCallbackWrappingTrustManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
/*----------------------------------------------------------------------- | ||
This file is part of the jSSLutils library. | ||
Copyright (c) 2011, Bruno Harbulot. | ||
All rights reserved. | ||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are met: | ||
* Redistributions of source code must retain the above copyright notice, | ||
this list of conditions and the following disclaimer. | ||
* Redistributions in binary form must reproduce the above copyright | ||
notice, this list of conditions and the following disclaimer in the | ||
documentation and/or other materials provided with the distribution. | ||
* Neither the name of the copyright holder nor the names of | ||
its contributors may be used to endorse or promote products derived | ||
from this software without specific prior written permission. | ||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | ||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
POSSIBILITY OF SUCH DAMAGE. | ||
Author........: Bruno Harbulot | ||
-----------------------------------------------------------------------*/ | ||
|
||
package org.jsslutils.sslcontext.trustmanagers; | ||
|
||
import java.io.IOException; | ||
import java.security.KeyStore; | ||
import java.security.KeyStoreException; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.security.cert.Certificate; | ||
import java.security.cert.CertificateException; | ||
import java.security.cert.X509Certificate; | ||
import java.util.Enumeration; | ||
import java.util.UUID; | ||
|
||
import javax.net.ssl.X509TrustManager; | ||
|
||
import org.jsslutils.sslcontext.X509TrustManagerWrapper; | ||
|
||
/** | ||
* @author Bruno Harbulot. | ||
*/ | ||
public class ServerCallbackWrappingTrustManager implements X509TrustManager { | ||
private final X509TrustManager trustManager; | ||
private final KeyStore localTrustStore; | ||
private final CheckServerTrustedCallback callback; | ||
|
||
/** | ||
* Creates a new instance from an existing X509TrustManager. | ||
* | ||
* @param trustManager | ||
* X509TrustManager to wrap. | ||
* @param callback | ||
* {@link CheckServerTrustedCallback} from the user-interface. | ||
* @param localTrustStore | ||
* {@link KeyStore} (loaded)to use as a trust store. | ||
* @param saveLocalTrustStore | ||
* Set to true to save the keystore, otherwise, it will only be | ||
* kept in memory. | ||
*/ | ||
public ServerCallbackWrappingTrustManager(X509TrustManager trustManager, | ||
CheckServerTrustedCallback callback, KeyStore localTrustStore) { | ||
this.trustManager = trustManager; | ||
this.localTrustStore = localTrustStore; | ||
this.callback = callback; | ||
} | ||
|
||
/** | ||
* Creates a new instance from an existing X509TrustManager. | ||
* | ||
* @param trustManager | ||
* X509TrustManager to wrap. | ||
* @param callback | ||
* {@link CheckServerTrustedCallback} from the user-interface. | ||
* @param localTrustStore | ||
* {@link KeyStore} to use as a trust store. | ||
* @param saveLocalTrustStore | ||
* Set to true to save the keystore, otherwise, it will only be | ||
* kept in memory. | ||
* @throws KeyStoreException | ||
* @throws IOException | ||
* @throws CertificateException | ||
* @throws NoSuchAlgorithmException | ||
*/ | ||
public ServerCallbackWrappingTrustManager(X509TrustManager trustManager, | ||
CheckServerTrustedCallback callback) throws KeyStoreException, | ||
NoSuchAlgorithmException, CertificateException, IOException { | ||
this(trustManager, callback, KeyStore.getInstance(KeyStore | ||
.getDefaultType())); | ||
this.localTrustStore.load(null); | ||
} | ||
|
||
/** | ||
* Checks that the client is trusted; in this case, it delegates this check | ||
* to the trust manager it wraps | ||
*/ | ||
public void checkClientTrusted(X509Certificate[] chain, String authType) | ||
throws CertificateException { | ||
this.trustManager.checkClientTrusted(chain, authType); | ||
} | ||
|
||
/** | ||
* Checks that the server is trusted; in this case, it accepts anything. | ||
*/ | ||
public void checkServerTrusted(X509Certificate[] chain, String authType) | ||
throws CertificateException { | ||
try { | ||
this.trustManager.checkServerTrusted(chain, authType); | ||
} catch (CertificateException e) { | ||
try { | ||
boolean certTrusted = false; | ||
Enumeration<String> aliases = this.localTrustStore.aliases(); | ||
while (aliases.hasMoreElements()) { | ||
String alias = aliases.nextElement(); | ||
Certificate cert = this.localTrustStore | ||
.getCertificate(alias); | ||
if (chain[0].equals(cert)) { | ||
certTrusted = true; | ||
break; | ||
} | ||
} | ||
if (certTrusted | ||
|| this.callback.checkServerTrusted(chain, authType)) { | ||
this.localTrustStore.setCertificateEntry(UUID.randomUUID() | ||
.toString(), chain[0]); | ||
} else { | ||
throw e; | ||
} | ||
} catch (KeyStoreException kse) { | ||
throw new CertificateException(kse); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Returns the accepted issuers; in this case, it's an empty array. | ||
*/ | ||
public X509Certificate[] getAcceptedIssuers() { | ||
return this.trustManager.getAcceptedIssuers(); | ||
} | ||
|
||
/** | ||
* Wrapper factory class that wraps existing X509TrustManagers into | ||
* X509TrustManagers that trust any clients. | ||
* | ||
* @author Bruno Harbulot. | ||
*/ | ||
public static class Wrapper implements X509TrustManagerWrapper { | ||
private final CheckServerTrustedCallback callback; | ||
private final KeyStore localTrustStore; | ||
|
||
public Wrapper(CheckServerTrustedCallback callback, | ||
KeyStore localTrustStore) { | ||
super(); | ||
this.callback = callback; | ||
this.localTrustStore = localTrustStore; | ||
} | ||
|
||
/** | ||
* Builds an X509TrustManager from another X509TrustManager. | ||
* | ||
* @param trustManager | ||
* original X509TrustManager. | ||
* @return wrapped X509TrustManager. | ||
*/ | ||
public X509TrustManager wrapTrustManager(X509TrustManager trustManager) { | ||
if (localTrustStore != null) { | ||
return new ServerCallbackWrappingTrustManager( | ||
(X509TrustManager) trustManager, callback, | ||
localTrustStore); | ||
} else { | ||
try { | ||
return new ServerCallbackWrappingTrustManager( | ||
(X509TrustManager) trustManager, callback); | ||
} catch (KeyStoreException e) { | ||
throw new RuntimeException(e); | ||
} catch (NoSuchAlgorithmException e) { | ||
throw new RuntimeException(e); | ||
} catch (CertificateException e) { | ||
throw new RuntimeException(e); | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
} | ||
} | ||
|
||
public static interface CheckServerTrustedCallback { | ||
public boolean checkServerTrusted(X509Certificate[] chain, | ||
String authType); | ||
} | ||
} |
182 changes: 182 additions & 0 deletions
182
jsslutils/src/test/java/org/jsslutils/sslcontext/test/CallbackTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
/*----------------------------------------------------------------------- | ||
This file is part of the jSSLutils library. | ||
Copyright (c) 2008-2009, The University of Manchester, United Kingdom. | ||
Copyright (c) 2011, Bruno Harbulot. | ||
All rights reserved. | ||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are met: | ||
* Redistributions of source code must retain the above copyright notice, | ||
this list of conditions and the following disclaimer. | ||
* Redistributions in binary form must reproduce the above copyright | ||
notice, this list of conditions and the following disclaimer in the | ||
documentation and/or other materials provided with the distribution. | ||
* Neither the name of the copyright holders nor the names of | ||
its contributors may be used to endorse or promote products derived | ||
from this software without specific prior written permission. | ||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | ||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
POSSIBILITY OF SUCH DAMAGE. | ||
Author........: Bruno Harbulot | ||
-----------------------------------------------------------------------*/ | ||
|
||
package org.jsslutils.sslcontext.test; | ||
|
||
import static org.junit.Assert.assertNotNull; | ||
import static org.junit.Assert.assertTrue; | ||
|
||
import java.io.IOException; | ||
import java.security.cert.X509Certificate; | ||
import java.util.concurrent.ExecutionException; | ||
import java.util.concurrent.Future; | ||
|
||
import javax.net.ssl.SSLContext; | ||
import javax.net.ssl.SSLServerSocket; | ||
|
||
import org.jsslutils.sslcontext.PKIXSSLContextFactory; | ||
import org.jsslutils.sslcontext.SSLContextFactory.SSLContextFactoryException; | ||
import org.jsslutils.sslcontext.trustmanagers.ServerCallbackWrappingTrustManager; | ||
import org.jsslutils.sslcontext.trustmanagers.ServerCallbackWrappingTrustManager.CheckServerTrustedCallback; | ||
import org.junit.Test; | ||
|
||
/** | ||
* Tests the SSLContext configured for X.509 without CRLs. It should accept both | ||
* the "good" and the "bad" certificate. | ||
* | ||
* @author Bruno Harbulot. | ||
* | ||
*/ | ||
public class CallbackTest extends MiniSslClientServer { | ||
protected PKIXSSLContextFactory clientSSLContextFactory; | ||
protected PKIXSSLContextFactory serverSSLContextFactory; | ||
|
||
public static CheckServerTrustedCallback ACCEPTING_CALLBACK = new CheckServerTrustedCallback() { | ||
public boolean checkServerTrusted(X509Certificate[] chain, | ||
String authType) { | ||
System.out | ||
.println("Asking whether to trust an unkown certificate: ACCEPTING."); | ||
return true; | ||
} | ||
}; | ||
|
||
public static CheckServerTrustedCallback REFUSING_CALLBACK = new CheckServerTrustedCallback() { | ||
public boolean checkServerTrusted(X509Certificate[] chain, | ||
String authType) { | ||
System.out | ||
.println("Asking whether to trust an unkown certificate: REFUSING."); | ||
return false; | ||
} | ||
}; | ||
|
||
@Test | ||
public void testInMemoryKeyStoreRefused() throws Exception { | ||
assertTrue(prepareSSLContextFactories()); | ||
this.clientSSLContextFactory | ||
.setTrustManagerWrapper(new ServerCallbackWrappingTrustManager.Wrapper( | ||
REFUSING_CALLBACK, null)); | ||
assertTrue(!runTest()); | ||
assertTrue(!runTest()); | ||
} | ||
|
||
@Test | ||
public void testInMemoryKeyStoreAccepted() throws Exception { | ||
assertTrue(prepareSSLContextFactories()); | ||
this.clientSSLContextFactory | ||
.setTrustManagerWrapper(new ServerCallbackWrappingTrustManager.Wrapper( | ||
ACCEPTING_CALLBACK, null)); | ||
assertTrue(runTest()); | ||
assertTrue(runTest()); | ||
} | ||
|
||
@Test | ||
public void testBadClient() throws Exception { | ||
|
||
} | ||
|
||
public boolean prepareSSLContextFactories() throws Exception { | ||
this.clientSSLContextFactory = new PKIXSSLContextFactory(); | ||
this.serverSSLContextFactory = new PKIXSSLContextFactory( | ||
getServerCertKeyStore(), MiniSslClientServer.KEYSTORE_PASSWORD, | ||
getCaKeyStore()); | ||
return true; | ||
} | ||
|
||
public boolean runTest() throws Exception { | ||
return runTest(clientSSLContextFactory.buildSSLContext(), | ||
serverSSLContextFactory.buildSSLContext()); | ||
} | ||
|
||
/** | ||
* This runs the main test: it runs a client and a server. | ||
* | ||
* @param sslClientContext | ||
* SSLContext to be used by the client. | ||
* @param sslServerContext | ||
* SSLContext to be used by the server. | ||
* @return true if the server accepted the SSL certificate. | ||
* @throws SSLContextFactoryException | ||
* @throws IOException | ||
*/ | ||
public boolean runTest(SSLContext sslClientContext, | ||
SSLContext sslServerContext) throws IOException, | ||
InterruptedException { | ||
|
||
final SSLServerSocket serverSocket = prepareServerSocket(sslServerContext); | ||
|
||
assertNotNull("Server socket not null", serverSocket); | ||
assertTrue("Server socket is bound", serverSocket.isBound()); | ||
|
||
Thread serverThread = runServer(serverSocket); | ||
|
||
Exception clientException = null; | ||
|
||
try { | ||
clientException = makeClientRequest(sslClientContext); | ||
} finally { | ||
synchronized (serverSocket) { | ||
if (!serverSocket.isClosed()) | ||
serverSocket.close(); | ||
} | ||
} | ||
synchronized (serverSocket) { | ||
assertTrue(serverSocket.isClosed()); | ||
} | ||
|
||
try { | ||
serverThread.join(); | ||
} catch (InterruptedException e) { | ||
e.printStackTrace(); | ||
} | ||
|
||
Throwable serverRequestException = null; | ||
Future<?> serverRequestFuture = serverRequestsFutures.poll(); | ||
try { | ||
serverRequestFuture.get(); | ||
} catch (ExecutionException e) { | ||
serverRequestException = e.getCause(); | ||
} | ||
|
||
System.out.println(); | ||
System.out.println("Server request exception: " | ||
+ serverRequestException); | ||
System.out.println("Client exception: " + clientException); | ||
System.out.println("Listening server exception: " | ||
+ this.listeningServerException); | ||
|
||
return clientException == null; | ||
} | ||
} |