I’m working on a Java Server that is using the Smack 4.1.0 API as a XMPP Client to connect to Google Cloud Messaging Cloud Connection Server (GCM CCS) in order to send messages to an Android app. I started with this example (https://developer.android.com/google/gcm/ccs.html), but as the Smack API has changed I adapted the code accordingly. By now my Smack Client is connecting successfully to GCM CCS, sends messages and receives ack/nack/control responses.
Unfortunately, the connection is only working properly, if I specify XMPPTCPConnectionConfiguration.Builder.setSecurityMode(SecurityMode.ifpossible) or (SecurityMode.disabled). When doing that XMPPTCPConnection.isSecureConnection() returns false. See the (relevant) code below:
static final String GCM_SERVER = "gcm.googleapis.com";
static final int GCM_PORT = 5235;
private XMPPTCPConnection connection;
private SSLContext sslCtx;
...
try {
KeyStore windowsRootTruststore = KeyStore.getInstance("Windows-ROOT", "SunMSCAPI");
windowsRootTruststore.load(null, null);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(windowsRootTruststore);
sslCtx = SSLContext.getInstance("TLS");
sslCtx.init(null, tmf.getTrustManagers(), null);
} catch (KeyStoreException | NoSuchProviderException | NoSuchAlgorithmException
| KeyManagementException | CertificateException e) {
e.printStackTrace();
}
...
XMPPTCPConnectionConfiguration config = XMPPTCPConnectionConfiguration.builder()
.setHost(GCM_SERVER)
.setPort(GCM_PORT)
.setServiceName(GCM_SERVER)
.setUsernameAndPassword(GCM_SENDER_ID + "@gcm.googleapis.com", GCM_PASSWORD)
.setCompressionEnabled(false)
.setSecurityMode(SecurityMode.ifpossible)
.setSendPresence(false)
.setSocketFactory(sslCtx.getSocketFactory())
.build();
connection = new XMPPTCPConnection(config);
Roster roster = Roster.getInstanceFor(connection);
roster.setRosterLoadedAtLogin(false);
connection.addConnectionListener(this);
connection.addAsyncStanzaListener(this, new StanzaTypeFilter(Message.class));
connection.addPacketInterceptor(new StanzaListener() {
@Override
public void processPacket(Stanza packet) throws NotConnectedException {
System.out.println("CCS_Client sent the following message: " + packet.toXML());
}
}, new StanzaTypeFilter(Message.class));
connection.connect();
connection.login();
System.out.println(connection.isSecureConnection());
According to Google “The connection has two important requirements: 1) You must initiate a Transport Layer Security (TLS) connection. …” (see link above). This sounds to me like a non-TLS encrypted connection would be refused. My problem comes into play with SecurityMode.required. When using that, Smack throws the following error code:
org.jivesoftware.smack.SmackException$SecurityRequiredByClientException: SSL/TLS required by client but not supported by server
at org.jivesoftware.smack.tcp.XMPPTCPConnection.afterFeaturesReceived(XMPPTCPConnection.java:898)
at org.jivesoftware.smack.AbstractXMPPConnection.parseFeatures(AbstractXMPPConnection.java:1367)
at org.jivesoftware.smack.tcp.XMPPTCPConnection.access$800(XMPPTCPConnection.java:139)
at org.jivesoftware.smack.tcp.XMPPTCPConnection$PacketReader.parsePackets(XMPPTCPConnection.java:998)
at org.jivesoftware.smack.tcp.XMPPTCPConnection$PacketReader.access$200(XMPPTCPConnection.java:937)
at org.jivesoftware.smack.tcp.XMPPTCPConnection$PacketReader$1.run(XMPPTCPConnection.java:952)
at java.lang.Thread.run(Thread.java:744)
I have been trying for two days to figure out why I can’t establish a SecurityMode.required connection but failed.
Using the same SSLContext/SSLSocketFactory as above, it works fine, if I connect to GCM CCS without the Smack API, just opening a TLS encrypted connection. In line with Google’s comment above, when passing a regular SocketFactory (not SSLSocketFactory) to the XMPPTCPConnectionConfiguration, the connection cannot be established:
org.jivesoftware.smack.SmackException$NoResponseException: No response received within reply timeout
So I am guessing (correct me if I’m wrong) that GCM CCS is indeed only accepting TLS connections. But if that is the case, why is my SecurityMode.required connection attempt rejected with “SSL/TLS required by client but not supported by server”?
I am also wondering if SecurityMode.disabled/SecurityMode.ifpossible is actually successfully establishing a TLS connection but isSecureConnection() is returning false nonetheless? Is that possible at all? In order to test this hypothesis, I wanted to test the underlying SSLsocket that is created within Smack (with SSLSocket.getSession().getCipherSuite() and getProtocol() after the completed Handshake). In order to do that, I was trying to pass a custom SSLSocketFactory which produces a custom SSLSocket (which would just output the CipherSuite and Protocol after the completed Handshake) to XMPPTCPConnectionConfiguration. But I can’t seem to get this working either.
How do I get a connection to GCM CCS with SecurityMode.required established, for which isSecureConnection() returns true?
Any help would be appreciated!