#3126: limit totp attempt

This commit is contained in:
Hivert Quentin committed 2026-11-10 14:27:05 +01:00
1 parent 1a6642c366
commit 49049f6d42
9 files changed
+105

No files matched your search

@@ -793,6 +793,16 @@ not block logins, and it is enforced even when failed login
Default value is `5`. Set to `0` to disable.
|S |SOGoMaximumFailedTotpCount
|Parameter used to control the number of wrong TOTP verification codes
allowed when logging in to the web interface. When reached, TOTP
verification is blocked for the account (even with a valid code) for
_SOGoFailedLoginBlockInterval_ seconds since the first wrong code.
This counter is independent of _SOGoMaximumFailedLoginCount_, which
only counts wrong passwords.
Default value is `5`. Set to `0` to disable.
|S |SOGoMaximumMessageSubmissionCount
|Parameter used to control the number of email messages a user can send
from SOGo's webmail interface, to _SOGoMaximumRecipientCount_, in
+5
View File
@@ -97,6 +97,11 @@
- (NSDictionary *) failedPasswordRecoveryCountForLogin: (NSString *) login;
- (void) setFailedTotpCount: (int) theCount
forLogin: (NSString *) theLogin;
- (NSDictionary *) failedTotpCountForLogin: (NSString *) login;
- (void) setMessageSubmissionsCount: (int) theCount
recipientsCount: (int) theRecipientsCount
forLogin: (NSString *) theLogin;
+17
View File
@@ -568,6 +568,23 @@ static memcached_st *handle = NULL;
ofType: @"failedrecovery"];
}
//
// SOGo TOTP verification failed counts (web login second factor).
//
- (void) setFailedTotpCount: (int) theCount
forLogin: (NSString *) theLogin
{
[self _setFailedCount: theCount
forLogin: theLogin
ofType: @"failedtotp"];
}
- (NSDictionary *) failedTotpCountForLogin: (NSString *) theLogin
{
return [self _failedCountForLogin: theLogin
ofType: @"failedtotp"];
}
//
//
//
+1
View File
@@ -209,4 +209,5 @@
SOGoPasswordRecoveryMode = "Disabled";
SOGoPasswordRecoveryQuestion = "SecretQuestion1";
SOGoMaximumFailedPasswordRecoveryCount = 5;
SOGoMaximumFailedTotpCount = 5;
}
+1
View File
@@ -128,6 +128,7 @@ NSComparisonResult languageSort(id el1, id el2, void *context);
- (int) maximumFailedLoginInterval;
- (int) failedLoginBlockInterval;
- (int) maximumFailedPasswordRecoveryCount;
- (int) maximumFailedTotpCount;
- (int) maximumMessageSizeLimit;
+11
View File
@@ -927,6 +927,17 @@ NSComparisonResult languageSort(id el1, id el2, void *context)
return [self integerForKey: @"SOGoMaximumFailedPasswordRecoveryCount"];
}
//
// Number of wrong TOTP verification codes allowed during
// SOGoFailedLoginBlockInterval seconds before TOTP verification
// is blocked for the account. Defaults to 5 (see SOGoDefaults.plist),
// 0 disables the protection.
//
- (int) maximumFailedTotpCount
{
return [self integerForKey: @"SOGoMaximumFailedTotpCount"];
}
//
//
//
+2
View File
@@ -112,6 +112,8 @@
perr: (SOGoPasswordPolicyError *) perr;
- (NSString *) getPasswordRecoveryTokenFor: (NSString *) username domain:(NSString *) domain;
- (BOOL) isPasswordRecoveryTokenValidFor: (NSString *) token uid: (NSString *) uid;
- (BOOL) isTotpVerificationBlockedForLogin: (NSString *) login
failedCount: (int *) failedCount;
@end
#endif /* SOGOUSERMANAGER_H */
+39
View File
@@ -1545,6 +1545,45 @@ static const NSString *kObfuscatedSecondaryEmailKey = @"obfuscatedSecondaryEmail
return jwtToken;
}
/**
* (TOTP) Check whether TOTP verification is temporarily blocked for login
* because of too many wrong codes. Resets the counter once the block interval is over.
* @param login User login (uid)
* @param failedCount Current number of failures (out)
* @return True if blocked
*/
- (BOOL) isTotpVerificationBlockedForLogin: (NSString *) login
failedCount: (int *) failedCount
{
NSDictionary *failedTotp;
SOGoSystemDefaults *sd;
unsigned int currentTime, startTime;
int maxFailed;
*failedCount = 0;
sd = [SOGoSystemDefaults sharedSystemDefaults];
maxFailed = [sd maximumFailedTotpCount];
if (maxFailed <= 0 || ![login length])
return NO;
failedTotp = [[SOGoCache sharedCache] failedTotpCountForLogin: login];
if (!failedTotp)
return NO;
currentTime = [[NSCalendarDate date] timeIntervalSince1970];
startTime = [[failedTotp objectForKey: @"InitialDate"] unsignedIntValue];
if (currentTime - startTime > (unsigned int) [sd failedLoginBlockInterval])
{
[[SOGoCache sharedCache] setFailedTotpCount: 0
forLogin: login];
return NO;
}
*failedCount = [[failedTotp objectForKey: @"FailedCount"] intValue];
return (*failedCount >= maxFailed);
}
/**
* (Secret Question) Check whether password recovery is temporarily blocked for uid
* because of too many wrong answers. Resets the counter once the block interval is over.
+19
View File
@@ -325,11 +325,22 @@ static const NSString *kJwtKey = @"jwt";
if ([ud totpEnabled])
{
NSString *verificationCode;
int totpFailedCount;
verificationCode = [params objectForKey: @"verificationCode"];
if ([verificationCode length] == 6 && [verificationCode unsignedIntValue] > 0)
{
unsigned int code;
// Too many wrong codes: refuse any code until the block interval is over
if ([[SOGoUserManager sharedUserManager] isTotpVerificationBlockedForLogin: [loggedInUser login]
failedCount: &totpFailedCount])
{
[self logWithFormat: @"TOTP verification from '%@' for user '%@' blocked - too many failed attempts",
remoteHost, username];
return [self _responseWithLDAPPolicyError: PolicyAccountLocked additionalInfos: nil];
}
const char *real_secret;
char *secret;
@@ -407,6 +418,9 @@ static const NSString *kJwtKey = @"jwt";
if (old_code != [verificationCode unsignedIntValue])
{
[self logWithFormat: @"Invalid TOTP key for '%@'", username];
if ([[SOGoSystemDefaults sharedSystemDefaults] maximumFailedTotpCount] > 0)
[[SOGoCache sharedCache] setFailedTotpCount: totpFailedCount + 1
forLogin: [loggedInUser login]];
[json setObject: [NSNumber numberWithInt: 1] forKey: @"totpInvalidKey"];
return [self responseWithStatus: 403 andJSONRepresentation: json];
}
@@ -415,6 +429,11 @@ static const NSString *kJwtKey = @"jwt";
[us setTotpKey: [[us userPrivateSalt] substringToIndex: 12]];
}
}
// Valid code: reset the failed TOTP count
if (totpFailedCount > 0)
[[SOGoCache sharedCache] setFailedTotpCount: 0
forLogin: [loggedInUser login]];
}
else
{