Skip to content

Commit

Permalink
feat(accounts): Add parameter to encrypt/decrypt auxiliary account's …
Browse files Browse the repository at this point in the history
…password
  • Loading branch information
QHivert committed Feb 1, 2024
1 parent f0010af commit e6bfab7
Show file tree
Hide file tree
Showing 11 changed files with 841 additions and 16 deletions.
19 changes: 19 additions & 0 deletions Documentation/SOGoInstallationGuide.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 10 additions & 1 deletion Main/sogod.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,16 @@
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
{
Expand Down
182 changes: 175 additions & 7 deletions SoObjects/SOGo/NSString+Crypto.m
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -379,8 +384,8 @@ - (NSString *) encodeAES128ECBBase64:(NSString *)passwordScheme encodedURL:(BOOL

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;
}

Expand All @@ -398,7 +403,7 @@ - (NSString *) encodeAES128ECBBase64:(NSString *)passwordScheme encodedURL:(BOOL
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;

Expand Down Expand Up @@ -451,8 +456,8 @@ - (NSString *) decodeAES128ECBBase64:(NSString *)passwordScheme encodedURL:(BOOL

#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];
Expand Down Expand Up @@ -516,4 +521,167 @@ - (NSString *) decodeAES128ECBBase64:(NSString *)passwordScheme encodedURL:(BOOL
#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
2 changes: 2 additions & 0 deletions SoObjects/SOGo/SOGoSystemDefaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ static const NSString *kDisableSharingCalendar = @"Calendar";
- (int) vmemLimit;
- (BOOL) trustProxyAuthentication;
- (NSString *) encryptionKey;
- (BOOL) isSogoSecretSet;
- (NSString *) sogoSecretValue;
- (BOOL) useRelativeURLs;
- (NSString *) sieveFolderEncoding;

Expand Down
56 changes: 56 additions & 0 deletions SoObjects/SOGo/SOGoSystemDefaults.m
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -353,6 +355,60 @@ - (NSString *) encryptionKey;
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"];
Expand Down
44 changes: 44 additions & 0 deletions SoObjects/SOGo/SOGoUser.m
Original file line number Diff line number Diff line change
Expand Up @@ -1041,7 +1041,51 @@ - (NSArray *) mailAccountsWithDelegatedIdentities: (BOOL) appendDeletegatedIdent
{
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];
}
}
}

Expand Down
Loading

0 comments on commit e6bfab7

Please sign in to comment.