Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -1,6 +1,6 @@ all: objfw-compile -Wall --lib 0.0 -o objxmpp *.m \ - `pkg-config --cflags --libs libidn` + `pkg-config --cflags --libs libidn libbsd` clean: rm -f *.o *.so *.dylib *.dll ADDED src/XMPPAuthenticator.h Index: src/XMPPAuthenticator.h ================================================================== --- /dev/null +++ src/XMPPAuthenticator.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2011, Florian Zeitz + * + * https://webkeks.org/hg/objxmpp/ + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice is present in all copies. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +/** + * \brief A base class for classes implementing authentication mechanisms + */ +@interface XMPPAuthenticator: OFObject +{ + /// The authzid to get authorization for + OFString *authzid; + /// The authcid to authenticate with + OFString *authcid; + /// The password to authenticate with + OFString *password; +} +@property (copy) OFString *authzid; +@property (copy) OFString *authcid; +@property (copy) OFString *password; + +/** + * Initializes an already allocated XMPPAuthenticator with an authcid + * and password. + * + * \param authcid The authcid to authenticate with + * \param password The password to authenticate with + * \return A initialized XMPPAuthenticator + */ +- initWithAuthcid: (OFString*)authcid + password: (OFString*)password; + +/** + * Initializes an already allocated XMPPSCRAMAuthenticator with an authzid, + * authcid and password. + * + * \param authzid The authzid to get authorization for + * \param authcid The authcid to authenticate with + * \param password The password to authenticate with + * \return A initialized XMPPAuthenticator + */ +- initWithAuthzid: (OFString*)authzid + authcid: (OFString*)authcid + password: (OFString*)password; + +/** + * \return A OFDataAray containing the initial authentication message + */ +- (OFDataArray*)getClientFirstMessage; + +/** + * \param challenge The challenge to generate a response for + * \return The response to the given challenge + */ +- (OFDataArray*)getResponseWithChallenge: (OFDataArray*)challenge; + +/** + * Checks whether the servers final message was valid + * + * \param message The servers final message + */ +- (void)parseServerFinalMessage: (OFDataArray*)message; +@end ADDED src/XMPPAuthenticator.m Index: src/XMPPAuthenticator.m ================================================================== --- /dev/null +++ src/XMPPAuthenticator.m @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2011, Florian Zeitz + * + * https://webkeks.org/hg/objxmpp/ + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice is present in all copies. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "XMPPAuthenticator.h" + +@implementation XMPPAuthenticator +@synthesize authzid; +@synthesize authcid; +@synthesize password; + +- initWithAuthcid: (OFString*)authcid_ + password: (OFString*)password_ +{ + return [self initWithAuthzid: nil + authcid: authcid_ + password: password_]; +} + +- initWithAuthzid: (OFString*)authzid_ + authcid: (OFString*)authcid_ + password: (OFString*)password_ +{ + self = [super init]; + + [self setAuthzid: authzid_]; + [self setAuthcid: authcid_]; + [self setPassword: password_]; + + return self; +} + +- (void)dealloc +{ + [authzid release]; + [authcid release]; + [password release]; + + [super dealloc]; +} + +- (OFDataArray*)getClientFirstMessage +{ + @throw [OFNotImplementedException newWithClass: isa + selector: _cmd]; +} + +- (OFDataArray*)getResponseWithChallenge: (OFDataArray*)challenge +{ + @throw [OFNotImplementedException newWithClass: isa + selector: _cmd]; +} + +- (void)parseServerFinalMessage: (OFDataArray*)message +{ + @throw [OFNotImplementedException newWithClass: isa + selector: _cmd]; +} +@end Index: src/XMPPConnection.h ================================================================== --- src/XMPPConnection.h +++ src/XMPPConnection.h @@ -26,10 +26,11 @@ @class XMPPConnection; @class XMPPJID; @class XMPPIQ; @class XMPPMessage; @class XMPPPresence; +@class XMPPAuthenticator; @protocol XMPPConnectionDelegate - (void)connectionWasClosed: (XMPPConnection*)conn; - (void)connection: (XMPPConnection*)conn didReceiveIQ: (XMPPIQ*)iq; @@ -61,10 +62,11 @@ short port; /// Whether to use TLS BOOL useTLS; id delegate; OFMutableArray *mechanisms; + XMPPAuthenticator *authModule; } @property (copy) OFString *username; @property (copy) OFString *password; @property (copy) OFString *server; Index: src/XMPPConnection.m ================================================================== --- src/XMPPConnection.m +++ src/XMPPConnection.m @@ -25,10 +25,12 @@ #include #include #import "XMPPConnection.h" +#import "XMPPSCRAMAuth.h" +#import "XMPPPLAINAuth.h" #import "XMPPStanza.h" #import "XMPPJID.h" #import "XMPPIQ.h" #import "XMPPExceptions.h" @@ -62,10 +64,11 @@ - (void)dealloc { [sock release]; [parser release]; [elementBuilder release]; + [authModule release]; [super dealloc]; } - (void)setUsername: (OFString*)username_ @@ -221,35 +224,20 @@ } parser.delegate = elementBuilder; } -- (void)_sendPLAINAuth +- (void)_sendAuth: (OFString*)name { OFXMLElement *authTag; - OFDataArray *message; - - message = [OFDataArray dataArrayWithItemSize: 1]; - /* XXX: authzid would go here */ - //[message addItem: authzid]; - /* separator */ - [message addItem: ""]; - /* authcid */ - [message addNItems: [username cStringLength] - fromCArray: [username cString]]; - /* separator */ - [message addItem: ""]; - /* passwd */ - [message addNItems: [password cStringLength] - fromCArray: [password cString]]; authTag = [OFXMLElement elementWithName: @"auth" namespace: NS_SASL]; [authTag addAttributeWithName: @"mechanism" - stringValue: @"PLAIN"]; + stringValue: name]; [authTag addChild: [OFXMLElement elementWithCharacters: - [message stringByBase64Encoding]]]; + [[authModule getClientFirstMessage] stringByBase64Encoding]]]; [self sendStanza: authTag]; } - (void)_sendResourceBind @@ -287,12 +275,22 @@ namespace: NS_BIND].firstObject; for (OFXMLElement *mech in [mechs.firstObject children]) [mechanisms addObject: [mech.children.firstObject stringValue]]; - if ([mechanisms containsObject: @"PLAIN"]) - [self _sendPLAINAuth]; + if ([mechanisms containsObject: @"SCRAM-SHA-1"]) { + authModule = [[XMPPSCRAMAuth alloc] + initWithAuthcid: username + password: password + hash: [OFSHA1Hash class]]; + [self _sendAuth: @"SCRAM-SHA-1"]; + } else if ([mechanisms containsObject: @"PLAIN"]) { + authModule = [[XMPPPLAINAuth alloc] + initWithAuthcid: username + password: password]; + [self _sendAuth: @"PLAIN"]; + } if (bind != nil) [self _sendResourceBind]; } @@ -308,25 +306,45 @@ [self _handleFeatures: elem]; return; } if ([elem.namespace isEqual: NS_SASL]) { - if ([elem.name isEqual: @"success"]) { + if ([elem.name isEqual: @"challenge"]) { + OFXMLElement *responseTag; + OFDataArray *challenge = + [OFDataArray dataArrayWithBase64EncodedString: + [elem.children.firstObject stringValue]]; + OFDataArray *response = + [authModule getResponseWithChallenge: challenge]; + + responseTag = [OFXMLElement elementWithName: @"response" + namespace: NS_SASL]; + [responseTag + addChild: [OFXMLElement elementWithCharacters: + [response stringByBase64Encoding]]]; + + [self sendStanza: responseTag]; + } else if ([elem.name isEqual: @"success"]) { + [authModule parseServerFinalMessage: + [OFDataArray dataArrayWithBase64EncodedString: + [elem.children.firstObject stringValue]]]; of_log(@"Auth successful"); /* Stream restart */ [mechanisms release]; mechanisms = [[OFMutableArray alloc] init]; parser.delegate = self; [self _startStream]; - return; - } - - if ([elem.name isEqual: @"failure"]) + } else if ([elem.name isEqual: @"failure"]) { of_log(@"Auth failed!"); - // FIXME: Handle! + // FIXME: Do more parsing/handling + @throw [XMPPAuthFailedException + newWithClass: isa + connection: self + reason: [elem stringValue]]; + } } if ([elem.name isEqual: @"iq"] && [elem.namespace isEqual: NS_CLIENT]) { XMPPIQ *iq = [XMPPIQ stanzaWithElement: elem]; Index: src/XMPPExceptions.h ================================================================== --- src/XMPPExceptions.h +++ src/XMPPExceptions.h @@ -21,10 +21,11 @@ */ #import @class XMPPConnection; +@class XMPPAuthenticator; @interface XMPPException: OFException { XMPPConnection *connection; } @@ -70,5 +71,20 @@ - initWithClass: (Class)class_ connection: (XMPPConnection*)conn operation: (OFString*)operation string: (OFString*)string; @end + +@interface XMPPAuthFailedException: XMPPException +{ + OFString *reason; +} + +@property (readonly, nonatomic) OFString *reason; + ++ newWithClass: (Class)class_ + connection: (XMPPConnection*)conn + reason: (OFString*)reason_; +- initWithClass: (Class)class_ + connection: (XMPPConnection*)conn + reason: (OFString*)reason_; +@end Index: src/XMPPExceptions.m ================================================================== --- src/XMPPExceptions.m +++ src/XMPPExceptions.m @@ -205,9 +205,70 @@ pool = [[OFAutoreleasePool alloc] init]; description = [[OFString alloc] initWithFormat: @"IDNA operation %@ failed on string '%@'!", operation, string]; [pool release]; + + return description; +} +@end + +@implementation XMPPAuthFailedException +@synthesize reason; + ++ newWithClass: (Class)class_ + connection: (XMPPConnection*)conn + reason: (OFString*)reason_; +{ + return [[self alloc] initWithClass: class_ + connection: conn + reason: reason_]; +} + +- initWithClass: (Class)class_ + connection: (XMPPConnection*)conn +{ + Class c = isa; + [self release]; + @throw [OFNotImplementedException newWithClass: c + selector: _cmd]; +} + +- initWithClass: (Class)class_ + connection: (XMPPConnection*)conn + reason: (OFString*)reason_ +{ + self = [super initWithClass: class_ + connection: conn]; + + @try { + reason = [reason_ copy]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [reason release]; + + [super dealloc]; +} + +- (OFString*)description +{ + OFAutoreleasePool *pool; + + if (description != nil) + return description; + + pool = [[OFAutoreleasePool alloc] init]; + description = [[OFString alloc] initWithFormat: + @"Authentication failed. Reason: %@!", reason]; + [pool release]; return description; } @end ADDED src/XMPPPLAINAuth.h Index: src/XMPPPLAINAuth.h ================================================================== --- /dev/null +++ src/XMPPPLAINAuth.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2011, Florian Zeitz + * + * https://webkeks.org/hg/objxmpp/ + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice is present in all copies. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#import "XMPPAuthenticator.h" + +/** + * \brief A class to authenticate using SASL PLAIN + */ +@interface XMPPPLAINAuth: XMPPAuthenticator +/** + * Creates a new autoreleased XMPPPLAINAuth with an authcid and password. + * + * \param authcid The authcid to authenticate with + * \param password The password to authenticate with + * \return A new autoreleased XMPPPLAINAuth + */ ++ PLAINAuthWithAuthcid: (OFString*)authcid + password: (OFString*)password; + +/** + * Creates a new autoreleased XMPPPLAINAuth with an authzid, + * authcid and password. + * + * \param authzid The authzid to get authorization for + * \param authcid The authcid to authenticate with + * \param password The password to authenticate with + * \return A new autoreleased XMPPPLAINAuth + */ ++ PLAINAuthWithAuthzid: (OFString*)authzid + authcid: (OFString*)authcid + password: (OFString*)password; +@end ADDED src/XMPPPLAINAuth.m Index: src/XMPPPLAINAuth.m ================================================================== --- /dev/null +++ src/XMPPPLAINAuth.m @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2011, Florian Zeitz + * + * https://webkeks.org/hg/objxmpp/ + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice is present in all copies. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "XMPPPLAINAuth.h" +#import "XMPPExceptions.h" + +@implementation XMPPPLAINAuth ++ PLAINAuthWithAuthcid: (OFString*)authcid + password: (OFString*)password +{ + return [[[self alloc] initWithAuthcid: authcid + password: password] autorelease]; +} + ++ PLAINAuthWithAuthzid: (OFString*)authzid + authcid: (OFString*)authcid + password: (OFString*)password +{ + return [[[self alloc] initWithAuthzid: authzid + authcid: authcid + password: password] autorelease]; +} + +- (OFDataArray*)getClientFirstMessage +{ + OFDataArray *message = [OFDataArray dataArrayWithItemSize: 1]; + /* authzid */ + if (authzid) + [message addItem: authzid]; + /* separator */ + [message addItem: ""]; + /* authcid */ + [message addNItems: [authcid cStringLength] + fromCArray: [authcid cString]]; + /* separator */ + [message addItem: ""]; + /* passwd */ + [message addNItems: [password cStringLength] + fromCArray: [password cString]]; + + return message; +} + +- (OFDataArray*)getResponseWithChallenge: (OFDataArray*)challenge +{ + @throw [XMPPAuthFailedException + newWithClass: isa + connection: nil + reason: @"Received a challenge during PLAIN auth"]; +} + +- (void)parseServerFinalMessage: (OFDataArray*)message +{ + return; +} +@end ADDED src/XMPPSCRAMAuth.h Index: src/XMPPSCRAMAuth.h ================================================================== --- /dev/null +++ src/XMPPSCRAMAuth.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2011, Florian Zeitz + * + * https://webkeks.org/hg/objxmpp/ + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice is present in all copies. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#import "XMPPAuthenticator.h" + +/** + * \brief A class to authenticate using SCRAM + */ +@interface XMPPSCRAMAuth: XMPPAuthenticator +{ + Class hashType; + OFString *cNonce; + OFString *GS2Header; + OFString *clientFirstMessageBare; + OFDataArray *serverSignature; +} + +/** + * Creates a new autoreleased XMPPSCRAMAuth with an authcid and password. + * + * \param authcid The authcid to authenticate with + * \param password The password to authenticate with + * \param hash The class to use for calulating hashes + * \return A new autoreleased XMPPSCRAMAuth + */ ++ SCRAMAuthWithAuthcid: (OFString*)authcid + password: (OFString*)password + hash: (Class)hash; + +/** + * Creates a new autoreleased XMPPSCRAMAuth with an authzid, + * authcid and password. + * + * \param authzid The authzid to get authorization for + * \param authcid The authcid to authenticate with + * \param password The password to authenticate with + * \param hash The class to use for calulating hashes + * \return A new autoreleased XMPPSCRAMAuth + */ ++ SCRAMAuthWithAuthzid: (OFString*)authzid + authcid: (OFString*)authcid + password: (OFString*)password + hash: (Class)hash; + +/** + * Initializes an already allocated XMPPSCRAMAuth with an authcid and password. + * + * \param authcid The authcid to authenticate with + * \param password The password to authenticate with + * \param hash The class to use for calulating hashes + * \return A initialized XMPPSCRAMAuth + */ +- initWithAuthcid: (OFString*)authcid + password: (OFString*)password + hash: (Class)hash; + +/** + * Initializes an already allocated XMPPSCRAMAuth with a authzid, + * authcid and password. + * + * \param authzid The authzid to get authorization for + * \param authcid The authcid to authenticate with + * \param password The password to authenticate with + * \param hash The class to use for calulating hashes + * \return A initialized XMPPSCRAMAuth + */ +- initWithAuthzid: (OFString*)authzid + authcid: (OFString*)authcid + password: (OFString*)password + hash: (Class)hash; +@end ADDED src/XMPPSCRAMAuth.m Index: src/XMPPSCRAMAuth.m ================================================================== --- /dev/null +++ src/XMPPSCRAMAuth.m @@ -0,0 +1,436 @@ +/* + * Copyright (c) 2011, Florian Zeitz + * + * https://webkeks.org/hg/objxmpp/ + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice is present in all copies. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +// FIXME: Remove this once libbsd includes arc4random_uniform() in it's headers +#define fake_arc4random_uniform(upper) \ + ((uint32_t) (arc4random() / (double) UINT32_MAX * upper)) + +#import "XMPPSCRAMAuth.h" +#import "XMPPExceptions.h" + +#define HMAC_IPAD 0x36 +#define HMAC_OPAD 0x5c + +@implementation XMPPSCRAMAuth + ++ SCRAMAuthWithAuthcid: (OFString*)authcid + password: (OFString*)password + hash: (Class)hash; +{ + return [[[self alloc] initWithAuthcid: authcid + password: password + hash: hash] autorelease]; +} + ++ SCRAMAuthWithAuthzid: (OFString*)authzid + authcid: (OFString*)authcid + password: (OFString*)password + hash: (Class)hash; +{ + return [[[self alloc] initWithAuthzid: authzid + authcid: authcid + password: password + hash: hash] autorelease]; +} + +- initWithAuthcid: (OFString*)authcid_ + password: (OFString*)password_ + hash: (Class)hash; +{ + return [self initWithAuthzid: nil + authcid: authcid_ + password: password_ + hash: hash]; +} + +- initWithAuthzid: (OFString*)authzid_ + authcid: (OFString*)authcid_ + password: (OFString*)password_ + hash: (Class)hash; +{ + self = [super initWithAuthzid: authzid_ + authcid: authcid_ + password: password_]; + + hashType = hash; + + return self; +} + +- (void)dealloc +{ + [GS2Header release]; + [clientFirstMessageBare release]; + [serverSignature release]; + [cNonce release]; + + [super dealloc]; +} + +- (OFString *)_genNonce +{ + OFMutableString *nonce = [OFMutableString string]; + uint32_t res, i; + for (i = 0; i < 64; i++) { + while((res = fake_arc4random_uniform(0x5e) + 0x21) == 0x2C); + [nonce appendFormat: @"%c", res]; + } + + return nonce; +} + +- (uint8_t *)_hmacWithKey: (OFDataArray*)key + data: (OFDataArray*)data +{ + size_t i, kSize, blockSize = [hashType blockSize]; + uint8_t *kCArray = NULL, *kI = NULL, *kO = NULL; + OFAutoreleasePool *pool = nil; + OFDataArray *k = nil; + OFHash *hash = nil; + + @try { + pool = [[OFAutoreleasePool alloc] init]; + k = [OFDataArray dataArrayWithItemSize: 1]; + if (key.itemSize * key.count > blockSize) { + hash = [[[hashType alloc] init] autorelease]; + [hash updateWithBuffer: [key cArray] + ofSize: key.itemSize * key.count]; + [k addNItems: [hashType digestSize] + fromCArray: [hash digest]]; + } else + [k addNItems: key.itemSize * key.count + fromCArray: [key cArray]]; + + kI = [self allocMemoryWithSize: blockSize * sizeof(uint8_t)]; + memset(kI, HMAC_IPAD, blockSize * sizeof(uint8_t)); + + kO = [self allocMemoryWithSize: blockSize * sizeof(uint8_t)]; + memset(kO, HMAC_OPAD, blockSize * sizeof(uint8_t)); + + kCArray = [k cArray]; + kSize = k.count; + for (i = 0; i < kSize; i++) { + kI[i] ^= kCArray[i]; + kO[i] ^= kCArray[i]; + } + + k = [OFDataArray dataArrayWithItemSize: 1]; + [k addNItems: blockSize + fromCArray: kI]; + [k addNItems: data.itemSize * data.count + fromCArray: [data cArray]]; + + hash = [[[hashType alloc] init] autorelease]; + [hash updateWithBuffer: [k cArray] + ofSize: k.count]; + k = [OFDataArray dataArrayWithItemSize: 1]; + [k addNItems: blockSize + fromCArray: kO]; + [k addNItems: [hashType digestSize] + fromCArray: [hash digest]]; + + hash = [[[hashType alloc] init] autorelease]; + [hash updateWithBuffer: [k cArray] + ofSize: k.count]; + + [hash retain]; + [pool release]; + pool = nil; + [hash autorelease]; + + return [hash digest]; + } @finally { + [pool release]; + [self freeMemory: kI]; + [self freeMemory: kO]; + } +} + +- (OFDataArray *)_hiWithData: (OFDataArray *)str + salt: (OFDataArray *)salt_ + iterationCount: (unsigned int)i +{ + uint8_t *result = NULL, *u, *uOld; + unsigned int j, k; + size_t digestSize; + OFAutoreleasePool *pool = nil; + OFDataArray *salty, *tmp, *ret; + + @try { + pool = [[OFAutoreleasePool alloc] init]; + digestSize = [hashType digestSize]; + result = [self + allocMemoryWithSize: digestSize * sizeof(uint8_t)]; + memset(result, 0, digestSize * sizeof(uint8_t)); + salty = [salt_ copy]; + [salty addNItems: 4 + fromCArray: "\0\0\0\1"]; + + uOld = [self _hmacWithKey: str + data: salty]; + [salty release]; + for (j = 0; j < digestSize; j++) + result[j] ^= uOld[j]; + + for (j = 0; j < i-1; j++) { + tmp = [OFDataArray dataArrayWithItemSize: 1]; + [tmp addNItems: digestSize + fromCArray: uOld]; + u = [self _hmacWithKey: str + data: tmp]; + for (k = 0; k < digestSize; k++) + result[k] ^= u[k]; + uOld = u; + } + + ret = [OFDataArray dataArrayWithItemSize: 1]; + [ret addNItems: digestSize + fromCArray: result]; + + [ret retain]; + [pool release]; + pool = nil; + + return [ret autorelease]; + } @finally { + [pool release]; + [self freeMemory: result]; + } +} + +- (OFDataArray*)getClientFirstMessage +{ + OFDataArray *ret = [OFDataArray dataArrayWithItemSize: 1]; + [GS2Header release]; + if (authzid) + GS2Header = [[OFString alloc] + initWithFormat: @"n,a=%@,", authzid]; + else + GS2Header = [[OFString alloc] initWithFormat: @"n,,"]; + + [cNonce release]; + cNonce = [[self _genNonce] retain]; + [clientFirstMessageBare release]; + clientFirstMessageBare = [[OFString alloc] + initWithFormat: @"n=%@,r=%@", authcid, cNonce]; + + [ret addNItems: [GS2Header cStringLength] + fromCArray: [GS2Header cString]]; + + [ret addNItems: [clientFirstMessageBare cStringLength] + fromCArray: [clientFirstMessageBare cString]]; + + return ret; +} + +- (OFDataArray*)getResponseWithChallenge: (OFDataArray*)challenge +{ + size_t i; + uint8_t *clientKey, *serverKey, *clientSignature; + intmax_t iterCount; + OFHash *hash; + OFDataArray *ret, *authMessage, *tmpArray, *salt, *saltedPassword; + OFString *tmpString, *sNonce; + OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; + + @try { + hash = [[[hashType alloc] init] autorelease]; + ret = [OFDataArray dataArrayWithItemSize: 1]; + authMessage = [OFDataArray dataArrayWithItemSize: 1]; + + OFString *chal = [OFString + stringWithCString: [challenge cArray] + length: + [challenge count] * [challenge itemSize]]; + + for (OFString *comp + in [chal componentsSeparatedByString: @","]) { + OFString *entry = [comp + substringFromIndex: 2 + toIndex: [comp length]]; + if ([comp hasPrefix: @"r="]) { + if (![entry hasPrefix: cNonce]) + @throw [XMPPAuthFailedException + newWithClass: isa + connection: nil + reason: + @"Received wrong nonce"]; + sNonce = entry; + } else if ([comp hasPrefix: @"s="]) + salt = [OFDataArray + dataArrayWithBase64EncodedString: entry]; + else if ([comp hasPrefix: @"i="]) + iterCount = [entry decimalValue]; + } + + // Add c= + // XXX: No channel binding for now + tmpArray = [OFDataArray dataArrayWithItemSize: 1]; + [tmpArray addNItems: [GS2Header cStringLength] + fromCArray: [GS2Header cString]]; + tmpString = [tmpArray stringByBase64Encoding]; + [ret addNItems: 2 + fromCArray: "c="]; + [ret addNItems: [tmpString cStringLength] + fromCArray: [tmpString cString]]; + + // Add r= + [ret addItem: ","]; + [ret addNItems: 2 + fromCArray: "r="]; + [ret addNItems: [sNonce cStringLength] + fromCArray: [sNonce cString]]; + + tmpArray = [OFDataArray dataArrayWithItemSize: 1]; + [tmpArray addNItems: [password cStringLength] + fromCArray: [password cString]]; + + /* + * IETF RFC 5802: + * SaltedPassword := Hi(Normalize(password), salt, i) + */ + saltedPassword = [self _hiWithData: tmpArray + salt: salt + iterationCount: iterCount]; + + /* + * IETF RFC 5802: + * AuthMessage := client-first-message-bare + "," + + * server-first-message + "," + + * client-final-message-without-proof + */ + [authMessage addNItems: [clientFirstMessageBare cStringLength] + fromCArray: [clientFirstMessageBare cString]]; + [authMessage addItem: ","]; + [authMessage addNItems: [challenge count] * [challenge itemSize] + fromCArray: [challenge cArray]]; + [authMessage addItem: ","]; + [authMessage addNItems: [ret count] + fromCArray: [ret cArray]]; + + /* + * IETF RFC 5802: + * ClientKey := HMAC(SaltedPassword, "Client Key") + */ + tmpArray = [OFDataArray dataArrayWithItemSize: 1]; + [tmpArray addNItems: 10 + fromCArray: "Client Key"]; + clientKey = [self _hmacWithKey: saltedPassword + data: tmpArray]; + + /* + * IETF RFC 5802: + * StoredKey := H(ClientKey) + */ + [hash updateWithBuffer: (void*) clientKey + ofSize: [hashType digestSize]]; + tmpArray = [OFDataArray dataArrayWithItemSize: 1]; + [tmpArray addNItems: [hashType digestSize] + fromCArray: [hash digest]]; + + /* + * IETF RFC 5802: + * ClientSignature := HMAC(StoredKey, AuthMessage) + */ + clientSignature = [self _hmacWithKey: tmpArray + data: authMessage]; + + /* + * IETF RFC 5802: + * ServerKey := HMAC(SaltedPassword, "Server Key") + */ + tmpArray = [OFDataArray dataArrayWithItemSize: 1]; + [tmpArray addNItems: 10 + fromCArray: "Server Key"]; + serverKey = [self _hmacWithKey: saltedPassword + data: tmpArray]; + + /* + * IETF RFC 5802: + * ServerSignature := HMAC(ServerKey, AuthMessage) + */ + tmpArray = [OFDataArray dataArrayWithItemSize: 1]; + [tmpArray addNItems: [hashType digestSize] + fromCArray: serverKey]; + serverSignature = [[OFDataArray alloc] initWithItemSize: 1]; + [serverSignature addNItems: [hashType digestSize] + fromCArray: [self _hmacWithKey: tmpArray + data: authMessage]]; + + /* + * IETF RFC 5802: + * ClientProof := ClientKey XOR ClientSignature + */ + tmpArray = [OFDataArray dataArrayWithItemSize: 1]; + for (i = 0; i < [hashType digestSize]; i++) { + uint8_t c = clientKey[i] ^ clientSignature[i]; + [tmpArray addItem: &c]; + } + + // Add p= + [ret addItem: ","]; + [ret addNItems: 2 + fromCArray: "p="]; + tmpString = [tmpArray stringByBase64Encoding]; + [ret addNItems: [tmpString cStringLength] + fromCArray: [tmpString cString]]; + + [ret retain]; + [pool release]; + pool = nil; + + return [ret autorelease]; + } @finally { + [pool release]; + } +} + +- (void)parseServerFinalMessage: (OFDataArray*)message +{ + OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; + @try { + OFString *mess = [OFString + stringWithCString: [message cArray] + length: [message count] * [message itemSize]]; + + OFString *value = [mess substringFromIndex: 2 + toIndex: [mess length]]; + + if ([mess hasPrefix: @"v="]) { + if ([value compare: + [serverSignature stringByBase64Encoding]]) + @throw [XMPPAuthFailedException + newWithClass: isa + connection: nil + reason: + @"Received wrong ServerSignature"]; + } else + @throw [XMPPAuthFailedException newWithClass: isa + connection: nil + reason: value]; + } @finally { + [pool release]; + } +} +@end Index: tests/test.m ================================================================== --- tests/test.m +++ tests/test.m @@ -95,8 +95,12 @@ [conn setPassword: [arguments objectAtIndex: 2]]; [conn setResource: @"ObjXMPP"]; [conn setUseTLS: NO]; [conn connect]; - [conn handleConnection]; + @try { + [conn handleConnection]; + } @catch (id e) { + of_log(@"%@", e); + } } @end