diff options
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 |