View Issue Details

IDProjectCategoryView StatusLast Update
0002060SOGoBackend Generalpublic2012-11-15 17:18
Reportersim Assigned To 
PrioritynormalSeveritymajorReproducibilityalways
Status closedResolutionfixed 
Product Version2.0.1 
Target Version2.0.2aFixed in Version2.0.2a 
Summary0002060: userPasswordAlgorithm = ssh.b64 creates {sha.b64}(null) for any password
Description

When updating a user password from the WebUI (SOGoPasswordChangeEnabled=YES), passwords are inserted into the LDAP database as "{sha.b64}(null)"

Additional Information

SOGo defaults sha to sha.hex, which is different than dovecot. Dovecot defaults sha to sha.b64.

For completeness I've tried the following variants:
sha.b64
sha.base64
SHA.B64
SHA.BASE64

All of them result in a password like {prefix}(null)

TagsNo tags attached.

Activities

wsourdeau

wsourdeau

2012-10-26 18:02

viewer   ~0004719

AZctually, when this occurs, you should see this message in your log:

"Unsupported user-password algorithm: blablal"

the_nic

the_nic

2012-10-27 13:26

reporter   ~0004727

"ssh" is not a real password scheme. This is maybe just a typo.

SHA (plus optionally ".b64", as well as the other variants) should work.

sim

sim

2012-10-29 05:40

reporter   ~0004731

Yes, it's supposed to be sha. And no it does not work. I more recently tried sha256 and sha512 and neither of them work - base64 or hex. There is no error in any log files pertaining to "Unsupported user-password algorithm".

"grep -R algorithm /var/log"

shows nothing

the_nic

the_nic

2012-10-30 16:27

reporter   ~0004750

I see the problem now.
I'll provide a fix. But for all sha-methods but "sha" the default encoding is base64 (as in dovecot)

sim

sim

2012-10-31 00:37

reporter   ~0004759

Fantastic! Thank you.

2012-10-31 12:38

 

0001-NString-Crypto-fix-encryption-with-schemes-having-an.patch (7,752 bytes)   
From 5a30265baeb364823219cec5ae9a7bb6e807dfa1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20H=C3=B6ft?= <Nicolas.Hoeft@gmail.com>
Date: Wed, 31 Oct 2012 13:33:42 +0100
Subject: [PATCH 1/2] NString+Crypto: fix encryption with schemes having an
 encoding

asCryptedPassUsingScheme was not removing the encoding from a scheme
(e.g. 'sha.b64' was not split up in sha and 'b64').

getDefaultEncodingForScheme now detects the encoding from such schemes
and returns both the encoding and the true scheme name.
---
 SoObjects/SOGo/NSString+Crypto.h |   2 +-
 SoObjects/SOGo/NSString+Crypto.m | 102 +++++++++++++++++++++++----------------
 2 files changed, 62 insertions(+), 42 deletions(-)

diff --git a/SoObjects/SOGo/NSString+Crypto.h b/SoObjects/SOGo/NSString+Crypto.h
index cc35a89..100bfac 100644
--- a/SoObjects/SOGo/NSString+Crypto.h
+++ b/SoObjects/SOGo/NSString+Crypto.h
@@ -56,7 +56,7 @@ typedef enum {
 - (NSString *) asSHA1String;
 - (NSString *) asMD5String;
 
-+ (keyEncoding) getDefaultEncodingForScheme: (NSString *) passwordScheme;
++ (NSArray *) getDefaultEncodingForScheme: (NSString *) passwordScheme;
 
 @end
 
diff --git a/SoObjects/SOGo/NSString+Crypto.m b/SoObjects/SOGo/NSString+Crypto.m
index f3370b8..7a720d4 100644
--- a/SoObjects/SOGo/NSString+Crypto.m
+++ b/SoObjects/SOGo/NSString+Crypto.m
@@ -71,8 +71,7 @@
 {
   NSString *scheme;
   NSString *pass;
-  NSArray *schemeComps;
-  keyEncoding encoding;
+  NSArray *encodingAndScheme;
   
   NSRange range;
   int selflen, len;
@@ -88,32 +87,11 @@
   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;
-        }
-    }
+  encodingAndScheme = [NSString getDefaultEncodingForScheme: scheme];
 
   pass = [self substringWithRange: range];
-  return [NSArray arrayWithObjects: scheme, pass, [NSNumber numberWithInt: encoding], nil];
+  // return array with [scheme, password, encoding]
+  return [NSArray arrayWithObjects: [encodingAndScheme objectAtIndex: 1], pass, [encodingAndScheme objectAtIndex: 0], nil];
 }
 
 /**
@@ -147,7 +125,7 @@
   if (encoding == encHex)
     {
       decodedData = [NSData decodeDataFromHexString: pass];
-      
+
       if(decodedData == nil)
         {
           decodedData = [NSData data];
@@ -208,8 +186,10 @@
  *
  * @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
+ * @param userEncoding The encoding (plain, hex, base64) to be used. If set to
+ *        encDefault, the encoding will be detected from scheme name.
+ * @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
@@ -217,6 +197,22 @@
 {
   keyEncoding dataEncoding;
   NSData* cryptedData;
+
+  // use default encoding scheme, when set to default
+  if (userEncoding == encDefault)
+    {
+      // the encoding needs to be detected before crypting,
+      // to get the plain scheme (without encoding identifier)
+      NSArray* encodingAndScheme;
+      encodingAndScheme = [NSString getDefaultEncodingForScheme: passwordScheme];
+      dataEncoding = [[encodingAndScheme objectAtIndex: 0] intValue];
+      passwordScheme = [encodingAndScheme objectAtIndex: 1];
+    }
+  else
+    {
+      dataEncoding = userEncoding;
+    }
+
   // convert NSString to NSData and apply encryption scheme
   cryptedData = [self dataUsingEncoding: NSUTF8StringEncoding];
   cryptedData = [cryptedData asCryptedPassUsingScheme: passwordScheme  withSalt: theSalt];
@@ -224,12 +220,6 @@
   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
@@ -250,19 +240,49 @@
 /**
  * Returns the encoding for a specified scheme
  *
- * @param passwordScheme The scheme for which to get the encoding.
+ * @param passwordScheme The scheme for which to get the encoding. Can be "scheme.encoding" in which case the encoding is returned
  * @see keyEncoding
- * @return returns the encoding, if unknown returns encPlain
+ * @return returns NSArray with elements {NSNumber encoding, NSString* scheme} where scheme is the 'real' scheme without the ".encoding" part.
+ * 'encoding' is stored as NSNumber in the array. If the encoding was not detected, encPlain is used for encoding.
  */
-+ (keyEncoding) getDefaultEncodingForScheme: (NSString *) passwordScheme
++ (NSArray *) getDefaultEncodingForScheme: (NSString *) passwordScheme
 {
+  NSArray *schemeComps;
+  NSString *trueScheme;
+  keyEncoding encoding = encPlain;
+
+  // 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 = [passwordScheme componentsSeparatedByString: @"."];
+  if ([schemeComps count] == 2)
+    {
+      trueScheme = [schemeComps objectAtIndex: 0];
+      NSString *stringEncoding;
+      // 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;
+        }
+    }
+   else
+    {
+      trueScheme = 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;
+      encoding = encHex;
     }
   else if ([passwordScheme caseInsensitiveCompare: @"smd5"] == NSOrderedSame ||
            [passwordScheme caseInsensitiveCompare: @"ldap-md5"] == NSOrderedSame ||
@@ -272,9 +292,9 @@
            [passwordScheme caseInsensitiveCompare: @"sha512"] == NSOrderedSame ||
            [passwordScheme caseInsensitiveCompare: @"ssha512"] == NSOrderedSame)
     {
-      return encBase64;
+      encoding = encBase64;
     }
-  return encPlain;
+  return [NSArray arrayWithObjects: [NSNumber numberWithInt: encoding], trueScheme, nil];
 }
 
 /**
-- 
1.8.0

2012-10-31 12:38

 

0002-SQLSource-LDAPSource-do-not-write-a-password-when-th.patch (4,432 bytes)   
From cd2f1b28875694787b718b45e201cd49d685ed78 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20H=C3=B6ft?= <Nicolas.Hoeft@gmail.com>
Date: Wed, 31 Oct 2012 13:34:10 +0100
Subject: [PATCH 2/2] SQLSource,LDAPSource: do not write a password when the
 scheme is unknown

_encryptPassword did not return nil when the password generated
from NSString+Crypto returned an error.

This patch changes this behaviour and also does not write the
password to the SQL or LDAP database when _encryptPassword returns
nil.
---
 SoObjects/SOGo/LDAPSource.m | 45 +++++++++++++++++++++++++++++----------------
 SoObjects/SOGo/SQLSource.m  | 15 ++++++++++-----
 2 files changed, 39 insertions(+), 21 deletions(-)

diff --git a/SoObjects/SOGo/LDAPSource.m b/SoObjects/SOGo/LDAPSource.m
index 81024a2..96a98ef 100644
--- a/SoObjects/SOGo/LDAPSource.m
+++ b/SoObjects/SOGo/LDAPSource.m
@@ -586,7 +586,10 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField
   pass = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm];
 
   if (pass == nil)
-    [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm];
+    {
+      [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm];
+      return nil;
+    }
 
   return [NSString stringWithFormat: @"{%@}%@", _userPasswordAlgorithm, pass];
 }
@@ -629,24 +632,34 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField
 		    NGLdapModification *mod;
 		    NGLdapAttribute *attr;
 		    NSArray *changes;
+           NSString* encryptedPass;
 		    
 		    attr = [[NGLdapAttribute alloc] initWithAttributeName: @"userPassword"];
 		    if ([_userPasswordAlgorithm isEqualToString: @"none"])
-		      [attr addStringValue:  newPassword];
-		    else
-		      [attr addStringValue: [self _encryptPassword: newPassword]];
-		    
-		    mod = [NGLdapModification replaceModification: attr];
-		    changes = [NSArray arrayWithObject: mod];
-		    *perr = PolicyNoError;
-
-		    if ([bindConnection bindWithMethod: @"simple"
-					binddn: userDN
-					credentials: oldPassword])
-		      didChange = [bindConnection modifyEntryWithDN: userDN
-						  changes: changes]; 
-		    else
-		      didChange = NO;
+             {
+               encryptedPass = newPassword;
+             }
+           else
+             {
+               encryptedPass = [self _encryptPassword: newPassword];
+             }
+           if(encryptedPass != nil)
+             {
+               [attr addStringValue: encryptedPass];
+               mod = [NGLdapModification replaceModification: attr];
+               changes = [NSArray arrayWithObject: mod];
+               *perr = PolicyNoError;
+
+               if ([bindConnection bindWithMethod: @"simple"
+                        binddn: userDN
+                        credentials: oldPassword])
+                 {
+                   didChange = [bindConnection modifyEntryWithDN: userDN
+                                changes: changes];
+                 }
+                else
+                  didChange = NO;
+              }
 		  }
 	      else
 		didChange = [bindConnection changePasswordAtDn: userDN
diff --git a/SoObjects/SOGo/SQLSource.m b/SoObjects/SOGo/SQLSource.m
index 2c50913..d3ceeca 100644
--- a/SoObjects/SOGo/SQLSource.m
+++ b/SoObjects/SOGo/SQLSource.m
@@ -187,7 +187,10 @@
   pass = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm];
 
   if (pass == nil)
-    [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm];
+    {
+      [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm];
+      return nil;
+    }
 
   if (_prependPasswordScheme)
     result = [NSString stringWithFormat: @"{%@}%@", _userPasswordAlgorithm, pass];
@@ -308,18 +311,20 @@
   NSString *sqlstr;
   BOOL didChange;
   BOOL isOldPwdOk;
-  
+
   isOldPwdOk = NO;
   didChange = NO;
-  
+
   // Verify current password
   isOldPwdOk = [self checkLogin:login password:oldPassword perr:perr expire:0 grace:0];
-  
+
   if (isOldPwdOk)
     {
       // Encrypt new password
       NSString *encryptedPassword = [self _encryptPassword: newPassword];
-      
+      if(encryptedPassword == nil)
+        return NO;
+
       // Save new password
       login = [login stringByReplacingString: @"'"  withString: @"''"];
       cm = [GCSChannelManager defaultChannelManager];
-- 
1.8.0

the_nic

the_nic

2012-10-31 12:40

reporter   ~0004762

Last edited: 2012-10-31 12:43

These are the patches. The first fixes the problem with valid schemes as "sha.b64" and the second will prevent the SQL and LDAP parts to write passwords as "(null)" to the database

@inverse: Please apply them with
"git apply-patch" or "git am"

ludovic

ludovic

2012-11-06 14:05

administrator   ~0004781

Patches applied: https://github.com/inverse-inc/sogo/commit/6ad59a8481972be77b1af56b93ec24dd6482aaf6

Issue History

Date Modified Username Field Change
2012-10-23 09:02 sim New Issue
2012-10-26 18:02 wsourdeau Note Added: 0004719
2012-10-27 13:26 the_nic Note Added: 0004727
2012-10-29 05:40 sim Note Added: 0004731
2012-10-30 16:27 the_nic Note Added: 0004750
2012-10-31 00:37 sim Note Added: 0004759
2012-10-31 12:38 the_nic File Added: 0001-NString-Crypto-fix-encryption-with-schemes-having-an.patch
2012-10-31 12:38 the_nic File Added: 0002-SQLSource-LDAPSource-do-not-write-a-password-when-th.patch
2012-10-31 12:40 the_nic Note Added: 0004762
2012-10-31 12:43 the_nic Note Edited: 0004762
2012-10-31 12:46 ludovic Target Version => 2.0.3
2012-11-06 14:05 ludovic Note Added: 0004781
2012-11-06 14:05 ludovic Status new => closed
2012-11-06 14:05 ludovic Resolution open => fixed
2012-11-06 14:05 ludovic Fixed in Version => 2.0.3
2012-11-15 17:18 francis Fixed in Version 2.0.3 => 2.0.2a
2012-11-15 17:18 francis Target Version 2.0.3 => 2.0.2a