summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorNick Hudson <nick.hudson@isode.com>2012-01-18 14:22:56 (GMT)
committerKevin Smith <git@kismith.co.uk>2012-02-13 15:12:46 (GMT)
commit9d6f73f13b981fb9430765b171d7b419cbe632cc (patch)
tree5bfdbdfeaa9dc3dc3ff87e92be2612f7aebb03b3 /src
parent12740e2b70c48e478af53de31624c388e1e89e0f (diff)
downloadstroke-9d6f73f13b981fb9430765b171d7b419cbe632cc.zip
stroke-9d6f73f13b981fb9430765b171d7b419cbe632cc.tar.bz2
Initial implementation of TLS support
Note that TLS won't be enabled with this patch unless you uncomment the change in PlatformTLSFactories. With that comment removed, then a new CoreClient session will attempt to negotiate TLS if the server supports it. Further changes are required to support this properly, as there appears not to be comprehensive support in the CoreClient class for dealing with situations when the server's certificate is not acceptable. There's also no support yet for setting up client certificates. Further changes will also be needed (see below) to support full parsing of subjectAltNames from server certificates. Significant changes are as follows - TLSProceed - FIXME comments removed - JavaConnection - changed so that it reads bytes from the socket's InputStream, rather than reading chars and then constructing a String out of them from which a byte array is then extracted. While this seemed to work for non-binary data (e.g. non-encrypted XMPP sessions), it breaks when you start sending binary (i.e. TLS) data. - JavaTLSConnectionFactory - implemented - PlatformTLSFactories - By having this return a JSSEContextFactory, then this will cause the client to try TLS if possible. But because other changes are needed to make this work properly, the current code still returns null. - JSSEContext - new class which uses an SSLEngine to handle TLS handshake and subsequent encryption/decryption. This is the main substance of the SSL implementation Note the "hack" in here to cope with SSLEngine requiring that some data be sent from the application before it will do a TLS handshake - JSSEContextFactory - just creates JSSEContexts - JavaCertificate - this wraps an X509Certificate and does *some* of the parsing of a certificate to look for stuff that is expected when verifying an XMPP server certificate (RFC 6120 and RFC 6125). Note that the JDK classes for parsing certificates don't provide an easy way to decode "OTHER" subjectAltNames, and so this implementation does not find XMPP or SRV subjectaltnames from the server certificate. This will need extra work. - JavaTrustManager - obtains the server certificate from the TLS handshake and verifies it. Currently the only verification done is to check that it's in date. More work will be needed to perform proper validation - Where necessary, Remko's copyright comments were changed from GNU to "All rights reserved". Isode copyright notices updated to "2012" Test-information: Set up XMPP server with its own certificate, and checked that TLS gets negotiated and starts OK (provided the server cert contains e.g. a DNS subjectAltName matching its own name). Subsequent operation appears to be as expected.
Diffstat (limited to 'src')
-rw-r--r--src/com/isode/stroke/elements/TLSProceed.java10
-rw-r--r--src/com/isode/stroke/network/JavaConnection.java37
-rw-r--r--src/com/isode/stroke/network/JavaTLSConnectionFactory.java21
-rw-r--r--src/com/isode/stroke/streamstack/StreamLayer.java15
-rw-r--r--src/com/isode/stroke/tls/Certificate.java2
-rw-r--r--src/com/isode/stroke/tls/CertificateVerificationError.java9
-rw-r--r--src/com/isode/stroke/tls/PlatformTLSFactories.java12
-rw-r--r--src/com/isode/stroke/tls/java/JSSEContext.java714
-rw-r--r--src/com/isode/stroke/tls/java/JSSEContextFactory.java33
-rw-r--r--src/com/isode/stroke/tls/java/JavaCertificate.java331
-rw-r--r--src/com/isode/stroke/tls/java/JavaTrustManager.java151
11 files changed, 1299 insertions, 36 deletions
diff --git a/src/com/isode/stroke/elements/TLSProceed.java b/src/com/isode/stroke/elements/TLSProceed.java
index 5966d17..4bebc64 100644
--- a/src/com/isode/stroke/elements/TLSProceed.java
+++ b/src/com/isode/stroke/elements/TLSProceed.java
@@ -1,10 +1,9 @@
/*
* Copyright (c) 2010 Remko Tronçon
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * All rights reserved.
*/
/*
- * Copyright (c) 2010, Isode Limited, London, England.
+ * Copyright (c) 2010-2012, Isode Limited, London, England.
* All rights reserved.
*/
@@ -12,5 +11,8 @@ package com.isode.stroke.elements;
public class TLSProceed implements Element {
-//FIXME: parser/serialiser
+
+ public TLSProceed() {
+ //
+ }
}
diff --git a/src/com/isode/stroke/network/JavaConnection.java b/src/com/isode/stroke/network/JavaConnection.java
index d014f5d..9a3b5da 100644
--- a/src/com/isode/stroke/network/JavaConnection.java
+++ b/src/com/isode/stroke/network/JavaConnection.java
@@ -1,7 +1,6 @@
/*
* Copyright (c) 2010 Remko Tronçon
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * All rights reserved.
*/
/*
* Copyright (c) 2010-2012, Isode Limited, London, England.
@@ -9,23 +8,18 @@
*/
package com.isode.stroke.network;
-import com.isode.stroke.base.ByteArray;
-import com.isode.stroke.eventloop.Event.Callback;
-import com.isode.stroke.eventloop.EventLoop;
-import com.isode.stroke.eventloop.EventOwner;
-import java.io.BufferedReader;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
import java.io.IOException;
-import java.io.InputStreamReader;
+import java.io.InputStream;
import java.io.OutputStream;
-import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.eventloop.Event.Callback;
+import com.isode.stroke.eventloop.EventLoop;
+import com.isode.stroke.eventloop.EventOwner;
public class JavaConnection extends Connection implements EventOwner {
@@ -33,7 +27,7 @@ public class JavaConnection extends Connection implements EventOwner {
private final HostAddressPort address_;
private OutputStream write_;
- private BufferedReader read_;
+ private InputStream read_;
private final List<ByteArray> writeBuffer_ = Collections.synchronizedList(new ArrayList<ByteArray>());
public Worker(HostAddressPort address) {
@@ -44,7 +38,7 @@ public class JavaConnection extends Connection implements EventOwner {
try {
socket_ = new Socket(address_.getAddress().getInetAddress(), address_.getPort());
write_ = socket_.getOutputStream();
- read_ = new BufferedReader(new InputStreamReader(socket_.getInputStream(), "utf-8"));
+ read_ = socket_.getInputStream();
} catch (IOException ex) {
handleConnected(true);
return;
@@ -75,11 +69,13 @@ public class JavaConnection extends Connection implements EventOwner {
}
ByteArray data = new ByteArray();
try {
- while (read_.ready()) {
- char[] c = new char[1024];
- int i = read_.read(c, 0, c.length);
+ while (read_.available() != 0) {
+ byte[] b = new byte[1024];
+ int i = read_.read(b,0,b.length);
if (i > 0) {
- data.append(new String(c, 0, i));
+ for (int j=0; j<i; j++) {
+ data.append(b[j]);
+ }
}
}
} catch (IOException ex) {
@@ -107,7 +103,7 @@ public class JavaConnection extends Connection implements EventOwner {
private void handleConnected(final boolean error) {
eventLoop_.postEvent(new Callback() {
public void run() {
- onConnectFinished.emit(error);
+ onConnectFinished.emit(Boolean.valueOf(error));
}
});
}
@@ -182,4 +178,5 @@ public class JavaConnection extends Connection implements EventOwner {
private boolean disconnecting_ = false;
private Socket socket_;
private Worker worker_;
+
}
diff --git a/src/com/isode/stroke/network/JavaTLSConnectionFactory.java b/src/com/isode/stroke/network/JavaTLSConnectionFactory.java
new file mode 100644
index 0000000..56ef917
--- /dev/null
+++ b/src/com/isode/stroke/network/JavaTLSConnectionFactory.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2012, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+import com.isode.stroke.eventloop.EventLoop;
+
+public class JavaTLSConnectionFactory implements ConnectionFactory {
+
+ public JavaTLSConnectionFactory(EventLoop eventLoop) {
+ this.eventLoop = eventLoop;
+ }
+
+ public Connection createConnection() {
+ return JavaConnection.create(eventLoop);
+ }
+
+ private final EventLoop eventLoop;
+}
diff --git a/src/com/isode/stroke/streamstack/StreamLayer.java b/src/com/isode/stroke/streamstack/StreamLayer.java
index 3337e0c..ac9538b 100644
--- a/src/com/isode/stroke/streamstack/StreamLayer.java
+++ b/src/com/isode/stroke/streamstack/StreamLayer.java
@@ -1,10 +1,9 @@
/*
* Copyright (c) 2010 Remko Tronçon
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * All rights reserved.
*/
/*
- * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * Copyright (c) 2010-2012, Isode Limited, London, England.
* All rights reserved.
*/
@@ -44,6 +43,16 @@ public abstract class StreamLayer implements LowLayer, HighLayer {
assert childLayer != null;
childLayer.writeData(data);
}
+ @Override
+ public String toString() {
+ String className = this.getClass().getSimpleName();
+
+ // Include actual StreamLayer type based on class name of the object
+ return className +
+ "; " +
+ " parentLayer: " + parentLayer +
+ "; childLayer: " + childLayer;
+ }
private HighLayer parentLayer;
private LowLayer childLayer;
diff --git a/src/com/isode/stroke/tls/Certificate.java b/src/com/isode/stroke/tls/Certificate.java
index 3f22809..de23f94 100644
--- a/src/com/isode/stroke/tls/Certificate.java
+++ b/src/com/isode/stroke/tls/Certificate.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011 Isode Limited, London, England.
+ * Copyright (c) 2011-2012 Isode Limited, London, England.
* All rights reserved.
*/
/*
diff --git a/src/com/isode/stroke/tls/CertificateVerificationError.java b/src/com/isode/stroke/tls/CertificateVerificationError.java
index e7f71c5..0aca027 100644
--- a/src/com/isode/stroke/tls/CertificateVerificationError.java
+++ b/src/com/isode/stroke/tls/CertificateVerificationError.java
@@ -1,10 +1,9 @@
/*
* Copyright (c) 2010 Remko Tronçon
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
+ * All rights reserved.
*/
/*
- * Copyright (c) 2011, Isode Limited, London, England.
+ * Copyright (c) 2011-2012, Isode Limited, London, England.
* All rights reserved.
*/
package com.isode.stroke.tls;
@@ -26,7 +25,7 @@ public class CertificateVerificationError implements Error {
InvalidSignature,
InvalidCA,
InvalidServerIdentity,
- };
+ }
public CertificateVerificationError(Type type) {
if (type == null) {
@@ -35,5 +34,5 @@ public class CertificateVerificationError implements Error {
this.type = type;
}
public final Type type;
-};
+}
diff --git a/src/com/isode/stroke/tls/PlatformTLSFactories.java b/src/com/isode/stroke/tls/PlatformTLSFactories.java
index 1f8e136..f5b40d1 100644
--- a/src/com/isode/stroke/tls/PlatformTLSFactories.java
+++ b/src/com/isode/stroke/tls/PlatformTLSFactories.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011 Isode Limited, London, England.
+ * Copyright (c) 2012 Isode Limited, London, England.
* All rights reserved.
*/
/*
@@ -8,9 +8,15 @@
*/
package com.isode.stroke.tls;
+import com.isode.stroke.tls.java.JSSEContextFactory;
+
public class PlatformTLSFactories {
- public TLSContextFactory getTLSContextFactory() {
- /*FIXME: Implement*/
+ public TLSContextFactory getTLSContextFactory() {
+ // TODO: JSSEContextFactory is implemented, and so uncommenting
+ // this line will result in the client attempting TLS handshakes, but
+ // other support is required inside CoreClient etc. and so for the
+ // moment we just return null
+ //return new JSSEContextFactory();
return null;
}
diff --git a/src/com/isode/stroke/tls/java/JSSEContext.java b/src/com/isode/stroke/tls/java/JSSEContext.java
new file mode 100644
index 0000000..1d8fba7
--- /dev/null
+++ b/src/com/isode/stroke/tls/java/JSSEContext.java
@@ -0,0 +1,714 @@
+/* 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.java;
+
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Vector;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
+import javax.net.ssl.SSLException;
+
+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.PKCS12Certificate;
+import com.isode.stroke.tls.TLSContext;
+
+
+/**
+ * Concrete implementation of a TLSContext which uses SSLEngine
+ * and maybe other stuff? ..tbs...
+ *
+ */
+public class JSSEContext extends TLSContext {
+
+ private static class JSSEContextError {
+ public Throwable throwable;
+ public String message;
+ /**
+ * Create a new object
+ * @param t throwable; may be null
+ * @param m message; may be null
+ */
+ public JSSEContextError(Throwable t, String m) {
+ throwable = t;
+ message = m;
+ }
+ @Override
+ public String toString() {
+ return "JSSEContextError: " +
+ (message == null ? "No message" : message) + "; " +
+ (throwable == null ? "No exception" : throwable.getMessage());
+ }
+ }
+ /**
+ * If an error occurs, it will be added to this vector
+ */
+ private Vector<JSSEContextError> errorsEmitted = new Vector<JSSEContextError>();
+
+ /**
+ * Whether the handshake has finished
+ */
+ private boolean handshakeCompleted = false;
+
+ /**
+ * Determine whether an error has occurred. If an error has occurred, then
+ * you probably don't want to try doing any more stuff.
+ *
+ * @return <em>true</em> if an error has occurred, <em>false</em>
+ * otherwise
+ */
+ private boolean hasError() {
+ return (!errorsEmitted.isEmpty());
+ }
+ /**
+ * Emit an error, and keep track of which errors have been emitted
+ * @param t the Throwable which caused this error (may be null)
+ * @param m a String describing what caused this error (may be null)
+ */
+ private void emitError(Throwable t, String m) {
+ JSSEContextError jsseContextError = new JSSEContextError(t,m);
+ errorsEmitted.add(jsseContextError);
+ onError.emit();
+ }
+
+ @Override
+ public void connect() {
+ try {
+ doSetup();
+ }
+ catch (SSLException e) {
+ emitError(e,"doSetup() failed");
+ }
+ }
+
+ private void doSetup() throws SSLException {
+
+ // May throw NoSuchAlgorithmException
+ SSLContext sslContext = null;
+
+ sslContext = getSSLContext();
+
+ if (sslContext == null) {
+ throw new SSLException("Could not create SSLContext");
+ }
+
+ sslEngine = null;
+ try {
+ sslEngine = sslContext.createSSLEngine();
+ }
+ catch (UnsupportedOperationException e) {
+ // "the underlying provider does not implement the operation"
+ throw new SSLException(e);
+ }
+ catch (IllegalStateException e) {
+ // "the SSLContextImpl requires initialization and init() has not been called"
+ throw new SSLException(e);
+ }
+
+ sslEngine.setUseClientMode(true); // I am a client
+ sslEngine.setEnableSessionCreation(true); // can create new sessions
+
+
+ int appBufferMax = sslEngine.getSession().getApplicationBufferSize();
+ int netBufferMax = sslEngine.getSession().getPacketBufferSize();
+
+ // All buffers are normally in "write" mode. Access to all of them
+ // must be synchronized
+ plainToSend = ByteBuffer.allocate(appBufferMax + 50);
+ wrappedToSend = ByteBuffer.allocate(netBufferMax);
+ encryptedReceived = ByteBuffer.allocate(netBufferMax);
+ unwrappedReceived = ByteBuffer.allocate(appBufferMax + 50);
+
+ // Note that calling beginHandshake might not actually do anything;
+ // the SSLEngine may not actually send the handshake until it's had
+ // some data from the application. And the higher level won't send
+ // any data until it thinks the handshake is completed.
+ //
+ // So this is a hack to force the handshake to occur: on the assumption
+ // that the first thing to be sent once TLS is running is
+ // the "<" from the start of a tag, we send a less-than sign now,
+ // which we'll remember must be removed that from the first message
+ // we get told to send.
+
+ sslEngine.beginHandshake();
+
+ ByteArray ba = new ByteArray("<".getBytes());
+ hack = HackStatus.SENDING_FAKE_LT;
+ handleDataFromApplication(ba);
+
+ }
+
+
+ /**
+ * Unwrap any data in the "encryptedReceived" buffer and put it into
+ * the "unwrappedReceived" buffer. An event will be generated to the
+ * end-user's listener if there's anything pending in the
+ * unwrappedReceived buffer. Caller should check handshake status
+ * after this returns
+ *
+ * @return the number of bytes that SSLEngine consumed
+ */
+ private int unwrapPendingData()
+ {
+ SSLEngineResult sslEngineResult;
+ Status status;
+ int bytesProduced = 0;
+ int bytesConsumed = 0;
+ HandshakeStatus handshakeStatus = null;
+ ByteArray byteArray = null;
+
+ synchronized(recvMutex) {
+ try {
+ encryptedReceived.flip();
+ sslEngineResult = sslEngine.unwrap(encryptedReceived, unwrappedReceived);
+ encryptedReceived.compact();
+
+ // A call to unwrap can generate a status of FINISHED, which
+ // you won't get from SSLEngine.getHandshakeStatus. Such
+ // a status is an indication that we need to re-check whether
+ // anything's pending to be written
+ handshakeStatus = sslEngineResult.getHandshakeStatus();
+
+
+ bytesConsumed += sslEngineResult.bytesConsumed();
+ bytesProduced = sslEngineResult.bytesProduced();
+ }
+ catch (SSLException e) {
+ throw new RuntimeException("unwrap produced: " + e);
+ }
+
+
+ status = sslEngineResult.getStatus();
+ boolean finished = false;
+ while (!finished) {
+ switch (status) {
+ case BUFFER_UNDERFLOW:
+ // There's not enough data yet for engine to be able to decode
+ // a full message. Not a problem; assume that more will come
+ // in to the socket eventually
+ finished = true;
+ break;
+ case OK:
+ // Unwrap was OK
+ finished = true;
+ break;
+ case BUFFER_OVERFLOW:
+ // Not enough room in "unwrappedReceived" to write the data
+ // TODO: need to fix this
+ case CLOSED:
+ // Engine closed - don't expect this here
+ emitError(null, "SSLEngine.unwrap returned " + status);
+ return bytesConsumed;
+ }
+ }
+
+ if (handshakeStatus == HandshakeStatus.FINISHED) {
+ // Special case will happen when the handshake completes following
+ // an unwrap. The first time we tried wrapping some plain stuff,
+ // it triggers the handshake but won't itself have been dealt with.
+ // So now the handshake has finished, we have to try sending it
+ // again
+ handshakeCompleted = true;
+ wrapAndSendData();
+ onConnected.emit();
+ }
+
+ if (bytesProduced > 0) {
+ unwrappedReceived.flip();
+ byte[] result = new byte[0];
+ result = new byte[unwrappedReceived.remaining()];
+ unwrappedReceived.get(result);
+ unwrappedReceived.compact();
+ byteArray = new ByteArray(result);
+ }
+
+ }
+
+ // Now out of synchronized block
+ if (byteArray != null) {
+ onDataForApplication.emit(byteArray);
+ }
+ return bytesConsumed;
+
+ }
+
+ /**
+ * Use the SSLEngine to wrap everything that we've so far got
+ * in "plainToSend", and then send all of that to the socket. Caller
+ * is responsible for checking the handshake status on return
+ *
+ */
+ private void wrapAndSendData() {
+
+ int bytesSentToSocket = 0;
+ ByteArray byteArray = null;
+ SSLEngineResult sslEngineResult = null;
+ Status status = null;
+ boolean handshakeFinished = false;
+
+ synchronized(sendMutex) {
+ // Check if there's anything outstanding to be sent at the
+ // top of the loop, so that we clear the "wrappedToSend"
+ // buffer before asking the engine to encrypt anything
+ // TODO: is this required? I don't think anything gets put in
+ // wrappedToSend apart from in here?
+ wrappedToSend.flip();
+ if (wrappedToSend.hasRemaining()) {
+ byte[] b = new byte[(wrappedToSend.remaining())];
+ wrappedToSend.get(b);
+ byteArray = new ByteArray(b);
+ }
+ wrappedToSend.compact();
+ } // end synchronized
+
+ if (byteArray != null) {
+ int s = byteArray.getSize();
+
+ onDataForNetwork.emit(byteArray);
+ bytesSentToSocket += s;
+ byteArray = null;
+ }
+
+ // There's nothing waiting to be sent. Now see what new data needs
+ // encrypting
+ synchronized(sendMutex) {
+ plainToSend.flip();
+ if (!plainToSend.hasRemaining()) {
+ // Nothing more to be encrypted
+ plainToSend.compact();
+ return;
+ }
+ try {
+ sslEngineResult = sslEngine.wrap(plainToSend, wrappedToSend);
+ }
+ catch (SSLException e) {
+ // TODO: Is there anything more that can be done here?
+ // TODO: this is called inside the mutex, does this matter?
+ emitError(e,"SSLEngine.wrap failed");
+ return;
+ }
+ plainToSend.compact();
+
+ status = sslEngineResult.getStatus();
+
+ // FINISHED can only come back for wrap() or unwrap(); so check to
+ // see if we just had it
+ if (sslEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED) {
+ handshakeFinished = true;
+ }
+
+ switch (status) {
+ case OK:
+ // This is the only status we expect here. It means the
+ // data was successfully wrapped and that there's something
+ // to be sent.
+ wrappedToSend.flip();
+ if (wrappedToSend.hasRemaining()) {
+ byte[] b = new byte[(wrappedToSend.remaining())];
+ wrappedToSend.get(b);
+ byteArray = new ByteArray(b);
+ }
+ wrappedToSend.compact();
+ break;
+
+ case BUFFER_UNDERFLOW:
+ // Can't happen for a wrap
+ case CLOSED :
+ // ???
+ case BUFFER_OVERFLOW:
+ // The "wrappedToSend" buffer, which we had previously made
+ // sure was empty, isn't big enough.
+ // TODO: I don't think this can happen though
+ // TODO: Note that we're in sychronized block here
+ emitError(null, "SSLEngine.wrap returned " + status);
+ return;
+
+ }
+ } // end synchronized
+
+ if (handshakeFinished) {
+ handshakeCompleted = true;
+ onConnected.emit();
+ }
+
+ if (byteArray != null) {
+ int s = byteArray.getSize();
+ onDataForNetwork.emit(byteArray);
+ bytesSentToSocket += s;
+ byteArray = null;
+ }
+
+ // Note that there may still be stuff in "plainToSend" that hasn't
+ // yet been consumed
+ return;
+
+ }
+
+
+ /**
+ * Process the current handshake status.
+ * @return <em>true</em> if this method needs to be called again, or
+ * <em>false</em> if there's no more handshake status to be processed
+ */
+ private boolean processHandshakeStatus() {
+ HandshakeStatus handshakeStatus;
+
+ handshakeStatus = sslEngine.getHandshakeStatus();
+ switch (handshakeStatus) {
+ case NOT_HANDSHAKING:
+ // No handshaking going on - session is available, no more
+ // handshake status to process
+ return false;
+ case NEED_TASK:
+ runDelegatedTasks(false); // false==don't create separate threads
+
+ // after tasks have run, need to come back here and check
+ // handshake status again
+ return true;
+
+ case NEED_WRAP:
+ // SSLEngine wants some data that it can wrap for sending to the
+ // other side
+ wrapAndSendData();
+ // after sending data, need to check handshake status again
+ return true;
+ case NEED_UNWRAP:
+
+ // SSLEngine wants data from other side that it can unwrap and
+ // process
+ int consumed = unwrapPendingData();
+ return (consumed > 0);
+
+ case FINISHED:
+ // "This value is only generated by a call to wrap/unwrap when
+ // that call finishes a handshake. It is never generated by
+ // SSLEngine.getHandshakeStatus()"
+
+ default:
+ // There are no other values, but compiler requires this
+ throw new RuntimeException("unexpected handshake status " + handshakeStatus);
+ }
+ }
+
+ /**
+ * Create and start running delegated tasks for all pending delegated tasks
+ * @param createThreads <em>true</em> to run tasks in separate threads,
+ * <em>false</em> to run them all in series in the current thread
+ */
+ private void runDelegatedTasks(boolean createThreads)
+ {
+ Runnable nextTask = sslEngine.getDelegatedTask();
+
+ while (nextTask != null) {
+ final Runnable task = nextTask;
+ Thread delegatedTaskThread = new Thread() {
+ public void run() {
+ task.run();
+ }
+ };
+
+ if (createThreads) {
+ delegatedTaskThread.setDaemon(true);
+ delegatedTaskThread.start();
+ }
+ else {
+ delegatedTaskThread.run();
+ }
+ nextTask = sslEngine.getDelegatedTask();
+ }
+ }
+
+ /**
+ * This method must be called to inform the JSSEContext object of the
+ * certificate(s) which were presented by the peer during the handshake.
+ *
+ * <p>For example, an X509TrustManager implementation may obtain
+ * peer certificates inside <em>checkServerTrusted</em>, and call this
+ * method with those certificates.
+ *
+ * @param certs chain of certificates presented by the server. Will
+ * subsequently be returned by any call to {@link #getPeerCertificate()}
+ *
+ * @param certificateException any exception resulting from an attempt
+ * to verify the certificate. May be null. If non-null, then will be used
+ * to generate the value that is returned by any subsequent call to
+ * {@link #getPeerCertificateVerificationError()}
+ */
+ public void setPeerCertificateInfo(X509Certificate[] certs,
+ CertificateException certificateException) {
+ if (certs == null || certs.length == 0) {
+ return;
+ }
+
+ peerCertificate = new JavaCertificate(certs[0]);
+
+ // Swiften uses SSL_get_verify_result() for this, and the documentation
+ // for that says it "while the verification of a certificate can fail
+ // because of many reasons at the same time. Only the last verification
+ // error that occurred..is available".
+ // So once one problem is found, don't bother looking for others.
+
+ if (certificateException != null) {
+ if (certificateException instanceof CertificateNotYetValidException) {
+ peerCertificateVerificationError = new CertificateVerificationError(Type.NotYetValid);
+ return;
+
+ }
+
+ if (certificateException instanceof CertificateExpiredException) {
+ peerCertificateVerificationError = new CertificateVerificationError(Type.Expired);
+ return;
+ }
+ }
+
+ }
+
+
+ @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
+ return false;
+ }
+
+ @Override
+ public void handleDataFromNetwork(ByteArray data) {
+ if (hasError()) {
+ // We have previously seen, and reported, an error. Emit again
+ onError.emit();
+ return;
+ }
+ byte[] b = data.getData();
+
+ synchronized(recvMutex) {
+ try {
+ // TODO: could "encryptedReceived" already have stuff in it?
+ encryptedReceived.put(b);
+ }
+ catch (BufferOverflowException e) {
+ emitError(e, "Unable to add data to encryptedReceived");
+ return;
+ }
+ }
+
+ unwrapPendingData();
+
+ // Now keep checking SSLEngine until no more handshakes are required
+ do {
+ //
+ } while (processHandshakeStatus());
+
+ }
+
+ @Override
+ public void handleDataFromApplication(ByteArray data) {
+ if (hasError()) {
+ // We have previously seen, and reported, an error. Emit again
+ onError.emit();
+ return;
+ }
+ byte[] b = data.getData();
+
+ synchronized(sendMutex) {
+ try {
+ // Note that "plainToSend" may not be empty, because it's possible
+ // that calls to SSLEngine.wrap haven't yet consumed everything
+ // in there
+ switch (hack) {
+ case SENDING_FAKE_LT :
+ plainToSend.put(b);
+ hack = HackStatus.DISCARD_FIRST_LT;
+ break;
+
+ case DISCARD_FIRST_LT:
+ if (b.length > 0) {
+ if (b[0] == (byte)'<') {
+ plainToSend.put(b,1,b.length - 1);
+ hack = HackStatus.HACK_DONE;
+ }
+ else {
+ emitError(null,
+ "First character sent after TLS started was " +
+ b[0] + " and not '<'");
+ }
+ }
+ break;
+ case HACK_DONE:
+ plainToSend.put(b);
+ break;
+ }
+ }
+ catch (BufferOverflowException e) {
+ // TODO: anything else here?
+ emitError(e, "plainToSend.put failed");
+ return;
+ }
+ }
+
+ wrapAndSendData();
+
+ // Now keep checking SSLEngine until no more handshakes are required
+ do {
+ //
+ } while (processHandshakeStatus());
+
+ }
+
+ @Override
+ public Certificate getPeerCertificate() {
+ return peerCertificate;
+ }
+
+ @Override
+ public CertificateVerificationError getPeerCertificateVerificationError() {
+ return peerCertificateVerificationError;
+ }
+
+ @Override
+ public ByteArray getFinishMessage() {
+ // TODO: Doesn't appear to be an obvious way to get this
+ // information from SSLEngine et al. For now, return null.
+
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ String errors = null;
+ if (hasError()) {
+ errors = "; errors emitted:";
+ for (JSSEContextError e:errorsEmitted) {
+ errors += "\n " + e;
+ }
+ }
+
+ String result =
+ "JSSEContext with SSLEngine = " +
+ sslEngine +
+ "; handshakeCompleted=" + handshakeCompleted;
+
+ if (errors == null) {
+ return result + " (no errors)";
+ }
+ return result + errors;
+ }
+
+ /**
+ * Construct a new JSSEContext object.
+ */
+ public JSSEContext() {
+ //
+ }
+
+ /**
+ * Reference to the SSLEngine being used
+ */
+ private SSLEngine sslEngine;
+ /**
+ * Contains plaintext information supplied by the caller which is
+ * waiting to be encrypted and sent out over the socket.
+ */
+ private ByteBuffer plainToSend;
+
+ /**
+ * Contains encrypted information produced by the SSLEngine which is
+ * waiting to be sent over the socket
+ */
+ private ByteBuffer wrappedToSend;
+
+ /**
+ * Contains (presumably encrypted) information received from the socket
+ * which is waiting to be unwrapped by the SSLEngine.
+ */
+ private ByteBuffer encryptedReceived;
+
+ /**
+ * Contains data that the SSLEngine has unwrapped and is now waiting to
+ * be read by the caller
+ */
+ private ByteBuffer unwrappedReceived;
+ /**
+ * Used to synchronize access to both plainToSend and wrappedToSend
+ */
+ private Object sendMutex = new Object();
+ /**
+ * Used to synchronize access to both encryptedReceived and unwrappedReceived
+ */
+ private Object recvMutex = new Object();
+
+ /**
+ * The server certificate as obtained from the TLS handshake
+ */
+ private JavaCertificate peerCertificate = null;
+
+ /**
+ * The CertificateVerificationError derived from the peerCertificate. This
+ * may be null if no error was found.
+ */
+ private CertificateVerificationError peerCertificateVerificationError = null;
+
+ /**
+ * Used to remember what state we're in when doing the hack to overcome the
+ * issue of SSLEngine not starting to handshake until it's got some data
+ * to send
+ */
+ private static enum HackStatus { SENDING_FAKE_LT, DISCARD_FIRST_LT, HACK_DONE }
+ private static HackStatus hack = HackStatus.SENDING_FAKE_LT;
+
+
+ /**
+ * Set up the SSLContext and JavaTrustManager that will be used for this
+ * JSSEContext.
+ *
+ * TODO: We probably want a way to allow callers to supply their own
+ * values for SSLContext and TrustManager
+ *
+ * @return an SSLContext, or null if one cannot be created. In this case,
+ * an error will have been emitted.
+ */
+ private SSLContext getSSLContext()
+ {
+ try {
+ JavaTrustManager [] tm = new JavaTrustManager[] { new JavaTrustManager(this)};
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(
+ null, // KeyManager[]
+ tm, // TrustManager[]
+ null); // SecureRandom
+ return sslContext;
+ }
+ catch (SSLException e) {
+ emitError(e, "Couldn't create JavaTrustManager");
+ }
+ catch (NoSuchAlgorithmException e) {
+ emitError(e, "Couldn't create SSLContext");
+ }
+ catch (KeyManagementException e) {
+ emitError(e, "Couldn't initialise SSLContext");
+ }
+ return null;
+
+ }
+}
diff --git a/src/com/isode/stroke/tls/java/JSSEContextFactory.java b/src/com/isode/stroke/tls/java/JSSEContextFactory.java
new file mode 100644
index 0000000..0ddb4fd
--- /dev/null
+++ b/src/com/isode/stroke/tls/java/JSSEContextFactory.java
@@ -0,0 +1,33 @@
+/* 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.java;
+
+import com.isode.stroke.tls.TLSContext;
+import com.isode.stroke.tls.TLSContextFactory;
+
+/**
+ * Concrete implementation of a TLSContextFactory which uses SSLEngine
+ * and maybe other stuff? ..tbs...
+ *
+ */
+public class JSSEContextFactory implements TLSContextFactory {
+
+ @Override
+ public boolean canCreate() {
+ return true;
+ }
+
+ @Override
+ public TLSContext createTLSContext() {
+ return new JSSEContext();
+ }
+
+}
diff --git a/src/com/isode/stroke/tls/java/JavaCertificate.java b/src/com/isode/stroke/tls/java/JavaCertificate.java
new file mode 100644
index 0000000..5b326b9
--- /dev/null
+++ b/src/com/isode/stroke/tls/java/JavaCertificate.java
@@ -0,0 +1,331 @@
+/* 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.java;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.security.auth.x500.X500Principal;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.tls.Certificate;
+
+/**
+ * This class wraps a java.security.cert.X509Certificate to fulfil
+ * the requirements of the com.isode.stroke.tls.Certificate class.
+ */
+public class JavaCertificate extends Certificate {
+ private X509Certificate x509Certificate;
+
+ // There's no ASN.1 help for this in standard Java SDK so for the
+ // moment we'll hard-code in the values
+ /**
+ * ASN.1 encoded representation of OID "1.3.6.1.5.5.7.8.5"
+ */
+ protected static final byte[] ENCODED_ID_ON_XMPPADD_OID =
+ new byte[] { 0x06, 0x08, 0x21, 0x06, 0x01, 0x05, 0x05, 0x07, 0x08, 0x05 };
+
+ /**
+ * ASN.1 encoded representation of OID "1.3.6.1.5.5.7.8.7"
+ */
+ protected static final byte[] ENCODED_ID_ON_DNSSRV_OID =
+ new byte[] { 0x06, 0x08, 0x21, 0x06, 0x01, 0x05, 0x05, 0x07, 0x08, 0x07 };
+
+
+ private static enum GeneralNameType {
+ OTHERNAME(0),
+ RFC822NAME(1),
+ DNSNAME(2),
+ X400ADDRESS(3),
+ DIRECTORYNAME(4),
+ EDIPARTYNAME(5),
+ UNIFORMRESOURCEIDENTIFIER(6),
+ IPADDRESS(7),
+ REGISTEREDID(8);
+
+ private int val;
+ private GeneralNameType(int v) {
+ this.val = v;
+ }
+ static GeneralNameType getValue(int x) {
+ for (GeneralNameType g:values()) {
+ if (g.val == x) {
+ return g;
+ }
+ }
+ return null;
+ }
+ }
+
+ private void processSubjectAlternativeNames() {
+
+ Collection<List<?>> sans = null;
+
+ try {
+ // Process subject alternative names. This returns a sequence
+ // of general names
+ sans = x509Certificate.getSubjectAlternativeNames();
+ }
+ catch (CertificateParsingException e) {
+ // Leave all the subjectAltNames unparsed
+ return;
+ }
+
+ if (sans == null) {
+ // No subjectAltNames
+ return;
+ }
+
+ for (List<?> san : sans) {
+ // Each general name element contains an Integer representing the
+ // name type, and either a String or byte array containing the
+ // value
+ Integer type = (Integer)san.get(0);
+ GeneralNameType nameType = GeneralNameType.getValue(type.intValue());
+ switch (nameType) {
+ case DNSNAME: // String
+ dnsNames_.add((String)san.get(1));
+ break;
+ case OTHERNAME: // DER
+ byte[] encoded = (byte[])san.get(1);
+ // TODO: what you get here is something like
+ // LBER_SEQUENCE, length = 31 :
+ // tag : 0x06 (LBER_OID), length = 8
+ // value = 1.3.6.1.5.5.7.8.5 (i.e. ID_ON_XMPPADDR_OID)
+ // bytes = [00]=2B [01]=06 [02]=01 [03]=05 [04]=05 [05]=07 [06]=08 [07]=05
+ //
+ // CONTEXT[0], length = 19 :
+ // CONTEXT[0], length = 17 :
+ // tag : 0x0C UNIVERSAL[12] primitive, length = 15
+ // value = "funky.isode.net"
+ // bytes = [00]=66 [01]=75 [02]=6E [03]=6B [04]=79 [05]=2E [06]=69 [07]=73
+ // [08]=6F [09]=64 [0A]=65 [0B]=2E [0C]=6E [0D]=65 [0E]=74
+ //
+ // And a corresponding thing for a DNSSRV SAN.
+ // However, there's no general ASN.1 decoder in the standard
+ // java library, so we will have to implement our own. For
+ // now, we ignore these values.
+
+ break;
+ case DIRECTORYNAME: // String
+ case IPADDRESS: // String
+ case REGISTEREDID: // String representation of an OID
+ case RFC822NAME: // String
+ case UNIFORMRESOURCEIDENTIFIER: // String
+ case EDIPARTYNAME: // DER
+ case X400ADDRESS: // DER
+ default:
+ // Other types of subjectalt names are ignored
+ break;
+ }
+
+ }
+
+ }
+
+ /**
+ * Construct a new JavaCertificate by parsing an X509Certificate
+ *
+ * @param x509Cert an X509Certificate, which must not be null
+ */
+ public JavaCertificate(X509Certificate x509Cert)
+ {
+ if (x509Cert == null) {
+ throw new NullPointerException("x509Cert must not be null");
+ }
+ x509Certificate = x509Cert;
+
+ dnsNames_ = new ArrayList<String>();
+ srvNames_ = new ArrayList<String>();
+ xmppNames_ = new ArrayList<String>();
+
+ processSubjectAlternativeNames();
+
+
+ }
+
+ /**
+ * Return a reference to the X509Certificate object that this
+ * JavaCertificate is wrapping.
+ *
+ * @return an X509Certificate (won't be null).
+ */
+ public X509Certificate getX509Certificate() {
+ return x509Certificate;
+ }
+
+ /**
+ * Gets a String representation of the certificate subjectname
+ *
+ * @return certificate subject name, e.g. "CN=harry,O=acme"
+ */
+ @Override
+ public String getSubjectName() {
+ return x509Certificate.getSubjectX500Principal().toString();
+ }
+
+ /**
+ * Returns a list of all the commonname values from the certificate's
+ * subjectDN. For example, if the subjectDN is "CN=fred,O=acme,CN=bill"
+ * then the list returned would contain "fred" and "bill"
+ *
+ * @return a list containing the Strings representing common name values
+ * in the server certificate's subjectDN. Will never return null, but may
+ * return an empty list.
+ */
+ @Override
+ public List<String> getCommonNames() {
+ ArrayList<String> result = new ArrayList<String>();
+
+
+ // There isn't a convenient way to extract commonname values from
+ // the certificate's subject DN (short of parsing the encoded value
+ // ourselves). So instead, we get a String version, ensuring that
+ // any CN values have a prefix we can recognize (we could probably
+ // rely on "CN" but this allows us to have a more distinctive value)
+
+ X500Principal p = x509Certificate.getSubjectX500Principal();
+ Map<String, String> cnMap = new HashMap<String, String>();
+
+ // Request that the returned String will use our label for any values
+ // with the commonName OID
+ cnMap.put(cnOID, cnLabel);
+ String s = p.getName("RFC2253",cnMap);
+
+ String cnPrefix = cnLabel + "=";
+
+ int x = s.indexOf(cnPrefix);
+ if (x == -1) {
+ return result; // No CN values to add
+ }
+
+ // Crude attempt to split, noting that this may result in values
+ // that contain an escaped comma being chopped between more than one
+ // element, so we need to go through this subsequently and handle that..
+ String[] split=s.split(",");
+
+ boolean inQuote = false;
+ boolean escape = false;
+
+ int e = 0;
+ String field = "";
+
+ while (e < split.length) {
+ String element = split[e];
+ int quoteCount = 0;
+ for (int i=0; i<element.length(); i++) {
+ char c = element.charAt(i);
+ if (c == '"') {
+ quoteCount++;
+ }
+ }
+ escape = (element.endsWith("\\"));
+
+ inQuote = ((quoteCount % 2) == 1);
+ if (!inQuote && !escape) {
+ // We got to the end of a field
+ field += element;
+ if (field.startsWith(cnPrefix)) {
+ result.add(field.substring(cnPrefix.length()));
+ }
+ field = "";
+ }
+ else {
+ // the split has consumed a comma that was part of a quoted
+ // String.
+ field = field + element + ",";
+ }
+ e++;
+ }
+ return result;
+ }
+
+ /**
+ * Returns a list of all the SRV values held in "OTHER" type subjectAltName
+ * fields in the server's certificate.
+ *
+ * @return a list containing the Strings representing SRV subjectAltName
+ * values from the server certificate. Will never return null, but may
+ * return an empty list.
+ */
+ @Override
+ public List<String> getSRVNames() {
+ // TODO: At the moment it will always return
+ // an empty list -see processSubjectAlternativeNames()
+ return srvNames_;
+ }
+
+ /**
+ * Returns a list of all the DNS subjectAltName values from the server's
+ * certificate.
+ *
+ * @return a list containing the Strings representing DNS subjectAltName
+ * values from the server certificate. Will never return null, but may
+ * return an empty list.
+ */
+ @Override
+ public List<String> getDNSNames() {
+ return dnsNames_;
+ }
+
+ /**
+ * Returns a list of all the XMPP values held in "OTHER" type subjectAltName
+ * fields in the server's certificate.
+ *
+ * @return a list containing the Strings representing XMPP subjectAltName
+ * values from the server certificate. Will never return null, but may
+ * return an empty list.
+ */
+ @Override
+ public List<String> getXMPPAddresses() {
+ // TODO: At the moment it will always return
+ // an empty list -see processSubjectAlternativeNames()
+ return xmppNames_;
+ }
+
+ /**
+ * Return the encoded representation of the certificate
+ *
+ * @return the DER encoding of the certificate. Will return null if
+ * the certificate is not valid.
+ */
+ @Override
+ public ByteArray toDER() {
+ // TODO Auto-generated method stub
+ try {
+ byte[] r = x509Certificate.getEncoded();
+ return new ByteArray(r);
+ }
+ catch (CertificateEncodingException e) {
+ return null;
+ }
+ }
+
+ private List<String> dnsNames_ = null;
+ private List<String> srvNames_ = null;
+ private List<String> xmppNames_ = null;
+
+ /**
+ * OID for commonName
+ */
+ private final static String cnOID = "2.5.4.3";
+ /**
+ * String to be used to identify commonName values in a DN.
+ */
+ private final static String cnLabel = "COMMONNAME";
+
+}
diff --git a/src/com/isode/stroke/tls/java/JavaTrustManager.java b/src/com/isode/stroke/tls/java/JavaTrustManager.java
new file mode 100644
index 0000000..4be7edf
--- /dev/null
+++ b/src/com/isode/stroke/tls/java/JavaTrustManager.java
@@ -0,0 +1,151 @@
+/* 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.java;
+
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * A concrete X509TrustManager implementation which provides a trust manager
+ * based on the default java "pkcs12" keystore.
+ */
+public class JavaTrustManager implements X509TrustManager {
+
+ /**
+ * Construct a new object
+ * @param jsseContext reference to JSSEContext; must not be null.
+ *
+ * @throws SSLException if it was not possible to initialise the
+ * TrustManager or KeyStore
+ */
+ JavaTrustManager(JSSEContext jsseContext) throws SSLException {
+
+ if (jsseContext == null) {
+ throw new NullPointerException("JSSEContext may not be null");
+ }
+ this.jsseContext = jsseContext;
+
+ try {
+ // create a "default" JSSE X509TrustManager.
+
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ /*
+
+ // This is how you could load trust anchors
+ ks.load(new FileInputStream("trustedCerts"),
+ "passphrase".toCharArray());
+ */
+ TrustManagerFactory tmf =
+ TrustManagerFactory.getInstance("PKIX");
+ tmf.init(ks);
+
+ TrustManager tms [] = tmf.getTrustManagers();
+
+ /*
+ * Iterate over the returned trustmanagers, look
+ * for an instance of X509TrustManager. If found,
+ * use that as our "default" trust manager.
+ */
+ for (int i = 0; i < tms.length; i++) {
+ if (tms[i] instanceof X509TrustManager) {
+ pkixTrustManager = (X509TrustManager) tms[i];
+ return;
+ }
+ }
+ /*
+ * Find some other way to initialize, or else we have to fail the
+ * constructor.
+ */
+ throw new SSLException("Couldn't initialize");
+ }
+ catch (KeyStoreException e) {
+ throw new SSLException(e);
+ }
+ catch (NoSuchAlgorithmException e) {
+ throw new SSLException(e);
+ }
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException {
+ // It's not expected that a Stroke application will ever be in the
+ // position of checking client certificates. Just delegate to
+ // default trust manager
+ pkixTrustManager.checkClientTrusted(chain, authType);
+
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException {
+ CertificateException certificateException = null;
+
+
+ // TODO:
+ // Note that we don't call the superclass method here yet, because
+ // it will fail with like this until the TrustManagerFactory has
+ // been initialised with a suitable list of trust anchors
+ // java.lang.RuntimeException: Unexpected error:
+ // java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
+
+ /*
+ try {
+ pkixTrustManager.checkServerTrusted(chain, authType);
+ } catch (CertificateException e) {
+ certificateException = e;
+ }
+ catch (Exception e) {
+ emitError(e,"checkServerTrusted failed");
+ }
+ */
+
+ // TODO: The only type of verification done is the certificate validity.
+ // Need to make "checkServerTrusted" do certificate verification properly
+ // and pass in an appropriate CertificateException
+ if (chain != null && chain.length > 0) {
+ try {
+ chain[0].checkValidity();
+ }
+ catch (CertificateException e) {
+ certificateException = e;
+ }
+ }
+
+ jsseContext.setPeerCertificateInfo(chain, certificateException);
+
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ /*
+ * The default PKIX X509TrustManager, to which decisions can be
+ * delegated when we don't make them ourselves.
+ */
+ X509TrustManager pkixTrustManager;
+
+ /**
+ * The object who wants to know what server certificates appear
+ */
+ JSSEContext jsseContext;
+}