summaryrefslogtreecommitdiffstats
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/com/isode/stroke/tls/java/JSSEContext.java
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/com/isode/stroke/tls/java/JSSEContext.java')
-rw-r--r--src/com/isode/stroke/tls/java/JSSEContext.java714
1 files changed, 714 insertions, 0 deletions
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;
+
+ }
+}