/* * 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]; } - (void)setAuthzid: (OFString*)authzid_ { OFString *old = authzid; if (authzid_) { OFMutableString *new = [[OFMutableString alloc] initWithString: authzid_]; [new replaceOccurrencesOfString: @"=" withString: @"=3D"]; [new replaceOccurrencesOfString: @"," withString: @"=2C"]; authzid = [new copy]; [new release]; } else authzid = nil; [old release]; } - (void)setAuthcid: (OFString*)authcid_ { OFString *old = authcid; if (authcid_) { OFMutableString *new = [[OFMutableString alloc] initWithString: authcid_]; [new replaceOccurrencesOfString: @"=" withString: @"=3D"]; [new replaceOccurrencesOfString: @"," withString: @"=2C"]; authcid = [new copy]; [new release]; } else authcid = nil; [old release]; } - (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