- Update
This commit is contained in:
1 parent
d281d765ea
commit
5b074a5176
1261 files changed
+199158
-7303
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user