6
votes

I'm trying to connect my users via SSL from my iOS XMPP chat client to Openfire server.

In my iOS client:

- (void)setupStream 
{
    ...
    // BOOL values for security settings
    customCertEvaluation = NO;
    allowSelfSignedCertificates = YES;
    allowSSLHostNameMismatch = NO;
}

In my Openfire server's Security Settings > Client Connection Security, I've set:

Required - Clients can only connect to the server using secured connections.

Thus, the following delegate method will be called:

- (void)xmppStream:(XMPPStream *)sender willSecureWithSettings:(NSMutableDictionary *)settings 
{
    NSString *expectedCertName = [xmppStream.myJID domain];

    if (customCertEvaluation)
        [settings setObject:@(YES) forKey:GCDAsyncSocketManuallyEvaluateTrust];

    if (allowSelfSignedCertificates)
        [settings setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCFStreamSSLAllowsAnyRoot];

    if (allowSSLHostNameMismatch)
        [settings setObject:[NSNull null] forKey:(NSString *)kCFStreamSSLPeerName];

    else
        if (expectedCertName)
            [settings setObject:expectedCertName forKey:(NSString *)kCFStreamSSLPeerName];
}

I attempted this solution from this thread: XMPPFramework TLS/SSL connection with Openfire

However, when I run my application and attempt to connect to the server, I'd receive this error:

Security option unavailable - kCFStreamSSLAllowsAnyRoot - You must use manual trust evaluation

I looked through the GCDAsyncSocket class and realized kCFStreamSSLAllowsAnyRoot is stated as deprecated. An NSAssert was implemented to deliberately throw the error.

Next, I decided to change my BOOL values as such:

- (void)setupStream 
{
    ...
    // BOOL values for security settings
    // Manually evaluate trust
    customCertEvaluation = YES;
    allowSelfSignedCertificates = NO;
    allowSSLHostNameMismatch = NO;
}

This time, again, no connection could be made to the server but, no error was prompted.

I could connect to Openfire fine if I changed the Client Connection Security back to the original setting > Optional. But, I wouldn't be connected via SSL as indicated by a lock icon beside every user's status in Client Sessions.

My Android client (using Smack API for XMPP) connects to Openfire via SSL without issues. So I'm wondering if there's workaround I have to implement for my iOS client using XMPPFramework.

I would greatly appreciate any advices.

4

4 Answers

8
votes

Explanation

In the latest version of XMPP (after April 22), you can no longer use allowSelfSignedCertificates = YES with the following:

if (allowSelfSignedCertificates)
    [settings setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCFStreamSSLAllowsAnyRoot];`

This is because kCFStreamSSLAllowsAnyRoot & SSLSetAllowsAnyRoot have been deprecated.

 /* 
  * ==== The following UNAVAILABLE KEYS are: (with throw an exception)
  * - kCFStreamSSLAllowsAnyRoot (UNAVAILABLE)
  *     You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust).
  *     Corresponding deprecated method: SSLSetAllowsAnyRoot
  */

See XMPPFramework/GCDAsyncSocket.h & Deprecated Secure Transport Functions.


Solution

  1. Go to Openfire server > Security Settings > Client Connection Security

    Check: Required - Clients can only connect to the server using secured connections.

  2. Define variable in AppDelegate

    BOOL customCertEvaluation;
    
  3. Set variable in setupStream

    - (void)setupStream 
    {
        ...
        customCertEvaluation = YES;
    }
    
  4. Set security settings in willSecureWithSettings

    - (void)xmppStream:(XMPPStream *)sender willSecureWithSettings:(NSMutableDictionary *)settings
    {
        /*
         * Properly secure your connection by setting kCFStreamSSLPeerName 
         * to your server domain name
         */
        [settings setObject:xmppStream.myJID.domain forKey:(NSString *)kCFStreamSSLPeerName];
    
        /*
         * Use manual trust evaluation
         * as stated in the XMPPFramework/GCDAsyncSocket code documentation
         */
        if (customCertEvaluation)
            [settings setObject:@(YES) forKey:GCDAsyncSocketManuallyEvaluateTrust];
    }
    
  5. Validate peer manually

    /*
     * This is only called if the stream is secured with settings that include:
     * - GCDAsyncSocketManuallyEvaluateTrust == YES
     * That is, if a delegate implements xmppStream:willSecureWithSettings:, and plugs in that key/value pair.
     */
     - (void)xmppStream:(XMPPStream *)sender didReceiveTrust:(SecTrustRef)trust completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler
     {
         /* Custom validation for your certificate on server should be performed */
    
         completionHandler(YES); // After this line, SSL connection will be established
     }
    
1
votes

I was having the same issue, after i updated my XMPPFramework. After days of trying to find out what went wrong i came across this question, but the solution didn't work for me.

Here is what worked for me. The problem seems to originate from your xmppStream.startTLSPolicy. Setting startTLSPolicy explicitly worked for me.

xmppStream.startTLSPolicy = XMPPStreamStartTLSPolicyPreferred; // or
xmppStream.startTLSPolicy = XMPPStreamStartTLSPolicyRequired;

Here is an EXPLANATION of why it works.

In XMPPStream's handleStreamFeatures method, it turns out that. If your XMPP Server doesn't return starttls as 'required' and you don't set startTLSPolicy(default=XMPPStreamStartTLSPolicyAllowed) explicitly. The client will just do a normal connection and not a TLS one.

Here is section of code(for reference) in XMPPStream that is doing the checks.

/**
 * This method is called anytime we receive the server's stream features.
 * This method looks at the stream features, and handles any requirements so communication can continue.
**/
- (void)handleStreamFeatures
{
    NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue");

    XMPPLogTrace();

    // Extract the stream features
    NSXMLElement *features = [rootElement elementForName:@"stream:features"];

    // Check to see if TLS is required
    // Don't forget about that NSXMLElement bug you reported to apple (xmlns is required or element won't be found)
    NSXMLElement *f_starttls = [features elementForName:@"starttls" xmlns:@"urn:ietf:params:xml:ns:xmpp-tls"];

    if (f_starttls)
    {
        if ([f_starttls elementForName:@"required"] || [self startTLSPolicy] >= XMPPStreamStartTLSPolicyPreferred)
        {
            // TLS is required for this connection

            // Update state
            state = STATE_XMPP_STARTTLS_1;

            // Send the startTLS XML request
            [self sendStartTLSRequest];

            // We do not mark the stream as secure yet.
            // We're waiting to receive the <proceed/> response from the
            // server before we actually start the TLS handshake.

            // We're already listening for the response...
            return;
        }
    }
    else if (![self isSecure] && [self startTLSPolicy] == XMPPStreamStartTLSPolicyRequired)
    {
        // We must abort the connection as the server doesn't support our requirements.

        NSString *errMsg = @"The server does not support startTLS. And the startTLSPolicy is Required.";
        NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];

        otherError = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamUnsupportedAction userInfo:info];

        // Close the TCP connection.
        [self disconnect];

        // The socketDidDisconnect:withError: method will handle everything else
        return;
    }

    // Check to see if resource binding is required
    // Don't forget about that NSXMLElement bug you reported to apple (xmlns is required or element won't be found)
    NSXMLElement *f_bind = [features elementForName:@"bind" xmlns:@"urn:ietf:params:xml:ns:xmpp-bind"];

    if (f_bind)
    {
        // Start the binding process
        [self startBinding];

        // We're already listening for the response...
        return;
    }

    // It looks like all has gone well, and the connection should be ready to use now
    state = STATE_XMPP_CONNECTED;

    if (![self isAuthenticated])
    {
        [self setupKeepAliveTimer];

        // Notify delegates
        [multicastDelegate xmppStreamDidConnect:self];
    }
}
0
votes

You are trying to use outdated API, check iPhoneXMPP sample for the new one - https://github.com/robbiehanson/XMPPFramework/commit/73f3c35a930b91d27e62bc19e91d9cdcc02c6e42

0
votes
customCertEvaluation = YES;
allowSelfSignedCertificates = YES;
allowSSLHostNameMismatch = NO;  

try these this might help