- Update
This commit is contained in:
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in new issue
Block a user