mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-05-29 15:15:37 +00:00
fix(vulnerability): properly change the totp code after disabling it
This commit is contained in:
@@ -124,7 +124,8 @@
|
||||
|
||||
- (BOOL) isSuperUser;
|
||||
- (BOOL) canAuthenticate;
|
||||
- (NSString *) totpKey;
|
||||
- (NSString *) totpKey: (bool) isCheck;
|
||||
- (NSString *) oldtotpKey;
|
||||
|
||||
/* resource */
|
||||
- (BOOL) isResource;
|
||||
|
||||
@@ -1298,7 +1298,7 @@
|
||||
return [authValue boolValue];
|
||||
}
|
||||
|
||||
- (NSString *) totpKey
|
||||
- (NSString *) totpKey: (bool) isCheck
|
||||
{
|
||||
#if defined(MFA_CONFIG)
|
||||
NSString *key, *result;
|
||||
@@ -1307,6 +1307,51 @@
|
||||
|
||||
size_t s_len, secret_len;
|
||||
|
||||
//Until 5.12.4, SOGo had two problems with totp:
|
||||
// * It was not renew after a user disable it/renable it.
|
||||
// * The length was too small: 12 instead of the recommanded 20
|
||||
|
||||
if(![_defaults totpEnabled])
|
||||
{
|
||||
//Totp was not enabled
|
||||
//Only renew if this is not a check (happen when the user enable it for the first time and save its preferences
|
||||
//the saveAction will check the totp code but [_defaults totpEnabled] is still False )
|
||||
key = [[self userSettings] userCurrentTotpKey: !isCheck];
|
||||
}
|
||||
else
|
||||
{
|
||||
//Totp currently enabled
|
||||
key = [[self userSettings] userCurrentTotpKey: NO];
|
||||
}
|
||||
|
||||
s = [key UTF8String];
|
||||
s_len = strlen(s);
|
||||
|
||||
oath_init();
|
||||
oath_base32_encode(s,s_len, &secret, &secret_len);
|
||||
oath_done();
|
||||
|
||||
result = [[NSString alloc] initWithBytesNoCopy: secret
|
||||
length: secret_len
|
||||
encoding: NSASCIIStringEncoding
|
||||
freeWhenDone: YES];
|
||||
|
||||
return [result autorelease];
|
||||
#else
|
||||
return nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (NSString *) oldtotpKey
|
||||
{
|
||||
#if defined(MFA_CONFIG)
|
||||
//Was used before 5.12.5, is to not make obsolete totp profile set before
|
||||
NSString *key, *result;
|
||||
const char *s;
|
||||
char *secret;
|
||||
|
||||
size_t s_len, secret_len;
|
||||
|
||||
key = [[[self userSettings] userPrivateSalt] substringToIndex: 12];
|
||||
s = [key UTF8String];
|
||||
s_len = strlen(s);
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
- (NSArray *) subscribedCalendars;
|
||||
- (NSArray *) subscribedAddressBooks;
|
||||
- (NSString *) userPrivateSalt;
|
||||
- (NSString *) userCurrentTotpKey: (bool) renew;
|
||||
- (void) setTotpKey: (NSString* ) newKey;
|
||||
- (NSString *) userPublicSalt;
|
||||
- (void)enableForceResetPassword;
|
||||
- (void)disableForceResetPassword;
|
||||
|
||||
@@ -116,6 +116,42 @@ static Class SOGoUserProfileKlass = Nil;
|
||||
return salt;
|
||||
}
|
||||
|
||||
- (NSString *) userCurrentTotpKey: (bool) renew
|
||||
{
|
||||
NSMutableDictionary *values;
|
||||
NSString *key;
|
||||
|
||||
key = [[self dictionaryForKey: @"General"] objectForKey: @"totpKey"];
|
||||
|
||||
if (!key || renew)
|
||||
{
|
||||
key = [[[[NSProcessInfo processInfo] globallyUniqueString] asSHA1String] substringToIndex: 20];
|
||||
values = [self objectForKey: @"General"];
|
||||
|
||||
if (!values)
|
||||
values = [NSMutableDictionary dictionary];
|
||||
|
||||
[values setObject: key forKey: @"totpKey"];
|
||||
[self setObject: values forKey: @"General"];
|
||||
[self synchronize];
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
- (void) setTotpKey: (NSString* ) newKey
|
||||
{
|
||||
NSMutableDictionary *values;
|
||||
values = [self objectForKey: @"General"];
|
||||
|
||||
if (!values)
|
||||
values = [NSMutableDictionary dictionary];
|
||||
|
||||
[values setObject: newKey forKey: @"totpKey"];
|
||||
[self setObject: values forKey: @"General"];
|
||||
[self synchronize];
|
||||
}
|
||||
|
||||
- (void) enableForceResetPassword
|
||||
{
|
||||
[self setObject: [NSNumber numberWithInt:1] forKey: @"ForceResetPassword"];
|
||||
|
||||
@@ -338,7 +338,7 @@ static const NSString *kJwtKey = @"jwt";
|
||||
const auto time_step = OATH_TOTP_DEFAULT_TIME_STEP_SIZE;
|
||||
const auto digits = 6;
|
||||
|
||||
real_secret = [[loggedInUser totpKey] UTF8String];
|
||||
real_secret = [[loggedInUser totpKey: YES] UTF8String];
|
||||
|
||||
auto result = oath_init();
|
||||
auto t = time(NULL);
|
||||
@@ -366,13 +366,56 @@ static const NSString *kJwtKey = @"jwt";
|
||||
|
||||
if (code != [verificationCode unsignedIntValue])
|
||||
{
|
||||
[self logWithFormat: @"Invalid TOTP key for '%@'", username];
|
||||
[json setObject: [NSNumber numberWithInt: 1]
|
||||
forKey: @"totpInvalidKey"];
|
||||
return [self responseWithStatus: 403
|
||||
andJSONRepresentation: json];
|
||||
//With 5.12.5, the totpKey has changed (non longer from salt but from a propoer totpkey parameter)
|
||||
//To avoid making all old totp configuration obsolete, we're trying the verification code with
|
||||
//the old method first
|
||||
unsigned int old_code;
|
||||
const char *old_real_secret;
|
||||
char *old_secret;
|
||||
|
||||
size_t old_secret_len;
|
||||
|
||||
const auto old_time_step = OATH_TOTP_DEFAULT_TIME_STEP_SIZE;
|
||||
const auto old_digits = 6;
|
||||
|
||||
old_real_secret = [[loggedInUser oldtotpKey] UTF8String];
|
||||
|
||||
auto old_result = oath_init();
|
||||
auto old_time = time(NULL);
|
||||
auto old_left = old_time_step - (old_time % old_time_step);
|
||||
|
||||
char old_otp[old_digits + 1];
|
||||
|
||||
oath_base32_decode (old_real_secret,
|
||||
strlen(old_real_secret),
|
||||
&old_secret, &old_secret_len);
|
||||
|
||||
old_result = oath_totp_generate2(old_secret,
|
||||
old_secret_len,
|
||||
old_time,
|
||||
old_time_step,
|
||||
OATH_TOTP_DEFAULT_START_TIME,
|
||||
old_digits,
|
||||
0,
|
||||
old_otp);
|
||||
|
||||
sscanf(old_otp, "%u", &old_code);
|
||||
|
||||
oath_done();
|
||||
free(old_secret);
|
||||
|
||||
if (old_code != [verificationCode unsignedIntValue])
|
||||
{
|
||||
[self logWithFormat: @"Invalid TOTP key for '%@'", username];
|
||||
[json setObject: [NSNumber numberWithInt: 1] forKey: @"totpInvalidKey"];
|
||||
return [self responseWithStatus: 403 andJSONRepresentation: json];
|
||||
}
|
||||
else {
|
||||
//Move the old secret to the new parameter
|
||||
[us setTotpKey: [[us userPrivateSalt] substringToIndex: 12]];
|
||||
}
|
||||
}
|
||||
} // if ([verificationCode length] == 6 && [verificationCode unsignedIntValue] > 0)
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([us dictionaryForKey: @"General"] && ![[us dictionaryForKey: @"General"] objectForKey: @"PrivateSalt"])
|
||||
|
||||
@@ -1123,7 +1123,7 @@ static NSArray *reminderValues = nil;
|
||||
|
||||
- (NSString *) totpKey
|
||||
{
|
||||
return [[context activeUser] totpKey];
|
||||
return [[context activeUser] totpKey: NO];
|
||||
}
|
||||
|
||||
//
|
||||
@@ -1910,7 +1910,7 @@ static NSArray *reminderValues = nil;
|
||||
const auto time_step = OATH_TOTP_DEFAULT_TIME_STEP_SIZE;
|
||||
const auto digits = 6;
|
||||
|
||||
real_secret = [[user totpKey] UTF8String];
|
||||
real_secret = [[user totpKey: YES] UTF8String];
|
||||
|
||||
auto result = oath_init();
|
||||
auto t = time(NULL);
|
||||
|
||||
Reference in New Issue
Block a user