mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-04-03 12:28:51 +00:00
fix(web): use a distinct salt for TOTP authentication
If TOTP is enabled for a user, it will be disabled until the user configure it again, which will generate a new private salt.
This commit is contained in:
@@ -234,7 +234,7 @@
|
||||
|
||||
tags = [NSArray arrayWithObjects: @"DTSTAMP", @"DTSTART", @"DTEND", @"DUE", @"EXDATE", @"EXRULE", @"RRULE", @"RECURRENCE-ID", nil];
|
||||
uid = [[component uid] asCryptedPassUsingScheme: @"ssha256"
|
||||
withSalt: [[settings userSalt] dataUsingEncoding: NSASCIIStringEncoding]
|
||||
withSalt: [[settings userPublicSalt] dataUsingEncoding: NSASCIIStringEncoding]
|
||||
andEncoding: encHex
|
||||
keyPath: nil];
|
||||
|
||||
|
||||
@@ -1138,7 +1138,7 @@
|
||||
|
||||
size_t s_len, secret_len;
|
||||
|
||||
key = [[[self userSettings] userSalt] substringToIndex: 12];
|
||||
key = [[[self userSettings] userPrivateSalt] substringToIndex: 12];
|
||||
s = [key UTF8String];
|
||||
s_len = strlen(s);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SOGoUserSettings.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009-2016 Inverse inc.
|
||||
* Copyright (C) 2009-2021 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
|
||||
@@ -33,7 +33,8 @@
|
||||
|
||||
- (NSArray *) subscribedCalendars;
|
||||
- (NSArray *) subscribedAddressBooks;
|
||||
- (NSString *) userSalt;
|
||||
- (NSString *) userPrivateSalt;
|
||||
- (NSString *) userPublicSalt;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SOGoUserSettings.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009-2016 Inverse inc.
|
||||
* Copyright (C) 2009-2021 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
|
||||
@@ -70,7 +70,7 @@ static Class SOGoUserProfileKlass = Nil;
|
||||
return [self _subscribedFoldersForModule: @"Contacts"];
|
||||
}
|
||||
|
||||
- (NSString *) userSalt
|
||||
- (NSString *) userPublicSalt
|
||||
{
|
||||
NSMutableDictionary *values;
|
||||
NSString *salt;
|
||||
@@ -93,5 +93,27 @@ static Class SOGoUserProfileKlass = Nil;
|
||||
return salt;
|
||||
}
|
||||
|
||||
- (NSString *) userPrivateSalt
|
||||
{
|
||||
NSMutableDictionary *values;
|
||||
NSString *salt;
|
||||
|
||||
salt = [[self dictionaryForKey: @"General"] objectForKey: @"PrivateSalt"];
|
||||
|
||||
if (!salt)
|
||||
{
|
||||
salt = [[[NSProcessInfo processInfo] globallyUniqueString] asSHA1String];
|
||||
values = [self objectForKey: @"General"];
|
||||
|
||||
if (!values)
|
||||
values = [NSMutableDictionary dictionary];
|
||||
|
||||
[values setObject: salt forKey: @"PrivateSalt"];
|
||||
[self setObject: values forKey: @"General"];
|
||||
[self synchronize];
|
||||
}
|
||||
|
||||
return salt;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"Verification Code" = "Verification Code";
|
||||
"Enter the 6-digit verification code from your TOTP application." = "Enter the 6-digit verification code from your TOTP application.";
|
||||
"You provided an invalid TOTP key." = "You provided an invalid TOTP key.";
|
||||
"Two-factor authentication has been disabled. Visit the Preferences module to restore two-factor authentication and reconfigure your TOTP application." = "Two-factor authentication has been disabled. Visit the Preferences module to restore two-factor authentication and reconfigure your TOTP application.";
|
||||
|
||||
"Download" = "Download";
|
||||
"Language" = "Language";
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
WOCookie *authCookie, *xsrfCookie;
|
||||
SOGoWebAuthenticator *auth;
|
||||
SOGoUserDefaults *ud;
|
||||
SOGoUserSettings *us;
|
||||
SOGoUser *loggedInUser;
|
||||
NSDictionary *params;
|
||||
NSString *username, *password, *language, *domain, *remoteHost;
|
||||
@@ -235,7 +236,7 @@
|
||||
|| (expire < 0 && grace > 0) // password expired, grace still permits login
|
||||
|| (expire >= 0 && grace == -1))) // password about to expire OR ppolicy activated and passwd never changed
|
||||
{
|
||||
NSDictionary *json;
|
||||
NSMutableDictionary *json = [NSMutableDictionary dictionary];
|
||||
|
||||
[self logWithFormat: @"successful login from '%@' for user '%@' - expire = %d grace = %d", remoteHost, username, expire, grace];
|
||||
|
||||
@@ -248,9 +249,10 @@
|
||||
username = [[SOGoUserManager sharedUserManager] getUIDForEmail: username];
|
||||
|
||||
loggedInUser = [SOGoUser userWithLogin: username];
|
||||
ud = [loggedInUser userDefaults];
|
||||
|
||||
#if defined(MFA_CONFIG)
|
||||
if ([[loggedInUser userDefaults] totpEnabled])
|
||||
if ([ud totpEnabled])
|
||||
{
|
||||
NSString *verificationCode;
|
||||
|
||||
@@ -295,29 +297,45 @@
|
||||
if (code != [verificationCode unsignedIntValue])
|
||||
{
|
||||
[self logWithFormat: @"Invalid TOTP key for '%@'", username];
|
||||
json = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: 1]
|
||||
forKey: @"totpInvalidKey"];
|
||||
[json setObject: [NSNumber numberWithInt: 1]
|
||||
forKey: @"totpInvalidKey"];
|
||||
return [self responseWithStatus: 403
|
||||
andJSONRepresentation: json];
|
||||
andJSONRepresentation: json];
|
||||
}
|
||||
} // if ([verificationCode length] == 6 && [verificationCode unsignedIntValue] > 0)
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"Missing TOTP key for '%@', asking it..", username];
|
||||
json = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: 1]
|
||||
forKey: @"totpMissingKey"];
|
||||
return [self responseWithStatus: 202
|
||||
andJSONRepresentation: json];
|
||||
us = [loggedInUser userSettings];
|
||||
if ([us dictionaryForKey: @"General"] && ![[us dictionaryForKey: @"General"] objectForKey: @"PrivateSalt"])
|
||||
{
|
||||
// Since v5.3.0, a new salt is used for TOTP. If it's missing, disable TOTP and alert the user.
|
||||
[ud setTotpEnabled: NO];
|
||||
[ud synchronize];
|
||||
|
||||
[self logWithFormat: @"New TOTP key for '%@' must be created", username];
|
||||
[json setObject: [NSNumber numberWithInt: 1]
|
||||
forKey: @"totpDisabled"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"Missing TOTP key for '%@', asking it..", username];
|
||||
[json setObject: [NSNumber numberWithInt: 1]
|
||||
forKey: @"totpMissingKey"];
|
||||
return [self responseWithStatus: 202
|
||||
andJSONRepresentation: json];
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[self _checkAutoReloadWebCalendars: loggedInUser];
|
||||
|
||||
json = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[loggedInUser cn], @"cn",
|
||||
[NSNumber numberWithInt: expire], @"expire",
|
||||
[NSNumber numberWithInt: grace], @"grace", nil];
|
||||
[json setObject: [loggedInUser cn]
|
||||
forKey: @"cn"];
|
||||
[json setObject: [NSNumber numberWithInt: expire]
|
||||
forKey: @"expire"];
|
||||
[json setObject: [NSNumber numberWithInt: grace]
|
||||
forKey: @"grace"];
|
||||
|
||||
response = [self responseWithStatus: 200
|
||||
andJSONRepresentation: json];
|
||||
@@ -339,7 +357,6 @@
|
||||
[context setActiveUser: loggedInUser];
|
||||
if (language && [supportedLanguages containsObject: language])
|
||||
{
|
||||
ud = [loggedInUser userDefaults];
|
||||
[ud setLanguage: language];
|
||||
[ud synchronize];
|
||||
}
|
||||
|
||||
@@ -126,8 +126,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TOTP Code -->
|
||||
<var:if condition="isTotpEnabled">
|
||||
<!-- TOTP Code -->
|
||||
<div layout="row" layout-align="center center" layout-fill="layout-fill"
|
||||
ng-switch-when="totpcode">
|
||||
<div flex="80" flex-sm="50" flex-gt-sm="40">
|
||||
@@ -154,6 +154,27 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- TOTP has been disabled -->
|
||||
<div layout="row" layout-align="center center" layout-fill="layout-fill"
|
||||
ng-switch-when="totpdisabled">
|
||||
<div layout="column" layout-align="center center" flex-xs="flex-xs" flex-gt-xs="50">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">warning</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="app.cn">
|
||||
<var:string label:value="Welcome"/> {{app.cn}}
|
||||
</div>
|
||||
<div class="md-padding" layout="row" layout-align="start center">
|
||||
<md-icon>priority_high</md-icon>
|
||||
<div class="md-padding">
|
||||
<var:string label:value="Two-factor authentication has been disabled. Visit the Preferences module to restore two-factor authentication and reconfigure your TOTP application."/>
|
||||
</div>
|
||||
</div>
|
||||
<div layout="row" layout-align="end center">
|
||||
<md-button
|
||||
ng-click="app.continueLogin()"
|
||||
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</var:if>
|
||||
|
||||
<!-- Password policy: Password is expired -->
|
||||
@@ -193,20 +214,25 @@
|
||||
</div>
|
||||
|
||||
<!-- Password policy: Grace period -->
|
||||
<div layout="column" layout-align="center center"
|
||||
<div layout="row" layout-align="center center" layout-fill="layout-fill"
|
||||
ng-switch-when="passwordwillexpire">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">warning</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="app.cn">
|
||||
<var:string label:value="Welcome"/> {{app.cn}}
|
||||
<div layout="column" layout-align="center center" flex-xs="flex-xs" flex-gt-xs="50">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">warning</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="app.cn">
|
||||
<var:string label:value="Welcome"/> {{app.cn}}
|
||||
</div>
|
||||
<div class="md-padding" layout="row" layout-align="start center">
|
||||
<md-icon>priority_high</md-icon>
|
||||
<div class="md-padding">{{app.errorMessage}}</div>
|
||||
</div>
|
||||
<div layout="row" layout-align="end center">
|
||||
<md-button
|
||||
ng-click="app.loginState = 'passwordexpired'"><var:string label:value="Change your Password"/></md-button>
|
||||
<md-button
|
||||
ng-click="app.continueLogin()"
|
||||
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="md-default-theme md-warn md-hue-1 md-bg md-padding">
|
||||
{{app.errorMessage}}
|
||||
<md-button class="md-raised"
|
||||
ng-click="app.loginState = 'passwordexpired'"><var:string label:value="Change your Password"/></md-button>
|
||||
</div>
|
||||
<md-button
|
||||
ng-click="app.continueLogin()"
|
||||
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
|
||||
</div>
|
||||
|
||||
<!-- Logged in -->
|
||||
|
||||
@@ -252,7 +252,7 @@
|
||||
</md-checkbox>
|
||||
<div layout="row" layout-align="start center" layout-xs="column"
|
||||
layout-padding="layout-padding" layout-margin="layout-margin"
|
||||
ng-show="app.preferences.defaults.SOGoTOTPEnabled">
|
||||
ng-show="app.preferences.defaults.SOGoTOTPEnabled == 1">
|
||||
<div>
|
||||
<sg-qr-code var:text="totpKey" />
|
||||
</div>
|
||||
|
||||
@@ -97,6 +97,13 @@
|
||||
if (typeof data.totpMissingKey != 'undefined' && response.status == 202) {
|
||||
d.resolve({totpmissingkey: 1});
|
||||
}
|
||||
else if (typeof data.totpDisabled != 'undefined') {
|
||||
d.resolve({
|
||||
cn: data.cn,
|
||||
url: redirectUrl(username, domain),
|
||||
totpdisabled: 1
|
||||
});
|
||||
}
|
||||
// Check password policy
|
||||
else if (typeof data.expire != 'undefined' && typeof data.grace != 'undefined') {
|
||||
if (data.expire < 0 && data.grace > 0) {
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
function link(scope, element, attrs) {
|
||||
var width = parseInt(scope.width) || 256,
|
||||
height = parseInt(scope.height) || width,
|
||||
// See https://github.com/google/google-authenticator/wiki/Key-Uri-Format
|
||||
uri = 'otpauth://totp/SOGo:' + Settings.activeUser('email') + '?secret=' + scope.text.replace(/=+$/, '') + '&issuer=SOGo';
|
||||
new QRCode(element[0], {
|
||||
text: uri,
|
||||
|
||||
@@ -44,6 +44,11 @@
|
||||
if (data.totpmissingkey) {
|
||||
vm.loginState = 'totpcode';
|
||||
}
|
||||
else if (data.totpdisabled) {
|
||||
vm.loginState = 'totpdisabled';
|
||||
vm.cn = data.cn;
|
||||
vm.url = data.url;
|
||||
}
|
||||
else {
|
||||
vm.loginState = 'logged';
|
||||
vm.cn = data.cn;
|
||||
|
||||
Reference in New Issue
Block a user