1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-17 20:32:41 +01:00

Mark all existing password hashes as "legacy" and start upgrading digest formats

Summary:
Depends on D18907. Ref T13043. Ref T12509. We have some weird old password digest behavior that isn't terribly concerning, but also isn't great.

Specifically, old passwords were digested in weird ways before being hashed. Notably, account passwords were digested with usernames, so your password stops working if your username is chagned. Not the end of the world, but silly.

Mark all existing hashes as "v1", and automatically upgrade then when they're used or changed. Some day, far in the future, we could stop supporting these legacy digests and delete the code and passwords and just issue upgrade advice ("Passwords which haven't been used in more than two years no longer work."). But at least get things on a path toward sane, modern behavior.

Test Plan: Ran migration. Spot-checked that everthing in the database got marked as "v1". Used an existing password to login successfully. Verified that it was upgraded to a `null` (modern) digest. Logged in with it again.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13043, T12509

Differential Revision: https://secure.phabricator.com/D18908
This commit is contained in:
epriestley 2018-01-21 18:07:49 -08:00
parent 13ef5c6f23
commit 21e415299f
4 changed files with 51 additions and 33 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_auth.auth_password
ADD legacyDigestFormat VARCHAR(32) COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,4 @@
UPDATE {$NAMESPACE}_auth.auth_password
SET legacyDigestFormat = 'v1'
WHERE passwordType IN ('vcs', 'account')
AND legacyDigestFormat IS NULL;

View file

@ -12,6 +12,7 @@ final class PhabricatorAuthPassword
protected $passwordHash; protected $passwordHash;
protected $passwordSalt; protected $passwordSalt;
protected $isRevoked; protected $isRevoked;
protected $legacyDigestFormat;
private $object = self::ATTACHABLE; private $object = self::ATTACHABLE;
@ -38,6 +39,7 @@ final class PhabricatorAuthPassword
'passwordHash' => 'text128', 'passwordHash' => 'text128',
'passwordSalt' => 'text64', 'passwordSalt' => 'text64',
'isRevoked' => 'bool', 'isRevoked' => 'bool',
'legacyDigestFormat' => 'text32?',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'key_role' => array( 'key_role' => array(
@ -66,6 +68,12 @@ final class PhabricatorAuthPassword
} }
public function canUpgrade() { public function canUpgrade() {
// If this password uses a legacy digest format, we can upgrade it to the
// new digest format even if a better hasher isn't available.
if ($this->getLegacyDigestFormat() !== null) {
return true;
}
$hash = $this->newPasswordEnvelope(); $hash = $this->newPasswordEnvelope();
return PhabricatorPasswordHasher::canUpgradeHash($hash); return PhabricatorPasswordHasher::canUpgradeHash($hash);
} }
@ -110,6 +118,9 @@ final class PhabricatorAuthPassword
$new_salt = Filesystem::readRandomCharacters(64); $new_salt = Filesystem::readRandomCharacters(64);
$this->setPasswordSalt($new_salt); $this->setPasswordSalt($new_salt);
// Clear any legacy digest format to force a modern digest.
$this->setLegacyDigestFormat(null);
$digest = $this->digestPassword($password, $object); $digest = $this->digestPassword($password, $object);
$hash = $hasher->getPasswordHashForStorage($digest); $hash = $hasher->getPasswordHashForStorage($digest);
$raw_hash = $hash->openEnvelope(); $raw_hash = $hash->openEnvelope();

View file

@ -1590,7 +1590,7 @@ final class PhabricatorUser
// Applying salt while digesting passwords ensures that hashes are salted // Applying salt while digesting passwords ensures that hashes are salted
// whether we ultimately select a self-salting hasher or not. // whether we ultimately select a self-salting hasher or not.
// For legacy compatibility reasons, the VCS and Account password digest // For legacy compatibility reasons, old VCS and Account password digest
// algorithms are significantly more complicated than necessary to achieve // algorithms are significantly more complicated than necessary to achieve
// these goals. This is because they once used a different hashing and // these goals. This is because they once used a different hashing and
// salting process. When we upgraded to the modern modular hasher // salting process. When we upgraded to the modern modular hasher
@ -1602,43 +1602,44 @@ final class PhabricatorUser
// everything that a digest callback should without any needless legacy // everything that a digest callback should without any needless legacy
// baggage on top. // baggage on top.
switch ($password->getPasswordType()) { if ($password->getLegacyDigestFormat() == 'v1') {
case PhabricatorAuthPassword::PASSWORD_TYPE_VCS: switch ($password->getPasswordType()) {
// VCS passwords use an iterated HMAC SHA1 as a digest algorithm. They case PhabricatorAuthPassword::PASSWORD_TYPE_VCS:
// originally used this as a hasher, but it became a digest alorithm // Old VCS passwords use an iterated HMAC SHA1 as a digest algorithm.
// once hashing was upgraded to include bcrypt. // They originally used this as a hasher, but it became a digest
$digest = $envelope->openEnvelope(); // algorithm once hashing was upgraded to include bcrypt.
$salt = $this->getPHID(); $digest = $envelope->openEnvelope();
for ($ii = 0; $ii < 1000; $ii++) { $salt = $this->getPHID();
$digest = PhabricatorHash::weakDigest($digest, $salt); for ($ii = 0; $ii < 1000; $ii++) {
} $digest = PhabricatorHash::weakDigest($digest, $salt);
return new PhutilOpaqueEnvelope($digest); }
case PhabricatorAuthPassword::PASSWORD_TYPE_ACCOUNT: return new PhutilOpaqueEnvelope($digest);
// Account passwords use this weird mess of salt and do not digest case PhabricatorAuthPassword::PASSWORD_TYPE_ACCOUNT:
// the input to a standard length. // Account passwords previously used this weird mess of salt and did
// not digest the input to a standard length.
// TODO: We should build a migration pathway forward from this which // Beyond this being a weird special case, there are two actual
// uses a better (HMAC SHA256) digest algorithm. Beyond this being // problems with this, although neither are particularly severe:
// a weird special case, there are two actual problems with this,
// although neither are particularly severe:
// First, because we do not normalize the length of passwords, this // First, because we do not normalize the length of passwords, this
// algorithm may make us vulnerable to DOS attacks where attacker // algorithm may make us vulnerable to DOS attacks where an attacker
// attempt to use very long inputs to slow down hashers. // attempts to use a very long input to slow down hashers.
// Second, because the username is part of the hash algorithm, renaming // Second, because the username is part of the hash algorithm,
// a user breaks their password. This isn't a huge deal but it's pretty // renaming a user breaks their password. This isn't a huge deal but
// silly. There's no security justification for this behavior, I just // it's pretty silly. There's no security justification for this
// didn't think about the implication when I wrote it originally. // behavior, I just didn't think about the implication when I wrote
// it originally.
$parts = array( $parts = array(
$this->getUsername(), $this->getUsername(),
$envelope->openEnvelope(), $envelope->openEnvelope(),
$this->getPHID(), $this->getPHID(),
$password->getPasswordSalt(), $password->getPasswordSalt(),
); );
return new PhutilOpaqueEnvelope(implode('', $parts)); return new PhutilOpaqueEnvelope(implode('', $parts));
}
} }
// For passwords which do not have some crazy legacy reason to use some // For passwords which do not have some crazy legacy reason to use some