View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0001608 | SOGo | Backend General | public | 2012-01-29 15:28 | 2012-05-31 14:52 |
Reporter | the_nic | Assigned To | ludovic | ||
Priority | normal | Severity | feature | Reproducibility | N/A |
Status | closed | Resolution | fixed | ||
Product Version | 1.3.11 | ||||
Target Version | 1.3.16 | Fixed in Version | 1.3.16 | ||
Summary | 0001608: More password schemes for SQL backend | ||||
Description | Since the SQL backend only supports a few password schemes, I've created a patch which adds md5-crypt and ssha, sha256 and sha512. Could someone please review this patch? | ||||
Tags | No tags attached. | ||||
2012-01-29 15:28
|
nsstring+crypto.patch (6,614 bytes)
# # old_revision [df5dd81d62db331ba5c8de9f2257f9430c586534] # # patch "SoObjects/SOGo/GNUmakefile" # from [9e466added539ccacaecd830173afdf9d4755802] # to [0a6f033637d6af50a1768f0462e2bd4fe4c6909f] # # patch "SoObjects/SOGo/NSString+Utilities.h" # from [09d123e206a68c40e8fc0301e25d2e6052a2abf4] # to [ed9a551529054cdd3fb175e24db3316b24309987] # # patch "SoObjects/SOGo/NSString+Utilities.m" # from [26a0ba2fa690ba7464585e9959c25229992a66ed] # to [d7e02c1a2b5633935f3fe16fd8b154e949c01c9b] # # patch "SoObjects/SOGo/SQLSource.m" # from [3ba7f2ddd8b79c5caa63d11a0d9fea1cfe1e354a] # to [4544aedab94707f1aded7b4de8e5414f9b68a82f] # ============================================================ --- SoObjects/SOGo/GNUmakefile 9e466added539ccacaecd830173afdf9d4755802 +++ SoObjects/SOGo/GNUmakefile 0a6f033637d6af50a1768f0462e2bd4fe4c6909f @@ -46,6 +46,7 @@ SOGo_HEADER_FILES = \ NSObject+Utilities.h \ NSString+DAV.h \ NSString+Utilities.h \ + NSString+Crypto.h \ NSURL+DAV.h \ \ SOGoAuthenticator.h \ @@ -114,6 +115,7 @@ SOGo_OBJC_FILES = \ NSObject+Utilities.m \ NSString+DAV.m \ NSString+Utilities.m \ + NSString+Crypto.m \ NSURL+DAV.m \ \ SOGoSession.m \ ============================================================ --- SoObjects/SOGo/NSString+Utilities.h 09d123e206a68c40e8fc0301e25d2e6052a2abf4 +++ SoObjects/SOGo/NSString+Utilities.h ed9a551529054cdd3fb175e24db3316b24309987 @@ -66,10 +66,6 @@ - (id) objectFromJSONString; -- (NSString *) asCryptStringUsingSalt: (NSString *) theSalt; -- (NSString *) asMD5String; -- (NSString *) asSHA1String; - - (NSString *) asSafeSQLString; - (NSUInteger) countOccurrencesOfString: (NSString *) substring; ============================================================ --- SoObjects/SOGo/NSString+Utilities.m 26a0ba2fa690ba7464585e9959c25229992a66ed +++ SoObjects/SOGo/NSString+Utilities.m d7e02c1a2b5633935f3fe16fd8b154e949c01c9b @@ -21,10 +21,6 @@ * Boston, MA 02111-1307, USA. */ -#ifndef __OpenBSD__ -#include <crypt.h> -#endif - #import <Foundation/NSArray.h> #import <Foundation/NSCharacterSet.h> #import <Foundation/NSData.h> @@ -44,12 +40,6 @@ #import "NSString+Utilities.h" -#define _XOPEN_SOURCE 1 -#include <unistd.h> -#include <openssl/evp.h> -#include <openssl/md5.h> -#include <openssl/sha.h> - static NSMutableCharacterSet *urlNonEndingChars = nil; static NSMutableCharacterSet *urlAfterEndingChars = nil; static NSMutableCharacterSet *urlStartChars = nil; @@ -522,48 +512,6 @@ static int cssEscapingCount; return object; } -- (NSString *) asCryptStringUsingSalt: (NSString *) theSalt -{ - char *buf; - - // The salt is weak here, but who cares anyway, crypt should not - // be used anymore - buf = crypt([self UTF8String], [theSalt UTF8String]); - return [NSString stringWithUTF8String: buf]; -} - -- (NSString *) asMD5String -{ - unsigned char md[MD5_DIGEST_LENGTH]; - char buf[80]; - int i; - - memset(md, 0, MD5_DIGEST_LENGTH); - memset(buf, 0, 80); - - EVP_Digest((const void *) [self UTF8String], strlen([self UTF8String]), md, NULL, EVP_md5(), NULL); - for (i = 0; i < MD5_DIGEST_LENGTH; i++) - sprintf(&(buf[i*2]), "%02x", md[i]); - - return [NSString stringWithUTF8String: buf]; -} - -- (NSString *) asSHA1String -{ - unsigned char sha[SHA_DIGEST_LENGTH]; - char buf[80]; - int i; - - memset(sha, 0, SHA_DIGEST_LENGTH); - memset(buf, 0, 80); - - SHA1((const void *)[self UTF8String], strlen([self UTF8String]), sha); - for (i = 0; i < SHA_DIGEST_LENGTH; i++) - sprintf(&(buf[i*2]), "%02x", sha[i]); - - return [NSString stringWithUTF8String: buf]; -} - - (NSString *) asSafeSQLString { return [[self stringByReplacingString: @"\\" withString: @"\\\\"] ============================================================ --- SoObjects/SOGo/SQLSource.m 3ba7f2ddd8b79c5caa63d11a0d9fea1cfe1e354a +++ SoObjects/SOGo/SQLSource.m 4544aedab94707f1aded7b4de8e5414f9b68a82f @@ -39,6 +39,7 @@ #import "SOGoConstants.h" #import "NSString+Utilities.h" +#import "NSString+Crypto.h" #import "SQLSource.h" @@ -47,7 +48,7 @@ * * c_uid - will be used for authentication - it's a username or username@domain.tld) * c_name - which can be identical to c_uid - will be used to uniquely identify entries) - * c_password - password of the user, plain-text, md5 or sha encoded for now + * c_password - password of the user, plain-text, md5, sha, ssha, sha256, sha2512 encoded for now * c_cn - the user's common name * mail - the user's mail address * @@ -151,28 +152,8 @@ if (!plainPassword || !encryptedPassword) return NO; - if ([_userPasswordAlgorithm caseInsensitiveCompare: @"none"] == NSOrderedSame) - { - return [plainPassword isEqualToString: encryptedPassword]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"crypt"] == NSOrderedSame) - { - return [[plainPassword asCryptStringUsingSalt: encryptedPassword] isEqualToString: encryptedPassword]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5"] == NSOrderedSame) - { - return [[plainPassword asMD5String] isEqualToString: encryptedPassword]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"sha"] == NSOrderedSame) - { - - return [[plainPassword asSHA1String] isEqualToString: encryptedPassword]; - } - - - [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; - - return NO; + return [plainPassword isEqualToCrypted: encryptedPassword + withDefaultScheme: _userPasswordAlgorithm]; } /** @@ -183,26 +164,8 @@ */ - (NSString *) _encryptPassword: (NSString *) plainPassword { - if ([_userPasswordAlgorithm caseInsensitiveCompare: @"none"] == NSOrderedSame) - { - return plainPassword; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"crypt"] == NSOrderedSame) - { - return [plainPassword asCryptStringUsingSalt: [plainPassword asMD5String]]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5"] == NSOrderedSame) - { - return [plainPassword asMD5String]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"sha"] == NSOrderedSame) - { - return [plainPassword asSHA1String]; - } - - [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; - - return plainPassword; + return [NSString stringWithFormat: @"{%@}%@", _userPasswordAlgorithm, + [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm] ]; } // |
Your patch is incomplete - you forgot to show the NSString+Crypto category. |
|
Ah ok, I missed adding this to the mtn repository. Updated the patch My main issue with Obj-C was that I have no idea when to use autorelease (which seems to me some kind of garbage collection, right?). |
|
2012-01-29 17:48
|
nsstring+crypto_updated.patch (17,022 bytes)
# # old_revision [df5dd81d62db331ba5c8de9f2257f9430c586534] # # add_file "SoObjects/SOGo/NSString+Crypto.h" # content [05c7e1f20826bdcc22933b8bac31e2d444ae2ef6] # # add_file "SoObjects/SOGo/NSString+Crypto.m" # content [6e74b500ba1b5a864cc8b5cd13c0f2b39c226baf] # # patch "SoObjects/SOGo/GNUmakefile" # from [9e466added539ccacaecd830173afdf9d4755802] # to [0a6f033637d6af50a1768f0462e2bd4fe4c6909f] # # patch "SoObjects/SOGo/NSString+Utilities.h" # from [09d123e206a68c40e8fc0301e25d2e6052a2abf4] # to [ed9a551529054cdd3fb175e24db3316b24309987] # # patch "SoObjects/SOGo/NSString+Utilities.m" # from [26a0ba2fa690ba7464585e9959c25229992a66ed] # to [d7e02c1a2b5633935f3fe16fd8b154e949c01c9b] # # patch "SoObjects/SOGo/SQLSource.m" # from [3ba7f2ddd8b79c5caa63d11a0d9fea1cfe1e354a] # to [4544aedab94707f1aded7b4de8e5414f9b68a82f] # ============================================================ --- SoObjects/SOGo/GNUmakefile 9e466added539ccacaecd830173afdf9d4755802 +++ SoObjects/SOGo/GNUmakefile 0a6f033637d6af50a1768f0462e2bd4fe4c6909f @@ -46,6 +46,7 @@ SOGo_HEADER_FILES = \ NSObject+Utilities.h \ NSString+DAV.h \ NSString+Utilities.h \ + NSString+Crypto.h \ NSURL+DAV.h \ \ SOGoAuthenticator.h \ @@ -114,6 +115,7 @@ SOGo_OBJC_FILES = \ NSObject+Utilities.m \ NSString+DAV.m \ NSString+Utilities.m \ + NSString+Crypto.m \ NSURL+DAV.m \ \ SOGoSession.m \ ============================================================ --- SoObjects/SOGo/NSString+Utilities.h 09d123e206a68c40e8fc0301e25d2e6052a2abf4 +++ SoObjects/SOGo/NSString+Utilities.h ed9a551529054cdd3fb175e24db3316b24309987 @@ -66,10 +66,6 @@ - (id) objectFromJSONString; -- (NSString *) asCryptStringUsingSalt: (NSString *) theSalt; -- (NSString *) asMD5String; -- (NSString *) asSHA1String; - - (NSString *) asSafeSQLString; - (NSUInteger) countOccurrencesOfString: (NSString *) substring; ============================================================ --- SoObjects/SOGo/NSString+Utilities.m 26a0ba2fa690ba7464585e9959c25229992a66ed +++ SoObjects/SOGo/NSString+Utilities.m d7e02c1a2b5633935f3fe16fd8b154e949c01c9b @@ -21,10 +21,6 @@ * Boston, MA 02111-1307, USA. */ -#ifndef __OpenBSD__ -#include <crypt.h> -#endif - #import <Foundation/NSArray.h> #import <Foundation/NSCharacterSet.h> #import <Foundation/NSData.h> @@ -44,12 +40,6 @@ #import "NSString+Utilities.h" -#define _XOPEN_SOURCE 1 -#include <unistd.h> -#include <openssl/evp.h> -#include <openssl/md5.h> -#include <openssl/sha.h> - static NSMutableCharacterSet *urlNonEndingChars = nil; static NSMutableCharacterSet *urlAfterEndingChars = nil; static NSMutableCharacterSet *urlStartChars = nil; @@ -522,48 +512,6 @@ static int cssEscapingCount; return object; } -- (NSString *) asCryptStringUsingSalt: (NSString *) theSalt -{ - char *buf; - - // The salt is weak here, but who cares anyway, crypt should not - // be used anymore - buf = crypt([self UTF8String], [theSalt UTF8String]); - return [NSString stringWithUTF8String: buf]; -} - -- (NSString *) asMD5String -{ - unsigned char md[MD5_DIGEST_LENGTH]; - char buf[80]; - int i; - - memset(md, 0, MD5_DIGEST_LENGTH); - memset(buf, 0, 80); - - EVP_Digest((const void *) [self UTF8String], strlen([self UTF8String]), md, NULL, EVP_md5(), NULL); - for (i = 0; i < MD5_DIGEST_LENGTH; i++) - sprintf(&(buf[i*2]), "%02x", md[i]); - - return [NSString stringWithUTF8String: buf]; -} - -- (NSString *) asSHA1String -{ - unsigned char sha[SHA_DIGEST_LENGTH]; - char buf[80]; - int i; - - memset(sha, 0, SHA_DIGEST_LENGTH); - memset(buf, 0, 80); - - SHA1((const void *)[self UTF8String], strlen([self UTF8String]), sha); - for (i = 0; i < SHA_DIGEST_LENGTH; i++) - sprintf(&(buf[i*2]), "%02x", sha[i]); - - return [NSString stringWithUTF8String: buf]; -} - - (NSString *) asSafeSQLString { return [[self stringByReplacingString: @"\\" withString: @"\\\\"] ============================================================ --- SoObjects/SOGo/SQLSource.m 3ba7f2ddd8b79c5caa63d11a0d9fea1cfe1e354a +++ SoObjects/SOGo/SQLSource.m 4544aedab94707f1aded7b4de8e5414f9b68a82f @@ -39,6 +39,7 @@ #import "SOGoConstants.h" #import "NSString+Utilities.h" +#import "NSString+Crypto.h" #import "SQLSource.h" @@ -47,7 +48,7 @@ * * c_uid - will be used for authentication - it's a username or username@domain.tld) * c_name - which can be identical to c_uid - will be used to uniquely identify entries) - * c_password - password of the user, plain-text, md5 or sha encoded for now + * c_password - password of the user, plain-text, md5, sha, ssha, sha256, sha2512 encoded for now * c_cn - the user's common name * mail - the user's mail address * @@ -151,28 +152,8 @@ if (!plainPassword || !encryptedPassword) return NO; - if ([_userPasswordAlgorithm caseInsensitiveCompare: @"none"] == NSOrderedSame) - { - return [plainPassword isEqualToString: encryptedPassword]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"crypt"] == NSOrderedSame) - { - return [[plainPassword asCryptStringUsingSalt: encryptedPassword] isEqualToString: encryptedPassword]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5"] == NSOrderedSame) - { - return [[plainPassword asMD5String] isEqualToString: encryptedPassword]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"sha"] == NSOrderedSame) - { - - return [[plainPassword asSHA1String] isEqualToString: encryptedPassword]; - } - - - [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; - - return NO; + return [plainPassword isEqualToCrypted: encryptedPassword + withDefaultScheme: _userPasswordAlgorithm]; } /** @@ -183,26 +164,8 @@ */ - (NSString *) _encryptPassword: (NSString *) plainPassword { - if ([_userPasswordAlgorithm caseInsensitiveCompare: @"none"] == NSOrderedSame) - { - return plainPassword; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"crypt"] == NSOrderedSame) - { - return [plainPassword asCryptStringUsingSalt: [plainPassword asMD5String]]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5"] == NSOrderedSame) - { - return [plainPassword asMD5String]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"sha"] == NSOrderedSame) - { - return [plainPassword asSHA1String]; - } - - [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; - - return plainPassword; + return [NSString stringWithFormat: @"{%@}%@", _userPasswordAlgorithm, + [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm] ]; } // ============================================================ --- /dev/null +++ SoObjects/SOGo/NSString+Crypto.h 05c7e1f20826bdcc22933b8bac31e2d444ae2ef6 @@ -0,0 +1,48 @@ +/* NSString+Crypto.h - this file is part of SOGo + * + * Author: Nicolas Höft + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef NSSTRING_CRYPTO_H +#define NSSTRING_CRYPTO_H + +#import <Foundation/NSString.h> + +@class NSObject; + +@interface NSString (SOGoCryptoExtension) +- (BOOL) isEqualToCrypted: (NSString *) cryptedPassword + withDefaultScheme: (NSString *) theScheme; + +- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme + withSalt: (NSString*) theSalt; +- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme; + ++ (NSString *) generateSaltForLength: (unsigned int) theLength; +- (NSArray *) splitPasswordWithDefaultScheme: (NSString *) defaultScheme; + +- (NSString *) asCryptStringUsingSalt: (NSString *) theSalt; +- (NSString *) asMD5String; +- (NSString *) asMD5CryptStringUsingSalt: (NSString*) theSalt; +- (NSString *) asSHA1String; +- (NSString *) asSSHAStringUsingSalt: (NSString*) theSalt; +- (NSString *) asSHA256String; +- (NSString *) asSHA512String; +@end + +#endif /* NSSTRING_CRYPTO_H */ ============================================================ --- /dev/null +++ SoObjects/SOGo/NSString+Crypto.m 6e74b500ba1b5a864cc8b5cd13c0f2b39c226baf @@ -0,0 +1,290 @@ +/* NSString+Crypto.m - this file is part of SOGo + * + * + * Author: Nicolas Höft + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __OpenBSD__ +#include <crypt.h> +#endif + +#import <Foundation/NSArray.h> + +#import "NSString+Crypto.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + + +#define _XOPEN_SOURCE 1 +#include <unistd.h> +#include <openssl/evp.h> +#include <openssl/md5.h> +#include <openssl/sha.h> + + +@implementation NSString (SOGoCryptoExtension) + ++ (NSString *) generateSaltForLength: (unsigned int) theLength +{ + char *buf; + int fd; + int i; + char rnd; + + fd = open("/dev/urandom", O_RDONLY); + + if (fd > 0) + { + i = theLength; + buf = (char *)malloc(theLength); + while(--i) + { + read(fd, &rnd, 1); + // generate only printable characters between 32 and 94 + buf[i] = (char)(((float)rnd / 256.)*94. + 32); + } + close(fd); + return [NSString stringWithUTF8String: buf]; + } + return nil; +} + +- (NSString *) extractCryptScheme +{ + NSRange r; + NSString* pwscheme; + int len; + + len = [self length]; + if(len == 0) + return @""; + if ([self characterAtIndex:0] != '{') + return @""; + + r = [self rangeOfString:@"}" options:(NSLiteralSearch)]; + if (r.length == 0) + return @""; + + r.length = (r.location - 1); + r.location = 1; + pwscheme = [[self substringWithRange:r] lowercaseString]; + [pwscheme autorelease]; + return pwscheme; +} + +- (NSString *) extractSalt: (NSString *) theScheme +{ + if([theScheme caseInsensitiveCompare: @"crypt"] == NSOrderedSame || + [theScheme caseInsensitiveCompare: @"md5-crypt"] == NSOrderedSame) + { + return self; + } + else if([theScheme caseInsensitiveCompare: @"ssha"] == NSOrderedSame) + { + return [self substringFromIndex: SHA_DIGEST_LENGTH*2]; + } + return @""; +} + +- (NSArray *) splitPasswordWithDefaultScheme: (NSString *) defaultScheme +{ + NSString *scheme; + NSString *pass; + NSString *salt; + NSRange range; + int selflen, len; + + selflen = [self length]; + + scheme = [self extractCryptScheme]; + len = [scheme length]; + if(len > 0) + range = NSMakeRange (len+2, selflen-len-2); + else + range = NSMakeRange (0, selflen); + if(len == 0) + scheme = defaultScheme; + + pass = [self substringWithRange: range]; + salt = [pass extractSalt: scheme]; + + return [NSArray arrayWithObjects: scheme, pass, salt, nil]; +} + + +- (BOOL) isEqualToCrypted: (NSString *) cryptedPassword + withDefaultScheme: (NSString *) theScheme +{ + NSArray *passInfo; + NSString *selfCrypted; + + passInfo = [cryptedPassword splitPasswordWithDefaultScheme: theScheme]; + selfCrypted = [self asCryptedPassUsingScheme: [passInfo objectAtIndex:0] + withSalt: [passInfo objectAtIndex:2] ]; + if( [selfCrypted isEqualToString: [passInfo objectAtIndex:1] ] == YES ) + return YES; + return NO; +} + +- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme +{ + return [self asCryptedPassUsingScheme: passwordScheme withSalt: @""]; +} + +- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme + withSalt: (NSString*) theSalt +{ + if ([passwordScheme caseInsensitiveCompare: @"none"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"plain"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"cleartext"] == NSOrderedSame) + { + return self; + } + else if ([passwordScheme caseInsensitiveCompare: @"crypt"] == NSOrderedSame) + { + return [self asCryptStringUsingSalt: theSalt]; + } + else if ([passwordScheme caseInsensitiveCompare: @"md5-crypt"] == NSOrderedSame) + { + return [self asMD5CryptStringUsingSalt: theSalt]; + } + else if ([passwordScheme caseInsensitiveCompare: @"md5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"plain-md5"] == NSOrderedSame) + { + return [self asMD5String]; + } + else if ([passwordScheme caseInsensitiveCompare: @"sha"] == NSOrderedSame) + { + return [self asSHA1String]; + } + else if ([passwordScheme caseInsensitiveCompare: @"ssha"] == NSOrderedSame) + { + return [self asSSHAStringUsingSalt: theSalt]; + } + else if ([passwordScheme caseInsensitiveCompare: @"sha256"] == NSOrderedSame) + { + return [self asSHA256String]; + } + else if ([passwordScheme caseInsensitiveCompare: @"sha512"] == NSOrderedSame) + { + return [self asSHA512String]; + } + return self; +} + +- (NSString *) asCryptStringUsingSalt: (NSString *) theSalt +{ + if([theSalt length] == 0) theSalt = [NSString generateSaltForLength: 10]; + char *buf; + + // The salt is weak here, but who cares anyway, crypt should not + // be used anymore + buf = crypt([self UTF8String], [theSalt UTF8String]); + return [NSString stringWithUTF8String: buf]; +} + +- (NSString *) asMD5String +{ + unsigned char md[MD5_DIGEST_LENGTH]; + char buf[MD5_DIGEST_LENGTH*2+1]; + int i; + + memset(md, 0, MD5_DIGEST_LENGTH); + memset(buf, 0, MD5_DIGEST_LENGTH*2+1); + + EVP_Digest((const void *) [self UTF8String], strlen([self UTF8String]), md, NULL, EVP_md5(), NULL); + for (i = 0; i < MD5_DIGEST_LENGTH; i++) + sprintf(&(buf[i*2]), "%02x", md[i]); + + return [NSString stringWithUTF8String: buf]; +} + +- (NSString *) asSSHAStringUsingSalt: (NSString*) theSalt +{ + NSString * saltedPass; + + if([theSalt length] == 0) theSalt = [NSString generateSaltForLength: 10]; + + saltedPass = [[NSString stringWithFormat: @"%@%@", self, theSalt] asSHA1String]; + return [NSString stringWithFormat: @"%@%@", saltedPass, theSalt]; +} + +- (NSString *) asSHA1String +{ + unsigned char sha[SHA_DIGEST_LENGTH]; + char buf[SHA_DIGEST_LENGTH*2+1]; + int i; + + memset(sha, 0, SHA_DIGEST_LENGTH); + memset(buf, 0, SHA_DIGEST_LENGTH*2+1); + + SHA1((const void *)[self UTF8String], strlen([self UTF8String]), sha); + for (i = 0; i < SHA_DIGEST_LENGTH; i++) + sprintf(&(buf[i*2]), "%02x", sha[i]); + + return [NSString stringWithUTF8String: buf]; +} + +- (NSString *) asSHA256String +{ + unsigned char sha[SHA256_DIGEST_LENGTH]; + char buf[SHA256_DIGEST_LENGTH*2+1]; + int i; + + memset(sha, 0, SHA256_DIGEST_LENGTH); + memset(buf, 0, SHA256_DIGEST_LENGTH*2+1); + + SHA256((const void *)[self UTF8String], strlen([self UTF8String]), sha); + for (i = 0; i < SHA256_DIGEST_LENGTH; i++) + sprintf(&(buf[i*2]), "%02x", sha[i]); + + return [NSString stringWithUTF8String: buf]; +} + +- (NSString *) asSHA512String +{ + unsigned char sha[SHA256_DIGEST_LENGTH]; + char buf[SHA512_DIGEST_LENGTH*2+1]; + int i; + + memset(sha, 0, SHA512_DIGEST_LENGTH); + memset(buf, 0, SHA512_DIGEST_LENGTH*2+1); + + SHA512((const void *)[self UTF8String], strlen([self UTF8String]), sha); + for (i = 0; i < SHA512_DIGEST_LENGTH; i++) + sprintf(&(buf[i*2]), "%02x", sha[i]); + + return [NSString stringWithUTF8String: buf]; +} + + +- (NSString *) asMD5CryptStringUsingSalt: (NSString*) theSalt +{ + char *buf; + + if([theSalt length] == 0) + theSalt = [NSString stringWithFormat: @"$1$%@$", [NSString generateSaltForLength: 10]]; + + buf = crypt([self UTF8String], [theSalt UTF8String]); + return [NSString stringWithUTF8String: buf]; +} + +@end |
Any feedback? I'd be willing to improve it, if there are any comments.. |
|
This patch looks really great. Do you think you could implement SSHA256 and SSHA512 as well? That would make SOGo fit nicely into dovecot. |
|
This shouldn't be an issue to add SSHA256/515, just look for SSHA in the source, the rest is copy&paste and minor adjustment. |
|
Could we also make these algorithms available to the LDAP backend as well? |
|
I am interested in SSHA for LDAP, too :-) |
|
See comments in bug 0001804 about using extended operations to modify ldap passwords. (The encryption/encoding is then left over for the ldap server to descide) |
|
I am actually rewriting the patch right now, including SSHA(1/256/512) with base64 encoding in order to make it more compatible with dovecot (see http://wiki.dovecot.org/Authentication/PasswordSchemes ) |
|
2012-05-20 11:34
|
crypto_v3.patch (34,277 bytes)
# # old_revision [471dc0f5e3b2a6fc571aca4e14c0a5161cf7af0b] # # add_file "SoObjects/SOGo/NSData+Crypto.h" # content [c9187ee4b0ebd1f7a2eeeb0f13837493aa284580] # # add_file "SoObjects/SOGo/NSData+Crypto.m" # content [0ea66d4e8b75ff60c4e4c57980860ffcd6fe3572] # # add_file "SoObjects/SOGo/NSString+Crypto.h" # content [50ffbd75973627dda748687423fb4bbb7b798724] # # add_file "SoObjects/SOGo/NSString+Crypto.m" # content [2bb1ed1c269a147c2285a0d2f4629997d20961bf] # # patch "SoObjects/SOGo/GNUmakefile" # from [9e466added539ccacaecd830173afdf9d4755802] # to [0e4d9af53dddf014fe6fccf1d81e751d8dd2bc3c] # # patch "SoObjects/SOGo/LDAPSource.m" # from [49cd1d275323dd81ba0ad5566e542b725856c4a9] # to [29be142502d24ed06e5b41dbd330ef24efb4a4b6] # # patch "SoObjects/SOGo/NSString+Utilities.h" # from [d2661ef19253d96d64dcf11127ffe6882f90b4d4] # to [c38d80d6ebe9592a9f2ee954282718d7265dec51] # # patch "SoObjects/SOGo/NSString+Utilities.m" # from [800b97cfc8f806e14c310ed8628c94a9934fd80a] # to [eb4c7cd585c64cb8fb0273c3e89be36da11e5f24] # # patch "SoObjects/SOGo/SOGoUserManager.m" # from [9bf968b36558145edd71840dd180876c1d5d7175] # to [344659dcdf8d929bb824d8856d05c1de85ce801e] # # patch "SoObjects/SOGo/SQLSource.m" # from [8e7fb09f6d34231ed0e1c57db516961d7a8bf0bb] # to [3928a5ed6dca41cf9fa04a0f15c964b13d000747] # ============================================================ --- SoObjects/SOGo/GNUmakefile 9e466added539ccacaecd830173afdf9d4755802 +++ SoObjects/SOGo/GNUmakefile 0e4d9af53dddf014fe6fccf1d81e751d8dd2bc3c @@ -46,6 +46,8 @@ SOGo_HEADER_FILES = \ NSObject+Utilities.h \ NSString+DAV.h \ NSString+Utilities.h \ + NSString+Crypto.h \ + NSData+Crypto.h \ NSURL+DAV.h \ \ SOGoAuthenticator.h \ @@ -114,6 +116,8 @@ SOGo_OBJC_FILES = \ NSObject+Utilities.m \ NSString+DAV.m \ NSString+Utilities.m \ + NSString+Crypto.m \ + NSData+Crypto.m \ NSURL+DAV.m \ \ SOGoSession.m \ ============================================================ --- SoObjects/SOGo/LDAPSource.m 49cd1d275323dd81ba0ad5566e542b725856c4a9 +++ SoObjects/SOGo/LDAPSource.m 29be142502d24ed06e5b41dbd330ef24efb4a4b6 @@ -40,6 +40,7 @@ #import "LDAPSourceSchema.h" #import "NSArray+Utilities.h" #import "NSString+Utilities.h" +#import "NSString+Crypto.h" #import "SOGoDomainDefaults.h" #import "SOGoSystemDefaults.h" @@ -639,26 +640,13 @@ andMultipleBookingsField: (NSString *) n */ - (NSString *) _encryptPassword: (NSString *) plainPassword { - if ([_userPasswordAlgorithm caseInsensitiveCompare: @"none"] == NSOrderedSame) - { - return plainPassword; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"crypt"] == NSOrderedSame) - { - return [NSString stringWithFormat: @"{CRYPT}%@", [plainPassword asCryptStringUsingSalt: [plainPassword asMD5String]]]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5"] == NSOrderedSame) - { - return [NSString stringWithFormat: @"{MD5}%@", [plainPassword asMD5String]]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"sha"] == NSOrderedSame) - { - return [NSString stringWithFormat: @"{SHA}%@", [plainPassword asSHA1String]]; - } - - [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; - - return plainPassword; + NSString *password; + password = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm]; + + if (password == nil) + [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; + + return [NSString stringWithFormat: @"{%@}%@", _userPasswordAlgorithm, password]; } // ============================================================ --- /dev/null +++ SoObjects/SOGo/NSData+Crypto.h c9187ee4b0ebd1f7a2eeeb0f13837493aa284580 @@ -0,0 +1,56 @@ +/* NSData+Crypto.h - this file is part of SOGo + * + * Author: Nicolas Höft + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef NSDATA_CRYPTO_H +#define NSDATA_CRYPTO_H + +#import <Foundation/NSData.h> + +@class NSObject; + +@interface NSData (SOGoCryptoExtension) + +- (NSData *) asCryptedPassUsingScheme: (NSString *) passwordScheme + withSalt: (NSData *) theSalt; + +- (NSData *) asMD5; +- (NSData *) asSMD5UsingSalt: (NSData *) theSalt; +- (NSData *) asSHA1; +- (NSData *) asSSHAUsingSalt: (NSData *) theSalt; +- (NSData *) asSHA256; +- (NSData *) asSSHA256UsingSalt: (NSData *) theSalt; +- (NSData *) asSHA512; +- (NSData *) asSSHA512UsingSalt: (NSData *) theSalt; + +- (NSData *) asCryptUsingSalt: (NSData *) theSalt; +- (NSData *) asMD5CryptUsingSalt: (NSData *) theSalt; + +- (NSData *) extractSalt: (NSString *) theScheme; + ++ (NSData *) generateSaltForLength: (unsigned int) theLength + withBase64: (BOOL) doBase64; ++ (NSData *) generateSaltForLength: (unsigned int) theLength; + ++ (NSString *) encodeDataAsHexString: (NSData* ) theData; ++ (NSData *) decodeDataFromHexString: (NSString* ) theString; + +@end + +#endif /* NSDATA_CRYPTO_H */ ============================================================ --- /dev/null +++ SoObjects/SOGo/NSData+Crypto.m 0ea66d4e8b75ff60c4e4c57980860ffcd6fe3572 @@ -0,0 +1,411 @@ +/* NSData+Crypto.m - this file is part of SOGo + * + * + * Author: Nicolas Höft + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __OpenBSD__ +#include <crypt.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#define _XOPEN_SOURCE 1 +#include <unistd.h> +#include <openssl/evp.h> +#include <openssl/md5.h> +#include <openssl/sha.h> + +#import <NGExtensions/NGBase64Coding.h> +#import "NSData+Crypto.h" + +unsigned charTo4Bits(char c); + + +@implementation NSData (SOGoCryptoExtension) + ++ (NSString *) encodeDataAsHexString: (NSData *) theData +{ + unsigned int byteLength = [theData length], byteCounter = 0; + unsigned int stringLength = (byteLength * 2) + 1, stringCounter = 0; + unsigned char dstBuffer[stringLength]; + unsigned char srcBuffer[byteLength]; + unsigned char *srcPtr = srcBuffer; + [theData getBytes: srcBuffer]; + const unsigned char t[16] = "0123456789abcdef"; + + for (; byteCounter < byteLength; byteCounter++) + { + unsigned src = *srcPtr; + dstBuffer[stringCounter++] = t[src >> 4]; + dstBuffer[stringCounter++] = t[src & 15]; + srcPtr++; + } + + dstBuffer[stringCounter] = '\0'; + return [NSString stringWithUTF8String: (char*)dstBuffer]; +} + ++ (NSData *) decodeDataFromHexString: (NSString *) theString +{ + unsigned int stringLength = [theString length]; + unsigned int byteLength = stringLength/2; + unsigned int byteCounter = 0; + unsigned char srcBuffer[stringLength]; + [theString getCString:(char *)srcBuffer]; + unsigned char *srcPtr = srcBuffer; + unsigned char dstBuffer[byteLength]; + unsigned char *dst = dstBuffer; + while (byteCounter < byteLength) + { + unsigned char c = *srcPtr++; + unsigned char d = *srcPtr++; + unsigned hi = 0, lo = 0; + hi = charTo4Bits(c); + lo = charTo4Bits(d); + if (hi == 255 || lo == 255) + { + //errorCase + return nil; + } + dstBuffer[byteCounter++] = ((hi << 4) | lo); + } + return [NSData dataWithBytes: dst length: byteLength]; +} + ++ (NSData *) generateSaltForLength: (unsigned int) theLength +{ + return [NSData generateSaltForLength: theLength withBase64: NO]; +} + ++ (NSData *) generateSaltForLength: (unsigned int) theLength + withBase64: (BOOL) doBase64 +{ + char *buf; + int fd; + NSData *data; + + fd = open("/dev/urandom", O_RDONLY); + + if (fd > 0) + { + buf = (char *)malloc(theLength); + read(fd, buf, theLength); + close(fd); + + data = [NSData dataWithBytesNoCopy: buf length: theLength freeWhenDone: YES]; + if(doBase64 == YES) + { + return [data dataByEncodingBase64WithLineLength: 1024]; + } + return data; + } + return nil; +} + +- (NSData *) asCryptedPassUsingScheme: (NSString *) passwordScheme + withSalt: (NSData *) theSalt +{ + if ([passwordScheme caseInsensitiveCompare: @"none"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"plain"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"cleartext"] == NSOrderedSame) + { + return self; + } + else if ([passwordScheme caseInsensitiveCompare: @"crypt"] == NSOrderedSame) + { + return [self asCryptUsingSalt: theSalt]; + } + else if ([passwordScheme caseInsensitiveCompare: @"md5-crypt"] == NSOrderedSame) + { + return [self asMD5CryptUsingSalt: theSalt]; + } + else if ([passwordScheme caseInsensitiveCompare: @"md5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"plain-md5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"ldap-md5"] == NSOrderedSame) + { + return [self asMD5]; + } + else if ([passwordScheme caseInsensitiveCompare: @"smd5"] == NSOrderedSame) + { + return [self asSMD5UsingSalt: theSalt]; + } + else if ([passwordScheme caseInsensitiveCompare: @"sha"] == NSOrderedSame) + { + return [self asSHA1]; + } + else if ([passwordScheme caseInsensitiveCompare: @"ssha"] == NSOrderedSame) + { + return [self asSSHAUsingSalt: theSalt]; + } + else if ([passwordScheme caseInsensitiveCompare: @"sha256"] == NSOrderedSame) + { + return [self asSHA256]; + } + else if ([passwordScheme caseInsensitiveCompare: @"ssha256"] == NSOrderedSame) + { + return [self asSSHA256UsingSalt: theSalt]; + } + else if ([passwordScheme caseInsensitiveCompare: @"sha512"] == NSOrderedSame) + { + return [self asSHA512]; + } + else if ([passwordScheme caseInsensitiveCompare: @"ssha512"] == NSOrderedSame) + { + return [self asSSHA512UsingSalt: theSalt]; + } + // in case the scheme was not detected, return nil + return nil; +} + + +- (NSData *) asMD5 +{ + unsigned char md[MD5_DIGEST_LENGTH]; + memset(md, 0, MD5_DIGEST_LENGTH); + + EVP_Digest([self bytes], [self length], md, NULL, EVP_md5(), NULL); + + return [NSData dataWithBytes: md length: MD5_DIGEST_LENGTH]; +} + +- (NSData *) asSHA1 +{ + unsigned char sha[SHA_DIGEST_LENGTH]; + memset(sha, 0, SHA_DIGEST_LENGTH); + + SHA1([self bytes], [self length], sha); + + return [NSData dataWithBytes: sha length: SHA_DIGEST_LENGTH]; +} + +- (NSData *) asSHA256 +{ + unsigned char sha[SHA256_DIGEST_LENGTH]; + memset(sha, 0, SHA256_DIGEST_LENGTH); + + SHA256([self bytes], [self length], sha); + + return [NSData dataWithBytes: sha length: SHA256_DIGEST_LENGTH]; +} + +- (NSData *) asSHA512 +{ + unsigned char sha[SHA512_DIGEST_LENGTH]; + memset(sha, 0, SHA512_DIGEST_LENGTH); + + SHA512([self bytes], [self length], sha); + + return [NSData dataWithBytes: sha length: SHA512_DIGEST_LENGTH]; +} + +- (NSData *) asSSHAUsingSalt: (NSData *) theSalt +{ + // SSHA works following: SSHA(pass, salt) = SHA1(pass + salt) + salt + NSMutableData *sshaData; + + // generate salt, if not available + if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8]; + + // put the pass and salt together as one data array + sshaData = [NSMutableData dataWithData: self]; + [sshaData appendData: theSalt]; + // generate SHA1 from pass + salt + sshaData = [NSMutableData dataWithData: [sshaData asSHA1]]; + // append salt again + [sshaData appendData: theSalt]; + + return sshaData; +} + +- (NSData *) asSSHA256UsingSalt: (NSData *) theSalt +{ + // SSHA256 works following: SSHA(pass, salt) = SHA256(pass + salt) + salt + NSMutableData *sshaData; + + // generate salt, if not available + if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8]; + + // put the pass and salt together as one data array + sshaData = [NSMutableData dataWithData: self]; + [sshaData appendData: theSalt]; + // generate SHA1 from pass + salt + sshaData = [NSMutableData dataWithData: [sshaData asSHA256]]; + // append salt again + [sshaData appendData: theSalt]; + + return sshaData; +} + +- (NSData *) asSSHA512UsingSalt: (NSData *) theSalt +{ + // SSHA512 works following: SSHA(pass, salt) = SHA512(pass + salt) + salt + NSMutableData *sshaData; + + // generate salt, if not available + if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8]; + + // put the pass and salt together as one data array + sshaData = [NSMutableData dataWithData: self]; + [sshaData appendData: theSalt]; + // generate SHA1 from pass + salt + sshaData = [NSMutableData dataWithData: [sshaData asSHA512]]; + // append salt again + [sshaData appendData: theSalt]; + + return sshaData; +} + +- (NSData *) asSMD5UsingSalt: (NSData *) theSalt +{ + // SMD5 works following: SMD5(pass, salt) = MD5(pass + salt) + salt + NSMutableData *smdData; + + // generate salt, if not available + if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8]; + + // put the pass and salt together as one data array + smdData = [NSMutableData dataWithData: self]; + [smdData appendData: theSalt]; + // generate SHA1 from pass + salt + smdData = [NSMutableData dataWithData: [smdData asMD5]]; + // append salt again + [smdData appendData: theSalt]; + + return smdData; +} + + +- (NSData *) asMD5CryptUsingSalt: (NSData *) theSalt +{ + char *buf; + NSString *saltString; + NSString *cryptString; + + // crypt() works with strings, so convert NSData to strings + cryptString = [[NSString alloc] initWithData: self encoding: NSUTF8StringEncoding]; + [cryptString autorelease]; + + if ([theSalt length] == 0) + { + // make sure these characters are all printable by using base64 + NSData *saltData = [NSData generateSaltForLength: 8 withBase64: YES]; + NSString *tmpStr = [[NSString alloc] initWithData: saltData encoding: NSASCIIStringEncoding]; + [tmpStr autorelease]; + // and prepend a "$1$" to mark this as md5-crypt + saltString = [NSString stringWithFormat: @"$1$%@$", tmpStr]; + } + else + { + saltString = [[NSString alloc] initWithData: theSalt encoding: NSUTF8StringEncoding]; + [saltString autorelease]; + } + + buf = crypt([cryptString UTF8String], [saltString UTF8String]); + return [NSData dataWithBytes: buf length: strlen(buf)]; +} + +- (NSData *) asCryptUsingSalt: (NSData *) theSalt +{ + char *buf; + NSString *saltString; + NSString *cryptString; + + // crypt() works with strings, so convert NSData to strings + cryptString = [[NSString alloc] initWithData: self encoding: NSUTF8StringEncoding]; + [cryptString autorelease]; + + if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8 withBase64: YES]; + + saltString = [[NSString alloc] initWithData: theSalt encoding: NSUTF8StringEncoding]; + [saltString autorelease]; + + // The salt is weak here, but who cares anyway, crypt should not + // be used anymore + buf = crypt([cryptString UTF8String], [saltString UTF8String]); + return [NSData dataWithBytes: buf length: strlen(buf)]; +} + +- (NSData *) extractSalt: (NSString *) theScheme +{ + NSRange r; + int len; + len = [self length]; + if (len == 0) + return [NSData data]; + + // for the ssha schemes the salt is appended at the endif + // so the range with the salt are bytes after each digest length + if ([theScheme caseInsensitiveCompare: @"crypt"] == NSOrderedSame || + [theScheme caseInsensitiveCompare: @"md5-crypt"] == NSOrderedSame) + { + // for crypt schemes simply use the whole string + // the crypt() function is able to extract it by itself + r = NSMakeRange(0, len); + } + else if ([theScheme caseInsensitiveCompare: @"ssha"] == NSOrderedSame) + { + r = NSMakeRange(SHA_DIGEST_LENGTH, len - SHA_DIGEST_LENGTH); + } + else if ([theScheme caseInsensitiveCompare: @"ssha256"] == NSOrderedSame) + { + r = NSMakeRange(SHA256_DIGEST_LENGTH, len - SHA256_DIGEST_LENGTH); + } + else if ([theScheme caseInsensitiveCompare: @"ssha512"] == NSOrderedSame) + { + r = NSMakeRange(SHA512_DIGEST_LENGTH, len - SHA512_DIGEST_LENGTH); + } + else if ([theScheme caseInsensitiveCompare: @"smd5"] == NSOrderedSame) + { + r = NSMakeRange(MD5_DIGEST_LENGTH, len - MD5_DIGEST_LENGTH); + } + else + { + // return empty string on unknown scheme + return [NSData data]; + } + + return [self subdataWithRange: r]; +} + +@end + +unsigned charTo4Bits(char c) +{ + unsigned bits = 0; + if (c > '/' && c < ':') + { + bits = c - '0'; + } + else if (c > '@' && c < 'G') + { + bits = (c- 'A') + 10; + } + else if (c > '`' && c < 'g') + { + bits = (c- 'a') + 10; + } + else + { + bits = 255; + } + return bits; +} ============================================================ --- /dev/null +++ SoObjects/SOGo/NSString+Crypto.h 50ffbd75973627dda748687423fb4bbb7b798724 @@ -0,0 +1,58 @@ +/* NSString+Crypto.h - this file is part of SOGo + * + * Author: Nicolas Höft + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef NSSTRING_CRYPTO_H +#define NSSTRING_CRYPTO_H + +#import <Foundation/NSData.h> +#import <Foundation/NSString.h> + +typedef enum { + encDefault, + encPlain, + encHex, + encBase64, +} keyEncoding; + +@class NSObject; + +@interface NSString (SOGoCryptoExtension) + +- (BOOL) isEqualToCrypted: (NSString *) cryptedPassword + withDefaultScheme: (NSString *) theScheme; + +- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme + withSalt: (NSData *) theSalt + andEncoding: (keyEncoding) encoding; + +// this method uses the default encoding (base64, plain, hex) +// and generates a salt when necessary +- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme; + +- (NSArray *) splitPasswordWithDefaultScheme: (NSString *) defaultScheme; + +- (NSString *) asSHA1String; +- (NSString *) asMD5String; + ++ (keyEncoding) getDefaultEncodingForScheme: (NSString *) passwordScheme; + +@end + +#endif /* NSSTRING_CRYPTO_H */ ============================================================ --- /dev/null +++ SoObjects/SOGo/NSString+Crypto.m 2bb1ed1c269a147c2285a0d2f4629997d20961bf @@ -0,0 +1,248 @@ +/* NSString+Crypto.m - this file is part of SOGo + * + * + * Author: Nicolas Höft + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import <Foundation/NSArray.h> +#import <Foundation/NSValue.h> + +#import "NSString+Crypto.h" +#import "NSData+Crypto.h" +#import <NGExtensions/NGBase64Coding.h> + +@implementation NSString (SOGoCryptoExtension) + +- (NSString *) extractCryptScheme +{ + NSRange r; + int len; + + len = [self length]; + if (len == 0) + return @""; + if ([self characterAtIndex:0] != '{') + return @""; + + r = [self rangeOfString:@"}" options:(NSLiteralSearch)]; + if (r.length == 0) + return @""; + + r.length = (r.location - 1); + r.location = 1; + return [[self substringWithRange:r] lowercaseString]; +} + + +// This function returns an two-element array containing +// the scheme and the rest of the (complete, including optional salt) password +- (NSArray *) splitPasswordWithDefaultScheme: (NSString *) defaultScheme +{ + NSString *scheme; + NSString *pass; + NSArray *schemeComps; + keyEncoding encoding; + + NSRange range; + int selflen, len; + + selflen = [self length]; + + scheme = [self extractCryptScheme]; + len = [scheme length]; + if (len > 0) + range = NSMakeRange (len+2, selflen-len-2); + else + range = NSMakeRange (0, selflen); + if (len == 0) + scheme = defaultScheme; + + encoding = [NSString getDefaultEncodingForScheme: scheme]; + + // get the encoding which may be part of the scheme + // e.g. ssha.hex forces a hex encoded ssha scheme + // possible is "b64" or "hex" + schemeComps = [scheme componentsSeparatedByString: @"."]; + if ([schemeComps count] == 2) + { + NSString *stringEncoding; + // scheme without encoding string is the first item + scheme = [schemeComps objectAtIndex: 0]; + // encoding string is second item + stringEncoding = [schemeComps objectAtIndex: 1]; + if ([stringEncoding caseInsensitiveCompare: @"hex"] == NSOrderedSame) + { + encoding = encHex; + } + else if ([stringEncoding caseInsensitiveCompare: @"b64"] == NSOrderedSame) + { + encoding = encBase64; + } + } + + pass = [self substringWithRange: range]; + return [NSArray arrayWithObjects: scheme, pass, [NSNumber numberWithInt: encoding], nil]; +} + + +- (BOOL) isEqualToCrypted: (NSString *) cryptedPassword + withDefaultScheme: (NSString *) theScheme +{ + NSArray *passInfo; + NSString *selfCrypted; + NSString *pass; + NSString *scheme; + NSData *salt; + NSData *decodedData; + NSNumber *encodingNumber; + keyEncoding encoding; + + // split scheme and pass + passInfo = [cryptedPassword splitPasswordWithDefaultScheme: theScheme]; + + scheme = [passInfo objectAtIndex: 0]; + pass = [passInfo objectAtIndex: 1]; + encodingNumber = [passInfo objectAtIndex: 2]; + encoding = [encodingNumber integerValue]; + + if (encoding == encHex) + { + decodedData = [NSData decodeDataFromHexString: pass]; + + if(decodedData == nil) + { + decodedData = [NSData data]; + } + else + { + // decoding was successful, now make sure + // that the pass is in lowercase since decodeDataFromHexString uses + // lowercase charaters, too + pass = [pass lowercaseString]; + } + } + else if(encoding == encBase64) + { + decodedData = [pass dataByDecodingBase64]; + if(decodedData == nil) + { + decodedData = [NSData data]; + } + } + else + { + decodedData = [pass dataUsingEncoding: NSUTF8StringEncoding]; + } + + salt = [decodedData extractSalt: scheme]; + + // encrypt self with the salt an compare the results + selfCrypted = [self asCryptedPassUsingScheme: scheme + withSalt: salt + andEncoding: encoding]; + // return always false when there was a problem + if (selfCrypted == nil) + return NO; + + if ([selfCrypted isEqualToString: pass] == YES) + return YES; + return NO; +} + +- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme +{ + return [self asCryptedPassUsingScheme: passwordScheme + withSalt: [NSData data] + andEncoding: encDefault]; +} + +- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme + withSalt: (NSData *) theSalt + andEncoding: (keyEncoding) userEncoding +{ + keyEncoding dataEncoding; + NSData* cryptedData; + // convert NSString to NSData and apply encryption scheme + cryptedData = [self dataUsingEncoding: NSUTF8StringEncoding]; + cryptedData = [cryptedData asCryptedPassUsingScheme: passwordScheme withSalt: theSalt]; + // abort on unsupported scheme or error + if (cryptedData == nil) + return nil; + + // use default encoding scheme, when set to default + if (userEncoding == encDefault) + dataEncoding = [NSString getDefaultEncodingForScheme: passwordScheme]; + else + dataEncoding = userEncoding; + + if (dataEncoding == encHex) + { + // hex encoding + return [NSData encodeDataAsHexString: cryptedData]; + } + else if(dataEncoding == encBase64) + { + // base64 encoding + NSString *s = [[NSString alloc] initWithData: [cryptedData dataByEncodingBase64WithLineLength: 1024] + encoding: NSASCIIStringEncoding]; + return [s autorelease]; + } + + // plain string + return [[[NSString alloc] initWithData: cryptedData encoding: NSUTF8StringEncoding] autorelease]; +} + ++ (keyEncoding) getDefaultEncodingForScheme: (NSString *) passwordScheme +{ + // in order to keep backwards-compatibility, hex encoding is used for sha1 here + if ([passwordScheme caseInsensitiveCompare: @"md5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"plain-md5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"sha"] == NSOrderedSame) + { + return encHex; + } + else if ([passwordScheme caseInsensitiveCompare: @"smd5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"ldap-md5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"ssha"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"sha256"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"ssha256"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"sha512"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"ssha512"] == NSOrderedSame) + { + return encBase64; + } + return encPlain; +} + +// backwards-compatibility +- (NSString *) asSHA1String; +{ + NSData *cryptData; + cryptData = [self dataUsingEncoding: NSUTF8StringEncoding]; + return [NSData encodeDataAsHexString: [cryptData asSHA1] ]; +} + +// backwards-compatibility +- (NSString *) asMD5String; +{ + NSData *cryptData; + cryptData = [self dataUsingEncoding: NSUTF8StringEncoding]; + return [NSData encodeDataAsHexString: [cryptData asMD5] ]; +} + +@end ============================================================ --- SoObjects/SOGo/NSString+Utilities.h d2661ef19253d96d64dcf11127ffe6882f90b4d4 +++ SoObjects/SOGo/NSString+Utilities.h c38d80d6ebe9592a9f2ee954282718d7265dec51 @@ -66,10 +66,6 @@ - (id) objectFromJSONString; -- (NSString *) asCryptStringUsingSalt: (NSString *) theSalt; -- (NSString *) asMD5String; -- (NSString *) asSHA1String; - - (NSString *) asSafeSQLString; - (NSUInteger) countOccurrencesOfString: (NSString *) substring; ============================================================ --- SoObjects/SOGo/NSString+Utilities.m 800b97cfc8f806e14c310ed8628c94a9934fd80a +++ SoObjects/SOGo/NSString+Utilities.m eb4c7cd585c64cb8fb0273c3e89be36da11e5f24 @@ -21,10 +21,6 @@ * Boston, MA 02111-1307, USA. */ -#ifndef __OpenBSD__ -#include <crypt.h> -#endif - #import <Foundation/NSArray.h> #import <Foundation/NSCharacterSet.h> #import <Foundation/NSData.h> @@ -45,12 +41,6 @@ #import "NSString+Utilities.h" -#define _XOPEN_SOURCE 1 -#include <unistd.h> -#include <openssl/evp.h> -#include <openssl/md5.h> -#include <openssl/sha.h> - static NSMutableCharacterSet *urlNonEndingChars = nil; static NSMutableCharacterSet *urlAfterEndingChars = nil; static NSMutableCharacterSet *urlStartChars = nil; @@ -534,48 +524,6 @@ static int cssEscapingCount; return object; } -- (NSString *) asCryptStringUsingSalt: (NSString *) theSalt -{ - char *buf; - - // The salt is weak here, but who cares anyway, crypt should not - // be used anymore - buf = crypt([self UTF8String], [theSalt UTF8String]); - return [NSString stringWithUTF8String: buf]; -} - -- (NSString *) asMD5String -{ - unsigned char md[MD5_DIGEST_LENGTH]; - char buf[80]; - int i; - - memset(md, 0, MD5_DIGEST_LENGTH); - memset(buf, 0, 80); - - EVP_Digest((const void *) [self UTF8String], strlen([self UTF8String]), md, NULL, EVP_md5(), NULL); - for (i = 0; i < MD5_DIGEST_LENGTH; i++) - sprintf(&(buf[i*2]), "%02x", md[i]); - - return [NSString stringWithUTF8String: buf]; -} - -- (NSString *) asSHA1String -{ - unsigned char sha[SHA_DIGEST_LENGTH]; - char buf[80]; - int i; - - memset(sha, 0, SHA_DIGEST_LENGTH); - memset(buf, 0, 80); - - SHA1((const void *)[self UTF8String], strlen([self UTF8String]), sha); - for (i = 0; i < SHA_DIGEST_LENGTH; i++) - sprintf(&(buf[i*2]), "%02x", sha[i]); - - return [NSString stringWithUTF8String: buf]; -} - - (NSString *) asSafeSQLString { return [[self stringByReplacingString: @"\\" withString: @"\\\\"] ============================================================ --- SoObjects/SOGo/SOGoUserManager.m 9bf968b36558145edd71840dd180876c1d5d7175 +++ SoObjects/SOGo/SOGoUserManager.m 344659dcdf8d929bb824d8856d05c1de85ce801e @@ -33,6 +33,7 @@ #import "NSArray+Utilities.h" #import "NSString+Utilities.h" +#import "NSString+Crypto.h" #import "NSObject+Utilities.h" #import "SOGoDomainDefaults.h" #import "SOGoSource.h" ============================================================ --- SoObjects/SOGo/SQLSource.m 8e7fb09f6d34231ed0e1c57db516961d7a8bf0bb +++ SoObjects/SOGo/SQLSource.m 3928a5ed6dca41cf9fa04a0f15c964b13d000747 @@ -39,6 +39,7 @@ #import "SOGoConstants.h" #import "NSString+Utilities.h" +#import "NSString+Crypto.h" #import "SQLSource.h" @@ -47,7 +48,7 @@ * * c_uid - will be used for authentication - it's a username or username@domain.tld) * c_name - which can be identical to c_uid - will be used to uniquely identify entries) - * c_password - password of the user, plain-text, md5 or sha encoded for now + * c_password - password of the user, possible values: plain, md5, sha, ssha, sha256, ssha256, ssha2512, smd5, crypt, md5-crypt (with or without ".hex" or ".b64" appended) * c_cn - the user's common name * mail - the user's mail address * @@ -157,28 +158,8 @@ if (!plainPassword || !encryptedPassword) return NO; - if ([_userPasswordAlgorithm caseInsensitiveCompare: @"none"] == NSOrderedSame) - { - return [plainPassword isEqualToString: encryptedPassword]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"crypt"] == NSOrderedSame) - { - return [[plainPassword asCryptStringUsingSalt: encryptedPassword] isEqualToString: encryptedPassword]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5"] == NSOrderedSame) - { - return [[plainPassword asMD5String] isEqualToString: encryptedPassword]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"sha"] == NSOrderedSame) - { - - return [[plainPassword asSHA1String] isEqualToString: encryptedPassword]; - } - - - [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; - - return NO; + return [plainPassword isEqualToCrypted: encryptedPassword + withDefaultScheme: _userPasswordAlgorithm]; } /** @@ -189,26 +170,13 @@ */ - (NSString *) _encryptPassword: (NSString *) plainPassword { - if ([_userPasswordAlgorithm caseInsensitiveCompare: @"none"] == NSOrderedSame) - { - return plainPassword; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"crypt"] == NSOrderedSame) - { - return [plainPassword asCryptStringUsingSalt: [plainPassword asMD5String]]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5"] == NSOrderedSame) - { - return [plainPassword asMD5String]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"sha"] == NSOrderedSame) - { - return [plainPassword asSHA1String]; - } - - [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; - - return plainPassword; + NSString *password; + password = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm]; + + if (password == nil) + [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; + + return [NSString stringWithFormat: @"{%@}%@", _userPasswordAlgorithm, password]; } // |
I've now added my third version of the cryptographic extension for SQL (and now also LDAP). BTW: you may change line 90 of NSString+Crypto.m to this: else if ([stringEncoding caseInsensitiveCompare: @"b64"] == NSOrderedSame || And a further note explaining the patch (v3): The conversation will be handled by the NSString+Crypto extension. It also handles the splitting of the scheme from a string like "{SSHA.b64}986H5cS9JcDYQeJd6wKaITMho4M9CrXM" into the scheme and the binary data. I also moves the functions asMD5String and asSHA1String NSString crypto extension for consistency. I hope this is getting added soon. |
|
2012-05-22 12:09
|
crypto_v4.patch (43,524 bytes)
# # old_revision [471dc0f5e3b2a6fc571aca4e14c0a5161cf7af0b] # # add_file "SoObjects/SOGo/NSData+Crypto.h" # content [4e1a09d2530e3934966cf7547ffc6ee9411b4d0d] # # add_file "SoObjects/SOGo/NSData+Crypto.m" # content [6d71c327de926a32a542f1cbdf923c1d6435c3f2] # # add_file "SoObjects/SOGo/NSString+Crypto.h" # content [a15042732241b52c399c8667dc0fd281f97bc131] # # add_file "SoObjects/SOGo/NSString+Crypto.m" # content [24e1ff6773c04e588410609f312824912d333911] # # patch "SoObjects/SOGo/GNUmakefile" # from [9e466added539ccacaecd830173afdf9d4755802] # to [0e4d9af53dddf014fe6fccf1d81e751d8dd2bc3c] # # patch "SoObjects/SOGo/LDAPSource.m" # from [49cd1d275323dd81ba0ad5566e542b725856c4a9] # to [0570acb4f559ddabe647634f52a877d17dbbce2d] # # patch "SoObjects/SOGo/NSString+Utilities.h" # from [d2661ef19253d96d64dcf11127ffe6882f90b4d4] # to [c38d80d6ebe9592a9f2ee954282718d7265dec51] # # patch "SoObjects/SOGo/NSString+Utilities.m" # from [800b97cfc8f806e14c310ed8628c94a9934fd80a] # to [eb4c7cd585c64cb8fb0273c3e89be36da11e5f24] # # patch "SoObjects/SOGo/SOGoUserManager.m" # from [9bf968b36558145edd71840dd180876c1d5d7175] # to [344659dcdf8d929bb824d8856d05c1de85ce801e] # # patch "SoObjects/SOGo/SQLSource.m" # from [8e7fb09f6d34231ed0e1c57db516961d7a8bf0bb] # to [569afbdf32cf6a95315d8e99cd910a0abaa2e9ad] # ============================================================ --- SoObjects/SOGo/GNUmakefile 9e466added539ccacaecd830173afdf9d4755802 +++ SoObjects/SOGo/GNUmakefile 0e4d9af53dddf014fe6fccf1d81e751d8dd2bc3c @@ -46,6 +46,8 @@ SOGo_HEADER_FILES = \ NSObject+Utilities.h \ NSString+DAV.h \ NSString+Utilities.h \ + NSString+Crypto.h \ + NSData+Crypto.h \ NSURL+DAV.h \ \ SOGoAuthenticator.h \ @@ -114,6 +116,8 @@ SOGo_OBJC_FILES = \ NSObject+Utilities.m \ NSString+DAV.m \ NSString+Utilities.m \ + NSString+Crypto.m \ + NSData+Crypto.m \ NSURL+DAV.m \ \ SOGoSession.m \ ============================================================ --- SoObjects/SOGo/LDAPSource.m 49cd1d275323dd81ba0ad5566e542b725856c4a9 +++ SoObjects/SOGo/LDAPSource.m 0570acb4f559ddabe647634f52a877d17dbbce2d @@ -40,6 +40,7 @@ #import "LDAPSourceSchema.h" #import "NSArray+Utilities.h" #import "NSString+Utilities.h" +#import "NSString+Crypto.h" #import "SOGoDomainDefaults.h" #import "SOGoSystemDefaults.h" @@ -639,26 +640,13 @@ andMultipleBookingsField: (NSString *) n */ - (NSString *) _encryptPassword: (NSString *) plainPassword { - if ([_userPasswordAlgorithm caseInsensitiveCompare: @"none"] == NSOrderedSame) - { - return plainPassword; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"crypt"] == NSOrderedSame) - { - return [NSString stringWithFormat: @"{CRYPT}%@", [plainPassword asCryptStringUsingSalt: [plainPassword asMD5String]]]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5"] == NSOrderedSame) - { - return [NSString stringWithFormat: @"{MD5}%@", [plainPassword asMD5String]]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"sha"] == NSOrderedSame) - { - return [NSString stringWithFormat: @"{SHA}%@", [plainPassword asSHA1String]]; - } - - [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; - - return plainPassword; + NSString *pass; + pass = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm]; + + if (pass == nil) + [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; + + return [NSString stringWithFormat: @"{%@}%@", _userPasswordAlgorithm, pass]; } // ============================================================ --- /dev/null +++ SoObjects/SOGo/NSData+Crypto.h 4e1a09d2530e3934966cf7547ffc6ee9411b4d0d @@ -0,0 +1,57 @@ +/* NSData+Crypto.h - this file is part of SOGo + * + * Author: Nicolas Höft + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef NSDATA_CRYPTO_H +#define NSDATA_CRYPTO_H + +#import <Foundation/NSData.h> + +@class NSObject; + +@interface NSData (SOGoCryptoExtension) + +- (NSData *) asCryptedPassUsingScheme: (NSString *) passwordScheme + withSalt: (NSData *) theSalt; + +- (NSData *) asMD5; +- (NSData *) asSMD5UsingSalt: (NSData *) theSalt; +- (NSData *) asSHA1; +- (NSData *) asSSHAUsingSalt: (NSData *) theSalt; +- (NSData *) asSHA256; +- (NSData *) asSSHA256UsingSalt: (NSData *) theSalt; +- (NSData *) asSHA512; +- (NSData *) asSSHA512UsingSalt: (NSData *) theSalt; +- (NSData *) asCramMD5; + +- (NSData *) asCryptUsingSalt: (NSData *) theSalt; +- (NSData *) asMD5CryptUsingSalt: (NSData *) theSalt; + +- (NSData *) extractSalt: (NSString *) theScheme; + ++ (NSData *) generateSaltForLength: (unsigned int) theLength + withBase64: (BOOL) doBase64; ++ (NSData *) generateSaltForLength: (unsigned int) theLength; + ++ (NSString *) encodeDataAsHexString: (NSData* ) theData; ++ (NSData *) decodeDataFromHexString: (NSString* ) theString; + +@end + +#endif /* NSDATA_CRYPTO_H */ ============================================================ --- /dev/null +++ SoObjects/SOGo/NSData+Crypto.m 6d71c327de926a32a542f1cbdf923c1d6435c3f2 @@ -0,0 +1,617 @@ +/* NSData+Crypto.m - this file is part of SOGo + * + * + * Author: Nicolas Höft + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __OpenBSD__ +#include <crypt.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#define _XOPEN_SOURCE 1 +#include <unistd.h> +#include <openssl/evp.h> +#include <openssl/md5.h> +#include <openssl/sha.h> + +#import <Foundation/NSArray.h> +#import <NGExtensions/NGBase64Coding.h> +#import "NSData+Crypto.h" + +unsigned charTo4Bits(char c); + + +@implementation NSData (SOGoCryptoExtension) + +/** + * Covert binary data to hex encoded data (lower-case). + * + * @param theData The NSData to be converted into a hex-encoded string. + * @return Hex-Encoded data + */ ++ (NSString *) encodeDataAsHexString: (NSData *) theData +{ + unsigned int byteLength = [theData length], byteCounter = 0; + unsigned int stringLength = (byteLength * 2) + 1, stringCounter = 0; + unsigned char dstBuffer[stringLength]; + unsigned char srcBuffer[byteLength]; + unsigned char *srcPtr = srcBuffer; + [theData getBytes: srcBuffer]; + const unsigned char t[16] = "0123456789abcdef"; + + for (; byteCounter < byteLength; byteCounter++) + { + unsigned src = *srcPtr; + dstBuffer[stringCounter++] = t[src >> 4]; + dstBuffer[stringCounter++] = t[src & 15]; + srcPtr++; + } + + dstBuffer[stringCounter] = '\0'; + return [NSString stringWithUTF8String: (char*)dstBuffer]; +} + +/** + * Covert hex-encoded data to binary data. + * + * @param theString The hex-encoded string to be converted into binary data (works both for upper and lowe case characters) + * @return binary data or nil if unsuccessful + */ ++ (NSData *) decodeDataFromHexString: (NSString *) theString +{ + unsigned int stringLength = [theString length]; + unsigned int byteLength = stringLength/2; + unsigned int byteCounter = 0; + unsigned char srcBuffer[stringLength]; + [theString getCString:(char *)srcBuffer]; + unsigned char *srcPtr = srcBuffer; + unsigned char dstBuffer[byteLength]; + unsigned char *dst = dstBuffer; + while (byteCounter < byteLength) + { + unsigned char c = *srcPtr++; + unsigned char d = *srcPtr++; + unsigned hi = 0, lo = 0; + hi = charTo4Bits(c); + lo = charTo4Bits(d); + if (hi == 255 || lo == 255) + { + //errorCase + return nil; + } + dstBuffer[byteCounter++] = ((hi << 4) | lo); + } + return [NSData dataWithBytes: dst length: byteLength]; +} + +/** + * Generate a binary key which can be used for salting hashes. + * + * @param theLength length of the binary data to be generated in bytes + * @return Pseudo-random binary data with length theLength or nil, if an error occured + */ ++ (NSData *) generateSaltForLength: (unsigned int) theLength +{ + return [NSData generateSaltForLength: theLength withBase64: NO]; +} + +/** + * Generate a binary key which can be used for salting hashes. When using + * with doBase64 == YES then the data will be longer than theLength + * + * @param theLength Length of the binary data to be generated in bytes + * @param doBase64 Convert the data into Base-64 before retuning it, be aware that this makes the binary data longer + * @return Pseudo-random binary data with length theLength or nil, if an error occured + */ ++ (NSData *) generateSaltForLength: (unsigned int) theLength + withBase64: (BOOL) doBase64 +{ + char *buf; + int fd; + NSData *data; + + fd = open("/dev/urandom", O_RDONLY); + + if (fd > 0) + { + buf = (char *)malloc(theLength); + read(fd, buf, theLength); + close(fd); + + data = [NSData dataWithBytesNoCopy: buf length: theLength freeWhenDone: YES]; + if(doBase64 == YES) + { + return [data dataByEncodingBase64WithLineLength: 1024]; + } + return data; + } + return nil; +} + +/** + * Encrypt/Hash the data with a given scheme + * + * @param passwordScheme The scheme to use for hashing/encryption. + * @param theSalt The salt to be used. If none is given but needed, it will be generated + * @return Binary data from the encryption by the specified scheme. On error the funciton returns nil. + */ +- (NSData *) asCryptedPassUsingScheme: (NSString *) passwordScheme + withSalt: (NSData *) theSalt +{ + if ([passwordScheme caseInsensitiveCompare: @"none"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"plain"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"cleartext"] == NSOrderedSame) + { + return self; + } + else if ([passwordScheme caseInsensitiveCompare: @"crypt"] == NSOrderedSame) + { + return [self asCryptUsingSalt: theSalt]; + } + else if ([passwordScheme caseInsensitiveCompare: @"md5-crypt"] == NSOrderedSame) + { + return [self asMD5CryptUsingSalt: theSalt]; + } + else if ([passwordScheme caseInsensitiveCompare: @"md5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"plain-md5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"ldap-md5"] == NSOrderedSame) + { + return [self asMD5]; + } + else if ([passwordScheme caseInsensitiveCompare: @"cram-md5"] == NSOrderedSame) + { + return [self asCramMD5]; + } + else if ([passwordScheme caseInsensitiveCompare: @"smd5"] == NSOrderedSame) + { + return [self asSMD5UsingSalt: theSalt]; + } + else if ([passwordScheme caseInsensitiveCompare: @"sha"] == NSOrderedSame) + { + return [self asSHA1]; + } + else if ([passwordScheme caseInsensitiveCompare: @"ssha"] == NSOrderedSame) + { + return [self asSSHAUsingSalt: theSalt]; + } + else if ([passwordScheme caseInsensitiveCompare: @"sha256"] == NSOrderedSame) + { + return [self asSHA256]; + } + else if ([passwordScheme caseInsensitiveCompare: @"ssha256"] == NSOrderedSame) + { + return [self asSSHA256UsingSalt: theSalt]; + } + else if ([passwordScheme caseInsensitiveCompare: @"sha512"] == NSOrderedSame) + { + return [self asSHA512]; + } + else if ([passwordScheme caseInsensitiveCompare: @"ssha512"] == NSOrderedSame) + { + return [self asSSHA512UsingSalt: theSalt]; + } + // in case the scheme was not detected, return nil + return nil; +} + + +/** + * Hash the data with MD5. Uses openssl functions to generate it + * + * @return Binary data from MD5 hashing. On error the funciton returns nil. + */ +- (NSData *) asMD5 +{ + unsigned char md5[MD5_DIGEST_LENGTH]; + memset(md5, 0, MD5_DIGEST_LENGTH); + + MD5([self bytes], [self length], md5); + + return [NSData dataWithBytes: md5 length: MD5_DIGEST_LENGTH]; +} + +/** + * Hash the data with CRAM-MD5. Uses openssl functions to generate it. + * + * Note that the actual CRAM-MD5 algorithm also needs a challenge + * but this is not provided, this function actually calculalates + * only the context data which can be used for the challange-response + * algorithm then. This is just the underlying algorithm to store the passwords. + * + * The code is adopts the dovecot behaviour of storing the passwords + * + * @return Binary data from CRAM-MD5 'hashing'. On error the funciton returns nil. + */ +- (NSData *) asCramMD5 +{ + + MD5_CTX ctx; + unsigned char inner[64]; + unsigned char outer[64]; + unsigned char result[32]; + unsigned char *r; + int i; + int len; + NSData *key; + + if ([self length] > 64) + { + key = [self asMD5]; + } + else + { + key = self; + } + + len = [key length]; + // fill with both inner and outer with key + memcpy(inner, [key bytes], len); + // make sure the rest of the bytes is zero + memset(inner + len, 0, 64 - len); + memcpy(outer, inner, 64); + + for (i = 0; i < 64; i++) + { + inner[i] ^= 0x36; + outer[i] ^= 0x5c; + } +// this transformation is needed for the correct cast to binary data +#define CDPUT(p, c) { \ + *p = (c) & 0xff; p++; \ + *p = (c) >> 8 & 0xff; p++; \ + *p = (c) >> 16 & 0xff; p++; \ + *p = (c) >> 24 & 0xff; p++; \ +} + + // generate first set of context bytes from outer data + MD5_Init(&ctx); + MD5_Transform(&ctx, outer); + r = result; + // convert this to correct binary data according to RFC 1321 + CDPUT(r, ctx.A); + CDPUT(r, ctx.B); + CDPUT(r, ctx.C); + CDPUT(r, ctx.D); + + // second set with inner data is appended to result string + MD5_Init(&ctx); + MD5_Transform(&ctx, inner); + // convert this to correct binary data + CDPUT(r, ctx.A); + CDPUT(r, ctx.B); + CDPUT(r, ctx.C); + CDPUT(r, ctx.D); + + return [NSData dataWithBytes: result length: 32]; +} + +/** + * Hash the data with SHA1. Uses openssl functions to generate it. + * + * @return Binary data from SHA1 hashing. On error the funciton returns nil. + */ +- (NSData *) asSHA1 +{ + unsigned char sha[SHA_DIGEST_LENGTH]; + memset(sha, 0, SHA_DIGEST_LENGTH); + + SHA1([self bytes], [self length], sha); + + return [NSData dataWithBytes: sha length: SHA_DIGEST_LENGTH]; +} + +/** + * Hash the data with SHA256. Uses openssl functions to generate it. + * + * @return Binary data from SHA256 hashing. On error the funciton returns nil. + */ +- (NSData *) asSHA256 +{ + unsigned char sha[SHA256_DIGEST_LENGTH]; + memset(sha, 0, SHA256_DIGEST_LENGTH); + + SHA256([self bytes], [self length], sha); + + return [NSData dataWithBytes: sha length: SHA256_DIGEST_LENGTH]; +} + +/** + * Hash the data with SHA512. Uses openssl functions to generate it. + * + * @return Binary data from SHA512 hashing. On error the funciton returns nil. + */ +- (NSData *) asSHA512 +{ + unsigned char sha[SHA512_DIGEST_LENGTH]; + memset(sha, 0, SHA512_DIGEST_LENGTH); + + SHA512([self bytes], [self length], sha); + + return [NSData dataWithBytes: sha length: SHA512_DIGEST_LENGTH]; +} + +/** + * Hash the data with SSHA. Uses openssl functions to generate it. + * + * SSHA works following: SSHA(pass, salt) = SHA1(pass + salt) + saltData + * + * @param theSalt The salt to be used must not be nil, if empty, one will be generated + * @return Binary data from SHA1 hashing. On error the funciton returns nil. + */ +- (NSData *) asSSHAUsingSalt: (NSData *) theSalt +{ + // + NSMutableData *sshaData; + + // generate salt, if not available + if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8]; + + // put the pass and salt together as one data array + sshaData = [NSMutableData dataWithData: self]; + [sshaData appendData: theSalt]; + // generate SHA1 from pass + salt + sshaData = [NSMutableData dataWithData: [sshaData asSHA1]]; + // append salt again + [sshaData appendData: theSalt]; + + return sshaData; +} + +/** + * Hash the data with SSHA256. Uses openssl functions to generate it. + * + * SSHA256 works following: SSHA256(pass, salt) = SHA256(pass + salt) + saltData + * + * @param theSalt The salt to be used must not be nil, if empty, one will be generated + * @return Binary data from SHA1 hashing. On error the funciton returns nil. + */ + +- (NSData *) asSSHA256UsingSalt: (NSData *) theSalt +{ + NSMutableData *sshaData; + + // generate salt, if not available + if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8]; + + // put the pass and salt together as one data array + sshaData = [NSMutableData dataWithData: self]; + [sshaData appendData: theSalt]; + // generate SHA1 from pass + salt + sshaData = [NSMutableData dataWithData: [sshaData asSHA256]]; + // append salt again + [sshaData appendData: theSalt]; + + return sshaData; +} + +/** + * Hash the data with SSHA512. Uses openssl functions to generate it. + * + * SSHA works following: SSHA512(pass, salt) = SHA512(pass + salt) + saltData + * + * @param theSalt The salt to be used must not be nil, if empty, one will be generated + * @return Binary data from SHA512 hashing. On error the funciton returns nil. + */ + +- (NSData *) asSSHA512UsingSalt: (NSData *) theSalt +{ + NSMutableData *sshaData; + + // generate salt, if not available + if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8]; + + // put the pass and salt together as one data array + sshaData = [NSMutableData dataWithData: self]; + [sshaData appendData: theSalt]; + // generate SHA1 from pass + salt + sshaData = [NSMutableData dataWithData: [sshaData asSHA512]]; + // append salt again + [sshaData appendData: theSalt]; + + return sshaData; +} + +/** + * Hash the data with SMD5. Uses openssl functions to generate it. + * + * SMD5 works following: SMD5(pass, salt) = MD5(pass + salt) + saltData + * + * @param theSalt The salt to be used must not be nil, if empty, one will be generated + * @return Binary data from SMD5 hashing. On error the funciton returns nil. + */ +- (NSData *) asSMD5UsingSalt: (NSData *) theSalt +{ + // SMD5 works following: SMD5(pass, salt) = MD5(pass + salt) + salt + NSMutableData *smdData; + + // generate salt, if not available + if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8]; + + // put the pass and salt together as one data array + smdData = [NSMutableData dataWithData: self]; + [smdData appendData: theSalt]; + // generate SHA1 from pass + salt + smdData = [NSMutableData dataWithData: [smdData asMD5]]; + // append salt again + [smdData appendData: theSalt]; + + return smdData; +} + + +/** + * Hash the data with CRYPT-MD5 as used in /etc/passwd nowadays. Uses crypt() function to generate it. + * + * + * @param theSalt The salt to be used must not be nil, if empty, one will be generated. It must be printable characters only. + * @return Binary data from CRYPT-MD5 hashing. On error the funciton returns nil. + */ +- (NSData *) asMD5CryptUsingSalt: (NSData *) theSalt +{ + char *buf; + NSMutableData *saltData; + NSString *cryptString; + NSString *saltString; + + if ([theSalt length] == 0) + { + // make sure these characters are all printable by using base64 + theSalt = [NSData generateSaltForLength: 8 withBase64: YES]; + } + cryptString = [[NSString alloc] initWithData: self encoding: NSUTF8StringEncoding]; + + NSString * magic = @"$1$"; + saltData = [NSMutableData dataWithData: [magic dataUsingEncoding: NSUTF8StringEncoding]]; + [saltData appendData: theSalt]; + // terminate with "$" + [saltData appendData: [@"$" dataUsingEncoding: NSUTF8StringEncoding]]; + + saltString = [[NSString alloc] initWithData: saltData encoding: NSUTF8StringEncoding]; + + buf = crypt([cryptString UTF8String], [saltString UTF8String]); + [cryptString release]; + [saltString release]; + if (!buf) + return nil; + return [NSData dataWithBytes: buf length: strlen(buf)]; +} + +/** + * Hash the data using crypt() function. + * + * @param theSalt The salt to be used must not be nil, if empty, one will be generated + * @return Binary data from CRYPT-MD5 hashing. On error the funciton returns nil. + */ +- (NSData *) asCryptUsingSalt: (NSData *) theSalt +{ + char *buf; + NSString *saltString; + NSString *cryptString; + + // crypt() works with strings, so convert NSData to strings + cryptString = [[NSString alloc] initWithData: self encoding: NSUTF8StringEncoding]; + + if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8 withBase64: YES]; + + saltString = [[NSString alloc] initWithData: theSalt encoding: NSUTF8StringEncoding]; + + // The salt is weak here, but who cares anyway, crypt should not + // be used anymore + buf = crypt([cryptString UTF8String], [saltString UTF8String]); + [saltString release]; + [cryptString release]; + if (!buf) + return nil; + return [NSData dataWithBytes: buf length: strlen(buf)]; +} + +/** + * Get the salt from a password encrypted with a specied scheme + * + * @param theScheme Needed to get the salt correctly out of the pass + * @return The salt, if one was available in the password/scheme, else empty data + */ +- (NSData *) extractSalt: (NSString *) theScheme +{ + NSRange r; + int len; + len = [self length]; + if (len == 0) + return [NSData data]; + + // for the ssha schemes the salt is appended at the endif + // so the range with the salt are bytes after each digest length + if ([theScheme caseInsensitiveCompare: @"crypt"] == NSOrderedSame) + { + // for crypt schemes simply use the whole string + // the crypt() function is able to extract it by itself + r = NSMakeRange(0, len); + } + else if ([theScheme caseInsensitiveCompare: @"md5-crypt"] == NSOrderedSame) + { + // md5 crypt is generated the following "$1$<salt>$<encrypted pass>" + NSString *cryptString; + NSArray *cryptParts; + cryptString = [NSString stringWithUTF8String: [self bytes] ]; + cryptParts = [cryptString componentsSeparatedByString: @"$"]; + // correct number of elements (first one is an empty string) + if ([cryptParts count] != 4) + { + return [NSData data]; + } + // second is the identifier of md5-crypt + else if( [[cryptParts objectAtIndex: 1] caseInsensitiveCompare: @"1"] != NSOrderedSame ) + { + return [NSData data]; + } + // third is the salt; convert it to NSData + return [[cryptParts objectAtIndex: 2] dataUsingEncoding: NSUTF8StringEncoding]; + } + else if ([theScheme caseInsensitiveCompare: @"ssha"] == NSOrderedSame) + { + r = NSMakeRange(SHA_DIGEST_LENGTH, len - SHA_DIGEST_LENGTH); + } + else if ([theScheme caseInsensitiveCompare: @"ssha256"] == NSOrderedSame) + { + r = NSMakeRange(SHA256_DIGEST_LENGTH, len - SHA256_DIGEST_LENGTH); + } + else if ([theScheme caseInsensitiveCompare: @"ssha512"] == NSOrderedSame) + { + r = NSMakeRange(SHA512_DIGEST_LENGTH, len - SHA512_DIGEST_LENGTH); + } + else if ([theScheme caseInsensitiveCompare: @"smd5"] == NSOrderedSame) + { + r = NSMakeRange(MD5_DIGEST_LENGTH, len - MD5_DIGEST_LENGTH); + } + else + { + // return empty string on unknown scheme + return [NSData data]; + } + + return [self subdataWithRange: r]; +} + +@end + +unsigned charTo4Bits(char c) +{ + unsigned bits = 0; + if (c > '/' && c < ':') + { + bits = c - '0'; + } + else if (c > '@' && c < 'G') + { + bits = (c- 'A') + 10; + } + else if (c > '`' && c < 'g') + { + bits = (c- 'a') + 10; + } + else + { + bits = 255; + } + return bits; +} ============================================================ --- /dev/null +++ SoObjects/SOGo/NSString+Crypto.h a15042732241b52c399c8667dc0fd281f97bc131 @@ -0,0 +1,59 @@ +/* NSString+Crypto.h - this file is part of SOGo + * + * Author: Nicolas Höft + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef NSSTRING_CRYPTO_H +#define NSSTRING_CRYPTO_H + +#import <Foundation/NSData.h> +#import <Foundation/NSString.h> + +typedef enum { + encDefault, //!< default encoding, let the algorithm decide + encPlain, //!< the data is plain text, simply convert to string + encHex, //!< the data is hex encoded + encBase64, //!< base64 encoding +} keyEncoding; + +@class NSObject; + +@interface NSString (SOGoCryptoExtension) + + +- (BOOL) isEqualToCrypted: (NSString *) cryptedPassword + withDefaultScheme: (NSString *) theScheme; + +- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme + withSalt: (NSData *) theSalt + andEncoding: (keyEncoding) encoding; + +// this method uses the default encoding (base64, plain, hex) +// and generates a salt when necessary +- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme; + +- (NSArray *) splitPasswordWithDefaultScheme: (NSString *) defaultScheme; + +- (NSString *) asSHA1String; +- (NSString *) asMD5String; + ++ (keyEncoding) getDefaultEncodingForScheme: (NSString *) passwordScheme; + +@end + +#endif /* NSSTRING_CRYPTO_H */ ============================================================ --- /dev/null +++ SoObjects/SOGo/NSString+Crypto.m 24e1ff6773c04e588410609f312824912d333911 @@ -0,0 +1,301 @@ +/* NSString+Crypto.m - this file is part of SOGo + * + * + * Author: Nicolas Höft + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import <Foundation/NSArray.h> +#import <Foundation/NSValue.h> + +#import "NSString+Crypto.h" +#import "NSData+Crypto.h" +#import <NGExtensions/NGBase64Coding.h> + +@implementation NSString (SOGoCryptoExtension) + +/** + * Extracts the scheme from a string formed "{scheme}pass". + * + * @return The scheme or an empty string if the string did not contained a scheme in the format above + */ +- (NSString *) extractCryptScheme +{ + NSRange r; + int len; + + len = [self length]; + if (len == 0) + return @""; + if ([self characterAtIndex:0] != '{') + return @""; + + r = [self rangeOfString:@"}" options:(NSLiteralSearch)]; + if (r.length == 0) + return @""; + + r.length = (r.location - 1); + r.location = 1; + return [[self substringWithRange:r] lowercaseString]; +} + + +/** + * Split a password of the form {scheme}pass into an array of its components: + * {NSString *scheme, NString *pass, NSInteger encoding}, where encoding is + * the enum keyEncoding converted to an integer value. + * + * @param defaultScheme If no scheme is given in cryptedPassword, fall back to this scheme + * @see asCryptedPassUsingScheme + * @see keyEncoding + * @return NSArray with the three elements described above + */ +- (NSArray *) splitPasswordWithDefaultScheme: (NSString *) defaultScheme +{ + NSString *scheme; + NSString *pass; + NSArray *schemeComps; + keyEncoding encoding; + + NSRange range; + int selflen, len; + + selflen = [self length]; + + scheme = [self extractCryptScheme]; + len = [scheme length]; + if (len > 0) + range = NSMakeRange (len+2, selflen-len-2); + else + range = NSMakeRange (0, selflen); + if (len == 0) + scheme = defaultScheme; + + encoding = [NSString getDefaultEncodingForScheme: scheme]; + + // get the encoding which may be part of the scheme + // e.g. ssha.hex forces a hex encoded ssha scheme + // possible is "b64" or "hex" + schemeComps = [scheme componentsSeparatedByString: @"."]; + if ([schemeComps count] == 2) + { + NSString *stringEncoding; + // scheme without encoding string is the first item + scheme = [schemeComps objectAtIndex: 0]; + // encoding string is second item + stringEncoding = [schemeComps objectAtIndex: 1]; + if ([stringEncoding caseInsensitiveCompare: @"hex"] == NSOrderedSame) + { + encoding = encHex; + } + else if ([stringEncoding caseInsensitiveCompare: @"b64"] == NSOrderedSame || + [stringEncoding caseInsensitiveCompare: @"base64"] == NSOrderedSame) + { + encoding = encBase64; + } + } + + pass = [self substringWithRange: range]; + return [NSArray arrayWithObjects: scheme, pass, [NSNumber numberWithInt: encoding], nil]; +} + +/** + * Compare the hex or base64 encoded password with an encrypted password + * + * @param cryptedPassword The password to compare with, format {scheme}pass , "{scheme}" is optional + * @param theScheme If no scheme is given in cryptedPassword, fall back to this scheme + * @see asCryptedPassUsingScheme + * @return YES if the passwords are identical using this encryption scheme + */ +- (BOOL) isEqualToCrypted: (NSString *) cryptedPassword + withDefaultScheme: (NSString *) theScheme +{ + NSArray *passInfo; + NSString *selfCrypted; + NSString *pass; + NSString *scheme; + NSData *salt; + NSData *decodedData; + NSNumber *encodingNumber; + keyEncoding encoding; + + // split scheme and pass + passInfo = [cryptedPassword splitPasswordWithDefaultScheme: theScheme]; + + scheme = [passInfo objectAtIndex: 0]; + pass = [passInfo objectAtIndex: 1]; + encodingNumber = [passInfo objectAtIndex: 2]; + encoding = [encodingNumber integerValue]; + + if (encoding == encHex) + { + decodedData = [NSData decodeDataFromHexString: pass]; + + if(decodedData == nil) + { + decodedData = [NSData data]; + } + else + { + // decoding was successful, now make sure + // that the pass is in lowercase since decodeDataFromHexString uses + // lowercase charaters, too + pass = [pass lowercaseString]; + } + } + else if(encoding == encBase64) + { + decodedData = [pass dataByDecodingBase64]; + if(decodedData == nil) + { + decodedData = [NSData data]; + } + } + else + { + decodedData = [pass dataUsingEncoding: NSUTF8StringEncoding]; + } + + salt = [decodedData extractSalt: scheme]; + + // encrypt self with the salt an compare the results + selfCrypted = [self asCryptedPassUsingScheme: scheme + withSalt: salt + andEncoding: encoding]; + // return always false when there was a problem + if (selfCrypted == nil) + return NO; + + if ([selfCrypted isEqualToString: pass] == YES) + return YES; + return NO; +} + +/** + * Calls asCryptedPassUsingScheme:withSalt:andEncoding: with an empty salt and uses + * the default encoding. + * + * @param passwordScheme + * @return If successful, the encrypted and encoded NSString of the format {scheme}pass, or nil if the scheme did not exists or an error occured + */ +- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme +{ + return [self asCryptedPassUsingScheme: passwordScheme + withSalt: [NSData data] + andEncoding: encDefault]; +} + +/** + * Uses NSData -asCryptedPassUsingScheme to encrypt the string and converts the + * binary data back to a readable string using userEncoding + * + * @param passwordScheme The scheme to use + * @param theSalt The binary data of the salt + * @param userEncoding The encoding (plain, hex, base64) to be used + * @return If successful, the encrypted and encoded NSString of the format {scheme}pass, or nil if the scheme did not exists or an error occured + */ +- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme + withSalt: (NSData *) theSalt + andEncoding: (keyEncoding) userEncoding +{ + keyEncoding dataEncoding; + NSData* cryptedData; + // convert NSString to NSData and apply encryption scheme + cryptedData = [self dataUsingEncoding: NSUTF8StringEncoding]; + cryptedData = [cryptedData asCryptedPassUsingScheme: passwordScheme withSalt: theSalt]; + // abort on unsupported scheme or error + if (cryptedData == nil) + return nil; + + // use default encoding scheme, when set to default + if (userEncoding == encDefault) + dataEncoding = [NSString getDefaultEncodingForScheme: passwordScheme]; + else + dataEncoding = userEncoding; + + if (dataEncoding == encHex) + { + // hex encoding + return [NSData encodeDataAsHexString: cryptedData]; + } + else if(dataEncoding == encBase64) + { + // base64 encoding + NSString *s = [[NSString alloc] initWithData: [cryptedData dataByEncodingBase64WithLineLength: 1024] + encoding: NSASCIIStringEncoding]; + return [s autorelease]; + } + + // plain string + return [[[NSString alloc] initWithData: cryptedData encoding: NSUTF8StringEncoding] autorelease]; +} + +/** + * Returns the encoding for a specified scheme + * + * @param passwordScheme The scheme for which to get the encoding. + * @see keyEncoding + * @return returns the encoding, if unknown returns encPlain + */ ++ (keyEncoding) getDefaultEncodingForScheme: (NSString *) passwordScheme +{ + // in order to keep backwards-compatibility, hex encoding is used for sha1 here + if ([passwordScheme caseInsensitiveCompare: @"md5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"plain-md5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"sha"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"cram-md5"] == NSOrderedSame) + { + return encHex; + } + else if ([passwordScheme caseInsensitiveCompare: @"smd5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"ldap-md5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"ssha"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"sha256"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"ssha256"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"sha512"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"ssha512"] == NSOrderedSame) + { + return encBase64; + } + return encPlain; +} + +/** + * Encrypts the data with SHA1 scheme and returns the hex-encoded data + * + * @return If successful, sha1 encrypted and with hex encoded string + */ +- (NSString *) asSHA1String; +{ + NSData *cryptData; + cryptData = [self dataUsingEncoding: NSUTF8StringEncoding]; + return [NSData encodeDataAsHexString: [cryptData asSHA1] ]; +} + +/** + * Encrypts the data with Plain MD5 scheme and returns the hex-encoded data + * + * @return If successful, MD5 encrypted and with hex encoded string + */ +- (NSString *) asMD5String; +{ + NSData *cryptData; + cryptData = [self dataUsingEncoding: NSUTF8StringEncoding]; + return [NSData encodeDataAsHexString: [cryptData asMD5] ]; +} + +@end ============================================================ --- SoObjects/SOGo/NSString+Utilities.h d2661ef19253d96d64dcf11127ffe6882f90b4d4 +++ SoObjects/SOGo/NSString+Utilities.h c38d80d6ebe9592a9f2ee954282718d7265dec51 @@ -66,10 +66,6 @@ - (id) objectFromJSONString; -- (NSString *) asCryptStringUsingSalt: (NSString *) theSalt; -- (NSString *) asMD5String; -- (NSString *) asSHA1String; - - (NSString *) asSafeSQLString; - (NSUInteger) countOccurrencesOfString: (NSString *) substring; ============================================================ --- SoObjects/SOGo/NSString+Utilities.m 800b97cfc8f806e14c310ed8628c94a9934fd80a +++ SoObjects/SOGo/NSString+Utilities.m eb4c7cd585c64cb8fb0273c3e89be36da11e5f24 @@ -21,10 +21,6 @@ * Boston, MA 02111-1307, USA. */ -#ifndef __OpenBSD__ -#include <crypt.h> -#endif - #import <Foundation/NSArray.h> #import <Foundation/NSCharacterSet.h> #import <Foundation/NSData.h> @@ -45,12 +41,6 @@ #import "NSString+Utilities.h" -#define _XOPEN_SOURCE 1 -#include <unistd.h> -#include <openssl/evp.h> -#include <openssl/md5.h> -#include <openssl/sha.h> - static NSMutableCharacterSet *urlNonEndingChars = nil; static NSMutableCharacterSet *urlAfterEndingChars = nil; static NSMutableCharacterSet *urlStartChars = nil; @@ -534,48 +524,6 @@ static int cssEscapingCount; return object; } -- (NSString *) asCryptStringUsingSalt: (NSString *) theSalt -{ - char *buf; - - // The salt is weak here, but who cares anyway, crypt should not - // be used anymore - buf = crypt([self UTF8String], [theSalt UTF8String]); - return [NSString stringWithUTF8String: buf]; -} - -- (NSString *) asMD5String -{ - unsigned char md[MD5_DIGEST_LENGTH]; - char buf[80]; - int i; - - memset(md, 0, MD5_DIGEST_LENGTH); - memset(buf, 0, 80); - - EVP_Digest((const void *) [self UTF8String], strlen([self UTF8String]), md, NULL, EVP_md5(), NULL); - for (i = 0; i < MD5_DIGEST_LENGTH; i++) - sprintf(&(buf[i*2]), "%02x", md[i]); - - return [NSString stringWithUTF8String: buf]; -} - -- (NSString *) asSHA1String -{ - unsigned char sha[SHA_DIGEST_LENGTH]; - char buf[80]; - int i; - - memset(sha, 0, SHA_DIGEST_LENGTH); - memset(buf, 0, 80); - - SHA1((const void *)[self UTF8String], strlen([self UTF8String]), sha); - for (i = 0; i < SHA_DIGEST_LENGTH; i++) - sprintf(&(buf[i*2]), "%02x", sha[i]); - - return [NSString stringWithUTF8String: buf]; -} - - (NSString *) asSafeSQLString { return [[self stringByReplacingString: @"\\" withString: @"\\\\"] ============================================================ --- SoObjects/SOGo/SOGoUserManager.m 9bf968b36558145edd71840dd180876c1d5d7175 +++ SoObjects/SOGo/SOGoUserManager.m 344659dcdf8d929bb824d8856d05c1de85ce801e @@ -33,6 +33,7 @@ #import "NSArray+Utilities.h" #import "NSString+Utilities.h" +#import "NSString+Crypto.h" #import "NSObject+Utilities.h" #import "SOGoDomainDefaults.h" #import "SOGoSource.h" ============================================================ --- SoObjects/SOGo/SQLSource.m 8e7fb09f6d34231ed0e1c57db516961d7a8bf0bb +++ SoObjects/SOGo/SQLSource.m 569afbdf32cf6a95315d8e99cd910a0abaa2e9ad @@ -39,6 +39,7 @@ #import "SOGoConstants.h" #import "NSString+Utilities.h" +#import "NSString+Crypto.h" #import "SQLSource.h" @@ -47,7 +48,7 @@ * * c_uid - will be used for authentication - it's a username or username@domain.tld) * c_name - which can be identical to c_uid - will be used to uniquely identify entries) - * c_password - password of the user, plain-text, md5 or sha encoded for now + * c_password - password of the user, possible values: plain, md5, plain-md5, sha, ssha, sha256, ssha256, ssha2512, smd5, crypt, md5-crypt, cram-md5 (with or without ".hex" or ".b64" appended) * c_cn - the user's common name * mail - the user's mail address * @@ -157,28 +158,8 @@ if (!plainPassword || !encryptedPassword) return NO; - if ([_userPasswordAlgorithm caseInsensitiveCompare: @"none"] == NSOrderedSame) - { - return [plainPassword isEqualToString: encryptedPassword]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"crypt"] == NSOrderedSame) - { - return [[plainPassword asCryptStringUsingSalt: encryptedPassword] isEqualToString: encryptedPassword]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5"] == NSOrderedSame) - { - return [[plainPassword asMD5String] isEqualToString: encryptedPassword]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"sha"] == NSOrderedSame) - { - - return [[plainPassword asSHA1String] isEqualToString: encryptedPassword]; - } - - - [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; - - return NO; + return [plainPassword isEqualToCrypted: encryptedPassword + withDefaultScheme: _userPasswordAlgorithm]; } /** @@ -189,26 +170,13 @@ */ - (NSString *) _encryptPassword: (NSString *) plainPassword { - if ([_userPasswordAlgorithm caseInsensitiveCompare: @"none"] == NSOrderedSame) - { - return plainPassword; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"crypt"] == NSOrderedSame) - { - return [plainPassword asCryptStringUsingSalt: [plainPassword asMD5String]]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5"] == NSOrderedSame) - { - return [plainPassword asMD5String]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"sha"] == NSOrderedSame) - { - return [plainPassword asSHA1String]; - } - - [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; - - return plainPassword; + NSString *password; + password = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm]; + + if (password == nil) + [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; + + return [NSString stringWithFormat: @"{%@}%@", _userPasswordAlgorithm, password]; } // |
The v4 patch adds CRAM-MD5 as scheme and documents all methods implemented by me. |
|
supportedExtension: 1.3.6.1.4.1.4203.1.11.1 and leave it at the LDAP side to handle password changes. http://www.networksorcery.com/enp/rfc/rfc3062.txt LDAP password changes related to http://www.sogo.nu/bugs/view.php?id=1804&nbn=2 |
|
Yes, for LDAP this is clearly the better solution (if supported), but as long as this extension is not supported, this may be a way... Anyways, for SQL based authentication this is clearly needed. |
|
2012-05-22 13:53
|
sqlsource_prepend_pw_schemes.patch (5,001 bytes)
# # old_revision [471dc0f5e3b2a6fc571aca4e14c0a5161cf7af0b] # # patch "SoObjects/SOGo/SQLSource.h" # from [da3279f8ce2fc584f8c4e28ab8718a2d59e0d182] # to [c336b0e395923d3dbd0112dfe61fff4d0e64ab02] # # patch "SoObjects/SOGo/SQLSource.m" # from [8e7fb09f6d34231ed0e1c57db516961d7a8bf0bb] # to [16b4bee44ea4099f441c684a17aead9329be0bae] # ============================================================ --- SoObjects/SOGo/SQLSource.h da3279f8ce2fc584f8c4e28ab8718a2d59e0d182 +++ SoObjects/SOGo/SQLSource.h c336b0e395923d3dbd0112dfe61fff4d0e64ab02 @@ -45,6 +45,7 @@ NSString *_imapHostField; NSString *_userPasswordAlgorithm; NSURL *_viewURL; + BOOL _prependPasswordScheme; /* resources handling */ NSString *_kindField; ============================================================ --- SoObjects/SOGo/SQLSource.m 8e7fb09f6d34231ed0e1c57db516961d7a8bf0bb +++ SoObjects/SOGo/SQLSource.m 16b4bee44ea4099f441c684a17aead9329be0bae @@ -39,6 +39,7 @@ #import "SOGoConstants.h" #import "NSString+Utilities.h" +#import "NSString+Crypto.h" #import "SQLSource.h" @@ -47,7 +48,10 @@ * * c_uid - will be used for authentication - it's a username or username@domain.tld) * c_name - which can be identical to c_uid - will be used to uniquely identify entries) - * c_password - password of the user, plain-text, md5 or sha encoded for now + * c_password - password of the user, can be encoded in {scheme}pass format, or when stored without + * scheme it uses the scheme set in userPasswordAlgorithm. + * Possible algorithms are: plain, md5, crypt-md5, sha, ssha (including 256/512 variants), + * cram-md5, smd5, crypt, crypt-md5 * c_cn - the user's common name * mail - the user's mail address * @@ -63,8 +67,12 @@ * canAuthenticate = YES; * isAddressBook = YES; * userPasswordAlgorithm = md5; + * prependPasswordScheme = YES; * } * + * If prependPasswordScheme is set to YES, the generated passwords will have the format {scheme}password. + * If it is NO (the default), the password will be written to database without encryption scheme. + * */ @implementation SQLSource @@ -126,6 +134,10 @@ ASSIGN(_kindField, [udSource objectForKey: @"KindFieldName"]); ASSIGN(_multipleBookingsField, [udSource objectForKey: @"MultipleBookingsFieldName"]); ASSIGN(_domainField, [udSource objectForKey: @"DomainFieldName"]); + if ([udSource objectForKey: @"prependPasswordScheme"]) + _prependPasswordScheme = [[udSource objectForKey: @"prependPasswordScheme"] boolValue]; + else + _prependPasswordScheme = NO; if (!_userPasswordAlgorithm) _userPasswordAlgorithm = @"none"; @@ -157,28 +169,8 @@ if (!plainPassword || !encryptedPassword) return NO; - if ([_userPasswordAlgorithm caseInsensitiveCompare: @"none"] == NSOrderedSame) - { - return [plainPassword isEqualToString: encryptedPassword]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"crypt"] == NSOrderedSame) - { - return [[plainPassword asCryptStringUsingSalt: encryptedPassword] isEqualToString: encryptedPassword]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5"] == NSOrderedSame) - { - return [[plainPassword asMD5String] isEqualToString: encryptedPassword]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"sha"] == NSOrderedSame) - { - - return [[plainPassword asSHA1String] isEqualToString: encryptedPassword]; - } - - - [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; - - return NO; + return [plainPassword isEqualToCrypted: encryptedPassword + withDefaultScheme: _userPasswordAlgorithm]; } /** @@ -189,26 +181,20 @@ */ - (NSString *) _encryptPassword: (NSString *) plainPassword { - if ([_userPasswordAlgorithm caseInsensitiveCompare: @"none"] == NSOrderedSame) - { - return plainPassword; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"crypt"] == NSOrderedSame) - { - return [plainPassword asCryptStringUsingSalt: [plainPassword asMD5String]]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5"] == NSOrderedSame) - { - return [plainPassword asMD5String]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"sha"] == NSOrderedSame) - { - return [plainPassword asSHA1String]; - } + NSString *pass; + NSString* result; - [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; - - return plainPassword; + pass = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm]; + + if (pass == nil) + [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; + + if (_prependPasswordScheme) + result = [NSString stringWithFormat: @"{%@}%@", _userPasswordAlgorithm, pass]; + else + result = pass; + + return result; } // |
The patch sqlsource_prepend_pw_schemes.patch adds a new option to the SQL settings, it is now optional to add the scheme in the format "{scheme}pass", the default is to add the pass without the scheme to be more backwards-compatible. |
|
Is there any news when this (md5-crypt) will be included in a SOGo 1.3.15+ release? I really need this to have SOGo handle my old style /etc/passwd passwords. At the moment SOGo can read them but not write. |
|
2012-05-31 14:25
|
crypto_v5.patch (45,206 bytes)
# # old_revision [471dc0f5e3b2a6fc571aca4e14c0a5161cf7af0b] # # add_file "SoObjects/SOGo/NSData+Crypto.h" # content [4e1a09d2530e3934966cf7547ffc6ee9411b4d0d] # # add_file "SoObjects/SOGo/NSData+Crypto.m" # content [6d71c327de926a32a542f1cbdf923c1d6435c3f2] # # add_file "SoObjects/SOGo/NSString+Crypto.h" # content [a15042732241b52c399c8667dc0fd281f97bc131] # # add_file "SoObjects/SOGo/NSString+Crypto.m" # content [24e1ff6773c04e588410609f312824912d333911] # # patch "SoObjects/SOGo/GNUmakefile" # from [9e466added539ccacaecd830173afdf9d4755802] # to [0e4d9af53dddf014fe6fccf1d81e751d8dd2bc3c] # # patch "SoObjects/SOGo/LDAPSource.m" # from [49cd1d275323dd81ba0ad5566e542b725856c4a9] # to [0570acb4f559ddabe647634f52a877d17dbbce2d] # # patch "SoObjects/SOGo/NSString+Utilities.h" # from [d2661ef19253d96d64dcf11127ffe6882f90b4d4] # to [c38d80d6ebe9592a9f2ee954282718d7265dec51] # # patch "SoObjects/SOGo/NSString+Utilities.m" # from [800b97cfc8f806e14c310ed8628c94a9934fd80a] # to [eb4c7cd585c64cb8fb0273c3e89be36da11e5f24] # # patch "SoObjects/SOGo/SOGoUserManager.m" # from [9bf968b36558145edd71840dd180876c1d5d7175] # to [344659dcdf8d929bb824d8856d05c1de85ce801e] # # patch "SoObjects/SOGo/SQLSource.h" # from [da3279f8ce2fc584f8c4e28ab8718a2d59e0d182] # to [c336b0e395923d3dbd0112dfe61fff4d0e64ab02] # # patch "SoObjects/SOGo/SQLSource.m" # from [8e7fb09f6d34231ed0e1c57db516961d7a8bf0bb] # to [16b4bee44ea4099f441c684a17aead9329be0bae] # ============================================================ --- SoObjects/SOGo/GNUmakefile 9e466added539ccacaecd830173afdf9d4755802 +++ SoObjects/SOGo/GNUmakefile 0e4d9af53dddf014fe6fccf1d81e751d8dd2bc3c @@ -46,6 +46,8 @@ SOGo_HEADER_FILES = \ NSObject+Utilities.h \ NSString+DAV.h \ NSString+Utilities.h \ + NSString+Crypto.h \ + NSData+Crypto.h \ NSURL+DAV.h \ \ SOGoAuthenticator.h \ @@ -114,6 +116,8 @@ SOGo_OBJC_FILES = \ NSObject+Utilities.m \ NSString+DAV.m \ NSString+Utilities.m \ + NSString+Crypto.m \ + NSData+Crypto.m \ NSURL+DAV.m \ \ SOGoSession.m \ ============================================================ --- SoObjects/SOGo/LDAPSource.m 49cd1d275323dd81ba0ad5566e542b725856c4a9 +++ SoObjects/SOGo/LDAPSource.m 0570acb4f559ddabe647634f52a877d17dbbce2d @@ -40,6 +40,7 @@ #import "LDAPSourceSchema.h" #import "NSArray+Utilities.h" #import "NSString+Utilities.h" +#import "NSString+Crypto.h" #import "SOGoDomainDefaults.h" #import "SOGoSystemDefaults.h" @@ -639,26 +640,13 @@ andMultipleBookingsField: (NSString *) n */ - (NSString *) _encryptPassword: (NSString *) plainPassword { - if ([_userPasswordAlgorithm caseInsensitiveCompare: @"none"] == NSOrderedSame) - { - return plainPassword; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"crypt"] == NSOrderedSame) - { - return [NSString stringWithFormat: @"{CRYPT}%@", [plainPassword asCryptStringUsingSalt: [plainPassword asMD5String]]]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5"] == NSOrderedSame) - { - return [NSString stringWithFormat: @"{MD5}%@", [plainPassword asMD5String]]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"sha"] == NSOrderedSame) - { - return [NSString stringWithFormat: @"{SHA}%@", [plainPassword asSHA1String]]; - } - - [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; - - return plainPassword; + NSString *pass; + pass = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm]; + + if (pass == nil) + [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; + + return [NSString stringWithFormat: @"{%@}%@", _userPasswordAlgorithm, pass]; } // ============================================================ --- /dev/null +++ SoObjects/SOGo/NSData+Crypto.h 4e1a09d2530e3934966cf7547ffc6ee9411b4d0d @@ -0,0 +1,57 @@ +/* NSData+Crypto.h - this file is part of SOGo + * + * Author: Nicolas Höft + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef NSDATA_CRYPTO_H +#define NSDATA_CRYPTO_H + +#import <Foundation/NSData.h> + +@class NSObject; + +@interface NSData (SOGoCryptoExtension) + +- (NSData *) asCryptedPassUsingScheme: (NSString *) passwordScheme + withSalt: (NSData *) theSalt; + +- (NSData *) asMD5; +- (NSData *) asSMD5UsingSalt: (NSData *) theSalt; +- (NSData *) asSHA1; +- (NSData *) asSSHAUsingSalt: (NSData *) theSalt; +- (NSData *) asSHA256; +- (NSData *) asSSHA256UsingSalt: (NSData *) theSalt; +- (NSData *) asSHA512; +- (NSData *) asSSHA512UsingSalt: (NSData *) theSalt; +- (NSData *) asCramMD5; + +- (NSData *) asCryptUsingSalt: (NSData *) theSalt; +- (NSData *) asMD5CryptUsingSalt: (NSData *) theSalt; + +- (NSData *) extractSalt: (NSString *) theScheme; + ++ (NSData *) generateSaltForLength: (unsigned int) theLength + withBase64: (BOOL) doBase64; ++ (NSData *) generateSaltForLength: (unsigned int) theLength; + ++ (NSString *) encodeDataAsHexString: (NSData* ) theData; ++ (NSData *) decodeDataFromHexString: (NSString* ) theString; + +@end + +#endif /* NSDATA_CRYPTO_H */ ============================================================ --- /dev/null +++ SoObjects/SOGo/NSData+Crypto.m 6d71c327de926a32a542f1cbdf923c1d6435c3f2 @@ -0,0 +1,617 @@ +/* NSData+Crypto.m - this file is part of SOGo + * + * + * Author: Nicolas Höft + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __OpenBSD__ +#include <crypt.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#define _XOPEN_SOURCE 1 +#include <unistd.h> +#include <openssl/evp.h> +#include <openssl/md5.h> +#include <openssl/sha.h> + +#import <Foundation/NSArray.h> +#import <NGExtensions/NGBase64Coding.h> +#import "NSData+Crypto.h" + +unsigned charTo4Bits(char c); + + +@implementation NSData (SOGoCryptoExtension) + +/** + * Covert binary data to hex encoded data (lower-case). + * + * @param theData The NSData to be converted into a hex-encoded string. + * @return Hex-Encoded data + */ ++ (NSString *) encodeDataAsHexString: (NSData *) theData +{ + unsigned int byteLength = [theData length], byteCounter = 0; + unsigned int stringLength = (byteLength * 2) + 1, stringCounter = 0; + unsigned char dstBuffer[stringLength]; + unsigned char srcBuffer[byteLength]; + unsigned char *srcPtr = srcBuffer; + [theData getBytes: srcBuffer]; + const unsigned char t[16] = "0123456789abcdef"; + + for (; byteCounter < byteLength; byteCounter++) + { + unsigned src = *srcPtr; + dstBuffer[stringCounter++] = t[src >> 4]; + dstBuffer[stringCounter++] = t[src & 15]; + srcPtr++; + } + + dstBuffer[stringCounter] = '\0'; + return [NSString stringWithUTF8String: (char*)dstBuffer]; +} + +/** + * Covert hex-encoded data to binary data. + * + * @param theString The hex-encoded string to be converted into binary data (works both for upper and lowe case characters) + * @return binary data or nil if unsuccessful + */ ++ (NSData *) decodeDataFromHexString: (NSString *) theString +{ + unsigned int stringLength = [theString length]; + unsigned int byteLength = stringLength/2; + unsigned int byteCounter = 0; + unsigned char srcBuffer[stringLength]; + [theString getCString:(char *)srcBuffer]; + unsigned char *srcPtr = srcBuffer; + unsigned char dstBuffer[byteLength]; + unsigned char *dst = dstBuffer; + while (byteCounter < byteLength) + { + unsigned char c = *srcPtr++; + unsigned char d = *srcPtr++; + unsigned hi = 0, lo = 0; + hi = charTo4Bits(c); + lo = charTo4Bits(d); + if (hi == 255 || lo == 255) + { + //errorCase + return nil; + } + dstBuffer[byteCounter++] = ((hi << 4) | lo); + } + return [NSData dataWithBytes: dst length: byteLength]; +} + +/** + * Generate a binary key which can be used for salting hashes. + * + * @param theLength length of the binary data to be generated in bytes + * @return Pseudo-random binary data with length theLength or nil, if an error occured + */ ++ (NSData *) generateSaltForLength: (unsigned int) theLength +{ + return [NSData generateSaltForLength: theLength withBase64: NO]; +} + +/** + * Generate a binary key which can be used for salting hashes. When using + * with doBase64 == YES then the data will be longer than theLength + * + * @param theLength Length of the binary data to be generated in bytes + * @param doBase64 Convert the data into Base-64 before retuning it, be aware that this makes the binary data longer + * @return Pseudo-random binary data with length theLength or nil, if an error occured + */ ++ (NSData *) generateSaltForLength: (unsigned int) theLength + withBase64: (BOOL) doBase64 +{ + char *buf; + int fd; + NSData *data; + + fd = open("/dev/urandom", O_RDONLY); + + if (fd > 0) + { + buf = (char *)malloc(theLength); + read(fd, buf, theLength); + close(fd); + + data = [NSData dataWithBytesNoCopy: buf length: theLength freeWhenDone: YES]; + if(doBase64 == YES) + { + return [data dataByEncodingBase64WithLineLength: 1024]; + } + return data; + } + return nil; +} + +/** + * Encrypt/Hash the data with a given scheme + * + * @param passwordScheme The scheme to use for hashing/encryption. + * @param theSalt The salt to be used. If none is given but needed, it will be generated + * @return Binary data from the encryption by the specified scheme. On error the funciton returns nil. + */ +- (NSData *) asCryptedPassUsingScheme: (NSString *) passwordScheme + withSalt: (NSData *) theSalt +{ + if ([passwordScheme caseInsensitiveCompare: @"none"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"plain"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"cleartext"] == NSOrderedSame) + { + return self; + } + else if ([passwordScheme caseInsensitiveCompare: @"crypt"] == NSOrderedSame) + { + return [self asCryptUsingSalt: theSalt]; + } + else if ([passwordScheme caseInsensitiveCompare: @"md5-crypt"] == NSOrderedSame) + { + return [self asMD5CryptUsingSalt: theSalt]; + } + else if ([passwordScheme caseInsensitiveCompare: @"md5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"plain-md5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"ldap-md5"] == NSOrderedSame) + { + return [self asMD5]; + } + else if ([passwordScheme caseInsensitiveCompare: @"cram-md5"] == NSOrderedSame) + { + return [self asCramMD5]; + } + else if ([passwordScheme caseInsensitiveCompare: @"smd5"] == NSOrderedSame) + { + return [self asSMD5UsingSalt: theSalt]; + } + else if ([passwordScheme caseInsensitiveCompare: @"sha"] == NSOrderedSame) + { + return [self asSHA1]; + } + else if ([passwordScheme caseInsensitiveCompare: @"ssha"] == NSOrderedSame) + { + return [self asSSHAUsingSalt: theSalt]; + } + else if ([passwordScheme caseInsensitiveCompare: @"sha256"] == NSOrderedSame) + { + return [self asSHA256]; + } + else if ([passwordScheme caseInsensitiveCompare: @"ssha256"] == NSOrderedSame) + { + return [self asSSHA256UsingSalt: theSalt]; + } + else if ([passwordScheme caseInsensitiveCompare: @"sha512"] == NSOrderedSame) + { + return [self asSHA512]; + } + else if ([passwordScheme caseInsensitiveCompare: @"ssha512"] == NSOrderedSame) + { + return [self asSSHA512UsingSalt: theSalt]; + } + // in case the scheme was not detected, return nil + return nil; +} + + +/** + * Hash the data with MD5. Uses openssl functions to generate it + * + * @return Binary data from MD5 hashing. On error the funciton returns nil. + */ +- (NSData *) asMD5 +{ + unsigned char md5[MD5_DIGEST_LENGTH]; + memset(md5, 0, MD5_DIGEST_LENGTH); + + MD5([self bytes], [self length], md5); + + return [NSData dataWithBytes: md5 length: MD5_DIGEST_LENGTH]; +} + +/** + * Hash the data with CRAM-MD5. Uses openssl functions to generate it. + * + * Note that the actual CRAM-MD5 algorithm also needs a challenge + * but this is not provided, this function actually calculalates + * only the context data which can be used for the challange-response + * algorithm then. This is just the underlying algorithm to store the passwords. + * + * The code is adopts the dovecot behaviour of storing the passwords + * + * @return Binary data from CRAM-MD5 'hashing'. On error the funciton returns nil. + */ +- (NSData *) asCramMD5 +{ + + MD5_CTX ctx; + unsigned char inner[64]; + unsigned char outer[64]; + unsigned char result[32]; + unsigned char *r; + int i; + int len; + NSData *key; + + if ([self length] > 64) + { + key = [self asMD5]; + } + else + { + key = self; + } + + len = [key length]; + // fill with both inner and outer with key + memcpy(inner, [key bytes], len); + // make sure the rest of the bytes is zero + memset(inner + len, 0, 64 - len); + memcpy(outer, inner, 64); + + for (i = 0; i < 64; i++) + { + inner[i] ^= 0x36; + outer[i] ^= 0x5c; + } +// this transformation is needed for the correct cast to binary data +#define CDPUT(p, c) { \ + *p = (c) & 0xff; p++; \ + *p = (c) >> 8 & 0xff; p++; \ + *p = (c) >> 16 & 0xff; p++; \ + *p = (c) >> 24 & 0xff; p++; \ +} + + // generate first set of context bytes from outer data + MD5_Init(&ctx); + MD5_Transform(&ctx, outer); + r = result; + // convert this to correct binary data according to RFC 1321 + CDPUT(r, ctx.A); + CDPUT(r, ctx.B); + CDPUT(r, ctx.C); + CDPUT(r, ctx.D); + + // second set with inner data is appended to result string + MD5_Init(&ctx); + MD5_Transform(&ctx, inner); + // convert this to correct binary data + CDPUT(r, ctx.A); + CDPUT(r, ctx.B); + CDPUT(r, ctx.C); + CDPUT(r, ctx.D); + + return [NSData dataWithBytes: result length: 32]; +} + +/** + * Hash the data with SHA1. Uses openssl functions to generate it. + * + * @return Binary data from SHA1 hashing. On error the funciton returns nil. + */ +- (NSData *) asSHA1 +{ + unsigned char sha[SHA_DIGEST_LENGTH]; + memset(sha, 0, SHA_DIGEST_LENGTH); + + SHA1([self bytes], [self length], sha); + + return [NSData dataWithBytes: sha length: SHA_DIGEST_LENGTH]; +} + +/** + * Hash the data with SHA256. Uses openssl functions to generate it. + * + * @return Binary data from SHA256 hashing. On error the funciton returns nil. + */ +- (NSData *) asSHA256 +{ + unsigned char sha[SHA256_DIGEST_LENGTH]; + memset(sha, 0, SHA256_DIGEST_LENGTH); + + SHA256([self bytes], [self length], sha); + + return [NSData dataWithBytes: sha length: SHA256_DIGEST_LENGTH]; +} + +/** + * Hash the data with SHA512. Uses openssl functions to generate it. + * + * @return Binary data from SHA512 hashing. On error the funciton returns nil. + */ +- (NSData *) asSHA512 +{ + unsigned char sha[SHA512_DIGEST_LENGTH]; + memset(sha, 0, SHA512_DIGEST_LENGTH); + + SHA512([self bytes], [self length], sha); + + return [NSData dataWithBytes: sha length: SHA512_DIGEST_LENGTH]; +} + +/** + * Hash the data with SSHA. Uses openssl functions to generate it. + * + * SSHA works following: SSHA(pass, salt) = SHA1(pass + salt) + saltData + * + * @param theSalt The salt to be used must not be nil, if empty, one will be generated + * @return Binary data from SHA1 hashing. On error the funciton returns nil. + */ +- (NSData *) asSSHAUsingSalt: (NSData *) theSalt +{ + // + NSMutableData *sshaData; + + // generate salt, if not available + if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8]; + + // put the pass and salt together as one data array + sshaData = [NSMutableData dataWithData: self]; + [sshaData appendData: theSalt]; + // generate SHA1 from pass + salt + sshaData = [NSMutableData dataWithData: [sshaData asSHA1]]; + // append salt again + [sshaData appendData: theSalt]; + + return sshaData; +} + +/** + * Hash the data with SSHA256. Uses openssl functions to generate it. + * + * SSHA256 works following: SSHA256(pass, salt) = SHA256(pass + salt) + saltData + * + * @param theSalt The salt to be used must not be nil, if empty, one will be generated + * @return Binary data from SHA1 hashing. On error the funciton returns nil. + */ + +- (NSData *) asSSHA256UsingSalt: (NSData *) theSalt +{ + NSMutableData *sshaData; + + // generate salt, if not available + if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8]; + + // put the pass and salt together as one data array + sshaData = [NSMutableData dataWithData: self]; + [sshaData appendData: theSalt]; + // generate SHA1 from pass + salt + sshaData = [NSMutableData dataWithData: [sshaData asSHA256]]; + // append salt again + [sshaData appendData: theSalt]; + + return sshaData; +} + +/** + * Hash the data with SSHA512. Uses openssl functions to generate it. + * + * SSHA works following: SSHA512(pass, salt) = SHA512(pass + salt) + saltData + * + * @param theSalt The salt to be used must not be nil, if empty, one will be generated + * @return Binary data from SHA512 hashing. On error the funciton returns nil. + */ + +- (NSData *) asSSHA512UsingSalt: (NSData *) theSalt +{ + NSMutableData *sshaData; + + // generate salt, if not available + if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8]; + + // put the pass and salt together as one data array + sshaData = [NSMutableData dataWithData: self]; + [sshaData appendData: theSalt]; + // generate SHA1 from pass + salt + sshaData = [NSMutableData dataWithData: [sshaData asSHA512]]; + // append salt again + [sshaData appendData: theSalt]; + + return sshaData; +} + +/** + * Hash the data with SMD5. Uses openssl functions to generate it. + * + * SMD5 works following: SMD5(pass, salt) = MD5(pass + salt) + saltData + * + * @param theSalt The salt to be used must not be nil, if empty, one will be generated + * @return Binary data from SMD5 hashing. On error the funciton returns nil. + */ +- (NSData *) asSMD5UsingSalt: (NSData *) theSalt +{ + // SMD5 works following: SMD5(pass, salt) = MD5(pass + salt) + salt + NSMutableData *smdData; + + // generate salt, if not available + if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8]; + + // put the pass and salt together as one data array + smdData = [NSMutableData dataWithData: self]; + [smdData appendData: theSalt]; + // generate SHA1 from pass + salt + smdData = [NSMutableData dataWithData: [smdData asMD5]]; + // append salt again + [smdData appendData: theSalt]; + + return smdData; +} + + +/** + * Hash the data with CRYPT-MD5 as used in /etc/passwd nowadays. Uses crypt() function to generate it. + * + * + * @param theSalt The salt to be used must not be nil, if empty, one will be generated. It must be printable characters only. + * @return Binary data from CRYPT-MD5 hashing. On error the funciton returns nil. + */ +- (NSData *) asMD5CryptUsingSalt: (NSData *) theSalt +{ + char *buf; + NSMutableData *saltData; + NSString *cryptString; + NSString *saltString; + + if ([theSalt length] == 0) + { + // make sure these characters are all printable by using base64 + theSalt = [NSData generateSaltForLength: 8 withBase64: YES]; + } + cryptString = [[NSString alloc] initWithData: self encoding: NSUTF8StringEncoding]; + + NSString * magic = @"$1$"; + saltData = [NSMutableData dataWithData: [magic dataUsingEncoding: NSUTF8StringEncoding]]; + [saltData appendData: theSalt]; + // terminate with "$" + [saltData appendData: [@"$" dataUsingEncoding: NSUTF8StringEncoding]]; + + saltString = [[NSString alloc] initWithData: saltData encoding: NSUTF8StringEncoding]; + + buf = crypt([cryptString UTF8String], [saltString UTF8String]); + [cryptString release]; + [saltString release]; + if (!buf) + return nil; + return [NSData dataWithBytes: buf length: strlen(buf)]; +} + +/** + * Hash the data using crypt() function. + * + * @param theSalt The salt to be used must not be nil, if empty, one will be generated + * @return Binary data from CRYPT-MD5 hashing. On error the funciton returns nil. + */ +- (NSData *) asCryptUsingSalt: (NSData *) theSalt +{ + char *buf; + NSString *saltString; + NSString *cryptString; + + // crypt() works with strings, so convert NSData to strings + cryptString = [[NSString alloc] initWithData: self encoding: NSUTF8StringEncoding]; + + if ([theSalt length] == 0) theSalt = [NSData generateSaltForLength: 8 withBase64: YES]; + + saltString = [[NSString alloc] initWithData: theSalt encoding: NSUTF8StringEncoding]; + + // The salt is weak here, but who cares anyway, crypt should not + // be used anymore + buf = crypt([cryptString UTF8String], [saltString UTF8String]); + [saltString release]; + [cryptString release]; + if (!buf) + return nil; + return [NSData dataWithBytes: buf length: strlen(buf)]; +} + +/** + * Get the salt from a password encrypted with a specied scheme + * + * @param theScheme Needed to get the salt correctly out of the pass + * @return The salt, if one was available in the password/scheme, else empty data + */ +- (NSData *) extractSalt: (NSString *) theScheme +{ + NSRange r; + int len; + len = [self length]; + if (len == 0) + return [NSData data]; + + // for the ssha schemes the salt is appended at the endif + // so the range with the salt are bytes after each digest length + if ([theScheme caseInsensitiveCompare: @"crypt"] == NSOrderedSame) + { + // for crypt schemes simply use the whole string + // the crypt() function is able to extract it by itself + r = NSMakeRange(0, len); + } + else if ([theScheme caseInsensitiveCompare: @"md5-crypt"] == NSOrderedSame) + { + // md5 crypt is generated the following "$1$<salt>$<encrypted pass>" + NSString *cryptString; + NSArray *cryptParts; + cryptString = [NSString stringWithUTF8String: [self bytes] ]; + cryptParts = [cryptString componentsSeparatedByString: @"$"]; + // correct number of elements (first one is an empty string) + if ([cryptParts count] != 4) + { + return [NSData data]; + } + // second is the identifier of md5-crypt + else if( [[cryptParts objectAtIndex: 1] caseInsensitiveCompare: @"1"] != NSOrderedSame ) + { + return [NSData data]; + } + // third is the salt; convert it to NSData + return [[cryptParts objectAtIndex: 2] dataUsingEncoding: NSUTF8StringEncoding]; + } + else if ([theScheme caseInsensitiveCompare: @"ssha"] == NSOrderedSame) + { + r = NSMakeRange(SHA_DIGEST_LENGTH, len - SHA_DIGEST_LENGTH); + } + else if ([theScheme caseInsensitiveCompare: @"ssha256"] == NSOrderedSame) + { + r = NSMakeRange(SHA256_DIGEST_LENGTH, len - SHA256_DIGEST_LENGTH); + } + else if ([theScheme caseInsensitiveCompare: @"ssha512"] == NSOrderedSame) + { + r = NSMakeRange(SHA512_DIGEST_LENGTH, len - SHA512_DIGEST_LENGTH); + } + else if ([theScheme caseInsensitiveCompare: @"smd5"] == NSOrderedSame) + { + r = NSMakeRange(MD5_DIGEST_LENGTH, len - MD5_DIGEST_LENGTH); + } + else + { + // return empty string on unknown scheme + return [NSData data]; + } + + return [self subdataWithRange: r]; +} + +@end + +unsigned charTo4Bits(char c) +{ + unsigned bits = 0; + if (c > '/' && c < ':') + { + bits = c - '0'; + } + else if (c > '@' && c < 'G') + { + bits = (c- 'A') + 10; + } + else if (c > '`' && c < 'g') + { + bits = (c- 'a') + 10; + } + else + { + bits = 255; + } + return bits; +} ============================================================ --- /dev/null +++ SoObjects/SOGo/NSString+Crypto.h a15042732241b52c399c8667dc0fd281f97bc131 @@ -0,0 +1,59 @@ +/* NSString+Crypto.h - this file is part of SOGo + * + * Author: Nicolas Höft + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef NSSTRING_CRYPTO_H +#define NSSTRING_CRYPTO_H + +#import <Foundation/NSData.h> +#import <Foundation/NSString.h> + +typedef enum { + encDefault, //!< default encoding, let the algorithm decide + encPlain, //!< the data is plain text, simply convert to string + encHex, //!< the data is hex encoded + encBase64, //!< base64 encoding +} keyEncoding; + +@class NSObject; + +@interface NSString (SOGoCryptoExtension) + + +- (BOOL) isEqualToCrypted: (NSString *) cryptedPassword + withDefaultScheme: (NSString *) theScheme; + +- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme + withSalt: (NSData *) theSalt + andEncoding: (keyEncoding) encoding; + +// this method uses the default encoding (base64, plain, hex) +// and generates a salt when necessary +- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme; + +- (NSArray *) splitPasswordWithDefaultScheme: (NSString *) defaultScheme; + +- (NSString *) asSHA1String; +- (NSString *) asMD5String; + ++ (keyEncoding) getDefaultEncodingForScheme: (NSString *) passwordScheme; + +@end + +#endif /* NSSTRING_CRYPTO_H */ ============================================================ --- /dev/null +++ SoObjects/SOGo/NSString+Crypto.m 24e1ff6773c04e588410609f312824912d333911 @@ -0,0 +1,301 @@ +/* NSString+Crypto.m - this file is part of SOGo + * + * + * Author: Nicolas Höft + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import <Foundation/NSArray.h> +#import <Foundation/NSValue.h> + +#import "NSString+Crypto.h" +#import "NSData+Crypto.h" +#import <NGExtensions/NGBase64Coding.h> + +@implementation NSString (SOGoCryptoExtension) + +/** + * Extracts the scheme from a string formed "{scheme}pass". + * + * @return The scheme or an empty string if the string did not contained a scheme in the format above + */ +- (NSString *) extractCryptScheme +{ + NSRange r; + int len; + + len = [self length]; + if (len == 0) + return @""; + if ([self characterAtIndex:0] != '{') + return @""; + + r = [self rangeOfString:@"}" options:(NSLiteralSearch)]; + if (r.length == 0) + return @""; + + r.length = (r.location - 1); + r.location = 1; + return [[self substringWithRange:r] lowercaseString]; +} + + +/** + * Split a password of the form {scheme}pass into an array of its components: + * {NSString *scheme, NString *pass, NSInteger encoding}, where encoding is + * the enum keyEncoding converted to an integer value. + * + * @param defaultScheme If no scheme is given in cryptedPassword, fall back to this scheme + * @see asCryptedPassUsingScheme + * @see keyEncoding + * @return NSArray with the three elements described above + */ +- (NSArray *) splitPasswordWithDefaultScheme: (NSString *) defaultScheme +{ + NSString *scheme; + NSString *pass; + NSArray *schemeComps; + keyEncoding encoding; + + NSRange range; + int selflen, len; + + selflen = [self length]; + + scheme = [self extractCryptScheme]; + len = [scheme length]; + if (len > 0) + range = NSMakeRange (len+2, selflen-len-2); + else + range = NSMakeRange (0, selflen); + if (len == 0) + scheme = defaultScheme; + + encoding = [NSString getDefaultEncodingForScheme: scheme]; + + // get the encoding which may be part of the scheme + // e.g. ssha.hex forces a hex encoded ssha scheme + // possible is "b64" or "hex" + schemeComps = [scheme componentsSeparatedByString: @"."]; + if ([schemeComps count] == 2) + { + NSString *stringEncoding; + // scheme without encoding string is the first item + scheme = [schemeComps objectAtIndex: 0]; + // encoding string is second item + stringEncoding = [schemeComps objectAtIndex: 1]; + if ([stringEncoding caseInsensitiveCompare: @"hex"] == NSOrderedSame) + { + encoding = encHex; + } + else if ([stringEncoding caseInsensitiveCompare: @"b64"] == NSOrderedSame || + [stringEncoding caseInsensitiveCompare: @"base64"] == NSOrderedSame) + { + encoding = encBase64; + } + } + + pass = [self substringWithRange: range]; + return [NSArray arrayWithObjects: scheme, pass, [NSNumber numberWithInt: encoding], nil]; +} + +/** + * Compare the hex or base64 encoded password with an encrypted password + * + * @param cryptedPassword The password to compare with, format {scheme}pass , "{scheme}" is optional + * @param theScheme If no scheme is given in cryptedPassword, fall back to this scheme + * @see asCryptedPassUsingScheme + * @return YES if the passwords are identical using this encryption scheme + */ +- (BOOL) isEqualToCrypted: (NSString *) cryptedPassword + withDefaultScheme: (NSString *) theScheme +{ + NSArray *passInfo; + NSString *selfCrypted; + NSString *pass; + NSString *scheme; + NSData *salt; + NSData *decodedData; + NSNumber *encodingNumber; + keyEncoding encoding; + + // split scheme and pass + passInfo = [cryptedPassword splitPasswordWithDefaultScheme: theScheme]; + + scheme = [passInfo objectAtIndex: 0]; + pass = [passInfo objectAtIndex: 1]; + encodingNumber = [passInfo objectAtIndex: 2]; + encoding = [encodingNumber integerValue]; + + if (encoding == encHex) + { + decodedData = [NSData decodeDataFromHexString: pass]; + + if(decodedData == nil) + { + decodedData = [NSData data]; + } + else + { + // decoding was successful, now make sure + // that the pass is in lowercase since decodeDataFromHexString uses + // lowercase charaters, too + pass = [pass lowercaseString]; + } + } + else if(encoding == encBase64) + { + decodedData = [pass dataByDecodingBase64]; + if(decodedData == nil) + { + decodedData = [NSData data]; + } + } + else + { + decodedData = [pass dataUsingEncoding: NSUTF8StringEncoding]; + } + + salt = [decodedData extractSalt: scheme]; + + // encrypt self with the salt an compare the results + selfCrypted = [self asCryptedPassUsingScheme: scheme + withSalt: salt + andEncoding: encoding]; + // return always false when there was a problem + if (selfCrypted == nil) + return NO; + + if ([selfCrypted isEqualToString: pass] == YES) + return YES; + return NO; +} + +/** + * Calls asCryptedPassUsingScheme:withSalt:andEncoding: with an empty salt and uses + * the default encoding. + * + * @param passwordScheme + * @return If successful, the encrypted and encoded NSString of the format {scheme}pass, or nil if the scheme did not exists or an error occured + */ +- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme +{ + return [self asCryptedPassUsingScheme: passwordScheme + withSalt: [NSData data] + andEncoding: encDefault]; +} + +/** + * Uses NSData -asCryptedPassUsingScheme to encrypt the string and converts the + * binary data back to a readable string using userEncoding + * + * @param passwordScheme The scheme to use + * @param theSalt The binary data of the salt + * @param userEncoding The encoding (plain, hex, base64) to be used + * @return If successful, the encrypted and encoded NSString of the format {scheme}pass, or nil if the scheme did not exists or an error occured + */ +- (NSString *) asCryptedPassUsingScheme: (NSString *) passwordScheme + withSalt: (NSData *) theSalt + andEncoding: (keyEncoding) userEncoding +{ + keyEncoding dataEncoding; + NSData* cryptedData; + // convert NSString to NSData and apply encryption scheme + cryptedData = [self dataUsingEncoding: NSUTF8StringEncoding]; + cryptedData = [cryptedData asCryptedPassUsingScheme: passwordScheme withSalt: theSalt]; + // abort on unsupported scheme or error + if (cryptedData == nil) + return nil; + + // use default encoding scheme, when set to default + if (userEncoding == encDefault) + dataEncoding = [NSString getDefaultEncodingForScheme: passwordScheme]; + else + dataEncoding = userEncoding; + + if (dataEncoding == encHex) + { + // hex encoding + return [NSData encodeDataAsHexString: cryptedData]; + } + else if(dataEncoding == encBase64) + { + // base64 encoding + NSString *s = [[NSString alloc] initWithData: [cryptedData dataByEncodingBase64WithLineLength: 1024] + encoding: NSASCIIStringEncoding]; + return [s autorelease]; + } + + // plain string + return [[[NSString alloc] initWithData: cryptedData encoding: NSUTF8StringEncoding] autorelease]; +} + +/** + * Returns the encoding for a specified scheme + * + * @param passwordScheme The scheme for which to get the encoding. + * @see keyEncoding + * @return returns the encoding, if unknown returns encPlain + */ ++ (keyEncoding) getDefaultEncodingForScheme: (NSString *) passwordScheme +{ + // in order to keep backwards-compatibility, hex encoding is used for sha1 here + if ([passwordScheme caseInsensitiveCompare: @"md5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"plain-md5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"sha"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"cram-md5"] == NSOrderedSame) + { + return encHex; + } + else if ([passwordScheme caseInsensitiveCompare: @"smd5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"ldap-md5"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"ssha"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"sha256"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"ssha256"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"sha512"] == NSOrderedSame || + [passwordScheme caseInsensitiveCompare: @"ssha512"] == NSOrderedSame) + { + return encBase64; + } + return encPlain; +} + +/** + * Encrypts the data with SHA1 scheme and returns the hex-encoded data + * + * @return If successful, sha1 encrypted and with hex encoded string + */ +- (NSString *) asSHA1String; +{ + NSData *cryptData; + cryptData = [self dataUsingEncoding: NSUTF8StringEncoding]; + return [NSData encodeDataAsHexString: [cryptData asSHA1] ]; +} + +/** + * Encrypts the data with Plain MD5 scheme and returns the hex-encoded data + * + * @return If successful, MD5 encrypted and with hex encoded string + */ +- (NSString *) asMD5String; +{ + NSData *cryptData; + cryptData = [self dataUsingEncoding: NSUTF8StringEncoding]; + return [NSData encodeDataAsHexString: [cryptData asMD5] ]; +} + +@end ============================================================ --- SoObjects/SOGo/NSString+Utilities.h d2661ef19253d96d64dcf11127ffe6882f90b4d4 +++ SoObjects/SOGo/NSString+Utilities.h c38d80d6ebe9592a9f2ee954282718d7265dec51 @@ -66,10 +66,6 @@ - (id) objectFromJSONString; -- (NSString *) asCryptStringUsingSalt: (NSString *) theSalt; -- (NSString *) asMD5String; -- (NSString *) asSHA1String; - - (NSString *) asSafeSQLString; - (NSUInteger) countOccurrencesOfString: (NSString *) substring; ============================================================ --- SoObjects/SOGo/NSString+Utilities.m 800b97cfc8f806e14c310ed8628c94a9934fd80a +++ SoObjects/SOGo/NSString+Utilities.m eb4c7cd585c64cb8fb0273c3e89be36da11e5f24 @@ -21,10 +21,6 @@ * Boston, MA 02111-1307, USA. */ -#ifndef __OpenBSD__ -#include <crypt.h> -#endif - #import <Foundation/NSArray.h> #import <Foundation/NSCharacterSet.h> #import <Foundation/NSData.h> @@ -45,12 +41,6 @@ #import "NSString+Utilities.h" -#define _XOPEN_SOURCE 1 -#include <unistd.h> -#include <openssl/evp.h> -#include <openssl/md5.h> -#include <openssl/sha.h> - static NSMutableCharacterSet *urlNonEndingChars = nil; static NSMutableCharacterSet *urlAfterEndingChars = nil; static NSMutableCharacterSet *urlStartChars = nil; @@ -534,48 +524,6 @@ static int cssEscapingCount; return object; } -- (NSString *) asCryptStringUsingSalt: (NSString *) theSalt -{ - char *buf; - - // The salt is weak here, but who cares anyway, crypt should not - // be used anymore - buf = crypt([self UTF8String], [theSalt UTF8String]); - return [NSString stringWithUTF8String: buf]; -} - -- (NSString *) asMD5String -{ - unsigned char md[MD5_DIGEST_LENGTH]; - char buf[80]; - int i; - - memset(md, 0, MD5_DIGEST_LENGTH); - memset(buf, 0, 80); - - EVP_Digest((const void *) [self UTF8String], strlen([self UTF8String]), md, NULL, EVP_md5(), NULL); - for (i = 0; i < MD5_DIGEST_LENGTH; i++) - sprintf(&(buf[i*2]), "%02x", md[i]); - - return [NSString stringWithUTF8String: buf]; -} - -- (NSString *) asSHA1String -{ - unsigned char sha[SHA_DIGEST_LENGTH]; - char buf[80]; - int i; - - memset(sha, 0, SHA_DIGEST_LENGTH); - memset(buf, 0, 80); - - SHA1((const void *)[self UTF8String], strlen([self UTF8String]), sha); - for (i = 0; i < SHA_DIGEST_LENGTH; i++) - sprintf(&(buf[i*2]), "%02x", sha[i]); - - return [NSString stringWithUTF8String: buf]; -} - - (NSString *) asSafeSQLString { return [[self stringByReplacingString: @"\\" withString: @"\\\\"] ============================================================ --- SoObjects/SOGo/SOGoUserManager.m 9bf968b36558145edd71840dd180876c1d5d7175 +++ SoObjects/SOGo/SOGoUserManager.m 344659dcdf8d929bb824d8856d05c1de85ce801e @@ -33,6 +33,7 @@ #import "NSArray+Utilities.h" #import "NSString+Utilities.h" +#import "NSString+Crypto.h" #import "NSObject+Utilities.h" #import "SOGoDomainDefaults.h" #import "SOGoSource.h" ============================================================ --- SoObjects/SOGo/SQLSource.h da3279f8ce2fc584f8c4e28ab8718a2d59e0d182 +++ SoObjects/SOGo/SQLSource.h c336b0e395923d3dbd0112dfe61fff4d0e64ab02 @@ -45,6 +45,7 @@ NSString *_imapHostField; NSString *_userPasswordAlgorithm; NSURL *_viewURL; + BOOL _prependPasswordScheme; /* resources handling */ NSString *_kindField; ============================================================ --- SoObjects/SOGo/SQLSource.m 8e7fb09f6d34231ed0e1c57db516961d7a8bf0bb +++ SoObjects/SOGo/SQLSource.m 16b4bee44ea4099f441c684a17aead9329be0bae @@ -39,6 +39,7 @@ #import "SOGoConstants.h" #import "NSString+Utilities.h" +#import "NSString+Crypto.h" #import "SQLSource.h" @@ -47,7 +48,10 @@ * * c_uid - will be used for authentication - it's a username or username@domain.tld) * c_name - which can be identical to c_uid - will be used to uniquely identify entries) - * c_password - password of the user, plain-text, md5 or sha encoded for now + * c_password - password of the user, can be encoded in {scheme}pass format, or when stored without + * scheme it uses the scheme set in userPasswordAlgorithm. + * Possible algorithms are: plain, md5, crypt-md5, sha, ssha (including 256/512 variants), + * cram-md5, smd5, crypt, crypt-md5 * c_cn - the user's common name * mail - the user's mail address * @@ -63,8 +67,12 @@ * canAuthenticate = YES; * isAddressBook = YES; * userPasswordAlgorithm = md5; + * prependPasswordScheme = YES; * } * + * If prependPasswordScheme is set to YES, the generated passwords will have the format {scheme}password. + * If it is NO (the default), the password will be written to database without encryption scheme. + * */ @implementation SQLSource @@ -126,6 +134,10 @@ ASSIGN(_kindField, [udSource objectForKey: @"KindFieldName"]); ASSIGN(_multipleBookingsField, [udSource objectForKey: @"MultipleBookingsFieldName"]); ASSIGN(_domainField, [udSource objectForKey: @"DomainFieldName"]); + if ([udSource objectForKey: @"prependPasswordScheme"]) + _prependPasswordScheme = [[udSource objectForKey: @"prependPasswordScheme"] boolValue]; + else + _prependPasswordScheme = NO; if (!_userPasswordAlgorithm) _userPasswordAlgorithm = @"none"; @@ -157,28 +169,8 @@ if (!plainPassword || !encryptedPassword) return NO; - if ([_userPasswordAlgorithm caseInsensitiveCompare: @"none"] == NSOrderedSame) - { - return [plainPassword isEqualToString: encryptedPassword]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"crypt"] == NSOrderedSame) - { - return [[plainPassword asCryptStringUsingSalt: encryptedPassword] isEqualToString: encryptedPassword]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5"] == NSOrderedSame) - { - return [[plainPassword asMD5String] isEqualToString: encryptedPassword]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"sha"] == NSOrderedSame) - { - - return [[plainPassword asSHA1String] isEqualToString: encryptedPassword]; - } - - - [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; - - return NO; + return [plainPassword isEqualToCrypted: encryptedPassword + withDefaultScheme: _userPasswordAlgorithm]; } /** @@ -189,26 +181,20 @@ */ - (NSString *) _encryptPassword: (NSString *) plainPassword { - if ([_userPasswordAlgorithm caseInsensitiveCompare: @"none"] == NSOrderedSame) - { - return plainPassword; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"crypt"] == NSOrderedSame) - { - return [plainPassword asCryptStringUsingSalt: [plainPassword asMD5String]]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5"] == NSOrderedSame) - { - return [plainPassword asMD5String]; - } - else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"sha"] == NSOrderedSame) - { - return [plainPassword asSHA1String]; - } + NSString *pass; + NSString* result; - [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; - - return plainPassword; + pass = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm]; + + if (pass == nil) + [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; + + if (_prependPasswordScheme) + result = [NSString stringWithFormat: @"{%@}%@", _userPasswordAlgorithm, pass]; + else + result = pass; + + return result; } // |
Date Modified | Username | Field | Change |
---|---|---|---|
2012-01-29 15:28 | the_nic | New Issue | |
2012-01-29 15:28 | the_nic | File Added: nsstring+crypto.patch | |
2012-01-29 16:00 | ludovic | Note Added: 0003337 | |
2012-01-29 17:47 | the_nic | Note Added: 0003338 | |
2012-01-29 17:48 | the_nic | File Added: nsstring+crypto_updated.patch | |
2012-03-04 12:24 | the_nic | Note Added: 0003520 | |
2012-03-16 18:36 | ludovic | Target Version | => 1.3.14 |
2012-03-23 12:33 | francis | Target Version | 1.3.14 => 1.3.15 |
2012-04-02 07:47 | BattleMage | Note Added: 0003682 | |
2012-04-16 15:43 | the_nic | Note Added: 0003743 | |
2012-05-09 15:41 | ludovic | Target Version | 1.3.15 => 1.3.16 |
2012-05-10 21:03 | pgauret | Note Added: 0003884 | |
2012-05-16 17:20 | chrroessner | Note Added: 0003917 | |
2012-05-16 18:17 | aschild | Note Added: 0003918 | |
2012-05-19 15:20 | the_nic | Note Added: 0003926 | |
2012-05-20 11:34 | the_nic | File Added: crypto_v3.patch | |
2012-05-20 11:47 | the_nic | Note Added: 0003927 | |
2012-05-20 12:30 | the_nic | Note Edited: 0003927 | |
2012-05-22 12:09 | the_nic | File Added: crypto_v4.patch | |
2012-05-22 12:10 | the_nic | Note Added: 0003931 | |
2012-05-22 12:20 | chrroessner | Note Added: 0003932 | |
2012-05-22 12:25 | the_nic | Note Added: 0003934 | |
2012-05-22 13:53 | the_nic | File Added: sqlsource_prepend_pw_schemes.patch | |
2012-05-22 13:55 | the_nic | Note Added: 0003935 | |
2012-05-31 06:44 | Hans de Groot | Note Added: 0003986 | |
2012-05-31 14:25 | the_nic | File Added: crypto_v5.patch | |
2012-05-31 14:52 | ludovic | Note Added: 0003988 | |
2012-05-31 14:52 | ludovic | Status | new => resolved |
2012-05-31 14:52 | ludovic | Fixed in Version | => 1.3.16 |
2012-05-31 14:52 | ludovic | Resolution | open => fixed |
2012-05-31 14:52 | ludovic | Assigned To | => ludovic |
2012-05-31 14:52 | ludovic | Status | resolved => closed |