使用Smack 4.1.0 API作为Google GCM CCS的XMPP客户端时,SecurityMode.required无法正常工作。

3
我正在开发一个Java服务器,使用Smack 4.1.0 API作为XMPP客户端连接到Google云消息传递云连接服务器(GCM CCS),以向Android应用程序发送消息。我从此示例开始(https://developer.android.com/google/gcm/ccs.html),但是由于Smack API已更改,因此我相应地适应了代码。现在,我的Smack客户端成功连接到GCM CCS,发送消息并接收确认/拒绝/控制响应。
不幸的是,只有当我指定XMPPTCPConnectionConfiguration.Builder.setSecurityMode(SecurityMode.ifpossible)或(SecurityMode.disabled)时,连接才能正常工作。这样做时,XMPPTCPConnection.isSecureConnection()返回false。请参见下面的(相关)代码:
    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());

根据Google的说法,“连接具有两个重要要求:1)必须启动传输层安全性(TLS)连接……”(请参见上面的链接)。这让我觉得非TLS加密连接会被拒绝。我的问题出现在使用SecurityMode.required时。当使用该模式时,Smack会抛出以下错误代码:
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)

我已经试了两天,想弄清为什么我无法建立一个SecurityMode.required的连接,但失败了。

使用上述相同的SSLContext/SSLSocketFactory,如果我连接到GCM CCS而没有Smack API,只是打开一个TLS加密的连接,它可以正常工作。根据Google的评论,当向XMPPTCPConnectionConfiguration传递一个常规SocketFactory(不是SSLSocketFactory)时,无法建立连接:

org.jivesoftware.smack.SmackException$NoResponseException: No response received within reply timeout

我猜测(如有错请纠正),GCM CCS只接受TLS连接。但如果是这样,为什么我的SecurityMode.required连接尝试被拒绝,并显示“客户端需要SSL/TLS但服务器不支持”?
我也想知道是否成功建立了TLS连接,但isSecureConnection()仍然返回false的情况下,SecurityMode.disabled/SecurityMode.ifpossible是否真实存在?这种可能性是否存在?为了测试这个假设,我想测试在Smack中创建的底层SSLsocket(使用完成握手后的SSLSocket.getSession().getCipherSuite()和getProtocol())。为此,我试图将自定义SSLSocketFactory传递给XMPPTCPConnectionConfiguration,以生成自定义SSLSocket(它只会在完成握手后输出CipherSuite和Protocol)。但我似乎也无法让它起作用。
如何使用SecurityMode.required与GCM CCS建立安全的连接,并使isSecureConnection()返回true?
非常感谢您的帮助!

也许Truststore可以解决你的问题,我不确定gcm css是否能够正常工作,但是使用xmpp服务器[openfire]它可以正常工作!!! - Dev
1
感谢您的建议。如您在代码片段中所见,我已经使用了一个信任存储库(“Windows-ROOT”)来提供所需的信任锚点。我检查了生成的TrustManagers以查看是否有问题。但是它们正确地“提取”了信任材料。我还使用默认的Java信任存储库测试了整个过程。这两个信任存储库都可以与GCM CCS的常规TLS连接正常工作。因此,我猜测信任存储库应该按预期工作。当使用任一信任存储库时,SecurityMode.required连接的Smack错误代码保持不变。 - Skinner
2个回答

0

我也遇到了Smack 4.1.6的同样问题。

  1. SecurityMode不能设置为requiredhttps://community.igniterealtime.org/thread/55808)。

  2. 在创建用户名时,必须使用项目编号而不是项目ID(可以在https://console.cloud.google.com中找到)。

  3. 这是我的愚蠢错误,但仍然需要注意。当为您的项目创建API密钥时,请仔细选择平台:由于某种神秘的原因,Android API密钥无法用于普通Java应用程序(哈哈)。

  4. 似乎服务名称可以是任何内容,只要不是null即可。


0

我也遇到了同样的问题,我对这个错误感到困惑的是:

https://groups.google.com/forum/#!topic/android-gcm/5mA7FImpTGo

这意味着 SSL 会话缓存出现问题(被 Google 禁用)。我在服务器端(我的一侧)尝试禁用它,但没有成功。

另一方面,您可能会看到更多类似于这样的内容:

org.jivesoftware.smack.roster.Roster     : Exception reloading roster
org.jivesoftware.smack.SmackException$NoResponseException: No response received within reply timeout. Timeout was 5000ms (~5s).

如果您能看到这里的问题: https://jira.spring.io/browse/INT-3972 我现在的解决方法是: https://github.com/puel/training/blob/master/gcm/gcm-server/src/main/java/org/sterl/gcm/_example/server/config/GcmConfig.java

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接