From fbb662365a064889da25ce2c705568ca31f27af1 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 24 Jan 2018 14:04:32 +0300 Subject: xmpp: SASL EXTERNAL --- .../src/main/java/com/juick/server/XMPPServer.java | 52 +++++++++++++++++++--- .../server/configuration/ApiAppConfiguration.java | 2 + .../java/com/juick/server/xmpp/s2s/Connection.java | 19 ++++++++ .../com/juick/server/xmpp/s2s/ConnectionIn.java | 27 ++++++++--- .../com/juick/server/xmpp/s2s/ConnectionOut.java | 25 ++++++++++- 5 files changed, 112 insertions(+), 13 deletions(-) diff --git a/juick-server/src/main/java/com/juick/server/XMPPServer.java b/juick-server/src/main/java/com/juick/server/XMPPServer.java index e2018213..643bc37b 100644 --- a/juick-server/src/main/java/com/juick/server/XMPPServer.java +++ b/juick-server/src/main/java/com/juick/server/XMPPServer.java @@ -33,6 +33,8 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import javax.net.ssl.*; +import java.security.InvalidAlgorithmParameterException; +import java.security.cert.*; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import java.io.FileInputStream; @@ -84,6 +86,9 @@ public class XMPPServer implements ConnectionListener, AutoCloseable { private final AtomicBoolean closeFlag = new AtomicBoolean(false); SSLContext sc; + CertificateFactory cf; + CertPathValidator cpv; + PKIXParameters params; private TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) { @@ -91,8 +96,9 @@ public class XMPPServer implements ConnectionListener, AutoCloseable { public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) { } + public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return null; + return new X509Certificate[0]; } } }; @@ -117,6 +123,16 @@ public class XMPPServer implements ConnectionListener, AutoCloseable { kmf.init(ks, keystorePassword.toCharArray()); sc = SSLContext.getInstance("TLSv1.2"); sc.init(kmf.getKeyManagers(), trustAllCerts, new SecureRandom()); + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + + Set ca = new HashSet<>(); + trustManagerFactory.init((KeyStore)null); + Arrays.stream(trustManagerFactory.getTrustManagers()).forEach(t -> Arrays.stream(((X509TrustManager)t).getAcceptedIssuers()).forEach(cert -> ca.add(new TrustAnchor(cert, null)))); + params = new PKIXParameters(ca); + params.setRevocationEnabled(false); + cpv = CertPathValidator.getInstance("PKIX"); + cf = CertificateFactory.getInstance( "X.509" ); tlsConfigured = true; } catch (Exception e) { logger.warn("tls unavailable"); @@ -295,10 +311,22 @@ public class XMPPServer implements ConnectionListener, AutoCloseable { try { connection.setSocket(sc.getSocketFactory().createSocket(connection.getSocket(), connection.getSocket().getInetAddress().getHostAddress(), connection.getSocket().getPort(), true)); - ((SSLSocket) connection.getSocket()).setUseClientMode(false); - ((SSLSocket) connection.getSocket()).startHandshake(); + SSLSocket sslSocket = (SSLSocket) connection.getSocket(); + sslSocket.addHandshakeCompletedListener(handshakeCompletedEvent -> { + try { + CertPath certPath = cf.generateCertPath(Arrays.asList(handshakeCompletedEvent.getPeerCertificates())); + cpv.validate(certPath, params); + connection.setTrusted(true); + logger.info("connection from {} is trusted", connection.from); + } catch (SSLPeerUnverifiedException | CertificateException | CertPathValidatorException | InvalidAlgorithmParameterException e) { + logger.info("connection from {} is NOT trusted, falling back to dialback", connection.from); + } + }); + sslSocket.setUseClientMode(false); + sslSocket.setNeedClientAuth(true); + sslSocket.startHandshake(); connection.setSecured(true); - logger.debug("stream {} secured", connection.streamID); + logger.debug("stream from {} secured", connection.streamID); connection.restartParser(); } catch (XmlPullParserException | IOException sex) { logger.warn("stream {} ssl error {}", connection.streamID, sex); @@ -314,9 +342,21 @@ public class XMPPServer implements ConnectionListener, AutoCloseable { Socket socket = outConnections.get(connection).get(); socket = sc.getSocketFactory().createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true); - ((SSLSocket) socket).startHandshake(); + SSLSocket sslSocket = (SSLSocket) socket; + sslSocket.addHandshakeCompletedListener(handshakeCompletedEvent -> { + try { + CertPath certPath = cf.generateCertPath(Arrays.asList(handshakeCompletedEvent.getPeerCertificates())); + cpv.validate(certPath, params); + connection.setTrusted(true); + logger.info("connection to {} is trusted", connection.to); + } catch (SSLPeerUnverifiedException | CertificateException | CertPathValidatorException | InvalidAlgorithmParameterException e) { + logger.info("connection to {} is NOT trusted, falling back to dialback", connection.to); + } + }); + sslSocket.setNeedClientAuth(true); + sslSocket.startHandshake(); connection.setSecured(true); - logger.debug("stream {} secured", connection.getStreamID()); + logger.debug("stream to {} secured", connection.getStreamID()); connection.setInputStream(socket.getInputStream()); connection.setOutputStream(socket.getOutputStream()); connection.restartStream(); diff --git a/juick-server/src/main/java/com/juick/server/configuration/ApiAppConfiguration.java b/juick-server/src/main/java/com/juick/server/configuration/ApiAppConfiguration.java index 91f5446a..001f72fe 100644 --- a/juick-server/src/main/java/com/juick/server/configuration/ApiAppConfiguration.java +++ b/juick-server/src/main/java/com/juick/server/configuration/ApiAppConfiguration.java @@ -49,6 +49,7 @@ import springfox.documentation.swagger2.annotations.EnableSwagger2; import javax.annotation.Nonnull; import javax.inject.Inject; +import java.time.Duration; import java.util.Collections; /** @@ -104,6 +105,7 @@ public class ApiAppConfiguration implements WebMvcConfigurer, WebSocketConfigure XmppSessionConfiguration configuration = XmppSessionConfiguration.builder() .extensions(Extension.of(com.juick.Message.class), Extension.of(MessageQuery.class)) .debugger(LogbackDebugger.class) + .defaultResponseTimeout(Duration.ofMillis(120000)) .build(); return BasicXmppSession.create(hostname, configuration); } diff --git a/juick-server/src/main/java/com/juick/server/xmpp/s2s/Connection.java b/juick-server/src/main/java/com/juick/server/xmpp/s2s/Connection.java index 6bf61169..4fa8e741 100644 --- a/juick-server/src/main/java/com/juick/server/xmpp/s2s/Connection.java +++ b/juick-server/src/main/java/com/juick/server/xmpp/s2s/Connection.java @@ -50,11 +50,14 @@ public class Connection { private Socket socket; public static final String NS_DB = "jabber:server:dialback"; public static final String NS_TLS = "urn:ietf:params:xml:ns:xmpp-tls"; + public static final String NS_SASL = "urn:ietf:params:xml:ns:xmpp-sasl"; public static final String NS_STREAM = "http://etherx.jabber.org/streams"; XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); XmlPullParser parser = factory.newPullParser(); OutputStreamWriter writer; private boolean secured = false; + private boolean authenticated = false; + private boolean trusted = false; @@ -136,4 +139,20 @@ public class Connection { public void setSocket(Socket socket) { this.socket = socket; } + + public boolean isAuthenticated() { + return authenticated; + } + + public void setAuthenticated(boolean authenticated) { + this.authenticated = authenticated; + } + + public boolean isTrusted() { + return trusted; + } + + public void setTrusted(boolean trusted) { + this.trusted = trusted; + } } diff --git a/juick-server/src/main/java/com/juick/server/xmpp/s2s/ConnectionIn.java b/juick-server/src/main/java/com/juick/server/xmpp/s2s/ConnectionIn.java index 9ee81d4d..414c6d8b 100644 --- a/juick-server/src/main/java/com/juick/server/xmpp/s2s/ConnectionIn.java +++ b/juick-server/src/main/java/com/juick/server/xmpp/s2s/ConnectionIn.java @@ -34,6 +34,7 @@ import java.util.Arrays; import java.util.List; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; /** * @author ugnich @@ -118,11 +119,12 @@ public class ConnectionIn extends Connection implements Runnable { if (valid[0]) { sendStanza(""); logger.debug("stream from {} {} dialback verify valid", vfrom, streamID); + setAuthenticated(true); } else { sendStanza(""); logger.warn("stream from {} {} dialback verify invalid", vfrom, streamID); } - } else if (tag.equals("presence") && checkFromTo(parser)) { + } else if (tag.equals("presence") && checkFromTo(parser) && isAuthenticated()) { String xml = XmlUtils.parseToString(parser, false); logger.debug("stream {} presence: {}", streamID, xml); xmpp.onStanzaReceived(xml); @@ -132,7 +134,7 @@ public class ConnectionIn extends Connection implements Runnable { logger.debug("stream {} message: {}", streamID, xml); xmpp.onStanzaReceived(xml); - } else if (tag.equals("iq") && checkFromTo(parser)) { + } else if (tag.equals("iq") && checkFromTo(parser) && isAuthenticated()) { updateTsRemoteData(); String type = parser.getAttributeValue(null, "type"); String xml = XmlUtils.parseToString(parser, false); @@ -140,10 +142,18 @@ public class ConnectionIn extends Connection implements Runnable { logger.debug("stream {} iq: {}", streamID, xml); xmpp.onStanzaReceived(xml); } - } else if (!isSecured() && tag.equals("starttls")) { + } else if (!isSecured() && tag.equals("starttls") && !isAuthenticated()) { listener.starttls(this); } else if (isSecured() && tag.equals("stream") && parser.getNamespace().equals(NS_STREAM)) { sendOpenStream(null, true); + } else if (isSecured() && tag.equals("auth") && parser.getNamespace().equals(NS_SASL) + && parser.getAttributeValue(null, "mechanism").equals("EXTERNAL") + && !isAuthenticated() && isTrusted()) { + sendStanza(""); + logger.info("stream {} authenticated externally", streamID); + this.from.add(Jid.of(from)); + setAuthenticated(true); + restartParser(); } else if (tag.equals("error")) { StreamError streamError = StreamError.parse(parser); logger.debug("Stream error from {}: {}", streamID, streamError.getCondition()); @@ -178,8 +188,14 @@ public class ConnectionIn extends Connection implements Runnable { xmpp.getJid().toEscapedString() + "' id='" + streamID + "' version='1.0'>"; if (xmppversionnew) { openStream += ""; - if (listener != null && listener.isTlsAvailable() && !isSecured() && !Arrays.asList(xmpp.brokenSSLhosts).contains(from)) { - openStream += ""; + if (listener != null && listener.isTlsAvailable() && !Arrays.asList(xmpp.brokenSSLhosts).contains(from)) { + if (!isSecured()) { + openStream += ""; + } else if (!isAuthenticated() && isTrusted()) { + openStream += "" + + "EXTERNAL" + + ""; + } } openStream += ""; } @@ -205,6 +221,7 @@ public class ConnectionIn extends Connection implements Runnable { } } } + logger.warn("rejected from {}, to {}, stream {}", cfrom, cto, from.stream().collect(Collectors.joining(","))); return false; } public void setListener(ConnectionListener listener) { diff --git a/juick-server/src/main/java/com/juick/server/xmpp/s2s/ConnectionOut.java b/juick-server/src/main/java/com/juick/server/xmpp/s2s/ConnectionOut.java index e3bd53e9..0c991553 100644 --- a/juick-server/src/main/java/com/juick/server/xmpp/s2s/ConnectionOut.java +++ b/juick-server/src/main/java/com/juick/server/xmpp/s2s/ConnectionOut.java @@ -22,6 +22,8 @@ import com.juick.xmpp.Stream; import com.juick.xmpp.extensions.StreamError; import com.juick.xmpp.extensions.StreamFeatures; import com.juick.xmpp.utils.XmlUtils; +import org.apache.commons.codec.Charsets; +import org.apache.commons.codec.binary.Base64; import org.apache.commons.text.RandomStringGenerator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +37,8 @@ import java.io.OutputStream; import java.net.SocketException; import java.util.UUID; +import static com.juick.server.xmpp.s2s.Connection.NS_SASL; + /** * @author ugnich */ @@ -43,7 +47,7 @@ public class ConnectionOut extends Stream { public static final String NS_TLS = "urn:ietf:params:xml:ns:xmpp-tls"; public static final String NS_DB = "jabber:server:dialback"; private boolean secured = false; - + private boolean trusted = false; public boolean streamReady = false; String checkSID = null; String dbKey = null; @@ -122,18 +126,27 @@ public class ConnectionOut extends Stream { && listener.securing(this)) { logger.debug("stream to {} {} securing", to.toEscapedString(), streamID); send(""); + } else if (secured && features.EXTERNAL >=0) { + String authid = Base64.encodeBase64String(from.toEscapedString().getBytes(Charsets.UTF_8)); + send(String.format("%s", authid)); + } else if (secured && streamReady) { + listener.ready(this); } else { processDialback(); } } else if (tag.equals("proceed") && parser.getNamespace().equals(NS_TLS)) { listener.proceed(this); + } else if (tag.equals("success") && parser.getNamespace().equals(NS_SASL)) { + streamReady = true; + restartStream(); + sendOpenStream(); } else if (secured && tag.equals("stream") && parser.getNamespace().equals(NS_STREAM)) { streamID = parser.getAttributeValue(null, "id"); } else if (tag.equals("error")) { StreamError streamError = StreamError.parse(parser); listener.dialbackError(this, streamError); } else { - String unhandledStanza = XmlUtils.parseToString(parser, true); + String unhandledStanza = XmlUtils.parseToString(parser, false); logger.warn("Unhandled stanza from {} {} : {}", to, streamID, unhandledStanza); } } @@ -164,4 +177,12 @@ public class ConnectionOut extends Stream { public void setSecured(boolean secured) { this.secured = secured; } + + public boolean isTrusted() { + return trusted; + } + + public void setTrusted(boolean trusted) { + this.trusted = trusted; + } } -- cgit v1.2.3