aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2018-01-24 14:04:32 +0300
committerGravatar Vitaly Takmazov2018-07-30 12:36:02 +0300
commitfbb662365a064889da25ce2c705568ca31f27af1 (patch)
tree315479dd7bfe9ea4420018363f2fc7daaca1ad05
parentc1e6e9dd484beb02dbd22f1b5ee3f86e8ca4aa0a (diff)
xmpp: SASL EXTERNAL
-rw-r--r--juick-server/src/main/java/com/juick/server/XMPPServer.java52
-rw-r--r--juick-server/src/main/java/com/juick/server/configuration/ApiAppConfiguration.java2
-rw-r--r--juick-server/src/main/java/com/juick/server/xmpp/s2s/Connection.java19
-rw-r--r--juick-server/src/main/java/com/juick/server/xmpp/s2s/ConnectionIn.java27
-rw-r--r--juick-server/src/main/java/com/juick/server/xmpp/s2s/ConnectionOut.java25
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<TrustAnchor> 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("<db:verify from='" + vto + "' to='" + vfrom + "' id='" + vid + "' type='valid'/>");
logger.debug("stream from {} {} dialback verify valid", vfrom, streamID);
+ setAuthenticated(true);
} else {
sendStanza("<db:verify from='" + vto + "' to='" + vfrom + "' id='" + vid + "' type='invalid'/>");
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("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>");
+ 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 += "<stream:features>";
- if (listener != null && listener.isTlsAvailable() && !isSecured() && !Arrays.asList(xmpp.brokenSSLhosts).contains(from)) {
- openStream += "<starttls xmlns=\"" + NS_TLS + "\"><optional/></starttls>";
+ if (listener != null && listener.isTlsAvailable() && !Arrays.asList(xmpp.brokenSSLhosts).contains(from)) {
+ if (!isSecured()) {
+ openStream += "<starttls xmlns='" + NS_TLS + "'><optional/></starttls>";
+ } else if (!isAuthenticated() && isTrusted()) {
+ openStream += "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
+ "<mechanism>EXTERNAL</mechanism>" +
+ "</mechanisms>";
+ }
}
openStream += "</stream:features>";
}
@@ -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("<starttls xmlns=\"" + NS_TLS + "\" />");
+ } else if (secured && features.EXTERNAL >=0) {
+ String authid = Base64.encodeBase64String(from.toEscapedString().getBytes(Charsets.UTF_8));
+ send(String.format("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='EXTERNAL'>%s</auth>", 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;
+ }
}