diff options
-rw-r--r-- | src/com/isode/stroke/client/CoreClient.java | 9 | ||||
-rw-r--r-- | src/com/isode/stroke/session/SessionStream.java | 7 | ||||
-rw-r--r-- | src/com/isode/stroke/streamstack/TLSLayer.java | 6 | ||||
-rw-r--r-- | src/com/isode/stroke/tls/CertificateWithKey.java | 24 | ||||
-rw-r--r-- | src/com/isode/stroke/tls/PKCS12Certificate.java | 79 | ||||
-rw-r--r-- | src/com/isode/stroke/tls/TLSContext.java | 6 | ||||
-rw-r--r-- | src/com/isode/stroke/tls/java/JSSEContext.java | 104 |
7 files changed, 208 insertions, 27 deletions
diff --git a/src/com/isode/stroke/client/CoreClient.java b/src/com/isode/stroke/client/CoreClient.java index c01d57a..49b6df7 100644 --- a/src/com/isode/stroke/client/CoreClient.java +++ b/src/com/isode/stroke/client/CoreClient.java @@ -31,6 +31,7 @@ import com.isode.stroke.signals.Slot; import com.isode.stroke.signals.Slot1; import com.isode.stroke.tls.CertificateTrustChecker; import com.isode.stroke.tls.CertificateVerificationError; +import com.isode.stroke.tls.CertificateWithKey; import com.isode.stroke.tls.PKCS12Certificate; import com.isode.stroke.tls.PlatformTLSFactories; @@ -169,8 +170,8 @@ public class CoreClient { assert (sessionStream_ == null); sessionStream_ = new BasicSessionStream(StreamType.ClientStreamType, connection_, payloadParserFactories_, payloadSerializers_, tlsFactories.getTLSContextFactory(), networkFactories.getTimerFactory(), eventLoop_); - if (certificate_ != null && !certificate_.isEmpty()) { - sessionStream_.setTLSCertificate(new PKCS12Certificate(certificate_, password_)); + if (certificate_ != null && !certificate_.isNull()) { + sessionStream_.setTLSCertificate(certificate_); } sessionStreamDataReadConnection_ = sessionStream_.onDataRead.connect(new Slot1<String>() { @@ -228,7 +229,7 @@ public class CoreClient { } } - public void setCertificate(String certificate) { + public void setCertificate(CertificateWithKey certificate) { certificate_ = certificate; } @@ -454,7 +455,7 @@ public class CoreClient { private Connection connection_; private BasicSessionStream sessionStream_; private ClientSession session_; - private String certificate_; + private CertificateWithKey certificate_; private boolean disconnectRequested_; private ClientOptions options; private CertificateTrustChecker certificateTrustChecker; diff --git a/src/com/isode/stroke/session/SessionStream.java b/src/com/isode/stroke/session/SessionStream.java index ee17a09..5dbb0fc 100644 --- a/src/com/isode/stroke/session/SessionStream.java +++ b/src/com/isode/stroke/session/SessionStream.java @@ -15,6 +15,7 @@ import com.isode.stroke.signals.Signal; import com.isode.stroke.signals.Signal1; import com.isode.stroke.tls.Certificate; import com.isode.stroke.tls.CertificateVerificationError; +import com.isode.stroke.tls.CertificateWithKey; import com.isode.stroke.tls.PKCS12Certificate; public abstract class SessionStream { @@ -60,7 +61,7 @@ public abstract class SessionStream { public abstract void resetXMPPParser(); - public void setTLSCertificate(PKCS12Certificate cert) { + public void setTLSCertificate(CertificateWithKey cert) { certificate = cert; } @@ -80,7 +81,7 @@ public abstract class SessionStream { public final Signal onTLSEncrypted = new Signal(); public final Signal1<String> onDataRead = new Signal1<String>(); public final Signal1<String> onDataWritten = new Signal1<String>(); - protected PKCS12Certificate getTLSCertificate() { + protected CertificateWithKey getTLSCertificate() { return certificate; } @@ -94,5 +95,5 @@ public abstract class SessionStream { "; " + (hasTLSCertificate() ? "has" : "no") + " certificate"; } - private PKCS12Certificate certificate; + private CertificateWithKey certificate; } diff --git a/src/com/isode/stroke/streamstack/TLSLayer.java b/src/com/isode/stroke/streamstack/TLSLayer.java index 7051cd3..1f213fc 100644 --- a/src/com/isode/stroke/streamstack/TLSLayer.java +++ b/src/com/isode/stroke/streamstack/TLSLayer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, Isode Limited, London, England. + * Copyright (c) 2010-2012, Isode Limited, London, England. * All rights reserved. */ /* @@ -14,7 +14,7 @@ import com.isode.stroke.signals.Signal; import com.isode.stroke.signals.Slot1; import com.isode.stroke.tls.Certificate; import com.isode.stroke.tls.CertificateVerificationError; -import com.isode.stroke.tls.PKCS12Certificate; +import com.isode.stroke.tls.CertificateWithKey; import com.isode.stroke.tls.TLSContext; import com.isode.stroke.tls.TLSContextFactory; @@ -50,7 +50,7 @@ public class TLSLayer extends StreamLayer { context.handleDataFromNetwork(data); } - public boolean setClientCertificate(PKCS12Certificate certificate) { + public boolean setClientCertificate(CertificateWithKey certificate) { return context.setClientCertificate(certificate); } diff --git a/src/com/isode/stroke/tls/CertificateWithKey.java b/src/com/isode/stroke/tls/CertificateWithKey.java new file mode 100644 index 0000000..9787add --- /dev/null +++ b/src/com/isode/stroke/tls/CertificateWithKey.java @@ -0,0 +1,24 @@ +/* Copyright (c) 2012, Isode Limited, London, England. + * All rights reserved. + * + * Acquisition and use of this software and related materials for any + * purpose requires a written licence agreement from Isode Limited, + * or a written licence from an organisation licensed by Isode Limited Limited + * to grant such a licence. + * + */ + +package com.isode.stroke.tls; +/** + * + */ +public abstract class CertificateWithKey { + public + CertificateWithKey() { + } + + + public abstract boolean isNull(); + + +} diff --git a/src/com/isode/stroke/tls/PKCS12Certificate.java b/src/com/isode/stroke/tls/PKCS12Certificate.java index 0a45f94..06b6b91 100644 --- a/src/com/isode/stroke/tls/PKCS12Certificate.java +++ b/src/com/isode/stroke/tls/PKCS12Certificate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 Isode Limited, London, England. + * Copyright (c) 2011-2012 Isode Limited, London, England. * All rights reserved. */ /* @@ -9,20 +9,52 @@ package com.isode.stroke.tls; import com.isode.stroke.base.ByteArray; +import com.isode.stroke.base.NotNull; -public class PKCS12Certificate { +public class PKCS12Certificate extends CertificateWithKey { public PKCS12Certificate() { } - public PKCS12Certificate(String filename, String password) { - password_ = password; + /** + * Construct a new object. + * @param filename the name of the P12 file, must not be null. + * @param password the password for the P12 file. Must not be null, + * but may be empty if no password is to be used. + */ + public PKCS12Certificate(String filename, char[] password) { + + NotNull.exceptIfNull(filename,"filename"); + NotNull.exceptIfNull(password,"password"); + filename_ = filename; + password_ = new char[password.length]; + System.arraycopy(password,0,password_,0,password.length); + data_ = new ByteArray(); data_.readFromFile(filename); } public boolean isNull() { return data_.isEmpty(); } + + public boolean isPrivateKeyExportable() { + /////Hopefully a PKCS12 is never missing a private key + return true; + } + + /** + * This returns the name of the P12 file. + * @return the P12 filename, never null. + */ + public String getCertStoreName() { + return filename_; + } + + public String getCertName() { + /* TODO */ + return null; + } + public ByteArray getData() { return data_; @@ -32,9 +64,44 @@ public class PKCS12Certificate { data_ = data; } - public String getPassword() { + /** + * Returns a reference to the password in this object. If {@link #reset()} + * has been called, then the method will return an empty array. + * @return the password for this object. + */ + public char[] getPassword() { return password_; } + @Override + public String toString() { + return "PKCS12Certificate based on file " + filename_; + } + + /** + * This method may be used once the PKCS12Certificate is no longer + * required, and will attempt to clear the memory containing the + * password in this object. After calling this method, you should + * not expect this object to be usable for subsequent authentication. + * + * <p>Note that this operation does <em>NOT</em> guarantee that all traces + * of the password will have been removed from memory. + */ + public void reset() { + if (password_ != null) { + for (int i=0; i<password_.length; i++) { + password_[i] = 'x'; + } + } + password_ = new char[] {}; + + } + + @Override + protected void finalize() { + reset(); + } + private ByteArray data_; - private String password_; + private char[] password_; + private String filename_; } diff --git a/src/com/isode/stroke/tls/TLSContext.java b/src/com/isode/stroke/tls/TLSContext.java index 49ec307..ec39a3b 100644 --- a/src/com/isode/stroke/tls/TLSContext.java +++ b/src/com/isode/stroke/tls/TLSContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, Isode Limited, London, England. + * Copyright (c) 2011-2012, Isode Limited, London, England. * All rights reserved. */ /* @@ -14,10 +14,10 @@ import com.isode.stroke.signals.Signal; import com.isode.stroke.signals.Signal1; public abstract class TLSContext { - //See SSLEngine for real implementation when the time comes + public abstract void connect(); - public abstract boolean setClientCertificate(PKCS12Certificate cert); + public abstract boolean setClientCertificate(CertificateWithKey cert); public abstract void handleDataFromNetwork(ByteArray data); public abstract void handleDataFromApplication(ByteArray data); diff --git a/src/com/isode/stroke/tls/java/JSSEContext.java b/src/com/isode/stroke/tls/java/JSSEContext.java index 887a2b7..c8e0640 100644 --- a/src/com/isode/stroke/tls/java/JSSEContext.java +++ b/src/com/isode/stroke/tls/java/JSSEContext.java @@ -10,11 +10,17 @@ package com.isode.stroke.tls.java; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; @@ -23,6 +29,8 @@ import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; @@ -34,6 +42,7 @@ import com.isode.stroke.base.ByteArray; import com.isode.stroke.tls.Certificate; import com.isode.stroke.tls.CertificateVerificationError; import com.isode.stroke.tls.CertificateVerificationError.Type; +import com.isode.stroke.tls.CertificateWithKey; import com.isode.stroke.tls.PKCS12Certificate; import com.isode.stroke.tls.TLSContext; @@ -110,7 +119,6 @@ public class JSSEContext extends TLSContext { } private void doSetup() throws SSLException { - SSLContext sslContext = getSSLContext(); if (sslContext == null) { @@ -592,11 +600,81 @@ public class JSSEContext extends TLSContext { @Override - public boolean setClientCertificate(PKCS12Certificate cert) { - /* TODO: NYI. - * It's possible this is going to change as a result of Alexey's work - * so will leave for now + public boolean setClientCertificate(CertificateWithKey cert) { + if (cert == null || cert.isNull()) { + emitError(null,cert + " has no useful contents"); + return false; + } + if (!(cert instanceof PKCS12Certificate)) { + emitError(null,"setClientCertificate can only work with PKCS12 objects"); + return false; + } + PKCS12Certificate p12 = (PKCS12Certificate)cert; + if (!p12.isPrivateKeyExportable()) { + emitError(null,cert + " does not have exportable private key"); + return false; + } + + /* Get a reference that can be used in any error messages */ + File p12File = new File(p12.getCertStoreName()); + + /* Attempt to build a usable identity from the P12 file. This set of + * operations can result in a variety of exceptions, all of which + * mean that the operation is regarded as having failed. + * If it works, then "myKeyManager_" will be initialised for use + * by any subsequent call to getSSLContext() */ + KeyStore keyStore = null; + KeyManagerFactory kmf = null; + + try { + keyStore = KeyStore.getInstance("PKCS12"); + kmf = KeyManagerFactory.getInstance("SunX509"); + + /* The PKCS12Certificate object has read the file contents already */ + ByteArray ba = p12.getData(); + byte[] p12Bytes = ba.getData(); + + ByteArrayInputStream bis = new ByteArrayInputStream(p12Bytes); + + /* Both of the next two calls require that we supply the password */ + keyStore.load(bis, p12.getPassword()); + kmf.init(keyStore, p12.getPassword()); + + KeyManager[] keyManagers = kmf.getKeyManagers(); + if (keyManagers == null || keyManagers.length == 0) { + emitError(null, "Unable to get KeyManager for SunX509"); + return false; + } + + /* Just take the first one (there probably will only be one) */ + myKeyManager_ = keyManagers[0]; + + return true; + + } + catch (KeyStoreException e) { + emitError(e, "Cannot get PKCS12 KeyStore"); + } + catch (NoSuchAlgorithmException e) { + emitError(e, "Unable to initialise KeyStore from " + p12File); + } + catch (CertificateException e) { + emitError(e, "Unable to load certificates from " + p12File); + } + catch (IOException e) { + if (e.getCause() != null && e.getCause() instanceof UnrecoverableKeyException) { + emitError(e, "Password incorrect for " +p12File); + } + else { + emitError(e, "Unable to read " + p12File); + } + } + catch (UnrecoverableKeyException e) { + emitError(e, "Unable to initialise KeyStore from " + p12File); + } + + /* Fall through here after any exception */ return false; } @@ -900,6 +978,9 @@ public class JSSEContext extends TLSContext { private final Logger logger_ = Logger.getLogger(this.getClass().getName()); + + private KeyManager myKeyManager_ = null; + /** * Set up the SSLContext and JavaTrustManager that will be used for this * JSSEContext. @@ -949,15 +1030,22 @@ public class JSSEContext extends TLSContext { GeneralSecurityException lastException = null; SSLContext sslContext = null; - for (String protocol:protocols) { try { sslContext = SSLContext.getInstance(protocol); - /* That worked */ + /* If a KeyManager has been set up in setClientCertificate() + * then use it; otherwise the "default" implementation will be + * used, which will be sufficient for starting TLS with no + * client certificate + */ + KeyManager[] keyManagers = null; + if (myKeyManager_ != null) { + keyManagers = new KeyManager[] { myKeyManager_ }; + } try { sslContext.init( - null, /* KeyManager[] */ + keyManagers, /* KeyManager[] */ tm, /* TrustManager[] */ null); /* SecureRandom */ |