summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/com/isode/stroke/client/CoreClient.java9
-rw-r--r--src/com/isode/stroke/session/SessionStream.java7
-rw-r--r--src/com/isode/stroke/streamstack/TLSLayer.java6
-rw-r--r--src/com/isode/stroke/tls/CertificateWithKey.java24
-rw-r--r--src/com/isode/stroke/tls/PKCS12Certificate.java79
-rw-r--r--src/com/isode/stroke/tls/TLSContext.java6
-rw-r--r--src/com/isode/stroke/tls/java/JSSEContext.java104
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 */