diff options
author | Tarun Gupta <tarun1995gupta@gmail.com> | 2015-07-24 17:01:42 (GMT) |
---|---|---|
committer | Nick Hudson <nick.hudson@isode.com> | 2015-08-14 15:32:08 (GMT) |
commit | 0a1f7199e26523dd2693f44a5841c5434cc9000d (patch) | |
tree | 8f28ebb20fbe60f420ea25055955ac3d246db549 | |
parent | dc2b35bee48261e8b06e12bd82a434af118e035e (diff) | |
download | stroke-0a1f7199e26523dd2693f44a5841c5434cc9000d.zip stroke-0a1f7199e26523dd2693f44a5841c5434cc9000d.tar.bz2 |
Completes TLS & Session.
Adds TLSError and TLSOptions.
Updates BasicSessionStream, SessionStream and Session.
Updates Client and Components to accomodate changes in TLS.
Also completes TLSLayer in StreamStack which was pending due to TLS port.
License:
This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details.
Test-Information:
Tests added for Certificate and ServerIdentityVerifier.
Test updated for ComponentSession.
All tests pass.
Change-Id: I34a8fe068c1e8af5348cc4ab49d3d1ed118ae833
23 files changed, 567 insertions, 79 deletions
diff --git a/src/com/isode/stroke/client/ClientOptions.java b/src/com/isode/stroke/client/ClientOptions.java index 4144214..b410094 100644 --- a/src/com/isode/stroke/client/ClientOptions.java +++ b/src/com/isode/stroke/client/ClientOptions.java @@ -8,6 +8,9 @@ */ package com.isode.stroke.client; + +import com.isode.stroke.tls.TLSOptions; + /** * Options for a client connection */ @@ -58,6 +61,11 @@ public class ClientOptions { public int manualPort; + /** + * Options passed to the TLS stack + */ + public TLSOptions tlsOptions = new TLSOptions(); + public enum UseTLS { NeverUseTLS, UseTLSWhenAvailable, diff --git a/src/com/isode/stroke/client/ClientSession.java b/src/com/isode/stroke/client/ClientSession.java index fe9481d..b8ec4a9 100644 --- a/src/com/isode/stroke/client/ClientSession.java +++ b/src/com/isode/stroke/client/ClientSession.java @@ -212,8 +212,8 @@ public class ClientSession { handleElement(p1); } }); - streamClosedConnection = stream.onClosed.connect(new Slot1<SessionStream.Error>(){ - public void call(final SessionStream.Error p1) { + streamClosedConnection = stream.onClosed.connect(new Slot1<com.isode.stroke.base.Error>(){ + public void call(final com.isode.stroke.base.Error p1) { handleStreamClosed(p1); } }); @@ -535,7 +535,7 @@ public class ClientSession { checkTrustOrFinish(certificateChain, verificationError); } else { - final ServerIdentityVerifier identityVerifier = new ServerIdentityVerifier(localJID); + final ServerIdentityVerifier identityVerifier = new ServerIdentityVerifier(localJID, idnConverter); if (identityVerifier.certificateVerifies(peerCertificate)) { continueAfterTLSEncrypted(); } @@ -560,7 +560,7 @@ public class ClientSession { sendStreamHeader(); } - private void handleStreamClosed(final SessionStream.Error streamError) { + private void handleStreamClosed(final com.isode.stroke.base.Error streamError) { final State previousState = state; state = State.Finished; diff --git a/src/com/isode/stroke/client/CoreClient.java b/src/com/isode/stroke/client/CoreClient.java index 39229a3..74ba031 100644 --- a/src/com/isode/stroke/client/CoreClient.java +++ b/src/com/isode/stroke/client/CoreClient.java @@ -29,6 +29,7 @@ import com.isode.stroke.signals.Slot2; import com.isode.stroke.tls.CertificateTrustChecker; import com.isode.stroke.tls.CertificateVerificationError; import com.isode.stroke.tls.CertificateWithKey; +import com.isode.stroke.tls.TLSOptions; /** * The central class for communicating with an XMPP server. @@ -246,7 +247,7 @@ public class CoreClient { connection_ = connection; assert (sessionStream_ == null); - sessionStream_ = new BasicSessionStream(StreamType.ClientStreamType, connection_, payloadParserFactories_, payloadSerializers_, networkFactories.getTLSContextFactory(), networkFactories.getTimerFactory()); + sessionStream_ = new BasicSessionStream(StreamType.ClientStreamType, connection_, payloadParserFactories_, payloadSerializers_, networkFactories.getTLSContextFactory(), networkFactories.getTimerFactory(), options.tlsOptions); if (certificate_ != null && !certificate_.isNull()) { sessionStream_.setTLSCertificate(certificate_); } @@ -352,8 +353,8 @@ public class CoreClient { break; /* Note: no case clause for "StreamError" */ } - } else if (error instanceof SessionStream.Error) { - SessionStream.Error actualError = (SessionStream.Error) error; + } else if (error instanceof SessionStream.SessionStreamError) { + SessionStream.SessionStreamError actualError = (SessionStream.SessionStreamError) error; switch (actualError.type) { case ParseError: clientError = new ClientError(ClientError.Type.XMLError); @@ -373,7 +374,7 @@ public class CoreClient { } } else if (error instanceof CertificateVerificationError) { CertificateVerificationError verificationError = (CertificateVerificationError)error; - switch (verificationError.type) { + switch (verificationError.getType()) { case UnknownError: clientError = new ClientError(ClientError.Type.UnknownCertificateError); break; diff --git a/src/com/isode/stroke/component/ComponentSession.java b/src/com/isode/stroke/component/ComponentSession.java index 40bb391..4fd2d3a 100644 --- a/src/com/isode/stroke/component/ComponentSession.java +++ b/src/com/isode/stroke/component/ComponentSession.java @@ -84,9 +84,9 @@ public class ComponentSession { handleElement(e1); } }); - onClosedConnection = stream.onClosed.connect(new Slot1<SessionStream.Error>() { + onClosedConnection = stream.onClosed.connect(new Slot1<com.isode.stroke.base.Error>() { @Override - public void call(SessionStream.Error e1) { + public void call(com.isode.stroke.base.Error e1) { handleStreamClosed(e1); } }); @@ -172,7 +172,7 @@ public class ComponentSession { stream.writeElement(new ComponentHandshake(ComponentHandshakeGenerator.getHandshake(header.getID(), secret, crypto))); } - private void handleStreamClosed(SessionStream.Error streamError) { + private void handleStreamClosed(com.isode.stroke.base.Error streamError) { State oldState = state; state = State.Finished; stream.setWhitespacePingEnabled(false); diff --git a/src/com/isode/stroke/component/CoreComponent.java b/src/com/isode/stroke/component/CoreComponent.java index 63b9cd5..cc51873 100644 --- a/src/com/isode/stroke/component/CoreComponent.java +++ b/src/com/isode/stroke/component/CoreComponent.java @@ -35,6 +35,7 @@ import com.isode.stroke.signals.Signal; import com.isode.stroke.signals.SignalConnection; import com.isode.stroke.signals.Slot1; import com.isode.stroke.client.StanzaChannel; +import com.isode.stroke.tls.TLSOptions; /** * The central class for communicating with an XMPP server as a component. @@ -178,8 +179,7 @@ public class CoreComponent extends Entity { connection_ = connection; assert(sessionStream_ == null); - //TODO: PORT TLSOPTIONS. - sessionStream_ = new BasicSessionStream(StreamType.ComponentStreamType, connection_, getPayloadParserFactories(), getPayloadSerializers(), null, networkFactories.getTimerFactory()); + sessionStream_ = new BasicSessionStream(StreamType.ComponentStreamType, connection_, getPayloadParserFactories(), getPayloadSerializers(), null, networkFactories.getTimerFactory(), new TLSOptions()); onDataReadConnection = sessionStream_.onDataRead.connect(new Slot1<SafeByteArray>() { @Override public void call(SafeByteArray s1) { @@ -235,8 +235,8 @@ public class CoreComponent extends Entity { break; } } - else if(error instanceof SessionStream.Error) { - SessionStream.Error actualError = (SessionStream.Error)error; + else if(error instanceof SessionStream.SessionStreamError) { + SessionStream.SessionStreamError actualError = (SessionStream.SessionStreamError)error; switch(actualError.type) { case ParseError: componentError = new ComponentError(ComponentError.Type.XMLError); diff --git a/src/com/isode/stroke/session/BasicSessionStream.java b/src/com/isode/stroke/session/BasicSessionStream.java index 9ba862d..4048363 100644 --- a/src/com/isode/stroke/session/BasicSessionStream.java +++ b/src/com/isode/stroke/session/BasicSessionStream.java @@ -20,6 +20,7 @@ import com.isode.stroke.parser.PayloadParserFactoryCollection; import com.isode.stroke.serializer.PayloadSerializerCollection; import com.isode.stroke.signals.Slot; import com.isode.stroke.signals.Slot1; +import com.isode.stroke.signals.SignalConnection; import com.isode.stroke.streamstack.CompressionLayer; import com.isode.stroke.streamstack.ConnectionLayer; import com.isode.stroke.streamstack.StreamStack; @@ -29,6 +30,8 @@ import com.isode.stroke.streamstack.XMPPLayer; import com.isode.stroke.tls.Certificate; import com.isode.stroke.tls.CertificateVerificationError; import com.isode.stroke.tls.TLSContextFactory; +import com.isode.stroke.tls.TLSOptions; +import com.isode.stroke.tls.TLSError; public class BasicSessionStream extends SessionStream { @@ -38,7 +41,8 @@ public class BasicSessionStream extends SessionStream { PayloadParserFactoryCollection payloadParserFactories, PayloadSerializerCollection payloadSerializers, TLSContextFactory tlsContextFactory, - TimerFactory timerFactory) { + TimerFactory timerFactory, + TLSOptions tlsOptions) { available = false; this.connection = connection; this.payloadParserFactories = payloadParserFactories; @@ -52,40 +56,40 @@ public class BasicSessionStream extends SessionStream { this.compressionLayer = null; this.tlsLayer = null; this.whitespacePingLayer = null; - + this.tlsOptions_ = tlsOptions; xmppLayer = new XMPPLayer(payloadParserFactories, payloadSerializers, streamType); - xmppLayer.onStreamStart.connect(new Slot1<ProtocolHeader>() { + onStreamStartConnection = xmppLayer.onStreamStart.connect(new Slot1<ProtocolHeader>() { public void call(ProtocolHeader p1) { handleStreamStartReceived(p1); } }); - xmppLayer.onElement.connect(new Slot1<Element>() { + onElementConnection = xmppLayer.onElement.connect(new Slot1<Element>() { public void call(Element p1) { handleElementReceived(p1); } }); - xmppLayer.onError.connect(new Slot() { + onErrorConnection = xmppLayer.onError.connect(new Slot() { public void call() { handleXMPPError(); } }); - xmppLayer.onDataRead.connect(new Slot1<SafeByteArray>() { + onDataReadConnection = xmppLayer.onDataRead.connect(new Slot1<SafeByteArray>() { public void call(SafeByteArray p1) { handleDataRead(p1); } }); - xmppLayer.onWriteData.connect(new Slot1<SafeByteArray>() { + onWriteDataConnection = xmppLayer.onWriteData.connect(new Slot1<SafeByteArray>() { public void call(SafeByteArray p1) { handleDataWritten(p1); } }); - connection.onDisconnected.connect(new Slot1<Connection.Error>() { + onDisconnectedConnection = connection.onDisconnected.connect(new Slot1<Connection.Error>() { public void call(Connection.Error p1) { handleConnectionFinished(p1); @@ -99,6 +103,28 @@ public class BasicSessionStream extends SessionStream { } + /** + * User will have to call disconnect() method to free up the resources. + */ + @Override + public void disconnect() { + if(tlsLayer != null) { + onErrorConnection.disconnect(); + onConnectedConnection.disconnect(); + tlsLayer = null; + } + whitespacePingLayer = null; + streamStack = null; + onDisconnectedConnection.disconnect(); + connectionLayer = null; + onStreamStartConnection.disconnect(); + onElementConnection.disconnect(); + onErrorConnection.disconnect(); + onDataReadConnection.disconnect(); + onWriteDataConnection.disconnect(); + xmppLayer = null; + } + public void writeHeader(ProtocolHeader header) { assert available; xmppLayer.writeHeader(header); @@ -133,18 +159,18 @@ public class BasicSessionStream extends SessionStream { public void addTLSEncryption() { assert available; - tlsLayer = new TLSLayer(tlsContextFactory); + tlsLayer = new TLSLayer(tlsContextFactory, tlsOptions_); if (hasTLSCertificate() && !tlsLayer.setClientCertificate(getTLSCertificate())) { - onClosed.emit(new Error(Error.Type.InvalidTLSCertificateError)); + onClosed.emit(new SessionStreamError(SessionStreamError.Type.InvalidTLSCertificateError)); } else { streamStack.addLayer(tlsLayer); - tlsLayer.onError.connect(new Slot() { + onErrorConnection = tlsLayer.onError.connect(new Slot1<TLSError>() { - public void call() { - handleTLSError(); + public void call(TLSError e) { + handleTLSError(e); } }); - tlsLayer.onConnected.connect(new Slot() { + onConnectedConnection = tlsLayer.onConnected.connect(new Slot() { public void call() { handleTLSConnected(); @@ -211,25 +237,25 @@ public class BasicSessionStream extends SessionStream { private void handleXMPPError() { available = false; - onClosed.emit(new Error(Error.Type.ParseError)); + onClosed.emit(new SessionStreamError(SessionStreamError.Type.ParseError)); } private void handleTLSConnected() { onTLSEncrypted.emit(); } - private void handleTLSError() { + private void handleTLSError(TLSError error) { available = false; - onClosed.emit(new Error(Error.Type.TLSError)); + onClosed.emit(error); } private void handleConnectionFinished(Connection.Error error) { available = false; if (Connection.Error.ReadError.equals(error)) { - onClosed.emit(new Error(Error.Type.ConnectionReadError)); + onClosed.emit(new SessionStreamError(SessionStreamError.Type.ConnectionReadError)); } else if (error != null) { - onClosed.emit(new Error(Error.Type.ConnectionWriteError)); + onClosed.emit(new SessionStreamError(SessionStreamError.Type.ConnectionWriteError)); } else { onClosed.emit(null); @@ -262,5 +288,12 @@ public class BasicSessionStream extends SessionStream { private TLSLayer tlsLayer; private WhitespacePingLayer whitespacePingLayer; private StreamStack streamStack; - + private TLSOptions tlsOptions_; + private SignalConnection onErrorConnection; + private SignalConnection onConnectedConnection; + private SignalConnection onDisconnectedConnection; + private SignalConnection onStreamStartConnection; + private SignalConnection onElementConnection; + private SignalConnection onDataReadConnection; + private SignalConnection onWriteDataConnection; } diff --git a/src/com/isode/stroke/session/Session.java b/src/com/isode/stroke/session/Session.java index 815be02..0255627 100644 --- a/src/com/isode/stroke/session/Session.java +++ b/src/com/isode/stroke/session/Session.java @@ -22,6 +22,7 @@ import com.isode.stroke.serializer.PayloadSerializerCollection; import com.isode.stroke.signals.Signal1; import com.isode.stroke.signals.Slot; import com.isode.stroke.signals.Slot1; +import com.isode.stroke.signals.SignalConnection; import com.isode.stroke.streamstack.ConnectionLayer; import com.isode.stroke.streamstack.StreamStack; import com.isode.stroke.streamstack.XMPPLayer; @@ -46,27 +47,31 @@ public abstract class Session { public Session( final Connection connection, final PayloadParserFactoryCollection payloadParserFactories, - final PayloadSerializerCollection payloadSerializers, - final EventLoop eventLoop) { + final PayloadSerializerCollection payloadSerializers) { this.connection = connection; - this.eventLoop = eventLoop; this.payloadParserFactories = payloadParserFactories; this.payloadSerializers = payloadSerializers; + xmppLayer = null; + connectionLayer = null; + streamStack = null; finishing = false; } public void startSession() { initializeStreamStack(); - handleSessionStarted(); + handleSessionStarted(); } public void finishSession() { + if (finishing) { + return; + } finishing = true; - connection.disconnect(); - handleSessionFinished(null); - finishing = false; - onSessionFinished.emit(null); + if (xmppLayer != null) { + xmppLayer.writeFooter(); + } + connection.disconnect(); } public void sendElement(Element stanza) { @@ -94,11 +99,14 @@ public abstract class Session { } protected void finishSession(SessionError error) { + if (finishing) { + return; + } finishing = true; - connection.disconnect(); - handleSessionFinished(error); - finishing = false; - onSessionFinished.emit(error); + if (xmppLayer != null) { + xmppLayer.writeFooter(); + } + connection.disconnect(); } protected void handleSessionStarted() { @@ -133,7 +141,7 @@ public abstract class Session { }); xmppLayer.onDataRead.connect(onDataRead); xmppLayer.onWriteData.connect(onDataWritten); - connection.onDisconnected.connect(new Slot1<Connection.Error>() { + onDisconnectedConnection = connection.onDisconnected.connect(new Slot1<Connection.Error>() { public void call(Connection.Error p1) { handleDisconnected(p1); @@ -151,28 +159,33 @@ public abstract class Session { public StreamStack getStreamStack() { return streamStack; - - } - /*void setFinished();*/ /* This seems to be unused in Swiften*/ + /*protected void setFinished();*/ /* This seems to be unused in Swiften*/ private void handleDisconnected(Connection.Error connectionError) { + onDisconnectedConnection.disconnect(); if (connectionError != null) { switch (connectionError) { case ReadError: - finishSession(SessionError.ConnectionReadError); + handleSessionFinished(SessionError.ConnectionReadError); + onSessionFinished.emit(SessionError.ConnectionReadError); break; case WriteError: - finishSession(SessionError.ConnectionWriteError); + handleSessionFinished(SessionError.ConnectionWriteError); + onSessionFinished.emit(SessionError.ConnectionWriteError); break; } - } else { - finishSession(); + } + else { + SessionError error = null; + handleSessionFinished(error); + onSessionFinished.emit(error); } } - private JID localJID; - private JID remoteJID; + + private JID localJID = new JID(); + private JID remoteJID = new JID(); private Connection connection; private PayloadParserFactoryCollection payloadParserFactories; private PayloadSerializerCollection payloadSerializers; @@ -180,5 +193,5 @@ public abstract class Session { private ConnectionLayer connectionLayer; private StreamStack streamStack; private boolean finishing; - private final EventLoop eventLoop; + private SignalConnection onDisconnectedConnection; } diff --git a/src/com/isode/stroke/session/SessionStream.java b/src/com/isode/stroke/session/SessionStream.java index 3171ec0..78d5588 100644 --- a/src/com/isode/stroke/session/SessionStream.java +++ b/src/com/isode/stroke/session/SessionStream.java @@ -6,12 +6,13 @@ * Copyright (c) 2010-2014, Isode Limited, London, England. * All rights reserved. */ -package com.isode.stroke.session; +package com.isode.stroke.session; import java.util.List; import com.isode.stroke.base.SafeByteArray; import com.isode.stroke.base.ByteArray; +import com.isode.stroke.base.Error; import com.isode.stroke.elements.Element; import com.isode.stroke.elements.ProtocolHeader; import com.isode.stroke.signals.Signal; @@ -22,7 +23,7 @@ import com.isode.stroke.tls.CertificateWithKey; public abstract class SessionStream { - public static class Error implements com.isode.stroke.base.Error { + public static class SessionStreamError implements Error { public enum Type { @@ -33,7 +34,7 @@ public abstract class SessionStream { ConnectionWriteError } - public Error(Type type) { + public SessionStreamError(Type type) { this.type = type; } public final Type type; @@ -64,7 +65,18 @@ public abstract class SessionStream { public abstract void setWhitespacePingEnabled(boolean enabled); public abstract void resetXMPPParser(); - + + public abstract void disconnect(); + + protected void finalize() throws Throwable { + try { + disconnect(); + } + finally { + super.finalize(); + } + } + public void setTLSCertificate(CertificateWithKey cert) { certificate = cert; } diff --git a/src/com/isode/stroke/streamstack/TLSLayer.java b/src/com/isode/stroke/streamstack/TLSLayer.java index 8244766..c7b40aa 100644 --- a/src/com/isode/stroke/streamstack/TLSLayer.java +++ b/src/com/isode/stroke/streamstack/TLSLayer.java @@ -13,17 +13,20 @@ import java.util.List; import com.isode.stroke.base.SafeByteArray; import com.isode.stroke.signals.Signal; +import com.isode.stroke.signals.Signal1; import com.isode.stroke.signals.Slot1; import com.isode.stroke.tls.Certificate; import com.isode.stroke.tls.CertificateVerificationError; import com.isode.stroke.tls.CertificateWithKey; import com.isode.stroke.tls.TLSContext; +import com.isode.stroke.tls.TLSOptions; +import com.isode.stroke.tls.TLSError; import com.isode.stroke.tls.TLSContextFactory; public class TLSLayer extends StreamLayer { - public TLSLayer(TLSContextFactory factory) { - context = factory.createTLSContext(); + public TLSLayer(TLSContextFactory factory, TLSOptions tlsOptions) { + context = factory.createTLSContext(tlsOptions); context.onDataForNetwork.connect(new Slot1<SafeByteArray>() { public void call(SafeByteArray p1) { @@ -72,7 +75,7 @@ public class TLSLayer extends StreamLayer { return context; } - public final Signal onError = new Signal();//needs port + public final Signal1<TLSError> onError = new Signal1<TLSError>(); public final Signal onConnected = new Signal(); private final TLSContext context; diff --git a/src/com/isode/stroke/tls/BlindCertificateTrustChecker.java b/src/com/isode/stroke/tls/BlindCertificateTrustChecker.java new file mode 100644 index 0000000..46866f2 --- /dev/null +++ b/src/com/isode/stroke/tls/BlindCertificateTrustChecker.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010-2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.tls; + +import com.isode.stroke.tls.CertificateTrustChecker; +import java.util.List; + +/** + * A certificate trust checker that trusts any ceritficate. + * + * This can be used to ignore any TLS certificate errors occurring + * during connection. + * + * @link Client#setAlwaysTrustCertificates() + */ +public class BlindCertificateTrustChecker implements CertificateTrustChecker { + + public boolean isCertificateTrusted(final List<Certificate> certificate) { + return true; + } +}
\ No newline at end of file diff --git a/src/com/isode/stroke/tls/Certificate.java b/src/com/isode/stroke/tls/Certificate.java index de23f94..fdd64c0 100644 --- a/src/com/isode/stroke/tls/Certificate.java +++ b/src/com/isode/stroke/tls/Certificate.java @@ -9,6 +9,7 @@ package com.isode.stroke.tls; import com.isode.stroke.base.ByteArray; +import com.isode.stroke.crypto.CryptoProvider; import com.isode.stroke.stringcodecs.Hexify; import com.isode.stroke.stringcodecs.SHA1; import java.util.List; @@ -31,8 +32,8 @@ public abstract class Certificate { public abstract ByteArray toDER(); - public String getSHA1Fingerprint() { - ByteArray hash = SHA1.getHash(toDER()); + public static String getSHA1Fingerprint(Certificate certificate, CryptoProvider crypto) { + ByteArray hash = crypto.getSHA1Hash(certificate.toDER()); StringBuilder s = new StringBuilder(); for (int i = 0; i < hash.getSize(); ++i) { if (i > 0) { diff --git a/src/com/isode/stroke/tls/CertificateVerificationError.java b/src/com/isode/stroke/tls/CertificateVerificationError.java index d76dc00..baff374 100644 --- a/src/com/isode/stroke/tls/CertificateVerificationError.java +++ b/src/com/isode/stroke/tls/CertificateVerificationError.java @@ -29,12 +29,21 @@ public class CertificateVerificationError implements Error { RevocationCheckFailed } + public CertificateVerificationError() { + this(Type.UnknownError); + } + public CertificateVerificationError(Type type) { if (type == null) { throw new IllegalStateException(); } this.type = type; } - public final Type type; + + public Type getType() { + return type; + } + + private final Type type; } diff --git a/src/com/isode/stroke/tls/ServerIdentityVerifier.java b/src/com/isode/stroke/tls/ServerIdentityVerifier.java index 20caae8..86ce803 100644 --- a/src/com/isode/stroke/tls/ServerIdentityVerifier.java +++ b/src/com/isode/stroke/tls/ServerIdentityVerifier.java @@ -9,15 +9,20 @@ */ package com.isode.stroke.tls; -import com.isode.stroke.idn.IDNA; +import com.isode.stroke.idn.IDNConverter; import com.isode.stroke.jid.JID; import java.util.List; public class ServerIdentityVerifier { - public ServerIdentityVerifier(JID jid) { + public ServerIdentityVerifier(JID jid, IDNConverter idnConverter) { + this.domainValid = false; domain = jid.getDomain(); - encodedDomain = IDNA.getEncoded(domain); + String domainResult = idnConverter.getIDNAEncoded(domain); + if (domainResult != null) { + encodedDomain = domainResult; + domainValid = true; + } } public boolean certificateVerifies(Certificate certificate) { @@ -69,6 +74,9 @@ public class ServerIdentityVerifier { } boolean matchesDomain(String s) { + if (!domainValid) { + return false; + } if (s.startsWith("*.")) { String matchString = s.substring(2); String matchDomain = encodedDomain; @@ -88,4 +96,5 @@ public class ServerIdentityVerifier { } private String domain; private String encodedDomain; + private boolean domainValid; } diff --git a/src/com/isode/stroke/tls/SimpleCertificate.java b/src/com/isode/stroke/tls/SimpleCertificate.java new file mode 100644 index 0000000..178d36d --- /dev/null +++ b/src/com/isode/stroke/tls/SimpleCertificate.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2010-2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.tls; + +import java.util.List; +import java.util.ArrayList; +import com.isode.stroke.base.ByteArray; + +public class SimpleCertificate extends Certificate { + + private String subjectName = ""; + private ByteArray der = new ByteArray(); + private List<String> commonNames = new ArrayList<String>(); + private List<String> dnsNames = new ArrayList<String>(); + private List<String> xmppAddresses = new ArrayList<String>(); + private List<String> srvNames = new ArrayList<String>(); + + public void setSubjectName(final String name) { + subjectName = name; + } + + public String getSubjectName() { + return subjectName; + } + + public List<String> getCommonNames() { + return commonNames; + } + + public void addCommonName(final String name) { + commonNames.add(name); + } + + public void addSRVName(final String name) { + srvNames.add(name); + } + + public void addDNSName(final String name) { + dnsNames.add(name); + } + + public void addXMPPAddress(final String addr) { + xmppAddresses.add(addr); + } + + public List<String> getSRVNames() { + return srvNames; + } + + public List<String> getDNSNames() { + return dnsNames; + } + + public List<String> getXMPPAddresses() { + return xmppAddresses; + } + + public ByteArray toDER() { + return der; + } + + public void setDER(final ByteArray der) { + this.der = der; + } + + private void parse() { + + } +}
\ No newline at end of file diff --git a/src/com/isode/stroke/tls/TLSContext.java b/src/com/isode/stroke/tls/TLSContext.java index 3f5e8d7..cd9f90d 100644 --- a/src/com/isode/stroke/tls/TLSContext.java +++ b/src/com/isode/stroke/tls/TLSContext.java @@ -15,6 +15,7 @@ import com.isode.stroke.base.ByteArray; import com.isode.stroke.base.SafeByteArray; import com.isode.stroke.signals.Signal; import com.isode.stroke.signals.Signal1; +import com.isode.stroke.tls.TLSError; public abstract class TLSContext { @@ -41,6 +42,6 @@ public abstract class TLSContext { public Signal1<SafeByteArray> onDataForNetwork = new Signal1<SafeByteArray>(); public Signal1<SafeByteArray> onDataForApplication = new Signal1<SafeByteArray>(); - public Signal onError = new Signal(); + public Signal1<TLSError> onError = new Signal1<TLSError>(); public Signal onConnected = new Signal(); } diff --git a/src/com/isode/stroke/tls/TLSContextFactory.java b/src/com/isode/stroke/tls/TLSContextFactory.java index 27e322f..f33539b 100644 --- a/src/com/isode/stroke/tls/TLSContextFactory.java +++ b/src/com/isode/stroke/tls/TLSContextFactory.java @@ -11,5 +11,7 @@ package com.isode.stroke.tls; public interface TLSContextFactory { boolean canCreate(); - TLSContext createTLSContext(); + TLSContext createTLSContext(TLSOptions tlsOptions); + //void setCheckCertificateRevocation(boolean b); + //void setDisconnectOnCardRemoval(boolean b); } diff --git a/src/com/isode/stroke/tls/TLSError.java b/src/com/isode/stroke/tls/TLSError.java new file mode 100644 index 0000000..619b747 --- /dev/null +++ b/src/com/isode/stroke/tls/TLSError.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2012-2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.tls; + +import com.isode.stroke.base.Error; + +public class TLSError implements Error { + + private Type type; + + public enum Type { + UnknownError, + CertificateCardRemoved + }; + + public TLSError() { + this(Type.UnknownError); + } + + public TLSError(Type type) { + this.type = type; + } + + public Type getType() { + return type; + } +}
\ No newline at end of file diff --git a/src/com/isode/stroke/tls/TLSOptions.java b/src/com/isode/stroke/tls/TLSOptions.java new file mode 100644 index 0000000..9c0b647 --- /dev/null +++ b/src/com/isode/stroke/tls/TLSOptions.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.tls; + +public class TLSOptions { + + /** + * This flag is not used in java, and is purely here to maintain + * consistency with Swiften + */ + public boolean schannelTLS1_0Workaround; + + public TLSOptions() { + schannelTLS1_0Workaround = false; + } +}
\ No newline at end of file diff --git a/src/com/isode/stroke/tls/java/JSSEContext.java b/src/com/isode/stroke/tls/java/JSSEContext.java index 17b7d4d..02f3b4d 100644 --- a/src/com/isode/stroke/tls/java/JSSEContext.java +++ b/src/com/isode/stroke/tls/java/JSSEContext.java @@ -53,6 +53,7 @@ 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; +import com.isode.stroke.tls.TLSError; @@ -121,7 +122,7 @@ public class JSSEContext extends TLSContext { */ logger_.log(Level.WARNING, jsseContextError.toString(), e); errorsEmitted.add(jsseContextError); - onError.emit(); + onError.emit(null); } @Override @@ -921,7 +922,7 @@ public class JSSEContext extends TLSContext { public void handleDataFromNetwork(SafeByteArray data) { if (hasError()) { /* We have previously seen, and reported, an error. Emit again */ - onError.emit(); + onError.emit(null); return; } @@ -999,7 +1000,7 @@ public class JSSEContext extends TLSContext { public void handleDataFromApplication(SafeByteArray data) { if (hasError()) { /* We have previously seen, and reported, an error. Emit again */ - onError.emit(); + onError.emit(null); return; } if (closeNotifyReceived) { diff --git a/src/com/isode/stroke/tls/java/JSSEContextFactory.java b/src/com/isode/stroke/tls/java/JSSEContextFactory.java index 63b184d..666ee77 100644 --- a/src/com/isode/stroke/tls/java/JSSEContextFactory.java +++ b/src/com/isode/stroke/tls/java/JSSEContextFactory.java @@ -14,6 +14,7 @@ import java.util.HashSet; import java.util.Set; import com.isode.stroke.tls.TLSContext; +import com.isode.stroke.tls.TLSOptions; import com.isode.stroke.tls.TLSContextFactory; /** @@ -33,7 +34,7 @@ public class JSSEContextFactory implements TLSContextFactory { } @Override - public TLSContext createTLSContext() { + public TLSContext createTLSContext(TLSOptions tlsOptions) { return new JSSEContext(restrictedCipherSuites); } diff --git a/test/com/isode/stroke/component/ComponentSessionTest.java b/test/com/isode/stroke/component/ComponentSessionTest.java index c7fa36b..5d6d21e 100644 --- a/test/com/isode/stroke/component/ComponentSessionTest.java +++ b/test/com/isode/stroke/component/ComponentSessionTest.java @@ -65,13 +65,17 @@ public class ComponentSessionTest { } public void close() { - onClosed.emit((SessionStream.Error)null); + onClosed.emit((SessionStream.SessionStreamError)null); } public boolean isOpen() { return available; } + public void disconnect() { + + } + public void writeHeader(final ProtocolHeader header) { receivedEvents.add(new Event(header)); } @@ -133,7 +137,7 @@ public class ComponentSessionTest { } public void breakConnection() { - onClosed.emit(new SessionStream.Error(SessionStream.Error.Type.ConnectionReadError)); + onClosed.emit(new SessionStream.SessionStreamError(SessionStream.SessionStreamError.Type.ConnectionReadError)); } public void sendStreamStart() { diff --git a/test/com/isode/stroke/tls/CertificateTest.java b/test/com/isode/stroke/tls/CertificateTest.java new file mode 100644 index 0000000..44d6701 --- /dev/null +++ b/test/com/isode/stroke/tls/CertificateTest.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2010-2013 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.tls; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import org.junit.Test; +import org.junit.Before; +import com.isode.stroke.crypto.CryptoProvider; +import com.isode.stroke.crypto.JavaCryptoProvider; +import com.isode.stroke.tls.SimpleCertificate; +import com.isode.stroke.tls.Certificate; +import com.isode.stroke.base.ByteArray; + +public class CertificateTest { + + @Test + public void testGetSHA1Fingerprint() { + SimpleCertificate testling = new SimpleCertificate(); + testling.setDER(new ByteArray("abcdefg")); + + assertEquals("2f:b5:e1:34:19:fc:89:24:68:65:e7:a3:24:f4:76:ec:62:4e:87:40", Certificate.getSHA1Fingerprint(testling, new JavaCryptoProvider())); + } +} diff --git a/test/com/isode/stroke/tls/ServerIdentityVerifierTest.java b/test/com/isode/stroke/tls/ServerIdentityVerifierTest.java new file mode 100644 index 0000000..17a8c5a --- /dev/null +++ b/test/com/isode/stroke/tls/ServerIdentityVerifierTest.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2010 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.tls; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import org.junit.Test; +import org.junit.Before; +import com.isode.stroke.crypto.CryptoProvider; +import com.isode.stroke.crypto.JavaCryptoProvider; +import com.isode.stroke.tls.SimpleCertificate; +import com.isode.stroke.tls.Certificate; +import com.isode.stroke.tls.ServerIdentityVerifier; +import com.isode.stroke.base.ByteArray; +import com.isode.stroke.idn.IDNConverter; +import com.isode.stroke.idn.ICUConverter; +import com.isode.stroke.jid.JID; + +public class ServerIdentityVerifierTest { + + private IDNConverter idnConverter; + + @Before + public void setUp() { + idnConverter = new ICUConverter(); + } + + @Test + public void testCertificateVerifies_WithoutMatchingDNSName() { + ServerIdentityVerifier testling = new ServerIdentityVerifier(new JID("foo@bar.com/baz"), idnConverter); + SimpleCertificate certificate = new SimpleCertificate(); + certificate.addDNSName("foo.com"); + + assertFalse(testling.certificateVerifies(certificate)); + } + + @Test + public void testCertificateVerifies_WithMatchingDNSName() { + ServerIdentityVerifier testling = new ServerIdentityVerifier(new JID("foo@bar.com/baz"), idnConverter); + SimpleCertificate certificate = new SimpleCertificate(); + certificate.addDNSName("bar.com"); + + assertTrue(testling.certificateVerifies(certificate)); + } + + @Test + public void testCertificateVerifies_WithSecondMatchingDNSName() { + ServerIdentityVerifier testling = new ServerIdentityVerifier(new JID("foo@bar.com/baz"), idnConverter); + SimpleCertificate certificate = new SimpleCertificate(); + certificate.addDNSName("foo.com"); + certificate.addDNSName("bar.com"); + + assertTrue(testling.certificateVerifies(certificate)); + } + + @Test + public void testCertificateVerifies_WithMatchingInternationalDNSName() { + ServerIdentityVerifier testling = new ServerIdentityVerifier(new JID("foo@tronçon.com/baz"), idnConverter); + SimpleCertificate certificate = new SimpleCertificate(); + certificate.addDNSName("xn--tronon-zua.com"); + + assertTrue(testling.certificateVerifies(certificate)); + } + + @Test + public void testCertificateVerifies_WithMatchingDNSNameWithWildcard() { + ServerIdentityVerifier testling = new ServerIdentityVerifier(new JID("foo@im.bar.com/baz"), idnConverter); + SimpleCertificate certificate = new SimpleCertificate(); + certificate.addDNSName("*.bar.com"); + + assertTrue(testling.certificateVerifies(certificate)); + } + + @Test + public void testCertificateVerifies_WithMatchingDNSNameWithWildcardMatchingNoComponents() { + ServerIdentityVerifier testling = new ServerIdentityVerifier(new JID("foo@bar.com/baz"), idnConverter); + SimpleCertificate certificate = new SimpleCertificate(); + certificate.addDNSName("*.bar.com"); + + assertFalse(testling.certificateVerifies(certificate)); + } + + @Test + public void testCertificateVerifies_WithDNSNameWithWildcardMatchingTwoComponents() { + ServerIdentityVerifier testling = new ServerIdentityVerifier(new JID("foo@xmpp.im.bar.com/baz"), idnConverter); + SimpleCertificate certificate = new SimpleCertificate(); + certificate.addDNSName("*.bar.com"); + + assertFalse(testling.certificateVerifies(certificate)); + } + + @Test + public void testCertificateVerifies_WithMatchingSRVNameWithoutService() { + ServerIdentityVerifier testling = new ServerIdentityVerifier(new JID("foo@bar.com/baz"), idnConverter); + SimpleCertificate certificate = new SimpleCertificate(); + certificate.addSRVName("bar.com"); + + assertFalse(testling.certificateVerifies(certificate)); + } + + @Test + public void testCertificateVerifies_WithMatchingSRVNameWithService() { + ServerIdentityVerifier testling = new ServerIdentityVerifier(new JID("foo@bar.com/baz"), idnConverter); + SimpleCertificate certificate = new SimpleCertificate(); + certificate.addSRVName("_xmpp-client.bar.com"); + + assertTrue(testling.certificateVerifies(certificate)); + } + + @Test + public void testCertificateVerifies_WithMatchingSRVNameWithServiceAndWildcard() { + ServerIdentityVerifier testling = new ServerIdentityVerifier(new JID("foo@im.bar.com/baz"), idnConverter); + SimpleCertificate certificate = new SimpleCertificate(); + certificate.addSRVName("_xmpp-client.*.bar.com"); + + assertTrue(testling.certificateVerifies(certificate)); + } + + @Test + public void testCertificateVerifies_WithMatchingSRVNameWithDifferentService() { + ServerIdentityVerifier testling = new ServerIdentityVerifier(new JID("foo@bar.com/baz"), idnConverter); + SimpleCertificate certificate = new SimpleCertificate(); + certificate.addSRVName("_xmpp-server.bar.com"); + + assertFalse(testling.certificateVerifies(certificate)); + } + + @Test + public void testCertificateVerifies_WithMatchingXmppAddr() { + ServerIdentityVerifier testling = new ServerIdentityVerifier(new JID("foo@bar.com/baz"), idnConverter); + SimpleCertificate certificate = new SimpleCertificate(); + certificate.addXMPPAddress("bar.com"); + + assertTrue(testling.certificateVerifies(certificate)); + } + + @Test + public void testCertificateVerifies_WithMatchingXmppAddrWithWildcard() { + ServerIdentityVerifier testling = new ServerIdentityVerifier(new JID("foo@im.bar.com/baz"), idnConverter); + SimpleCertificate certificate = new SimpleCertificate(); + certificate.addXMPPAddress("*.bar.com"); + + assertFalse(testling.certificateVerifies(certificate)); + } + + @Test + public void testCertificateVerifies_WithMatchingInternationalXmppAddr() { + ServerIdentityVerifier testling = new ServerIdentityVerifier(new JID("foo@tronçon.com/baz"), idnConverter); + SimpleCertificate certificate = new SimpleCertificate(); + certificate.addXMPPAddress("tronçon.com"); + + assertTrue(testling.certificateVerifies(certificate)); + } + + @Test + public void testCertificateVerifies_WithMatchingCNWithoutSAN() { + ServerIdentityVerifier testling = new ServerIdentityVerifier(new JID("foo@bar.com/baz"), idnConverter); + SimpleCertificate certificate = new SimpleCertificate(); + certificate.addCommonName("bar.com"); + + assertTrue(testling.certificateVerifies(certificate)); + } + + @Test + public void testCertificateVerifies_WithMatchingCNWithSAN() { + ServerIdentityVerifier testling = new ServerIdentityVerifier(new JID("foo@bar.com/baz"), idnConverter); + SimpleCertificate certificate = new SimpleCertificate(); + certificate.addSRVName("foo.com"); + certificate.addCommonName("bar.com"); + + assertFalse(testling.certificateVerifies(certificate)); + } +}
\ No newline at end of file |