Skip to content

Commit

Permalink
fix(web): use a distinct salt for TOTP authentication
Browse files Browse the repository at this point in the history
If TOTP is enabled for a user, it will be disabled until the user
configure it again, which will generate a new private salt.
  • Loading branch information
cgx committed Oct 15, 2021
1 parent ba86b0f commit d4da1fa
Show file tree
Hide file tree
Showing 11 changed files with 115 additions and 35 deletions.
2 changes: 1 addition & 1 deletion SoObjects/Appointments/SOGoCalendarComponent.m
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ - (void) _filterComponent: (iCalEntityObject *) component

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

Expand Down
2 changes: 1 addition & 1 deletion SoObjects/SOGo/SOGoUser.m
Original file line number Diff line number Diff line change
Expand Up @@ -1138,7 +1138,7 @@ - (NSString *) totpKey

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

Expand Down
5 changes: 3 additions & 2 deletions SoObjects/SOGo/SOGoUserSettings.h
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -33,7 +33,8 @@

- (NSArray *) subscribedCalendars;
- (NSArray *) subscribedAddressBooks;
- (NSString *) userSalt;
- (NSString *) userPrivateSalt;
- (NSString *) userPublicSalt;

@end

Expand Down
26 changes: 24 additions & 2 deletions SoObjects/SOGo/SOGoUserSettings.m
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -70,7 +70,7 @@ - (NSArray *) subscribedAddressBooks
return [self _subscribedFoldersForModule: @"Contacts"];
}

- (NSString *) userSalt
- (NSString *) userPublicSalt
{
NSMutableDictionary *values;
NSString *salt;
Expand All @@ -93,5 +93,27 @@ - (NSString *) userSalt
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
1 change: 1 addition & 0 deletions UI/MainUI/English.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
47 changes: 32 additions & 15 deletions UI/MainUI/SOGoRootPage.m
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ - (WOResponse *) connectAction
WOCookie *authCookie, *xsrfCookie;
SOGoWebAuthenticator *auth;
SOGoUserDefaults *ud;
SOGoUserSettings *us;
SOGoUser *loggedInUser;
NSDictionary *params;
NSString *username, *password, *language, *domain, *remoteHost;
Expand Down Expand Up @@ -235,7 +236,7 @@ - (WOResponse *) connectAction
|| (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];

Expand All @@ -248,9 +249,10 @@ - (WOResponse *) connectAction
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;

Expand Down Expand Up @@ -295,29 +297,45 @@ - (WOResponse *) connectAction
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];
Expand All @@ -339,7 +357,6 @@ - (WOResponse *) connectAction
[context setActiveUser: loggedInUser];
if (language && [supportedLanguages containsObject: language])
{
ud = [loggedInUser userDefaults];
[ud setLanguage: language];
[ud synchronize];
}
Expand Down
52 changes: 39 additions & 13 deletions UI/Templates/MainUI/SOGoRootPage.wox
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand All @@ -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 -->
Expand Down Expand Up @@ -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>
<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 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>
<md-button
ng-click="app.continueLogin()"
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
</div>

<!-- Logged in -->
Expand Down
2 changes: 1 addition & 1 deletion UI/Templates/PreferencesUI/UIxPreferences.wox
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
7 changes: 7 additions & 0 deletions UI/WebServerResources/js/Common/Authentication.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions UI/WebServerResources/js/Common/sgQrCode.directive.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions UI/WebServerResources/js/Main/Main.app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit d4da1fa

Please sign in to comment.