mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-02-17 07:33:57 +00:00
feat(accounts): Add parameter to encrypt/decrypt auxiliary account's password
This commit is contained in:
@@ -445,6 +445,25 @@ else, leave that value empty.
|
||||
|
||||
Defaults to `NO` when unset.
|
||||
|
||||
|S |SOGoSecretType
|
||||
|To be used with _SOGoSecretValue_. Parameter used to define what
|
||||
type is the secret: 'plain' to directly put the secret in _SOGoSecretValue_, 'env'
|
||||
to put the name of a environment variable in _SOGoSecretValue_
|
||||
'none' to not use any secret.
|
||||
For now, it is only used to encrypt/decrypt auxiliary account's password. the secret must be
|
||||
128 bits long i.e. 32 utf8 chars string.
|
||||
|
||||
Defaults to 'none' when unset
|
||||
|
||||
|S |SOGoSecretValue
|
||||
|Parameter used whenever SOGo need a secret to encrypt/decrypt. For now,
|
||||
only for password of auxiliary accounts. If _SOGoSecretType_ is 'plain',
|
||||
directly put the secret here. if _SOGoSecretType_ is 'env', put the name
|
||||
of the environment variable here. Must be set with _SOGoSecretType_.
|
||||
If _SOGoSecretType_ is not 'none', sogo won't start is the value is unfetchable or incorrect
|
||||
|
||||
There is no default value
|
||||
|
||||
|S |SOGoEncryptionKey
|
||||
|Parameter used to define a key to encrypt the passwords of remote Web
|
||||
calendars when _SOGoTrustProxyAuthentication_ is enabled.
|
||||
|
||||
11
Main/sogod.m
11
Main/sogod.m
@@ -48,7 +48,16 @@ main (int argc, char **argv, char **env)
|
||||
rc = 0;
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
[NSTimeZone setDefaultTimeZone: [sd timeZone]];
|
||||
WOWatchDogApplicationMain (@"SOGo", argc, (void *) argv);
|
||||
//Check if sogo secret is set and correct
|
||||
if([sd isSogoSecretSet] && ![sd sogoSecretValue])
|
||||
{
|
||||
rc =-1;
|
||||
NSLog (@"Sogo secret is not correctly set");
|
||||
}
|
||||
else
|
||||
{
|
||||
WOWatchDogApplicationMain (@"SOGo", argc, (void *) argv);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -35,10 +35,15 @@
|
||||
#endif
|
||||
|
||||
#import "aes.h"
|
||||
#define AES_KEY_SIZE 16
|
||||
#define AES_BLOCK_SIZE 16
|
||||
#define AES_128_KEY_SIZE 16
|
||||
#define AES_128_BLOCK_SIZE 16
|
||||
#define AES_256_KEY_SIZE 32
|
||||
#define AES_256_BLOCK_SIZE 16
|
||||
#define GMC_IV_LEN 12
|
||||
#define GMC_TAG_LEN 16
|
||||
|
||||
static const NSString *kAES128ECError = @"kAES128ECError";
|
||||
static const NSString *kAES256GCMError = @"kAES256GCMError";
|
||||
|
||||
@implementation NSString (SOGoCryptoExtension)
|
||||
|
||||
@@ -379,8 +384,8 @@ static const NSString *kAES128ECError = @"kAES128ECError";
|
||||
|
||||
value = nil;
|
||||
|
||||
if (AES_KEY_SIZE != [passwordScheme length]) {
|
||||
*ex = [NSException exceptionWithName: kAES128ECError reason: [NSString stringWithFormat:@"Key must be %d bits", (AES_KEY_SIZE * 8)] userInfo: nil];
|
||||
if (AES_128_KEY_SIZE != [passwordScheme length]) {
|
||||
*ex = [NSException exceptionWithName: kAES128ECError reason: [NSString stringWithFormat:@"Key must be %d bits", (AES_128_KEY_SIZE * 8)] userInfo: nil];
|
||||
return nil;
|
||||
}
|
||||
|
||||
@@ -398,7 +403,7 @@ static const NSString *kAES128ECError = @"kAES128ECError";
|
||||
EVP_CIPHER_CTX_set_padding(ctx, 1);
|
||||
|
||||
// Perform encryption
|
||||
c_len = [data length] + AES_BLOCK_SIZE;
|
||||
c_len = [data length] + AES_128_BLOCK_SIZE;
|
||||
ciphertext = malloc(c_len);
|
||||
f_len = 0;
|
||||
|
||||
@@ -451,8 +456,8 @@ static const NSString *kAES128ECError = @"kAES128ECError";
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
|
||||
if (AES_KEY_SIZE != [passwordScheme length]) {
|
||||
*ex = [NSException exceptionWithName: kAES128ECError reason: [NSString stringWithFormat:@"Key must be %d bits", (AES_KEY_SIZE * 8)] userInfo: nil];
|
||||
if (AES_128_KEY_SIZE != [passwordScheme length]) {
|
||||
*ex = [NSException exceptionWithName: kAES128ECError reason: [NSString stringWithFormat:@"Key must be %d bits", (AES_128_KEY_SIZE * 8)] userInfo: nil];
|
||||
return nil;
|
||||
}
|
||||
keyData = [passwordScheme dataUsingEncoding: NSUTF8StringEncoding];
|
||||
@@ -516,4 +521,167 @@ static const NSString *kAES128ECError = @"kAES128ECError";
|
||||
#endif
|
||||
}
|
||||
|
||||
- (NSDictionary *)encryptAES256GCM:(NSString *)passwordScheme exception:(NSException **)ex
|
||||
{
|
||||
|
||||
NSData *data, *keyData, *ivData, *tagData, *outputData;
|
||||
NSString *value;
|
||||
NSError *error;
|
||||
NSMutableDictionary* gcmDisctionary;
|
||||
int c_len, f_len;
|
||||
unsigned char *ciphertext;
|
||||
unsigned char tag[16];
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
EVP_CIPHER_CTX *ctx;
|
||||
#endif
|
||||
|
||||
value = nil;
|
||||
gcmDisctionary = [NSMutableDictionary dictionaryWithObject: @"" forKey: @"cypher"];
|
||||
|
||||
|
||||
if (AES_256_KEY_SIZE != [passwordScheme length]) {
|
||||
*ex = [NSException exceptionWithName: kAES256GCMError reason: [NSString stringWithFormat:@"Key must be %d bits", (AES_256_KEY_SIZE * 8)] userInfo: nil];
|
||||
return nil;
|
||||
}
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
|
||||
//Generate random IV
|
||||
ivData = [[NSFileHandle fileHandleForReadingAtPath:@"/dev/random"] readDataOfLength:GMC_IV_LEN];
|
||||
if (GMC_IV_LEN != [ivData length]) {
|
||||
*ex = [NSException exceptionWithName: kAES256GCMError reason: [NSString stringWithFormat:@"IV must be %d bits", (GMC_IV_LEN * 8)] userInfo: nil];
|
||||
return nil;
|
||||
}
|
||||
|
||||
data = [self dataUsingEncoding: NSUTF8StringEncoding];
|
||||
keyData = [passwordScheme dataUsingEncoding: NSUTF8StringEncoding];
|
||||
|
||||
//Set cipher encryption
|
||||
ctx = EVP_CIPHER_CTX_new();
|
||||
EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);
|
||||
EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);
|
||||
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, GMC_IV_LEN, NULL);
|
||||
EVP_EncryptInit_ex(ctx, NULL, NULL, [keyData bytes], [ivData bytes]);
|
||||
|
||||
//Start Encryption
|
||||
c_len = [data length];
|
||||
ciphertext = malloc(c_len);
|
||||
int status = 0;
|
||||
EVP_EncryptUpdate(ctx, ciphertext, &c_len, [data bytes], (int)[data length]);
|
||||
status = EVP_EncryptFinal_ex(ctx, ciphertext + c_len, &f_len);
|
||||
c_len += f_len;
|
||||
|
||||
outputData = nil;
|
||||
tagData = nil;
|
||||
if(status)
|
||||
{
|
||||
outputData = [NSData dataWithBytes: (char *)ciphertext length: c_len];
|
||||
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, GMC_TAG_LEN, tag);
|
||||
tagData = [NSData dataWithBytes: (char *)tag length: GMC_TAG_LEN];
|
||||
}
|
||||
else {
|
||||
*ex = [NSException exceptionWithName: kAES256GCMError reason:@"Encryption not successful" userInfo: nil];
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
||||
free(ciphertext);
|
||||
if(outputData && tagData)
|
||||
{
|
||||
[gcmDisctionary setObject: [outputData stringByEncodingBase64] forKey: @"cypher"];
|
||||
[gcmDisctionary setObject: [ivData stringByEncodingBase64] forKey: @"iv"];
|
||||
[gcmDisctionary setObject: [tagData stringByEncodingBase64] forKey: @"tag"];
|
||||
}
|
||||
else {
|
||||
*ex = [NSException exceptionWithName: kAES256GCMError reason:@"Empty data" userInfo: nil];
|
||||
}
|
||||
|
||||
return gcmDisctionary;
|
||||
|
||||
#else
|
||||
*ex = [NSException exceptionWithName:kAES256GCMError reason:@"Missing OpenSSL framework" userInfo: nil];
|
||||
return nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (NSString *)decryptAES256GCM:(NSString *)passwordScheme iv:(NSString *)ivString tag:(NSString *)tagString exception:(NSException **)ex
|
||||
{
|
||||
|
||||
NSData *keyData, *ivData, *tagData, *data, *outputData;
|
||||
NSString *inputString, *value;
|
||||
int p_len, f_len, rv;
|
||||
unsigned char *plaintext;
|
||||
|
||||
value = nil;
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
|
||||
keyData = [passwordScheme dataUsingEncoding: NSUTF8StringEncoding];
|
||||
ivData = [[NSData alloc] initWithBase64EncodedString: ivString options:0];
|
||||
tagData = [[NSData alloc] initWithBase64EncodedString: tagString options:0];
|
||||
|
||||
if (AES_256_KEY_SIZE != [keyData length]) {
|
||||
*ex = [NSException exceptionWithName: kAES256GCMError reason: [NSString stringWithFormat:@"Key must be %d bits", (AES_256_KEY_SIZE * 8)] userInfo: nil];
|
||||
return nil;
|
||||
}
|
||||
if (GMC_IV_LEN!= [ivData length]) {
|
||||
*ex = [NSException exceptionWithName: kAES256GCMError reason: [NSString stringWithFormat:@"Key must be %d bits", (GMC_IV_LEN * 8)] userInfo: nil];
|
||||
return nil;
|
||||
}
|
||||
if (GMC_TAG_LEN != [tagData length]) {
|
||||
*ex = [NSException exceptionWithName: kAES256GCMError reason: [NSString stringWithFormat:@"Tag must be %d bits", (GMC_TAG_LEN * 8)] userInfo: nil];
|
||||
return nil;
|
||||
}
|
||||
|
||||
inputString = [NSString stringWithString: self];
|
||||
data = [[NSData alloc] initWithBase64EncodedString: inputString options:0];
|
||||
|
||||
// Initialize OpenSSL
|
||||
EVP_CIPHER_CTX *ctx;
|
||||
ctx = EVP_CIPHER_CTX_new();
|
||||
|
||||
// Set up cipher parameters
|
||||
EVP_CIPHER_CTX_init(ctx);
|
||||
EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);
|
||||
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, GMC_IV_LEN, NULL);
|
||||
EVP_DecryptInit_ex(ctx, NULL, NULL, [keyData bytes], [ivData bytes]);
|
||||
|
||||
// Perform decryption
|
||||
p_len = [data length];
|
||||
plaintext = malloc(p_len);
|
||||
f_len = 0;
|
||||
|
||||
int status = 0;
|
||||
EVP_DecryptUpdate(ctx, plaintext, &p_len, [data bytes], [data length]);
|
||||
outputData = [NSData dataWithBytes: plaintext length: p_len];
|
||||
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, GMC_TAG_LEN, (void *)[tagData bytes]);
|
||||
rv = EVP_DecryptFinal_ex(ctx, plaintext + p_len, &f_len);
|
||||
p_len += f_len;
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
||||
if (rv > 0) {
|
||||
if (outputData) {
|
||||
value = [NSString stringWithUTF8String: [outputData bytes]];
|
||||
} else {
|
||||
*ex = [NSException exceptionWithName: kAES256GCMError reason:@"Decryption ok but output empty" userInfo: nil];
|
||||
}
|
||||
} else {
|
||||
*ex = [NSException exceptionWithName: kAES256GCMError reason:@"Decryption not ok" userInfo: nil];
|
||||
}
|
||||
|
||||
// Clean up
|
||||
free(plaintext);
|
||||
[data release];
|
||||
[ivData release];
|
||||
[tagData release];
|
||||
|
||||
return value;
|
||||
|
||||
#else
|
||||
*ex = [NSException exceptionWithName:kAES256GCMError reason:@"Missing OpenSSL framework" userInfo: nil];
|
||||
return self;
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -46,6 +46,8 @@ static const NSString *kDisableSharingCalendar = @"Calendar";
|
||||
- (int) vmemLimit;
|
||||
- (BOOL) trustProxyAuthentication;
|
||||
- (NSString *) encryptionKey;
|
||||
- (BOOL) isSogoSecretSet;
|
||||
- (NSString *) sogoSecretValue;
|
||||
- (BOOL) useRelativeURLs;
|
||||
- (NSString *) sieveFolderEncoding;
|
||||
|
||||
|
||||
@@ -25,10 +25,12 @@
|
||||
#import <Foundation/NSFileManager.h>
|
||||
#import <Foundation/NSFileManager.h>
|
||||
#import <Foundation/NSUserDefaults.h>
|
||||
#import <Foundation/NSProcessInfo.h>
|
||||
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
|
||||
#import "NSArray+Utilities.h"
|
||||
#import "NSString+Crypto.h"
|
||||
#import "NSDictionary+Utilities.h"
|
||||
#import "SOGoStartupLogger.h"
|
||||
|
||||
@@ -353,6 +355,60 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict,
|
||||
return [self stringForKey: @"SOGoEncryptionKey"];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL) isSogoSecretSet
|
||||
{
|
||||
NSString *type;
|
||||
type = [self stringForKey: @"SOGoSecretType"];
|
||||
if(!type || [type isEqualToString:@"none"])
|
||||
return NO;
|
||||
else
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *) sogoSecretValue
|
||||
{
|
||||
NSString *value, *type;
|
||||
NSDictionary *env;
|
||||
|
||||
type = [self stringForKey: @"SOGoSecretType"];
|
||||
if(!type)
|
||||
type = @"none";
|
||||
|
||||
if ([type isEqualToString:@"plain"])
|
||||
{
|
||||
value = [self stringForKey: @"SOGoSecretValue"];
|
||||
}
|
||||
else if ([type isEqualToString:@"env"])
|
||||
{
|
||||
value = [self stringForKey: @"SOGoSecretValue"];
|
||||
[self errorWithFormat: @"SOGo env fetching %@", value];
|
||||
if(!value || [value length] < 1)
|
||||
{
|
||||
[self errorWithFormat: @"SOGoSecretValue is not set!"];
|
||||
return nil;
|
||||
}
|
||||
env = [[NSProcessInfo processInfo] environment];
|
||||
value = [env objectForKey:value];
|
||||
}
|
||||
else if ([type isEqualToString:@"none"])
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
else {
|
||||
[self errorWithFormat: @"SOGo can't understand the type of secret SOGoSecretType"];
|
||||
return nil;
|
||||
}
|
||||
|
||||
if(!value || [value length] != 32){
|
||||
[self errorWithFormat: @"SOGo doesn't have a correct secret value of 32 chars SOGoSecretValue"];
|
||||
return nil;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL) useRelativeURLs
|
||||
{
|
||||
return [self boolForKey: @"WOUseRelativeURLs"];
|
||||
|
||||
@@ -1041,7 +1041,51 @@ static const NSString *kEncryptedUserNamePrefix = @"uenc";
|
||||
{
|
||||
auxAccounts = [[self userDefaults] auxiliaryMailAccounts];
|
||||
if (auxAccounts)
|
||||
{
|
||||
//Check if we need to decrypt password
|
||||
NSString* sogoSecret;
|
||||
sogoSecret = [[SOGoSystemDefaults sharedSystemDefaults] sogoSecretValue];
|
||||
if(sogoSecret)
|
||||
{
|
||||
int i;
|
||||
NSString *encryptedPassword, *password, *iv, *tag;
|
||||
NSDictionary *account, *accountPassword;
|
||||
NSException* exception = nil;
|
||||
for (i = 0; i < [auxAccounts count]; i++)
|
||||
{
|
||||
account = [auxAccounts objectAtIndex: i];
|
||||
if (![[account objectForKey: @"password"] isKindOfClass: [NSDictionary class]])
|
||||
{
|
||||
[self errorWithFormat:@"Can't decrypt the password for auxiliary account %@, is not a dictionnary",
|
||||
[account objectForKey: @"name"]];
|
||||
continue;
|
||||
}
|
||||
|
||||
accountPassword = [account objectForKey: @"password"];
|
||||
encryptedPassword = [accountPassword objectForKey: @"cypher"];
|
||||
iv = [accountPassword objectForKey: @"iv"];
|
||||
tag = [accountPassword objectForKey: @"tag"];
|
||||
NS_DURING
|
||||
{
|
||||
password = [encryptedPassword decryptAES256GCM: sogoSecret iv: iv tag: tag exception:&exception];
|
||||
}
|
||||
NS_HANDLER
|
||||
{
|
||||
[self errorWithFormat:@"Can't decrypt the password for auxiliary account %@",
|
||||
[account objectForKey: @"name"]];
|
||||
password = [account objectForKey: @"password"];
|
||||
}
|
||||
NS_ENDHANDLER
|
||||
if(exception)
|
||||
[self errorWithFormat:@"Can't decrypt the password for auxiliary account %@: %@",
|
||||
[account objectForKey: @"name"], [exception reason]];
|
||||
else
|
||||
[account setObject: password forKey: @"password"];
|
||||
}
|
||||
}
|
||||
|
||||
[mailAccounts addObjectsFromArray: auxAccounts];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
#import <NGObjWeb/WOContext+SoObjects.h>
|
||||
#import <NGObjWeb/WEClientCapabilities.h>
|
||||
|
||||
#import <SOGo/SOGoSystemDefaults.h>
|
||||
#import <SOGo/NSString+Crypto.h>
|
||||
|
||||
#import "NSString+Utilities.h"
|
||||
#import "SOGoSystemDefaults.h"
|
||||
#import "SOGoUserProfile.h"
|
||||
|
||||
@@ -27,7 +27,8 @@ $(SOGO_TOOL)_OBJC_FILES += \
|
||||
SOGoToolUserPreferences.m \
|
||||
SOGoToolManageACL.m \
|
||||
SOGoToolManageEAS.m \
|
||||
SOGoToolTruncateCalendar.m
|
||||
SOGoToolTruncateCalendar.m \
|
||||
SOGoToolUpdateSecret.m
|
||||
TOOL_NAME += $(SOGO_TOOL)
|
||||
|
||||
###
|
||||
|
||||
421
Tools/SOGoToolUpdateSecret.m
Normal file
421
Tools/SOGoToolUpdateSecret.m
Normal file
@@ -0,0 +1,421 @@
|
||||
/* SOGoToolUserPreferences.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2011-2019 Inverse inc.
|
||||
*
|
||||
* 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/NSData.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSUserDefaults.h>
|
||||
|
||||
#import <NGObjWeb/WOContext+SoObjects.h>
|
||||
|
||||
#import <GDLAccess/EOAdaptorChannel.h>
|
||||
|
||||
#import <GDLContentStore/GCSChannelManager.h>
|
||||
#import <GDLContentStore/GCSFolderManager.h>
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
#import <SOGo/NSString+Crypto.h>
|
||||
#import <SOGo/SOGoProductLoader.h>
|
||||
#import <SOGo/SOGoUserManager.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoDefaultsSource.h>
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
#import <SOGo/SOGoUserSettings.h>
|
||||
#import <SOGo/SOGoSystemDefaults.h>
|
||||
#import <Mailer/SOGoMailAccounts.h>
|
||||
#import <Mailer/SOGoMailAccount.h>
|
||||
|
||||
#import "SOGoTool.h"
|
||||
|
||||
@interface SOGoToolUpdateSecret : SOGoTool
|
||||
{
|
||||
NSArray *usersWithAuxiliaryAccounts;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation SOGoToolUpdateSecret
|
||||
|
||||
+ (NSString *) command
|
||||
{
|
||||
return @"update-secret";
|
||||
}
|
||||
|
||||
+ (NSString *) description
|
||||
{
|
||||
return @"Update all database data that needs to be encrypted with a new secret value";
|
||||
}
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
usersWithAuxiliaryAccounts = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
[usersWithAuxiliaryAccounts release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void) usage
|
||||
{
|
||||
fprintf (stderr, "update-secret -n new_secret -o old_secret\n\n"
|
||||
" -n new_secret the new secret value to encrypt with, if given alone it will assume the data are not currently encrypted\n"
|
||||
" -o old_secret the current value of the secret if any, if given alone, it will decrypts all current data\n"
|
||||
" The secret must be a 32 chars long utf-8 strings (128 bits)\n\n"
|
||||
" Example set a secret for the first time:\n"
|
||||
" sogo-tool update-secret -n exemple_NewSuperSecretOfLenght32\n"
|
||||
" Examples change for a new value of secret:\n"
|
||||
" sogo-tool update-secret -n exemple_NewSuperSecretOfLenght32:32 -o exemple_OldSuperSecretOfLenght32\n"
|
||||
" Examples unset the secret and come back to unencrypted data:\n"
|
||||
" sogo-tool update-secret -o exemple_OldSuperSecretOfLenght32\n");
|
||||
}
|
||||
|
||||
|
||||
- (BOOL) fetchAllUsersForAuxiliaryAccountPassword
|
||||
{
|
||||
NSAutoreleasePool *pool;
|
||||
SOGoUserManager *lm;
|
||||
NSDictionary *infos;
|
||||
NSString *user;
|
||||
SOGoSystemDefaults* sd;
|
||||
id allUsers;
|
||||
int count, max;
|
||||
|
||||
lm = [SOGoUserManager sharedUserManager];
|
||||
|
||||
GCSFolderManager *fm;
|
||||
GCSChannelManager *cm;
|
||||
NSURL *userProfileUrl;
|
||||
EOAdaptorChannel *fc;
|
||||
NSArray *users, *attrs;
|
||||
NSMutableArray *allSqlUsers;
|
||||
NSString *sql, *profileURL;
|
||||
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
profileURL = [sd profileURL];
|
||||
if (profileURL)
|
||||
userProfileUrl = [[NSURL alloc] initWithString: profileURL];
|
||||
else
|
||||
{
|
||||
NSLog(@"Can't find the value for SOGoProfileURL!");
|
||||
return NO;
|
||||
}
|
||||
|
||||
fm = [GCSFolderManager defaultFolderManager];
|
||||
cm = [fm channelManager];
|
||||
fc = [cm acquireOpenChannelForURL: userProfileUrl];
|
||||
if (fc)
|
||||
{
|
||||
allSqlUsers = [NSMutableArray new];
|
||||
sql = [NSString stringWithFormat: @"SELECT c_uid FROM %@ WHERE c_defaults LIKE '%%AuxiliaryMailAccounts\":[{%%'",
|
||||
[userProfileUrl gcsTableName]];
|
||||
[fc evaluateExpressionX: sql];
|
||||
attrs = [fc describeResults: NO];
|
||||
while ((infos = [fc fetchAttributes: attrs withZone: NULL]))
|
||||
{
|
||||
user = [infos objectForKey: @"c_uid"];
|
||||
if (user)
|
||||
[allSqlUsers addObject: user];
|
||||
}
|
||||
[cm releaseChannel: fc immediately: YES];
|
||||
|
||||
users = allSqlUsers;
|
||||
max = [users count];
|
||||
[allSqlUsers autorelease];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"Can't create channel to %@", userProfileUrl);
|
||||
return NO;
|
||||
}
|
||||
|
||||
ASSIGN (usersWithAuxiliaryAccounts, users);
|
||||
|
||||
return ([usersWithAuxiliaryAccounts count] > 0);
|
||||
}
|
||||
|
||||
- (BOOL) updateSecretFromPlainData: (NSString*) secret
|
||||
{
|
||||
BOOL rc;
|
||||
rc = [self fetchAllUsersForAuxiliaryAccountPassword];
|
||||
if(rc){
|
||||
int i;
|
||||
for(i=0; i < [usersWithAuxiliaryAccounts count]; i++){
|
||||
|
||||
SOGoUser* user;
|
||||
SOGoDefaultsSource *source;
|
||||
int count;
|
||||
NSDictionary* account;
|
||||
NSArray *aux;
|
||||
NSString *password;
|
||||
|
||||
user = [SOGoUser userWithLogin: [usersWithAuxiliaryAccounts objectAtIndex: i]];
|
||||
source = [user userDefaults];
|
||||
aux = [source objectForKey: @"AuxiliaryMailAccounts"];
|
||||
if(!aux)
|
||||
continue;
|
||||
|
||||
for (count = 0; count < [aux count]; count++)
|
||||
{
|
||||
account = [aux objectAtIndex: count];
|
||||
if(![[account objectForKey: @"password"] isKindOfClass: [NSString class]])
|
||||
{
|
||||
NSLog(@"Can't encrypt the password for auxiliary account %@, password is not a string, probabbly already encrypted",
|
||||
[account objectForKey: @"name"]);
|
||||
continue;
|
||||
}
|
||||
password = [account objectForKey: @"password"];
|
||||
if([password length] > 0)
|
||||
{
|
||||
NSString* newPassword;
|
||||
NSException* exception = nil;
|
||||
newPassword = [password encryptAES256GCM: secret exception:&exception];
|
||||
if(exception)
|
||||
NSLog(@"Can't encrypt the password: %@", [exception reason]);
|
||||
else
|
||||
[account setObject: newPassword forKey: @"password"];
|
||||
}
|
||||
else
|
||||
NSLog(@"Password not found! For user: %@ and account %@", user, account);
|
||||
}
|
||||
[source setObject: aux forKey: @"AuxiliaryMailAccounts"];
|
||||
[source synchronize];
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
- (void) updateSecretFromEncryptedData: (NSString*) newSecret oldSecret: (NSString*) oldSecret
|
||||
{
|
||||
BOOL rc;
|
||||
rc = [self fetchAllUsersForAuxiliaryAccountPassword];
|
||||
if(rc){
|
||||
int i;
|
||||
for(i=0; i < [usersWithAuxiliaryAccounts count]; i++){
|
||||
|
||||
SOGoUser* user;
|
||||
SOGoDefaultsSource *source;
|
||||
int count;
|
||||
NSDictionary* account, *accountPassword;
|
||||
NSArray *aux;
|
||||
NSString *password, *iv, *tag;
|
||||
|
||||
user = [SOGoUser userWithLogin: [usersWithAuxiliaryAccounts objectAtIndex: i]];
|
||||
source = [user userDefaults];
|
||||
aux = [source objectForKey: @"AuxiliaryMailAccounts"];
|
||||
if(!aux)
|
||||
continue;
|
||||
|
||||
for (count = 0; count < [aux count]; count++)
|
||||
{
|
||||
account = [aux objectAtIndex: count];
|
||||
if(![[account objectForKey: @"password"] isKindOfClass: [NSDictionary class]])
|
||||
{
|
||||
NSLog(@"Can't decrypt the password for auxiliary account %@, is not a dictionnary",
|
||||
[account objectForKey: @"name"]);
|
||||
continue;
|
||||
}
|
||||
accountPassword = [account objectForKey: @"password"];
|
||||
password = [accountPassword objectForKey: @"cypher"];
|
||||
iv = [accountPassword objectForKey: @"iv"];
|
||||
tag = [accountPassword objectForKey: @"tag"];
|
||||
if([password length] > 0)
|
||||
{
|
||||
NSString* decryptedPassword;
|
||||
NSDictionary* encryptedPassword;
|
||||
NSException* exception = nil;
|
||||
NS_DURING
|
||||
decryptedPassword = [password decryptAES256GCM: oldSecret iv: iv tag: tag exception:&exception];
|
||||
encryptedPassword = [decryptedPassword encryptAES256GCM: newSecret exception:&exception];
|
||||
NS_HANDLER
|
||||
encryptedPassword = accountPassword;
|
||||
NSLog(@"Can't decrypt the password, unexpected exception");
|
||||
NS_ENDHANDLER
|
||||
|
||||
if(exception)
|
||||
NSLog(@"Can't decrypt the password: %@", [exception reason]);
|
||||
else
|
||||
[account setObject: encryptedPassword forKey: @"password"];
|
||||
}
|
||||
else
|
||||
NSLog(@"Password not found! For user: %@ and account %@", user, account);
|
||||
}
|
||||
[source setObject: aux forKey: @"AuxiliaryMailAccounts"];
|
||||
[source synchronize];
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
- (BOOL) updateToPlainData: (NSString*) oldSecret
|
||||
{
|
||||
BOOL rc;
|
||||
rc = [self fetchAllUsersForAuxiliaryAccountPassword];
|
||||
if(rc){
|
||||
int i;
|
||||
for(i=0; i < [usersWithAuxiliaryAccounts count]; i++){
|
||||
|
||||
SOGoUser* user;
|
||||
SOGoDefaultsSource *source;
|
||||
int count;
|
||||
NSDictionary* account, *accountPassword;
|
||||
NSArray *aux;
|
||||
NSString *password, *iv, *tag;
|
||||
|
||||
user = [SOGoUser userWithLogin: [usersWithAuxiliaryAccounts objectAtIndex: i]];
|
||||
source = [user userDefaults];
|
||||
aux = [source objectForKey: @"AuxiliaryMailAccounts"];
|
||||
if(!aux)
|
||||
continue;
|
||||
|
||||
for (count = 0; count < [aux count]; count++)
|
||||
{
|
||||
account = [aux objectAtIndex: count];
|
||||
if(![[account objectForKey: @"password"] isKindOfClass: [NSDictionary class]])
|
||||
{
|
||||
NSLog(@"Can't decrypt the password for auxiliary account %@, is not a dictionnary",
|
||||
[account objectForKey: @"name"]);
|
||||
continue;
|
||||
}
|
||||
accountPassword = [account objectForKey: @"password"];
|
||||
password = [accountPassword objectForKey: @"cypher"];
|
||||
iv = [accountPassword objectForKey: @"iv"];
|
||||
tag = [accountPassword objectForKey: @"tag"];
|
||||
if([password length] > 0)
|
||||
{
|
||||
NSString* newPassword;
|
||||
NSException* exception = nil;
|
||||
NS_DURING
|
||||
newPassword = [password decryptAES256GCM: oldSecret iv: iv tag: tag exception:&exception];
|
||||
if(exception)
|
||||
NSLog(@"Can't decrypt the password: %@", [exception reason]);
|
||||
else
|
||||
[account setObject: newPassword forKey: @"password"];
|
||||
NS_HANDLER
|
||||
NSLog(@"Can't decrypt the password, unexpected exception");
|
||||
NS_ENDHANDLER
|
||||
}
|
||||
else
|
||||
NSLog(@"Password not found! For user: %@ and account %@", user, account);
|
||||
}
|
||||
[source setObject: aux forKey: @"AuxiliaryMailAccounts"];
|
||||
[source synchronize];
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL) checkArguments: (NSArray*)args
|
||||
{
|
||||
int size, i;
|
||||
NSString *type1, *type2;
|
||||
size = [args count];
|
||||
if (size != 2 && size != 4)
|
||||
{
|
||||
NSLog(@"Wrong number of arguments, should be 2 or 4 and was %d", size);
|
||||
return NO;
|
||||
}
|
||||
for(i=0; i<size; i++)
|
||||
{
|
||||
if(![[args objectAtIndex:i] isKindOfClass: [NSString class]])
|
||||
{
|
||||
NSLog(@"One of the argument is not a string");
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
type1 = [args objectAtIndex:0];
|
||||
if (![type1 isEqualToString: @"-n"] && ![type1 isEqualToString: @"-o"])
|
||||
{
|
||||
NSLog(@"First argument is not '-n' nor '-o' but %@", type1);
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([[args objectAtIndex:1] length] != 32)
|
||||
{
|
||||
NSLog(@"Second argument is supposed to be a 32 chars long secret but is %@", [args objectAtIndex:1]);
|
||||
return NO;
|
||||
}
|
||||
|
||||
if(size == 4)
|
||||
{
|
||||
type2 = [args objectAtIndex:2];
|
||||
if (![type2 isEqualToString: @"-n"] && ![type2 isEqualToString: @"-o"])
|
||||
{
|
||||
NSLog(@"Third argument is not '-n' nor '-o' but %@", type2);
|
||||
return NO;
|
||||
}
|
||||
if ([type2 isEqualToString: type1])
|
||||
{
|
||||
NSLog(@"Third argument (%@) cannot be the same as the first %@", type2, type1);
|
||||
return NO;
|
||||
}
|
||||
if ([[args objectAtIndex:3] length] != 32)
|
||||
{
|
||||
NSLog(@"Fourth argument is supposed to be a 32 chars long secret but is %@", [args objectAtIndex:3]);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL) run
|
||||
{
|
||||
|
||||
int max, i;
|
||||
BOOL rc;
|
||||
|
||||
max = [arguments count];
|
||||
rc = [self checkArguments: arguments];
|
||||
|
||||
if (!rc)
|
||||
{
|
||||
[self usage];
|
||||
}
|
||||
else
|
||||
{
|
||||
if(max == 2)
|
||||
{
|
||||
if([[arguments objectAtIndex:0] isEqualToString: @"-n"])
|
||||
[self updateSecretFromPlainData: [arguments objectAtIndex:1]];
|
||||
else
|
||||
[self updateToPlainData: [arguments objectAtIndex:1]];
|
||||
}
|
||||
else
|
||||
{
|
||||
if([[arguments objectAtIndex:0] isEqualToString: @"-n"])
|
||||
[self updateSecretFromEncryptedData: [arguments objectAtIndex:1] oldSecret: [arguments objectAtIndex:3]];
|
||||
else
|
||||
[self updateSecretFromEncryptedData: [arguments objectAtIndex:3] oldSecret: [arguments objectAtIndex:1]];
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -29,10 +29,11 @@
|
||||
#import <NGImap4/NGSieveClient.h>
|
||||
|
||||
#import <SOPE/NGCards/iCalRecurrenceRule.h>
|
||||
|
||||
#import <SOGo/SOGoSystemDefaults.h>
|
||||
#import <SOGo/NSArray+Utilities.h>
|
||||
#import <SOGo/NSObject+Utilities.h>
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
#import <SOGo/NSString+Crypto.h>
|
||||
#import <SOGo/SOGoDomainDefaults.h>
|
||||
#import <SOGo/SOGoSieveManager.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
@@ -493,8 +494,10 @@ static SoProduct *preferencesProduct = nil;
|
||||
if ([accounts count])
|
||||
{
|
||||
int i;
|
||||
NSDictionary *security;
|
||||
NSDictionary *security, *accountPassword;
|
||||
NSMutableDictionary *auxAccount, *limitedSecurity;
|
||||
NSString *password, *encryptedPassword, *sogoSecret, *iv, *tag;
|
||||
NSException* exception = nil;
|
||||
|
||||
for (i = 0; i < [accounts count]; i++)
|
||||
{
|
||||
@@ -510,6 +513,37 @@ static SoProduct *preferencesProduct = nil;
|
||||
}
|
||||
[auxAccount setObject: limitedSecurity forKey: @"security"];
|
||||
}
|
||||
|
||||
//Decrypt password if needed
|
||||
sogoSecret = [[SOGoSystemDefaults sharedSystemDefaults] sogoSecretValue];
|
||||
if (sogoSecret)
|
||||
{
|
||||
if(![[auxAccount objectForKey: @"password"] isKindOfClass: [NSDictionary class]])
|
||||
{
|
||||
[self errorWithFormat:@"Can't decrypt the password for auxiliary account %@, is not a dictionnary",
|
||||
[auxAccount objectForKey: @"name"]];
|
||||
continue;
|
||||
}
|
||||
accountPassword = [auxAccount objectForKey: @"password"];
|
||||
encryptedPassword = [accountPassword objectForKey: @"cypher"];
|
||||
iv = [accountPassword objectForKey: @"iv"];
|
||||
tag = [accountPassword objectForKey: @"tag"];
|
||||
if([encryptedPassword length] > 0)
|
||||
{
|
||||
NS_DURING
|
||||
password = [encryptedPassword decryptAES256GCM: sogoSecret iv: iv tag: tag exception:&exception];
|
||||
NS_HANDLER
|
||||
[self errorWithFormat:@"Can't decrypt the password for auxiliary account %@, probably not encrypted.",
|
||||
[auxAccount objectForKey: @"name"]];
|
||||
password = [auxAccount objectForKey: @"password"];
|
||||
NS_ENDHANDLER
|
||||
if(exception)
|
||||
[self errorWithFormat:@"Can't decrypt the password for auxiliary account %@: %@",
|
||||
[auxAccount objectForKey: @"name"], [exception reason]];
|
||||
else
|
||||
[auxAccount setObject: password forKey: @"password"];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// We inject our default mail account
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#import <SOGo/NSArray+Utilities.h>
|
||||
#import <SOGo/NSDictionary+Utilities.h>
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
#import <SOGo/NSString+Crypto.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserSettings.h>
|
||||
#import <SOGo/SOGoSieveManager.h>
|
||||
@@ -1532,6 +1533,7 @@ static NSArray *reminderValues = nil;
|
||||
int count, max;
|
||||
NSMutableArray *auxAccounts;
|
||||
NSMutableDictionary *account;
|
||||
NSString *sogoSecret, *password;
|
||||
|
||||
max = [accounts count];
|
||||
auxAccounts = [NSMutableArray arrayWithCapacity: max];
|
||||
@@ -1542,6 +1544,24 @@ static NSArray *reminderValues = nil;
|
||||
if ([self _validateAccount: account])
|
||||
{
|
||||
[self _updateAuxiliaryAccount: account];
|
||||
|
||||
//Encrypt password if needed
|
||||
sogoSecret = [[SOGoSystemDefaults sharedSystemDefaults] sogoSecretValue];
|
||||
//Note that password here will always be a string and not a dictionnary of gcm encryption as it comes from the saveAction Request
|
||||
password = [account objectForKey: @"password"];
|
||||
if(sogoSecret && [password length] > 0)
|
||||
{
|
||||
NSDictionary* newPassword;
|
||||
NSException* exception = nil;
|
||||
newPassword = [password encryptAES256GCM: sogoSecret exception:&exception];
|
||||
if(exception)
|
||||
[self errorWithFormat:@"Can't encrypt the password: %@", [exception reason]];
|
||||
else
|
||||
[account setObject: newPassword forKey: @"password"];
|
||||
}
|
||||
else
|
||||
[self warnWithFormat:@"Password for auxiliary accounts are not stored encrypted see SOGoSecretType"];
|
||||
|
||||
[auxAccounts addObject: account];
|
||||
}
|
||||
}
|
||||
@@ -1556,7 +1576,7 @@ static NSArray *reminderValues = nil;
|
||||
NSDictionary *oldAccount, *oldSecurity;
|
||||
NSEnumerator *comparisonAttributesList;
|
||||
NSMutableDictionary *newSecurity;
|
||||
NSString *comparisonAttribute, *password, *certificate;
|
||||
NSString *comparisonAttribute, *password, *certificate, *sogoSecret, *decryptedPassword;
|
||||
|
||||
comparisonAttributes = [NSArray arrayWithObjects: @"serverName", @"userName", nil];
|
||||
oldAccounts = [user mailAccounts];
|
||||
@@ -1580,11 +1600,59 @@ static NSArray *reminderValues = nil;
|
||||
// Use previous password if none is provided
|
||||
password = [newAccount objectForKey: @"password"];
|
||||
if (!password)
|
||||
password = [oldAccount objectForKey: @"password"];
|
||||
if (!password)
|
||||
password = @"";
|
||||
[newAccount setObject: password forKey: @"password"];
|
||||
{
|
||||
if([oldAccount objectForKey: @"password"])
|
||||
{
|
||||
if([[oldAccount objectForKey: @"password"] isKindOfClass: [NSDictionary class]])
|
||||
{
|
||||
//Old password is encrypted, decrypt it first as it will be encrypt again after
|
||||
NSDictionary *accountPassword;
|
||||
NSString *encryptedPassword, *iv, *tag;
|
||||
accountPassword = [oldAccount objectForKey: @"password"];
|
||||
encryptedPassword = [accountPassword objectForKey: @"cypher"];
|
||||
iv = [accountPassword objectForKey: @"iv"];
|
||||
tag = [accountPassword objectForKey: @"tag"];
|
||||
sogoSecret = [[SOGoSystemDefaults sharedSystemDefaults] sogoSecretValue];
|
||||
if(sogoSecret)
|
||||
{
|
||||
NSException* exception = nil;
|
||||
NS_DURING
|
||||
decryptedPassword = [encryptedPassword decryptAES256GCM: sogoSecret iv: iv tag: tag exception:&exception];
|
||||
if(exception)
|
||||
{
|
||||
[self errorWithFormat:@"Can't decrypt the password for auxiliary account %@: %@",
|
||||
[oldAccount objectForKey: @"name"], [exception reason]];
|
||||
decryptedPassword = @"";
|
||||
}
|
||||
else
|
||||
password = decryptedPassword;
|
||||
NS_HANDLER
|
||||
[self errorWithFormat:@"Can't decrypt the password for auxiliary account %@, probably wrong key",
|
||||
[oldAccount objectForKey: @"name"]];
|
||||
decryptedPassword = @"";
|
||||
NS_ENDHANDLER
|
||||
[newAccount setObject: decryptedPassword forKey: @"password"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat:@"Password currently stored for account %@ is encrypted but there is a no secret SOGoSecretValue to decrypt with",
|
||||
[newAccount objectForKey: @"name"]];
|
||||
[newAccount setObject: @"" forKey: @"password"];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Old password is not encrypted, nothing to do
|
||||
[newAccount setObject: [oldAccount objectForKey: @"password"] forKey: @"password"];
|
||||
}
|
||||
}
|
||||
else {
|
||||
//No password for this account, weird choice but possible
|
||||
[newAccount setObject: @"" forKey: @"password"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Keep previous certificate
|
||||
oldSecurity = [oldAccount objectForKey: @"security"];
|
||||
if (oldSecurity)
|
||||
|
||||
Reference in New Issue
Block a user