diff --git a/src/Makefile b/src/Makefile index 2eaf232..fc8500e 100644 --- a/src/Makefile +++ b/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 diff --git a/src/XMPPAuthenticator.h b/src/XMPPAuthenticator.h new file mode 100644 index 0000000..27d2318 --- /dev/null +++ b/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 diff --git a/src/XMPPAuthenticator.m b/src/XMPPAuthenticator.m new file mode 100644 index 0000000..308204b --- /dev/null +++ b/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 diff --git a/src/XMPPConnection.h b/src/XMPPConnection.h index 32788fc..528d849 100644 --- a/src/XMPPConnection.h +++ b/src/XMPPConnection.h @@ -28,6 +28,7 @@ @class XMPPIQ; @class XMPPMessage; @class XMPPPresence; +@class XMPPAuthenticator; @protocol XMPPConnectionDelegate - (void)connectionWasClosed: (XMPPConnection*)conn; @@ -63,6 +64,7 @@ BOOL useTLS; id delegate; OFMutableArray *mechanisms; + XMPPAuthenticator *authModule; } @property (copy) OFString *username; diff --git a/src/XMPPConnection.m b/src/XMPPConnection.m index b0195f0..68d0d71 100644 --- a/src/XMPPConnection.m +++ b/src/XMPPConnection.m @@ -27,6 +27,8 @@ #include #import "XMPPConnection.h" +#import "XMPPSCRAMAuth.h" +#import "XMPPPLAINAuth.h" #import "XMPPStanza.h" #import "XMPPJID.h" #import "XMPPIQ.h" @@ -64,6 +66,7 @@ [sock release]; [parser release]; [elementBuilder release]; + [authModule release]; [super dealloc]; } @@ -223,31 +226,16 @@ 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]; } @@ -289,8 +277,18 @@ 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]; @@ -310,7 +308,25 @@ } 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 */ @@ -319,12 +335,14 @@ 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"] && diff --git a/src/XMPPExceptions.h b/src/XMPPExceptions.h index 7261c0d..9926229 100644 --- a/src/XMPPExceptions.h +++ b/src/XMPPExceptions.h @@ -23,6 +23,7 @@ #import @class XMPPConnection; +@class XMPPAuthenticator; @interface XMPPException: OFException { @@ -72,3 +73,18 @@ 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 diff --git a/src/XMPPExceptions.m b/src/XMPPExceptions.m index 2d81544..db28f74 100644 --- a/src/XMPPExceptions.m +++ b/src/XMPPExceptions.m @@ -211,3 +211,64 @@ 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 diff --git a/src/XMPPPLAINAuth.h b/src/XMPPPLAINAuth.h new file mode 100644 index 0000000..d6849b9 --- /dev/null +++ b/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 diff --git a/src/XMPPPLAINAuth.m b/src/XMPPPLAINAuth.m new file mode 100644 index 0000000..2c35c24 --- /dev/null +++ b/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 diff --git a/src/XMPPSCRAMAuth.h b/src/XMPPSCRAMAuth.h new file mode 100644 index 0000000..5112961 --- /dev/null +++ b/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 diff --git a/src/XMPPSCRAMAuth.m b/src/XMPPSCRAMAuth.m new file mode 100644 index 0000000..523c5c3 --- /dev/null +++ b/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 diff --git a/tests/test.m b/tests/test.m index 762b520..7e993b2 100644 --- a/tests/test.m +++ b/tests/test.m @@ -97,6 +97,10 @@ OF_APPLICATION_DELEGATE(AppDelegate) [conn setUseTLS: NO]; [conn connect]; - [conn handleConnection]; + @try { + [conn handleConnection]; + } @catch (id e) { + of_log(@"%@", e); + } } @end