View Issue Details

IDProjectCategoryView StatusLast Update
0001608SOGoBackend Generalpublic2012-05-31 10:52
Reporterthe_nic Assigned Toludovic  
PrioritynormalSeverityfeatureReproducibilityN/A
Status closedResolutionfixed 
Product Version1.3.11 
Target Version1.3.16Fixed in Version1.3.16 
Summary0001608: More password schemes for SQL backend
DescriptionSince the SQL backend only supports a few password schemes, I've created a patch which adds md5-crypt and ssha, sha256 and sha512.
The sha versions are untested and my Objective-C knowledge is limited so I'm really unsure about memory leaks.
But as far as I could test, this seems to work.

Could someone please review this patch?
TagsNo tags attached.

Activities

2012-01-29 10: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] ];
 }
 
 //
nsstring+crypto.patch (6,614 bytes)
ludovic

ludovic

2012-01-29 11:00

administrator   ~0003337

Your patch is incomplete - you forgot to show the NSString+Crypto category.
the_nic

the_nic

2012-01-29 12:47

reporter   ~0003338

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 12: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
the_nic

the_nic

2012-03-04 07:24

reporter   ~0003520

Any feedback? I'd be willing to improve it, if there are any comments..
BattleMage

BattleMage

2012-04-02 03:47

reporter   ~0003682

This patch looks really great. Do you think you could implement SSHA256 and SSHA512 as well? That would make SOGo fit nicely into dovecot.
the_nic

the_nic

2012-04-16 11:43

reporter   ~0003743

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.
But the current ssha implementation is not tested, either. And AFAIK dovecot uses also base64-encoding, which is not support by this implementation (yet)
pgauret

pgauret

2012-05-10 17:03

reporter   ~0003884

Could we also make these algorithms available to the LDAP backend as well?
chrroessner

chrroessner

2012-05-16 13:20

reporter   ~0003917

I am interested in SSHA for LDAP, too :-)
aschild

aschild

2012-05-16 14:17

reporter   ~0003918

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)
the_nic

the_nic

2012-05-19 11:20

reporter   ~0003926

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 07: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];
 }
 
 //
crypto_v3.patch (34,277 bytes)
the_nic

the_nic

2012-05-20 07:47

reporter   ~0003927

Last edited: 2012-05-20 08:30

I've now added my third version of the cryptographic extension for SQL (and now also LDAP).
It supports SHA, SSHA (plus 256/512-variants), SMD5, CRYPT-MD5, CRYPT. For a description see the dovecot link above.

BTW: you may change line 90 of NSString+Crypto.m to this:

else if ([stringEncoding caseInsensitiveCompare: @"b64"] == NSOrderedSame ||
               [stringEncoding caseInsensitiveCompare: @"base64"] == NSOrderedSame)

And a further note explaining the patch (v3):
I've created one extension to NSData which is actually responsable for the encryption. Using NSData has the advantage to be usabe for any binary data, which is necessary for the salt generation. All the functions return NSData.
In order to convert the data to prinable strings, I am using base64 encoding and hex encoding. The encoding can be specified directly by appending ".b64" or ".hex" to the scheme (".base64" is a synonym for ".b64"). But there is also always a default encoding for each encryption.

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 08: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];
 }
 
 //
crypto_v4.patch (43,524 bytes)
the_nic

the_nic

2012-05-22 08:10

reporter   ~0003931

The v4 patch adds CRAM-MD5 as scheme and documents all methods implemented by me.
chrroessner

chrroessner

2012-05-22 08:20

reporter   ~0003932

> ...cryptographic extension for SQL (and now also LDAP).

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
the_nic

the_nic

2012-05-22 08:25

reporter   ~0003934

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 09: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_nic

the_nic

2012-05-22 09:55

reporter   ~0003935

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.
Hans de Groot

Hans de Groot

2012-05-31 02:44

reporter   ~0003986

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 10: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;
 }
 
 //
crypto_v5.patch (45,206 bytes)
ludovic

ludovic

2012-05-31 10:52

administrator   ~0003988

Fix pushed: http://mtn.inverse.ca/revision/diff/747fe8c2134a990c1cdf3b494c2b7776021cb736/with/3c4baa92c5634d4da061da739a9b544d04c44586

Issue History

Date Modified Username Field Change
2012-01-29 10:28 the_nic New Issue
2012-01-29 10:28 the_nic File Added: nsstring+crypto.patch
2012-01-29 11:00 ludovic Note Added: 0003337
2012-01-29 12:47 the_nic Note Added: 0003338
2012-01-29 12:48 the_nic File Added: nsstring+crypto_updated.patch
2012-03-04 07:24 the_nic Note Added: 0003520
2012-03-16 14:36 ludovic Target Version => 1.3.14
2012-03-23 08:33 francis Target Version 1.3.14 => 1.3.15
2012-04-02 03:47 BattleMage Note Added: 0003682
2012-04-16 11:43 the_nic Note Added: 0003743
2012-05-09 11:41 ludovic Target Version 1.3.15 => 1.3.16
2012-05-10 17:03 pgauret Note Added: 0003884
2012-05-16 13:20 chrroessner Note Added: 0003917
2012-05-16 14:17 aschild Note Added: 0003918
2012-05-19 11:20 the_nic Note Added: 0003926
2012-05-20 07:34 the_nic File Added: crypto_v3.patch
2012-05-20 07:47 the_nic Note Added: 0003927
2012-05-20 08:30 the_nic Note Edited: 0003927
2012-05-22 08:09 the_nic File Added: crypto_v4.patch
2012-05-22 08:10 the_nic Note Added: 0003931
2012-05-22 08:20 chrroessner Note Added: 0003932
2012-05-22 08:25 the_nic Note Added: 0003934
2012-05-22 09:53 the_nic File Added: sqlsource_prepend_pw_schemes.patch
2012-05-22 09:55 the_nic Note Added: 0003935
2012-05-31 02:44 Hans de Groot Note Added: 0003986
2012-05-31 10:25 the_nic File Added: crypto_v5.patch
2012-05-31 10:52 ludovic Note Added: 0003988
2012-05-31 10:52 ludovic Status new => resolved
2012-05-31 10:52 ludovic Fixed in Version => 1.3.16
2012-05-31 10:52 ludovic Resolution open => fixed
2012-05-31 10:52 ludovic Assigned To => ludovic
2012-05-31 10:52 ludovic Status resolved => closed