This commit is contained in:
giuseppenuc committed 2016-02-24 16:56:39 +01:00
1 parent d281d765ea
commit 5b074a5176
1261 files changed
+199158 -7303

No files matched your search

@@ -0,0 +1,45 @@
#import <Foundation/Foundation.h>
#import "XMPPSASLAuthentication.h"
#import "XMPP.h"
@interface XMPPAnonymousAuthentication : NSObject <XMPPSASLAuthentication>
- (id)initWithStream:(XMPPStream *)stream;
// This class implements the XMPPSASLAuthentication protocol.
//
// See XMPPSASLAuthentication.h for more information.
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface XMPPStream (XMPPAnonymousAuthentication)
/**
* Returns whether or not the server support anonymous authentication.
*
* This information is available after the stream is connected.
* In other words, after the delegate has received xmppStreamDidConnect: notification.
**/
- (BOOL)supportsAnonymousAuthentication;
/**
* This method attempts to start the anonymous authentication process.
*
* This method is asynchronous.
*
* If there is something immediately wrong,
* such as the stream is not connected or doesn't support anonymous authentication,
* the method will return NO and set the error.
* Otherwise the delegate callbacks are used to communicate auth success or failure.
*
* @see xmppStreamDidAuthenticate:
* @see xmppStream:didNotAuthenticate:
**/
- (BOOL)authenticateAnonymously:(NSError **)errPtr;
@end
@@ -0,0 +1,131 @@
#import "XMPPAnonymousAuthentication.h"
#import "XMPP.h"
#import "XMPPLogging.h"
#import "XMPPInternal.h"
#import "NSXMLElement+XMPP.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
// Log levels: off, error, warn, info, verbose
#if DEBUG
static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO; // | XMPP_LOG_FLAG_TRACE;
#else
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
#endif
/**
* Seeing a return statements within an inner block
* can sometimes be mistaken for a return point of the enclosing method.
* This makes inline blocks a bit easier to read.
**/
#define return_from_block return
@implementation XMPPAnonymousAuthentication
{
#if __has_feature(objc_arc_weak)
__weak XMPPStream *xmppStream;
#else
__unsafe_unretained XMPPStream *xmppStream;
#endif
}
+ (NSString *)mechanismName
{
return @"ANONYMOUS";
}
- (id)initWithStream:(XMPPStream *)stream
{
if ((self = [super init]))
{
xmppStream = stream;
}
return self;
}
- (id)initWithStream:(XMPPStream *)stream password:(NSString *)password
{
return [self initWithStream:stream];
}
- (BOOL)start:(NSError **)errPtr
{
// <auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="ANONYMOUS" />
NSXMLElement *auth = [NSXMLElement elementWithName:@"auth" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
[auth addAttributeWithName:@"mechanism" stringValue:@"ANONYMOUS"];
[xmppStream sendAuthElement:auth];
return YES;
}
- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse
{
// We're expecting a success response.
// If we get anything else we can safely assume it's the equivalent of a failure response.
if ([[authResponse name] isEqualToString:@"success"])
{
return XMPP_AUTH_SUCCESS;
}
else
{
return XMPP_AUTH_FAIL;
}
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPStream (XMPPAnonymousAuthentication)
- (BOOL)supportsAnonymousAuthentication
{
return [self supportsAuthenticationMechanism:[XMPPAnonymousAuthentication mechanismName]];
}
- (BOOL)authenticateAnonymously:(NSError **)errPtr
{
XMPPLogTrace();
__block BOOL result = YES;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
if ([self supportsAnonymousAuthentication])
{
XMPPAnonymousAuthentication *anonymousAuth = [[XMPPAnonymousAuthentication alloc] initWithStream:self];
result = [self authenticate:anonymousAuth error:&err];
}
else
{
NSString *errMsg = @"The server does not support anonymous authentication.";
NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg};
err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamUnsupportedAction userInfo:info];
result = NO;
}
}};
if (dispatch_get_specific(self.xmppQueueTag))
block();
else
dispatch_sync(self.xmppQueue, block);
if (errPtr)
*errPtr = err;
return result;
}
@end
@@ -0,0 +1,22 @@
#import <Foundation/Foundation.h>
#import "XMPPSASLAuthentication.h"
#import "XMPPStream.h"
@interface XMPPDeprecatedDigestAuthentication : NSObject <XMPPSASLAuthentication>
// This class implements the XMPPSASLAuthentication protocol.
//
// See XMPPSASLAuthentication.h for more information.
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface XMPPStream (XMPPDeprecatedDigestAuthentication)
- (BOOL)supportsDeprecatedDigestAuthentication;
@end
@@ -0,0 +1,162 @@
#import "XMPPDeprecatedDigestAuthentication.h"
#import "XMPP.h"
#import "XMPPInternal.h"
#import "XMPPLogging.h"
#import "NSData+XMPP.h"
#import "NSXMLElement+XMPP.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
// Log levels: off, error, warn, info, verbose
#if DEBUG
static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO; // | XMPP_LOG_FLAG_TRACE;
#else
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
#endif
@implementation XMPPDeprecatedDigestAuthentication
{
#if __has_feature(objc_arc_weak)
__weak XMPPStream *xmppStream;
#else
__unsafe_unretained XMPPStream *xmppStream;
#endif
NSString *password;
}
+ (NSString *)mechanismName
{
// This deprecated method isn't listed in the normal mechanisms list
return nil;
}
- (id)initWithStream:(XMPPStream *)stream password:(NSString *)inPassword
{
if ((self = [super init]))
{
xmppStream = stream;
password = inPassword;
}
return self;
}
- (BOOL)start:(NSError **)errPtr
{
XMPPLogTrace();
// The server does not appear to support SASL authentication (at least any type we can use)
// So we'll revert back to the old fashioned jabber:iq:auth mechanism
XMPPJID *myJID = xmppStream.myJID;
NSString *username = [myJID user];
NSString *resource = [myJID resource];
if ([resource length] == 0)
{
// If resource is nil or empty, we need to auto-create one
resource = [XMPPStream generateUUID];
}
NSString *rootID = [[[xmppStream rootElement] attributeForName:@"id"] stringValue];
NSString *digestStr = [NSString stringWithFormat:@"%@%@", rootID, password];
NSString *digest = [[[digestStr dataUsingEncoding:NSUTF8StringEncoding] xmpp_sha1Digest] xmpp_hexStringValue];
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:auth"];
[query addChild:[NSXMLElement elementWithName:@"username" stringValue:username]];
[query addChild:[NSXMLElement elementWithName:@"resource" stringValue:resource]];
[query addChild:[NSXMLElement elementWithName:@"digest" stringValue:digest]];
XMPPIQ *iq = [XMPPIQ iqWithType:@"set"];
[iq addChild:query];
[xmppStream sendAuthElement:iq];
return YES;
}
- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse
{
XMPPLogTrace();
// We used the old fashioned jabber:iq:auth mechanism
if ([[authResponse attributeStringValueForName:@"type"] isEqualToString:@"error"])
{
return XMPP_AUTH_FAIL;
}
else
{
return XMPP_AUTH_SUCCESS;
}
}
- (BOOL)shouldResendOpeningNegotiationAfterSuccessfulAuthentication
{
return NO;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPStream (XMPPDeprecatedDigestAuthentication)
/**
* This method only applies to servers that don't support XMPP version 1.0, as defined in RFC 3920.
* With these servers, we attempt to discover supported authentication modes via the jabber:iq:auth namespace.
**/
- (BOOL)supportsDeprecatedDigestAuthentication
{
__block BOOL result = NO;
dispatch_block_t block = ^{ @autoreleasepool {
// The root element can be properly queried for authentication mechanisms anytime after the
// stream:features are received, and TLS has been setup (if required)
if (self.state >= STATE_XMPP_POST_NEGOTIATION)
{
// Search for an iq element within the rootElement.
// Recall that some servers might stupidly add a "jabber:client" namespace which might cause problems
// if we simply used the elementForName method.
NSXMLElement *iq = nil;
NSUInteger i, count = [self.rootElement childCount];
for (i = 0; i < count; i++)
{
NSXMLNode *childNode = [self.rootElement childAtIndex:i];
if ([childNode kind] == NSXMLElementKind)
{
if ([[childNode name] isEqualToString:@"iq"])
{
iq = (NSXMLElement *)childNode;
}
}
}
NSXMLElement *query = [iq elementForName:@"query" xmlns:@"jabber:iq:auth"];
NSXMLElement *digest = [query elementForName:@"digest"];
result = (digest != nil);
}
}};
if (dispatch_get_specific(self.xmppQueueTag))
block();
else
dispatch_sync(self.xmppQueue, block);
return result;
}
@end
@@ -0,0 +1,22 @@
#import <Foundation/Foundation.h>
#import "XMPPSASLAuthentication.h"
#import "XMPPStream.h"
@interface XMPPDeprecatedPlainAuthentication : NSObject <XMPPSASLAuthentication>
// This class implements the XMPPSASLAuthentication protocol.
//
// See XMPPSASLAuthentication.h for more information.
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface XMPPStream (XMPPDeprecatedPlainAuthentication)
- (BOOL)supportsDeprecatedPlainAuthentication;
@end
@@ -0,0 +1,156 @@
#import "XMPPDeprecatedPlainAuthentication.h"
#import "XMPP.h"
#import "XMPPInternal.h"
#import "XMPPLogging.h"
#import "NSXMLElement+XMPP.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
// Log levels: off, error, warn, info, verbose
#if DEBUG
static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO; // | XMPP_LOG_FLAG_TRACE;
#else
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
#endif
@implementation XMPPDeprecatedPlainAuthentication
{
#if __has_feature(objc_arc_weak)
__weak XMPPStream *xmppStream;
#else
__unsafe_unretained XMPPStream *xmppStream;
#endif
NSString *password;
}
+ (NSString *)mechanismName
{
// This deprecated method isn't listed in the normal mechanisms list
return nil;
}
- (id)initWithStream:(XMPPStream *)stream password:(NSString *)inPassword
{
if ((self = [super init]))
{
xmppStream = stream;
password = inPassword;
}
return self;
}
- (BOOL)start:(NSError **)errPtr
{
XMPPLogTrace();
// The server does not appear to support SASL authentication (at least any type we can use)
// So we'll revert back to the old fashioned jabber:iq:auth mechanism
XMPPJID *myJID = xmppStream.myJID;
NSString *username = [myJID user];
NSString *resource = [myJID resource];
if ([resource length] == 0)
{
// If resource is nil or empty, we need to auto-create one
resource = [XMPPStream generateUUID];
}
NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:auth"];
[query addChild:[NSXMLElement elementWithName:@"username" stringValue:username]];
[query addChild:[NSXMLElement elementWithName:@"resource" stringValue:resource]];
[query addChild:[NSXMLElement elementWithName:@"password" stringValue:password]];
XMPPIQ *iq = [XMPPIQ iqWithType:@"set"];
[iq addChild:query];
[xmppStream sendAuthElement:iq];
return YES;
}
- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse
{
XMPPLogTrace();
// We used the old fashioned jabber:iq:auth mechanism
if ([[authResponse attributeStringValueForName:@"type"] isEqualToString:@"error"])
{
return XMPP_AUTH_FAIL;
}
else
{
return XMPP_AUTH_SUCCESS;
}
}
- (BOOL)shouldResendOpeningNegotiationAfterSuccessfulAuthentication
{
return NO;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPStream (XMPPDeprecatedPlainAuthentication)
/**
* This method only applies to servers that don't support XMPP version 1.0, as defined in RFC 3920.
* With these servers, we attempt to discover supported authentication modes via the jabber:iq:auth namespace.
**/
- (BOOL)supportsDeprecatedPlainAuthentication
{
__block BOOL result = NO;
dispatch_block_t block = ^{ @autoreleasepool {
// The root element can be properly queried for authentication mechanisms anytime after the
// stream:features are received, and TLS has been setup (if required)
if (self.state >= STATE_XMPP_POST_NEGOTIATION)
{
// Search for an iq element within the rootElement.
// Recall that some servers might stupidly add a "jabber:client" namespace which might cause problems
// if we simply used the elementForName method.
NSXMLElement *iq = nil;
NSUInteger i, count = [self.rootElement childCount];
for (i = 0; i < count; i++)
{
NSXMLNode *childNode = [self.rootElement childAtIndex:i];
if ([childNode kind] == NSXMLElementKind)
{
if ([[childNode name] isEqualToString:@"iq"])
{
iq = (NSXMLElement *)childNode;
}
}
}
NSXMLElement *query = [iq elementForName:@"query" xmlns:@"jabber:iq:auth"];
NSXMLElement *plain = [query elementForName:@"password"];
result = (plain != nil);
}
}};
if (dispatch_get_specific(self.xmppQueueTag))
block();
else
dispatch_sync(self.xmppQueue, block);
return result;
}
@end
@@ -0,0 +1,22 @@
#import <Foundation/Foundation.h>
#import "XMPPSASLAuthentication.h"
#import "XMPPStream.h"
@interface XMPPDigestMD5Authentication : NSObject <XMPPSASLAuthentication>
// This class implements the XMPPSASLAuthentication protocol.
//
// See XMPPSASLAuthentication.h for more information.
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface XMPPStream (XMPPDigestMD5Authentication)
- (BOOL)supportsDigestMD5Authentication;
@end
@@ -0,0 +1,336 @@
#import "XMPPDigestMD5Authentication.h"
#import "XMPP.h"
#import "XMPPLogging.h"
#import "XMPPInternal.h"
#import "NSData+XMPP.h"
#import "NSXMLElement+XMPP.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
// Log levels: off, error, warn, info, verbose
#if DEBUG
static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO; // | XMPP_LOG_FLAG_TRACE;
#else
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
#endif
@interface XMPPDigestMD5Authentication ()
{
#if __has_feature(objc_arc_weak)
__weak XMPPStream *xmppStream;
#else
__unsafe_unretained XMPPStream *xmppStream;
#endif
BOOL awaitingChallenge;
NSString *realm;
NSString *nonce;
NSString *qop;
NSString *cnonce;
NSString *digestURI;
NSString *username;
NSString *password;
}
// The properties are hooks (primarily for testing)
@property (nonatomic, strong) NSString *realm;
@property (nonatomic, strong) NSString *nonce;
@property (nonatomic, strong) NSString *qop;
@property (nonatomic, strong) NSString *cnonce;
@property (nonatomic, strong) NSString *digestURI;
@property (nonatomic, strong) NSString *username;
@property (nonatomic, strong) NSString *password;
- (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge;
- (NSString *)base64EncodedFullResponse;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPDigestMD5Authentication
+ (NSString *)mechanismName
{
return @"DIGEST-MD5";
}
@synthesize realm;
@synthesize nonce;
@synthesize qop;
@synthesize cnonce;
@synthesize digestURI;
@synthesize username;
@synthesize password;
- (id)initWithStream:(XMPPStream *)stream password:(NSString *)inPassword
{
return [self initWithStream:stream username:nil password:inPassword];
}
- (id)initWithStream:(XMPPStream *)stream username:(NSString *)inUsername password:(NSString *)inPassword
{
if ((self = [super init]))
{
xmppStream = stream;
username = inUsername;
password = inPassword;
}
return self;
}
- (BOOL)start:(NSError **)errPtr
{
XMPPLogTrace();
// <auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="DIGEST-MD5" />
NSXMLElement *auth = [NSXMLElement elementWithName:@"auth" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
[auth addAttributeWithName:@"mechanism" stringValue:@"DIGEST-MD5"];
[xmppStream sendAuthElement:auth];
awaitingChallenge = YES;
return YES;
}
- (XMPPHandleAuthResponse)handleAuth1:(NSXMLElement *)authResponse
{
XMPPLogTrace();
// We're expecting a challenge response.
// If we get anything else we're going to assume it's some kind of failure response.
if (![[authResponse name] isEqualToString:@"challenge"])
{
return XMPP_AUTH_FAIL;
}
// Extract components from incoming challenge
NSDictionary *auth = [self dictionaryFromChallenge:authResponse];
realm = auth[@"realm"];
nonce = auth[@"nonce"];
qop = auth[@"qop"];
// Fill out all the other variables
//
// Sometimes the realm isn't specified.
// In this case I believe the realm is implied as the virtual host name.
XMPPJID *myJID = xmppStream.myJID;
NSString *virtualHostName = [myJID domain];
NSString *serverHostName = xmppStream.hostName;
if (realm == nil)
{
if ([virtualHostName length] > 0)
realm = virtualHostName;
else
realm = serverHostName;
}
if ([virtualHostName length] > 0)
digestURI = [NSString stringWithFormat:@"xmpp/%@", virtualHostName];
else
digestURI = [NSString stringWithFormat:@"xmpp/%@", serverHostName];
if (cnonce == nil)
cnonce = [XMPPStream generateUUID];
if (username == nil)
{
username = [myJID user];
}
// Create and send challenge response element
NSXMLElement *response = [NSXMLElement elementWithName:@"response" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
[response setStringValue:[self base64EncodedFullResponse]];
[xmppStream sendAuthElement:response];
awaitingChallenge = NO;
return XMPP_AUTH_CONTINUE;
}
- (XMPPHandleAuthResponse)handleAuth2:(NSXMLElement *)authResponse
{
XMPPLogTrace();
if ([[authResponse name] isEqualToString:@"challenge"])
{
NSDictionary *auth = [self dictionaryFromChallenge:authResponse];
NSString *rspauth = auth[@"rspauth"];
if (rspauth == nil)
{
// We're getting another challenge?
// Not sure what this could possibly be, so for now we'll assume it's a failure.
return XMPP_AUTH_FAIL;
}
else
{
// We received another challenge, but it's really just an rspauth
// This is supposed to be included in the success element (according to the updated RFC)
// but many implementations incorrectly send it inside a second challenge request.
//
// Create and send empty challenge response element.
NSXMLElement *response =
[NSXMLElement elementWithName:@"response" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
[xmppStream sendAuthElement:response];
return XMPP_AUTH_CONTINUE;
}
}
else if ([[authResponse name] isEqualToString:@"success"])
{
return XMPP_AUTH_SUCCESS;
}
else
{
return XMPP_AUTH_FAIL;
}
}
- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)auth
{
XMPPLogTrace();
if (awaitingChallenge)
{
return [self handleAuth1:auth];
}
else
{
return [self handleAuth2:auth];
}
}
- (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge
{
// The value of the challenge stanza is base 64 encoded.
// Once "decoded", it's just a string of key=value pairs separated by commas.
NSData *base64Data = [[challenge stringValue] dataUsingEncoding:NSASCIIStringEncoding];
NSData *decodedData = [base64Data xmpp_base64Decoded];
NSString *authStr = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding];
XMPPLogVerbose(@"%@: Decoded challenge: %@", THIS_FILE, authStr);
NSArray *components = [authStr componentsSeparatedByString:@","];
NSMutableDictionary *auth = [NSMutableDictionary dictionaryWithCapacity:5];
for (NSString *component in components)
{
NSRange separator = [component rangeOfString:@"="];
if (separator.location != NSNotFound)
{
NSMutableString *key = [[component substringToIndex:separator.location] mutableCopy];
NSMutableString *value = [[component substringFromIndex:separator.location+1] mutableCopy];
if(key) CFStringTrimWhitespace((__bridge CFMutableStringRef)key);
if(value) CFStringTrimWhitespace((__bridge CFMutableStringRef)value);
if ([value hasPrefix:@"\""] && [value hasSuffix:@"\""] && [value length] > 2)
{
// Strip quotes from value
[value deleteCharactersInRange:NSMakeRange(0, 1)];
[value deleteCharactersInRange:NSMakeRange([value length]-1, 1)];
}
if(key && value)
{
auth[key] = value;
}
}
}
return auth;
}
- (NSString *)response
{
NSString *HA1str = [NSString stringWithFormat:@"%@:%@:%@", username, realm, password];
NSString *HA2str = [NSString stringWithFormat:@"AUTHENTICATE:%@", digestURI];
XMPPLogVerbose(@"HA1str: %@", HA1str);
XMPPLogVerbose(@"HA2str: %@", HA2str);
NSData *HA1dataA = [[HA1str dataUsingEncoding:NSUTF8StringEncoding] xmpp_md5Digest];
NSData *HA1dataB = [[NSString stringWithFormat:@":%@:%@", nonce, cnonce] dataUsingEncoding:NSUTF8StringEncoding];
XMPPLogVerbose(@"HA1dataA: %@", HA1dataA);
XMPPLogVerbose(@"HA1dataB: %@", HA1dataB);
NSMutableData *HA1data = [NSMutableData dataWithCapacity:([HA1dataA length] + [HA1dataB length])];
[HA1data appendData:HA1dataA];
[HA1data appendData:HA1dataB];
XMPPLogVerbose(@"HA1data: %@", HA1data);
NSString *HA1 = [[HA1data xmpp_md5Digest] xmpp_hexStringValue];
NSString *HA2 = [[[HA2str dataUsingEncoding:NSUTF8StringEncoding] xmpp_md5Digest] xmpp_hexStringValue];
XMPPLogVerbose(@"HA1: %@", HA1);
XMPPLogVerbose(@"HA2: %@", HA2);
NSString *responseStr = [NSString stringWithFormat:@"%@:%@:00000001:%@:auth:%@",
HA1, nonce, cnonce, HA2];
XMPPLogVerbose(@"responseStr: %@", responseStr);
NSString *response = [[[responseStr dataUsingEncoding:NSUTF8StringEncoding] xmpp_md5Digest] xmpp_hexStringValue];
XMPPLogVerbose(@"response: %@", response);
return response;
}
- (NSString *)base64EncodedFullResponse
{
NSMutableString *buffer = [NSMutableString stringWithCapacity:100];
[buffer appendFormat:@"username=\"%@\",", username];
[buffer appendFormat:@"realm=\"%@\",", realm];
[buffer appendFormat:@"nonce=\"%@\",", nonce];
[buffer appendFormat:@"cnonce=\"%@\",", cnonce];
[buffer appendFormat:@"nc=00000001,"];
[buffer appendFormat:@"qop=auth,"];
[buffer appendFormat:@"digest-uri=\"%@\",", digestURI];
[buffer appendFormat:@"response=%@,", [self response]];
[buffer appendFormat:@"charset=utf-8"];
XMPPLogVerbose(@"%@: Decoded response: %@", THIS_FILE, buffer);
NSData *utf8data = [buffer dataUsingEncoding:NSUTF8StringEncoding];
return [utf8data xmpp_base64Encoded];
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPStream (XMPPDigestMD5Authentication)
- (BOOL)supportsDigestMD5Authentication
{
return [self supportsAuthenticationMechanism:[XMPPDigestMD5Authentication mechanismName]];
}
@end
@@ -0,0 +1,22 @@
#import <Foundation/Foundation.h>
#import "XMPPSASLAuthentication.h"
#import "XMPPStream.h"
@interface XMPPPlainAuthentication : NSObject <XMPPSASLAuthentication>
// This class implements the XMPPSASLAuthentication protocol.
//
// See XMPPSASLAuthentication.h for more information.
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface XMPPStream (XMPPPlainAuthentication)
- (BOOL)supportsPlainAuthentication;
@end
@@ -0,0 +1,114 @@
#import "XMPPPlainAuthentication.h"
#import "XMPP.h"
#import "XMPPLogging.h"
#import "XMPPInternal.h"
#import "NSData+XMPP.h"
#import "NSXMLElement+XMPP.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
// Log levels: off, error, warn, info, verbose
#if DEBUG
static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO; // | XMPP_LOG_FLAG_TRACE;
#else
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
#endif
@implementation XMPPPlainAuthentication
{
#if __has_feature(objc_arc_weak)
__weak XMPPStream *xmppStream;
#else
__unsafe_unretained XMPPStream *xmppStream;
#endif
NSString *username;
NSString *password;
}
+ (NSString *)mechanismName
{
return @"PLAIN";
}
- (id)initWithStream:(XMPPStream *)stream password:(NSString *)inPassword
{
return [self initWithStream:stream username:nil password:inPassword];
}
- (id)initWithStream:(XMPPStream *)stream username:(NSString *)inUsername password:(NSString *)inPassword
{
if ((self = [super init]))
{
xmppStream = stream;
username = inUsername;
password = inPassword;
}
return self;
}
- (BOOL)start:(NSError **)errPtr
{
XMPPLogTrace();
// From RFC 4616 - PLAIN SASL Mechanism:
// [authzid] UTF8NUL authcid UTF8NUL passwd
//
// authzid: authorization identity
// authcid: authentication identity (username)
// passwd : password for authcid
NSString *authUsername = username;
if (!authUsername)
{
authUsername = [xmppStream.myJID user];
}
NSString *payload = [NSString stringWithFormat:@"\0%@\0%@", authUsername, password];
NSString *base64 = [[payload dataUsingEncoding:NSUTF8StringEncoding] xmpp_base64Encoded];
// <auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">Base-64-Info</auth>
NSXMLElement *auth = [NSXMLElement elementWithName:@"auth" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
[auth addAttributeWithName:@"mechanism" stringValue:@"PLAIN"];
[auth setStringValue:base64];
[xmppStream sendAuthElement:auth];
return YES;
}
- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse
{
XMPPLogTrace();
// We're expecting a success response.
// If we get anything else we can safely assume it's the equivalent of a failure response.
if ([[authResponse name] isEqualToString:@"success"])
{
return XMPP_AUTH_SUCCESS;
}
else
{
return XMPP_AUTH_FAIL;
}
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPStream (XMPPPlainAuthentication)
- (BOOL)supportsPlainAuthentication
{
return [self supportsAuthenticationMechanism:[XMPPPlainAuthentication mechanismName]];
}
@end
@@ -0,0 +1,21 @@
//
// XMPPSCRAMSHA1Authentication.h
// iPhoneXMPP
//
// Created by David Chiles on 3/21/14.
//
//
#import <Foundation/Foundation.h>
#import "XMPPSASLAuthentication.h"
#import "XMPPStream.h"
@interface XMPPSCRAMSHA1Authentication : NSObject <XMPPSASLAuthentication>
@end
@interface XMPPStream (XMPPSCRAMSHA1Authentication)
- (BOOL)supportsSCRAMSHA1Authentication;
@end
@@ -0,0 +1,342 @@
//
// XMPPSCRAMSHA1Authentication.m
// iPhoneXMPP
//
// Created by David Chiles on 3/21/14.
//
//
#import "XMPPSCRAMSHA1Authentication.h"
#import "XMPP.h"
#import "XMPPLogging.h"
#import "XMPPStream.h"
#import "XMPPInternal.h"
#import "NSData+XMPP.h"
#import "XMPPStringPrep.h"
#import <CommonCrypto/CommonKeyDerivation.h>
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
// Log levels: off, error, warn, info, verbose
#if DEBUG
static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO; // | XMPP_LOG_FLAG_TRACE;
#else
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
#endif
@interface XMPPSCRAMSHA1Authentication ()
{
#if __has_feature(objc_arc_weak)
__weak XMPPStream *xmppStream;
#else
__unsafe_unretained XMPPStream *xmppStream;
#endif
}
@property (nonatomic) BOOL awaitingChallenge;
@property (nonatomic, strong) NSString *username;
@property (nonatomic, strong) NSString *password;
@property (nonatomic, strong) NSString *clientNonce;
@property (nonatomic, strong) NSString *combinedNonce;
@property (nonatomic, strong) NSString *salt;
@property (nonatomic, strong) NSNumber *count;
@property (nonatomic, strong) NSString *serverMessage1;
@property (nonatomic, strong) NSString *clientFirstMessageBare;
@property (nonatomic, strong) NSData *serverSignatureData;
@property (nonatomic, strong) NSData *clientProofData;
@property (nonatomic) CCHmacAlgorithm hashAlgorithm;
@end
///////////RFC5802 http://tools.ietf.org/html/rfc5802 //////////////
//Channel binding not yet supported
@implementation XMPPSCRAMSHA1Authentication
+ (NSString *)mechanismName
{
return @"SCRAM-SHA-1";
}
- (id)initWithStream:(XMPPStream *)stream password:(NSString *)password
{
return [self initWithStream:stream username:nil password:password];
}
- (id)initWithStream:(XMPPStream *)stream username:(NSString *)username password:(NSString *)password
{
if ((self = [super init])) {
xmppStream = stream;
if (username)
{
_username = username;
}
else
{
_username = [XMPPStringPrep prepNode:[xmppStream.myJID user]];
}
_password = [XMPPStringPrep prepPassword:password];
_hashAlgorithm = kCCHmacAlgSHA1;
}
return self;
}
- (BOOL)start:(NSError **)errPtr
{
XMPPLogTrace();
if(self.username.length || self.password.length) {
NSXMLElement *auth = [NSXMLElement elementWithName:@"auth" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
[auth addAttributeWithName:@"mechanism" stringValue:@"SCRAM-SHA-1"];
[auth setStringValue:[self clientMessage1]];
[xmppStream sendAuthElement:auth];
self.awaitingChallenge = YES;
return YES;
}
else {
return NO;
}
}
- (XMPPHandleAuthResponse)handleAuth1:(NSXMLElement *)authResponse
{
XMPPLogTrace();
// We're expecting a challenge response.
// If we get anything else we're going to assume it's some kind of failure response.
if (![[authResponse name] isEqualToString:@"challenge"])
{
return XMPP_AUTH_FAIL;
}
NSDictionary *auth = [self dictionaryFromChallenge:authResponse];
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
self.combinedNonce = auth[@"r"];
self.salt = auth[@"s"];
self.count = [numberFormatter numberFromString:auth[@"i"]];
//We have all the necessary information to calculate client proof and server signature
if ([self calculateProofs]) {
NSXMLElement *response = [NSXMLElement elementWithName:@"response" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
[response setStringValue:[self clientMessage2]];
[xmppStream sendAuthElement:response];
self.awaitingChallenge = NO;
return XMPP_AUTH_CONTINUE;
}
else {
return XMPP_AUTH_FAIL;
}
}
- (XMPPHandleAuthResponse)handleAuth2:(NSXMLElement *)authResponse
{
XMPPLogTrace();
NSDictionary *auth = [self dictionaryFromChallenge:authResponse];
if ([[authResponse name] isEqual:@"success"]) {
NSString *receivedServerSignature = auth[@"v"];
if([self.serverSignatureData isEqualToData:[[receivedServerSignature dataUsingEncoding:NSUTF8StringEncoding] xmpp_base64Decoded]]){
return XMPP_AUTH_SUCCESS;
}
else {
return XMPP_AUTH_FAIL;
}
}
else {
return XMPP_AUTH_FAIL;
}
}
- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)auth
{
XMPPLogTrace();
if (self.awaitingChallenge) {
return [self handleAuth1:auth];
}
else {
return [self handleAuth2:auth];
}
}
- (NSString *)clientMessage1
{
self.clientNonce = [XMPPStream generateUUID];
self.clientFirstMessageBare = [NSString stringWithFormat:@"n=%@,r=%@",self.username,self.clientNonce];
NSData *message1Data = [[NSString stringWithFormat:@"n,,%@",self.clientFirstMessageBare] dataUsingEncoding:NSUTF8StringEncoding];
return [message1Data xmpp_base64Encoded];
}
- (NSString *)clientMessage2
{
NSString *clientProofString = [self.clientProofData xmpp_base64Encoded];
NSData *message2Data = [[NSString stringWithFormat:@"c=biws,r=%@,p=%@",self.combinedNonce,clientProofString] dataUsingEncoding:NSUTF8StringEncoding];
return [message2Data xmpp_base64Encoded];
}
- (BOOL)calculateProofs
{
//Check to see that we have a password, salt and iteration count above 4096 (from RFC5802)
if (!self.password.length || !self.salt.length || self.count.unsignedIntegerValue < 4096) {
return NO;
}
NSData *passwordData = [self.password dataUsingEncoding:NSUTF8StringEncoding];
NSData *saltData = [[self.salt dataUsingEncoding:NSUTF8StringEncoding] xmpp_base64Decoded];
NSData *saltedPasswordData = [self HashWithAlgorithm:self.hashAlgorithm password:passwordData salt:saltData iterations:[self.count unsignedIntValue]];
NSData *clientKeyData = [self HashWithAlgorithm:self.hashAlgorithm data:[@"Client Key" dataUsingEncoding:NSUTF8StringEncoding] key:saltedPasswordData];
NSData *serverKeyData = [self HashWithAlgorithm:self.hashAlgorithm data:[@"Server Key" dataUsingEncoding:NSUTF8StringEncoding] key:saltedPasswordData];
NSData *storedKeyData = [clientKeyData xmpp_sha1Digest];
NSData *authMessageData = [[NSString stringWithFormat:@"%@,%@,c=biws,r=%@",self.clientFirstMessageBare,self.serverMessage1,self.combinedNonce] dataUsingEncoding:NSUTF8StringEncoding];
NSData *clientSignatureData = [self HashWithAlgorithm:self.hashAlgorithm data:authMessageData key:storedKeyData];
self.serverSignatureData = [self HashWithAlgorithm:self.hashAlgorithm data:authMessageData key:serverKeyData];
self.clientProofData = [self xorData:clientKeyData withData:clientSignatureData];
//check to see that we caclulated some client proof and server signature
if (self.clientProofData && self.serverSignatureData) {
return YES;
}
else {
return NO;
}
}
- (NSData *)HashWithAlgorithm:(CCHmacAlgorithm) algorithm password:(NSData *)passwordData salt:(NSData *)saltData iterations:(NSUInteger)rounds
{
NSMutableData *mutableSaltData = [saltData mutableCopy];
UInt8 zeroHex= 0x00;
UInt8 oneHex= 0x01;
NSData *zeroData = [[NSData alloc] initWithBytes:&zeroHex length:sizeof(zeroHex)];
NSData *oneData = [[NSData alloc] initWithBytes:&oneHex length:sizeof(oneHex)];
[mutableSaltData appendData:zeroData];
[mutableSaltData appendData:zeroData];
[mutableSaltData appendData:zeroData];
[mutableSaltData appendData:oneData];
NSData *result = [self HashWithAlgorithm:algorithm data:mutableSaltData key:passwordData];
NSData *previous = [result copy];
for (int i = 1; i < rounds; i++) {
previous = [self HashWithAlgorithm:algorithm data:previous key:passwordData];
result = [self xorData:result withData:previous];
}
return result;
}
- (NSData *)HashWithAlgorithm:(CCHmacAlgorithm) algorithm data:(NSData *)data key:(NSData *)key
{
unsigned char cHMAC[CC_SHA1_DIGEST_LENGTH];
CCHmac(algorithm, [key bytes], [key length], [data bytes], [data length], cHMAC);
return [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];
}
- (NSData *)xorData:(NSData *)data1 withData:(NSData *)data2
{
NSMutableData *result = data1.mutableCopy;
char *dataPtr = (char *)result.mutableBytes;
char *keyData = (char *)data2.bytes;
char *keyPtr = keyData;
int keyIndex = 0;
for (int x = 0; x < data1.length; x++) {
*dataPtr = *dataPtr ^ *keyPtr;
dataPtr++;
keyPtr++;
if (++keyIndex == data2.length) {
keyIndex = 0;
keyPtr = keyData;
}
}
return result;
}
- (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge
{
// The value of the challenge stanza is base 64 encoded.
// Once "decoded", it's just a string of key=value pairs separated by commas.
NSData *base64Data = [[challenge stringValue] dataUsingEncoding:NSASCIIStringEncoding];
NSData *decodedData = [base64Data xmpp_base64Decoded];
self.serverMessage1 = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding];
XMPPLogVerbose(@"%@: Decoded challenge: %@", THIS_FILE, self.serverMessage1);
NSArray *components = [self.serverMessage1 componentsSeparatedByString:@","];
NSMutableDictionary *auth = [NSMutableDictionary dictionaryWithCapacity:5];
for (NSString *component in components)
{
NSRange separator = [component rangeOfString:@"="];
if (separator.location != NSNotFound)
{
NSMutableString *key = [[component substringToIndex:separator.location] mutableCopy];
NSMutableString *value = [[component substringFromIndex:separator.location+1] mutableCopy];
if(key) CFStringTrimWhitespace((__bridge CFMutableStringRef)key);
if(value) CFStringTrimWhitespace((__bridge CFMutableStringRef)value);
if ([value hasPrefix:@"\""] && [value hasSuffix:@"\""] && [value length] > 2)
{
// Strip quotes from value
[value deleteCharactersInRange:NSMakeRange(0, 1)];
[value deleteCharactersInRange:NSMakeRange([value length]-1, 1)];
}
if(key && value)
{
auth[key] = value;
}
}
}
return auth;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPStream (XMPPSCRAMSHA1Authentication)
- (BOOL)supportsSCRAMSHA1Authentication
{
return [self supportsAuthenticationMechanism:[XMPPSCRAMSHA1Authentication mechanismName]];
}
@end
@@ -0,0 +1,56 @@
#import <Foundation/Foundation.h>
#import "XMPPSASLAuthentication.h"
#import "XMPPStream.h"
@interface XMPPXFacebookPlatformAuthentication : NSObject <XMPPSASLAuthentication>
/**
* You should use this init method (as opposed the one defined in the XMPPSASLAuthentication protocol).
**/
- (id)initWithStream:(XMPPStream *)stream appId:(NSString *)appId accessToken:(NSString *)accessToken;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface XMPPStream (XMPPXFacebookPlatformAuthentication)
/**
* Facebook Chat X-FACEBOOK-PLATFORM SASL authentication initialization.
* This is a convienence init method to help configure Facebook Chat.
**/
- (id)initWithFacebookAppId:(NSString *)fbAppId;
/**
* The appId can be passed to custom authentication classes.
* For example, the appId is used for Facebook Chat X-FACEBOOK-PLATFORM SASL authentication.
**/
@property (readwrite, copy) NSString *facebookAppId;
/**
* Returns whether or not the server supports X-FACEBOOK-PLATFORM authentication.
*
* This information is available after the stream is connected.
* In other words, after the delegate has received xmppStreamDidConnect: notification.
**/
- (BOOL)supportsXFacebookPlatformAuthentication;
/**
* This method attempts to start the facebook oauth authentication process.
*
* This method is asynchronous.
*
* If there is something immediately wrong,
* such as the stream is not connected or doesn't have a set appId or accessToken,
* the method will return NO and set the error.
* Otherwise the delegate callbacks are used to communicate auth success or failure.
*
* @see xmppStreamDidAuthenticate:
* @see xmppStream:didNotAuthenticate:
**/
- (BOOL)authenticateWithFacebookAccessToken:(NSString *)accessToken error:(NSError **)errPtr;
@end
@@ -0,0 +1,327 @@
#import "XMPPXFacebookPlatformAuthentication.h"
#import "XMPP.h"
#import "XMPPLogging.h"
#import "XMPPInternal.h"
#import "NSData+XMPP.h"
#import <objc/runtime.h>
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
// Log levels: off, error, warn, info, verbose
#if DEBUG
static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO; // | XMPP_LOG_FLAG_TRACE;
#else
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
#endif
static NSString *const XMPPFacebookChatHostName = @"chat.facebook.com";
static char facebookAppIdKey;
@interface XMPPXFacebookPlatformAuthentication ()
{
#if __has_feature(objc_arc_weak)
__weak XMPPStream *xmppStream;
#else
__unsafe_unretained XMPPStream *xmppStream;
#endif
BOOL awaitingChallenge;
NSString *appId;
NSString *accessToken;
NSString *nonce;
NSString *method;
}
- (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge;
- (NSString *)base64EncodedFullResponse;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPXFacebookPlatformAuthentication
+ (NSString *)mechanismName
{
return @"X-FACEBOOK-PLATFORM";
}
- (id)initWithStream:(XMPPStream *)stream password:(NSString *)password
{
if ((self = [super init]))
{
xmppStream = stream;
}
return self;
}
- (id)initWithStream:(XMPPStream *)stream appId:(NSString *)inAppId accessToken:(NSString *)inAccessToken
{
if ((self = [super init]))
{
xmppStream = stream;
appId = inAppId;
accessToken = inAccessToken;
}
return self;
}
- (BOOL)start:(NSError **)errPtr
{
if (!appId || !accessToken)
{
NSString *errMsg = @"Missing facebook appId and/or accessToken.";
NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg};
NSError *err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info];
if (errPtr) *errPtr = err;
return NO;
}
// <auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="X-FACEBOOK-PLATFORM" />
NSXMLElement *auth = [NSXMLElement elementWithName:@"auth" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
[auth addAttributeWithName:@"mechanism" stringValue:@"X-FACEBOOK-PLATFORM"];
[xmppStream sendAuthElement:auth];
awaitingChallenge = YES;
return YES;
}
- (XMPPHandleAuthResponse)handleAuth1:(NSXMLElement *)authResponse
{
XMPPLogTrace();
// We're expecting a challenge response.
// If we get anything else we're going to assume it's some kind of failure response.
if (![[authResponse name] isEqualToString:@"challenge"])
{
return XMPP_AUTH_FAIL;
}
// Extract components from incoming challenge
NSDictionary *auth = [self dictionaryFromChallenge:authResponse];
nonce = auth[@"nonce"];
method = auth[@"method"];
// Create and send challenge response element
NSXMLElement *response = [NSXMLElement elementWithName:@"response" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
[response setStringValue:[self base64EncodedFullResponse]];
[xmppStream sendAuthElement:response];
awaitingChallenge = NO;
return XMPP_AUTH_CONTINUE;
}
- (XMPPHandleAuthResponse)handleAuth2:(NSXMLElement *)authResponse
{
XMPPLogTrace();
// We're expecting a success response.
// If we get anything else we can safely assume it's the equivalent of a failure response.
if ([[authResponse name] isEqualToString:@"success"])
{
return XMPP_AUTH_SUCCESS;
}
else
{
return XMPP_AUTH_FAIL;
}
}
- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse
{
if (awaitingChallenge)
{
return [self handleAuth1:authResponse];
}
else
{
return [self handleAuth2:authResponse];
}
}
- (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge
{
// The value of the challenge stanza is base 64 encoded.
// Once "decoded", it's just a string of key=value pairs separated by ampersands.
NSData *base64Data = [[challenge stringValue] dataUsingEncoding:NSASCIIStringEncoding];
NSData *decodedData = [base64Data xmpp_base64Decoded];
NSString *authStr = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding];
XMPPLogVerbose(@"%@: decoded challenge: %@", THIS_FILE, authStr);
NSArray *components = [authStr componentsSeparatedByString:@"&"];
NSMutableDictionary *auth = [NSMutableDictionary dictionaryWithCapacity:3];
for (NSString *component in components)
{
NSRange separator = [component rangeOfString:@"="];
if (separator.location != NSNotFound)
{
NSString *key = [[component substringToIndex:separator.location]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *value = [[component substringFromIndex:separator.location+1]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([value hasPrefix:@"\""] && [value hasSuffix:@"\""] && [value length] > 2)
{
// Strip quotes from value
value = [value substringWithRange:NSMakeRange(1,([value length]-2))];
}
auth[key] = value;
}
}
return auth;
}
- (NSString *)base64EncodedFullResponse
{
if (!appId || !accessToken || !method || !nonce)
{
return nil;
}
srand([[NSDate date] timeIntervalSince1970]);
NSMutableString *buffer = [NSMutableString stringWithCapacity:250];
[buffer appendFormat:@"method=%@&", method];
[buffer appendFormat:@"nonce=%@&", nonce];
[buffer appendFormat:@"access_token=%@&", accessToken];
[buffer appendFormat:@"api_key=%@&", appId];
[buffer appendFormat:@"call_id=%d&", rand()];
[buffer appendFormat:@"v=%@",@"1.0"];
XMPPLogVerbose(@"XMPPXFacebookPlatformAuthentication: response for facebook: %@", buffer);
NSData *utf8data = [buffer dataUsingEncoding:NSUTF8StringEncoding];
return [utf8data xmpp_base64Encoded];
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPStream (XMPPXFacebookPlatformAuthentication)
- (id)initWithFacebookAppId:(NSString *)fbAppId
{
if ((self = [self init])) // Note: Using [self init], NOT [super init]
{
self.facebookAppId = fbAppId;
self.myJID = [XMPPJID jidWithString:XMPPFacebookChatHostName];
// As of October 8, 2011, Facebook doesn't have their XMPP SRV records set.
// And, as per the XMPP specification, we MUST check the XMPP SRV records for an IP address,
// before falling back to a traditional A record lookup.
//
// So we're setting the hostname as a minor optimization to avoid the SRV timeout delay.
self.hostName = XMPPFacebookChatHostName;
}
return self;
}
- (NSString *)facebookAppId
{
__block NSString *result = nil;
dispatch_block_t block = ^{
result = objc_getAssociatedObject(self, &facebookAppIdKey);
};
if (dispatch_get_specific(self.xmppQueueTag))
block();
else
dispatch_sync(self.xmppQueue, block);
return result;
}
- (void)setFacebookAppId:(NSString *)inFacebookAppId
{
NSString *newFacebookAppId = [inFacebookAppId copy];
dispatch_block_t block = ^{
objc_setAssociatedObject(self, &facebookAppIdKey, newFacebookAppId, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
};
if (dispatch_get_specific(self.xmppQueueTag))
block();
else
dispatch_async(self.xmppQueue, block);
}
- (BOOL)supportsXFacebookPlatformAuthentication
{
return [self supportsAuthenticationMechanism:[XMPPXFacebookPlatformAuthentication mechanismName]];
}
/**
* This method attempts to connect to the Facebook Chat servers
* using the Facebook OAuth token returned by the Facebook OAuth 2.0 authentication process.
**/
- (BOOL)authenticateWithFacebookAccessToken:(NSString *)accessToken error:(NSError **)errPtr
{
XMPPLogTrace();
__block BOOL result = YES;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
if ([self supportsXFacebookPlatformAuthentication])
{
XMPPXFacebookPlatformAuthentication *facebookAuth =
[[XMPPXFacebookPlatformAuthentication alloc] initWithStream:self
appId:self.facebookAppId
accessToken:accessToken];
result = [self authenticate:facebookAuth error:&err];
}
else
{
NSString *errMsg = @"The server does not support X-FACEBOOK-PLATFORM authentication.";
NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg};
err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamUnsupportedAction userInfo:info];
result = NO;
}
}};
if (dispatch_get_specific(self.xmppQueueTag))
block();
else
dispatch_sync(self.xmppQueue, block);
if (errPtr)
*errPtr = err;
return result;
}
@end
@@ -0,0 +1,28 @@
//
// XMPPXOAuth2Google.h
// Off the Record
//
// Created by David Chiles on 9/13/13.
// Copyright (c) 2013 Chris Ballinger. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "XMPPSASLAuthentication.h"
#import "XMPPStream.h"
@interface XMPPXOAuth2Google : NSObject <XMPPSASLAuthentication>
-(id)initWithStream:(XMPPStream *)stream accessToken:(NSString *)accessToken;
@end
@interface XMPPStream (XMPPXOAuth2Google)
- (BOOL)supportsXOAuth2GoogleAuthentication;
- (BOOL)authenticateWithGoogleAccessToken:(NSString *)accessToken error:(NSError **)errPtr;
@end
@@ -0,0 +1,180 @@
//
// XMPPXOAuth2Google.m
// Off the Record
//
// Created by David Chiles on 9/13/13.
// Copyright (c) 2013 Chris Ballinger. All rights reserved.
//
#import "XMPPXOAuth2Google.h"
#import "XMPP.h"
#import "XMPPLogging.h"
#import "XMPPInternal.h"
#import "NSData+XMPP.h"
#import <objc/runtime.h>
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
// Log levels: off, error, warn, info, verbose
#if DEBUG
static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO; // | XMPP_LOG_FLAG_TRACE;
#else
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
#endif
static NSString *const XMPPGoogleTalkHostName = @"talk.google.com";
@interface XMPPXOAuth2Google ()
{
#if __has_feature(objc_arc_weak)
__weak XMPPStream *xmppStream;
#else
__unsafe_unretained XMPPStream *xmppStream;
#endif
//BOOL awaitingChallenge;
//NSString *appId;
NSString *accessToken;
//NSString *nonce;
//NSString *method;
}
@end
@implementation XMPPXOAuth2Google
+ (NSString *)mechanismName
{
return @"X-OAUTH2";
}
- (id)initWithStream:(XMPPStream *)stream password:(NSString *)password
{
if ((self = [super init]))
{
xmppStream = stream;
xmppStream.hostName = XMPPGoogleTalkHostName;
}
return self;
}
-(id)initWithStream:(XMPPStream *)stream accessToken:(NSString *)inAccessToken
{
if (self = [super init]) {
xmppStream = stream;
accessToken = inAccessToken;
}
return self;
}
- (BOOL)start:(NSError **)errPtr
{
if (!accessToken)
{
NSString *errMsg = @"Missing facebook accessToken.";
NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg};
NSError *err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info];
if (errPtr) *errPtr = err;
return NO;
}
XMPPLogTrace();
// From RFC 4616 - PLAIN SASL Mechanism:
// [authzid] UTF8NUL authcid UTF8NUL passwd
//
// authzid: authorization identity
// authcid: authentication identity (username)
// passwd : password for authcid
NSString *username = [xmppStream.myJID user];
NSString *payload = [NSString stringWithFormat:@"\0%@\0%@", username, accessToken];
NSString *base64 = [[payload dataUsingEncoding:NSUTF8StringEncoding] xmpp_base64Encoded];
// <auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">Base-64-Info</auth>
NSXMLElement *auth = [NSXMLElement elementWithName:@"auth" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
[auth addAttributeWithName:@"mechanism" stringValue:@"X-OAUTH2"];
[auth addAttributeWithName:@"auth:service" stringValue:@"oauth2"];
[auth addAttributeWithName:@"xmlns:auth" stringValue:@"http://www.google.com/talk/protocol/auth"];
[auth setStringValue:base64];
[xmppStream sendAuthElement:auth];
return YES;
}
- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse
{
XMPPLogTrace();
// We're expecting a success response.
// If we get anything else we can safely assume it's the equivalent of a failure response.
if ([[authResponse name] isEqualToString:@"success"])
{
return XMPP_AUTH_SUCCESS;
}
else
{
return XMPP_AUTH_FAIL;
}
}
@end
@implementation XMPPStream (XMPPXOAuth2Google)
- (BOOL)supportsXOAuth2GoogleAuthentication
{
return [self supportsAuthenticationMechanism:[XMPPXOAuth2Google mechanismName]];
}
- (BOOL)authenticateWithGoogleAccessToken:(NSString *)accessToken error:(NSError **)errPtr
{
XMPPLogTrace();
__block BOOL result = YES;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
if ([self supportsXOAuth2GoogleAuthentication])
{
XMPPXOAuth2Google * googleAuth = [[XMPPXOAuth2Google alloc] initWithStream:self
accessToken:accessToken];
result = [self authenticate:googleAuth error:&err];
}
else
{
NSString *errMsg = @"The server does not support X-OATH2-GOOGLE authentication.";
NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg};
err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamUnsupportedAction userInfo:info];
result = NO;
}
}};
if (dispatch_get_specific(self.xmppQueueTag))
block();
else
dispatch_sync(self.xmppQueue, block);
if (errPtr)
*errPtr = err;
return result;
}
@end
+93
View File
@@ -0,0 +1,93 @@
#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE
#import "DDXML.h"
#endif
typedef NS_ENUM(NSInteger, XMPPBindResult) {
XMPP_BIND_CONTINUE, // The custom binding process is still ongoing.
XMPP_BIND_SUCCESS, // Custom binding succeeded.
// The stream should continue normal post-binding operation.
XMPP_BIND_FAIL_FALLBACK, // Custom binding failed.
// The stream should fallback to the standard binding protocol.
XMPP_BIND_FAIL_ABORT // Custom binding failed.
// The stream must abort the binding process.
// Further, because the stream is in a bad state (authenticated, but
// unable to complete the full handshake) it must immediately disconnect.
// The given NSError will be reported via xmppStreamDidDisconnect:withError:
};
/**
* Binding a JID resource is a standard part of the authentication process,
* and occurs after SASL authentication completes (which generally authenticates the JID username).
*
* This protocol may be used if there is a need to customize the binding process.
* For example:
*
* - Custom SASL authentication scheme required both username & resource
* - Custom SASL authentication scheme provided required resource in server response
* - Stream Management (XEP-0198) replaces binding with resumption from previously bound session
*
* A custom binding procedure may be plugged into an XMPPStream instance via the delegate method:
* - (id <XMPPCustomBinding>)xmppStreamWillBind;
**/
@protocol XMPPCustomBinding <NSObject>
@required
/**
* Attempts to start the custom binding process.
*
* If it isn't possible to start the process (perhaps due to missing information),
* this method should return XMPP_BIND_FAIL_FALLBACK or XMPP_BIND_FAIL_ABORT.
*
* (The error message is only used by xmppStream if this method returns XMPP_BIND_FAIL_ABORT.)
*
* If binding isn't needed (for example, because custom SASL authentication already handled it),
* this method should return XMPP_BIND_SUCCESS.
* In this case, xmppStream will immediately move to its post-binding operations.
*
* Otherwise this method should send whatever stanzas are needed to begin the binding process.
* And then return XMPP_BIND_CONTINUE.
*
* This method is called by automatically XMPPStream.
* You MUST NOT invoke this method manually.
**/
- (XMPPBindResult)start:(NSError **)errPtr;
/**
* After the custom binding process has started, all incoming xmpp stanzas are routed to this method.
* The method should process the stanza as appropriate, and return the coresponding result.
* If the process is not yet complete, it should return XMPP_BIND_CONTINUE,
* meaning the xmpp stream will continue to forward all incoming xmpp stanzas to this method.
*
* This method is called automatically by XMPPStream.
* You MUST NOT invoke this method manually.
**/
- (XMPPBindResult)handleBind:(NSXMLElement *)auth withError:(NSError **)errPtr;
@optional
/**
* Optionally implement this method to override the default behavior.
* By default behavior, we mean the behavior normally taken by xmppStream, which is:
*
* - IF the server includes <session xmlns='urn:ietf:params:xml:ns:xmpp-session'/> in its stream:features
* - AND xmppStream.skipStartSession property is NOT set
* - THEN xmppStream will send the session start request, and await the response before transitioning to authenticated
*
* Thus if you implement this method and return YES, then xmppStream will skip starting a session,
* regardless of the stream:features and the current xmppStream.skipStartSession property value.
*
* If you implement this method and return NO, then xmppStream will follow the default behavior detailed above.
* This means that, even if this method returns NO, the xmppStream may still skip starting a session if
* the server doesn't require it via its stream:features,
* or if the user has explicitly forbidden it via the xmppStream.skipStartSession property.
*
* The default value is NO.
**/
- (BOOL)shouldSkipStartSessionAfterSuccessfulBinding;
@end
+102
View File
@@ -0,0 +1,102 @@
#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE
#import "DDXML.h"
#endif
@class XMPPStream;
typedef NS_ENUM(NSInteger, XMPPHandleAuthResponse) {
XMPP_AUTH_FAIL, // Authentication failed.
// The delegate will be informed via xmppStream:didNotAuthenticate:
XMPP_AUTH_SUCCESS, // Authentication succeeded.
// The delegate will be informed via xmppStreamDidAuthenticate:
XMPP_AUTH_CONTINUE, // The authentication process is still ongoing.
};
@protocol XMPPSASLAuthentication <NSObject>
@required
/**
* Returns the associated mechanism name.
*
* An xmpp server sends a list of supported authentication mechanisms during the xmpp handshake.
* The list looks something like this:
*
* <stream:features>
* <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
* <mechanism>DIGEST-MD5</mechanism>
* <mechanism>X-FACEBOOK-PLATFORM</mechanism>
* <mechanism>X-YOUR-CUSTOM-AUTH-SCHEME</mechanism>
* </mechanisms>
* </stream:features>
*
* The mechanismName returned should match the value inside the <mechanism>HERE</mechanism>.
**/
+ (NSString *)mechanismName;
/**
* Standard init method.
*
* The XMPPStream class natively supports the standard authentication scheme (auth with password).
* If that method is used, then xmppStream will automatically create an authentication instance via this method.
* Which authentication class it chooses is based on the configured authentication priorities,
* and the auth mechanisms supported by the server.
*
* Not all authentication mechanisms will use this init method.
* For example:
* - they require an appId and authToken
* - they require a userName (not related to JID), privilegeLevel, and password
* - they require an eyeScan and voiceFingerprint
*
* In this case, the authentication mechanism class should provide it's own custom init method.
* However it should still implement this method, and then use the start method to notify of errors.
**/
- (id)initWithStream:(XMPPStream *)stream password:(NSString *)password;
/**
* Attempts to start the authentication process.
* The auth mechanism should send whatever stanzas are needed to begin the authentication process.
*
* If it isn't possible to start the authentication process (perhaps due to missing information),
* this method should return NO and set an appropriate error message.
* For example: "X-Custom-Platform authentication requires authToken"
* Otherwise this method should return YES.
*
* This method is called by automatically XMPPStream (via the authenticate: method).
* You should NOT invoke this method manually.
**/
- (BOOL)start:(NSError **)errPtr;
/**
* After the authentication process has started, all incoming xmpp stanzas are routed to this method.
* The authentication mechanism should process the stanza as appropriate, and return the coresponding result.
* If the authentication is not yet complete, it should return XMPP_AUTH_CONTINUE,
* meaning the xmpp stream will continue to forward all incoming xmpp stanzas to this method.
*
* This method is called automatically by XMPPStream (via the authenticate: method).
* You should NOT invoke this method manually.
**/
- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)auth;
@optional
/**
* Use this init method if the username used for authentication does not match the user part of the JID.
* If username is nil, the user part of the JID will be used.
* The standard init method uses this init method, passing nil for the username.
**/
- (id)initWithStream:(XMPPStream *)stream username:(NSString *)username password:(NSString *)password;
/**
* Optionally implement this method to override the default behavior.
* The default value is YES.
**/
- (BOOL)shouldResendOpeningNegotiationAfterSuccessfulAuthentication;
@end