Skip to content

Commit

Permalink
the control panel auth hmac message should also include the user's pa…
Browse files Browse the repository at this point in the history
…ssword so that resetting a password in the database forces that user to log in to the control panel again; also use a sha256 hmac
  • Loading branch information
JoshData committed Jun 6, 2015
1 parent 462a79c commit e9e6d94
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 10 deletions.
25 changes: 17 additions & 8 deletions management/auth.py
Expand Up @@ -88,8 +88,9 @@ def get_user_credentials(self, email, pw, env):
if email == "" or pw == "":
raise ValueError("Enter an email address and password.")

# The password might be a user-specific API key.
if hmac.compare_digest(self.create_user_key(email), pw):
# The password might be a user-specific API key. create_user_key raises
# a ValueError if the user does not exist.
if hmac.compare_digest(self.create_user_key(email, env), pw):
# OK.
pass
else:
Expand All @@ -111,18 +112,26 @@ def get_user_credentials(self, email, pw, env):
# Login failed.
raise ValueError("Invalid password.")

# Get privileges for authorization. This call should never fail on a valid user,
# but if the caller passed a user-specific API key then the user may no longer
# exist --- in that case, get_mail_user_privileges will return a tuple of an
# error message and an HTTP status code.
# Get privileges for authorization. This call should never fail because by this
# point we know the email address is a valid user. But on error the call will
# return a tuple of an error message and an HTTP status code.
privs = get_mail_user_privileges(email, env)
if isinstance(privs, tuple): raise ValueError(privs[0])

# Return a list of privileges.
return privs

def create_user_key(self, email):
return hmac.new(self.key.encode('ascii'), b"AUTH:" + email.encode("utf8"), digestmod="sha1").hexdigest()
def create_user_key(self, email, env):
# Store an HMAC with the client. The hashed message of the HMAC will be the user's
# email address & hashed password and the key will be the master API key. The user of
# course has their own email address and password. We assume they do not have the master
# API key (unless they are trusted anyway). The HMAC proves that they authenticated
# with us in some other way to get the HMAC. Including the password means that when
# a user's password is reset, the HMAC changes and they will correctly need to log
# in to the control panel again. This method raises a ValueError if the user does
# not exist, due to get_mail_password.
msg = b"AUTH:" + email.encode("utf8") + b" " + get_mail_password(email, env).encode("utf8")
return hmac.new(self.key.encode('ascii'), msg, digestmod="sha256").hexdigest()

def _generate_key(self):
raw_key = os.urandom(32)
Expand Down
2 changes: 1 addition & 1 deletion management/daemon.py
Expand Up @@ -118,7 +118,7 @@ def me():

# Is authorized as admin? Return an API key for future use.
if "admin" in privs:
resp["api_key"] = auth_service.create_user_key(email)
resp["api_key"] = auth_service.create_user_key(email, env)

# Return.
return json_response(resp)
Expand Down
7 changes: 6 additions & 1 deletion management/templates/users.html
Expand Up @@ -164,9 +164,14 @@ <h3>Existing mail users</h3>

function users_set_password(elem) {
var email = $(elem).parents('tr').attr('data-email');

var yourpw = "";
if (api_credentials != null && email == api_credentials[0])
yourpw = "<p class='text-danger'>If you change your own password, you will be logged out of this control panel and will need to log in again.</p>";

show_modal_confirm(
"Archive User",
$("<p>Set a new password for <b>" + email + "</b>?</p> <p><label for='users_set_password_pw' style='display: block; font-weight: normal'>New Password:</label><input type='password' id='users_set_password_pw'></p><p><small>Passwords must be at least four characters and may not contain spaces.</small></p>"),
$("<p>Set a new password for <b>" + email + "</b>?</p> <p><label for='users_set_password_pw' style='display: block; font-weight: normal'>New Password:</label><input type='password' id='users_set_password_pw'></p><p><small>Passwords must be at least four characters and may not contain spaces.</small>" + yourpw + "</p>"),
"Set Password",
function() {
api(
Expand Down
2 changes: 2 additions & 0 deletions security.md
Expand Up @@ -56,6 +56,8 @@ The cipher and protocol selection are chosen to support the following clients:

The passwords for mail users are stored on disk using the [SHA512-CRYPT](http://man7.org/linux/man-pages/man3/crypt.3.html) hashing scheme. ([source](management/mailconfig.py))

When using the web-based administrative control panel, after logging in an API key is placed in the browser's local storage (rather than, say, the user's actual password). The API key is an HMAC based on the user's email address and current password, and it is keyed by a secret known only to the control panel service. By resetting an administrator's password, any HMACs previously generated for that user will expire.

### Console access

Console access (e.g. via SSH) is configured by the system image used to create the box, typically from by a cloud virtual machine provider (e.g. Digital Ocean). Mail-in-a-Box does not set any console access settings, although it will warn the administrator in the System Status Checks if password-based login is turned on.
Expand Down

0 comments on commit e9e6d94

Please sign in to comment.