diff --git a/Documentation/SOGoInstallationGuide.asciidoc b/Documentation/SOGoInstallationGuide.asciidoc index 327eb3792..d69703d3e 100644 --- a/Documentation/SOGoInstallationGuide.asciidoc +++ b/Documentation/SOGoInstallationGuide.asciidoc @@ -1069,7 +1069,7 @@ Possible values are: `none`, `plain`, `crypt`, `md5`, `md5-crypt`, (plus setting of the encoding with `.b64` or `.hex`). For a more detailed description see -http://wiki.dovecot.org/Authentication/PasswordSchemes. +https://doc.dovecot.org/configuration_manual/authentication/password_schemes/. Note that `cram-md5` is not actually using cram-md5 (due to the lack of challenge-response mechanism), its just saving the intermediate MD5 @@ -1659,13 +1659,13 @@ they have the same name as popular LDAP attributes (such as `givenName`, |The default algorithm used for password encryption when changing passwords. Possible values are: `none`, `plain`, `crypt`, `md5`, `md5-crypt`, `smd5`, `cram-md5`, `ldap-md5`, and `sha`, `sha256`, -`sha256-crypt`, `sha512`, `sha512-crypt` and its ssha (e.g. `ssha` or -`ssha256`) variants and `sym-aes-128-cbc`. Passwords can have the -scheme prepended in the form `{scheme}encryptedPass`. +`sha256-crypt`, `sha512`, `sha512-crypt`, its ssha (e.g. `ssha` or +`ssha256`) variants, `blf-crypt`, and `sym-aes-128-cbc`. Passwords +can have the scheme prepended in the form `{scheme}encryptedPass`. If no scheme is given, _userPasswordAlgorithm_ is used instead. The schemes listed above follow the algorithms described in -http://wiki.dovecot.org/Authentication/PasswordSchemes. +https://doc.dovecot.org/configuration_manual/authentication/password_schemes/. Note that `cram-md5` is not actually using cram-md5 (due to the lack of challenge-response mechanism), its just saving the intermediate MD5 diff --git a/SoObjects/SOGo/NSData+Crypto.h b/SoObjects/SOGo/NSData+Crypto.h index 6292fa2f5..002965825 100644 --- a/SoObjects/SOGo/NSData+Crypto.h +++ b/SoObjects/SOGo/NSData+Crypto.h @@ -53,6 +53,7 @@ - (NSData *) asCryptUsingSalt: (NSData *) theSalt; - (NSData *) asMD5CryptUsingSalt: (NSData *) theSalt; +- (NSData *) asBlowfishCryptUsingSalt: (NSData *) theSalt; - (NSData *) extractSalt: (NSString *) theScheme; diff --git a/SoObjects/SOGo/NSData+Crypto.m b/SoObjects/SOGo/NSData+Crypto.m index b9b6c3369..cb049a616 100644 --- a/SoObjects/SOGo/NSData+Crypto.m +++ b/SoObjects/SOGo/NSData+Crypto.m @@ -1,6 +1,6 @@ /* NSData+Crypto.m - this file is part of SOGo * - * Copyright (C) 2012 Nicolas Höft + * Copyright (C) 2012, 2020 Nicolas Höft * Copyright (C) 2012-2020 Inverse inc. * Copyright (C) 2012 Jeroen Dekkers * @@ -50,6 +50,7 @@ #endif #include "aes.h" +#include "crypt_blowfish.h" #include "lmhash.h" #import @@ -62,6 +63,12 @@ static BOOL check_gnutls_init(void); static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input); #endif +#define BLF_CRYPT_DEFAULT_COMPLEXITY (5) +#define BLF_CRYPT_SALT_LEN (16) +#define BLF_CRYPT_BUFFER_LEN (128) +#define BLF_CRYPT_PREFIX_LEN (7+22+1) /* $2.$nn$ + salt */ +#define BLF_CRYPT_PREFIX "$2y" + @implementation NSData (SOGoCryptoExtension) @@ -245,6 +252,10 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input); { return [self asSHA512CryptUsingSalt: theSalt]; } + else if ([passwordScheme caseInsensitiveCompare: @"blf-crypt"] == NSOrderedSame) + { + return [self asBlowfishCryptUsingSalt: theSalt]; + } else if ([[passwordScheme lowercaseString] hasPrefix: @"sym"]) { // We first support one sym cipher, AES-128-CBC. If something else is provided @@ -764,6 +775,52 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input); return [NSData dataWithBytes: buf length: strlen(buf)]; } +/** + * Hash the data using blowfish-crypt + * @param theSalt The salt to be used must not be nil, if empty, one will be generated + */ +- (NSData *) asBlowfishCryptUsingSalt: (NSData *) theSalt +{ + NSString *cleartext; + char hashed_password[BLF_CRYPT_BUFFER_LEN]; + char magic_salt[BLF_CRYPT_PREFIX_LEN]; // contains $2.$nn$ + salt + + if ([theSalt length] == 0) + { + // generate a salt with default complexity if none was provided + NSData* salt = [NSData generateSaltForLength: BLF_CRYPT_SALT_LEN]; + if (_crypt_gensalt_blowfish_rn(BLF_CRYPT_PREFIX, BLF_CRYPT_DEFAULT_COMPLEXITY, + [salt bytes], BLF_CRYPT_SALT_LEN, + magic_salt, BLF_CRYPT_PREFIX_LEN) == NULL) + return nil; + } + else + { + const char* salt = [theSalt bytes]; + if ([theSalt length] < BLF_CRYPT_PREFIX_LEN || + salt[0] != '$' || salt[1] != '2' || + salt[2] < 'a' || salt[2] > 'z' || + salt[3] != '$') + { + return nil; + } + memcpy(magic_salt, salt, BLF_CRYPT_PREFIX_LEN); + } + + cleartext = [[NSString alloc] initWithData: self encoding: NSUTF8StringEncoding]; + const char* password = [cleartext UTF8String]; + + char* bf_res = _crypt_blowfish_rn(password, magic_salt, + hashed_password, BLF_CRYPT_BUFFER_LEN); + [cleartext autorelease]; + + if (bf_res == NULL) + return nil; + + return [NSData dataWithBytes: hashed_password length: strlen(hashed_password)]; +} + + /** * Get the salt from a password encrypted with a specied scheme * diff --git a/Tests/Unit/TestNSString+Crypto.m b/Tests/Unit/TestNSString+Crypto.m index 7d53eebc1..37d5e4162 100644 --- a/Tests/Unit/TestNSString+Crypto.m +++ b/Tests/Unit/TestNSString+Crypto.m @@ -60,4 +60,29 @@ } } +- (void) test_blowfish +{ + NSString *error; + // well-known comparison + NSString *blf_key = @"123456"; + NSString *blf_hash = @"{BLF-CRYPT}$2a$05$tLVuFQTgdwrZmixu.QMxoedUAUEeIFIBv89Ur5mQ6F1vBL8Vw1mXO"; + error = [NSString stringWithFormat: + @"string '%@' wrong BLF-CRYPT: '%@'", + blf_key, blf_hash]; + testWithMessage([blf_key isEqualToCrypted:blf_hash withDefaultScheme: @"CRYPT" keyPath: nil], error); + + // generate a new blowfish-crypt key + NSString *blf_prefix = @"$2y$05$"; + + NSString *blf_result = [blf_key asCryptedPassUsingScheme: @"blf-crypt" keyPath: nil]; + + error = [NSString stringWithFormat: + @"returned hash '%@' has incorrect BLF-CRYPT prefix: '%@'", + blf_result, blf_prefix]; + + testWithMessage([blf_result hasPrefix: blf_prefix], error); + + test([blf_key isEqualToCrypted:blf_result withDefaultScheme: @"BLF-CRYPT" keyPath: nil]); +} + @end