diff --git a/resources/sql/autopatches/20140218.passwords.3.vcsextend.sql b/resources/sql/autopatches/20140218.passwords.3.vcsextend.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20140218.passwords.3.vcsextend.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_repository.repository_vcspassword + CHANGE passwordHash passwordHash VARCHAR(128) COLLATE utf8_bin NOT NULL; diff --git a/resources/sql/autopatches/20140218.passwords.4.vcs.php b/resources/sql/autopatches/20140218.passwords.4.vcs.php new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20140218.passwords.4.vcs.php @@ -0,0 +1,27 @@ +establishConnection('w'); + +echo "Upgrading password hashing for VCS passwords.\n"; + +$best_hasher = PhabricatorPasswordHasher::getBestHasher(); +foreach (new LiskMigrationIterator($table) as $password) { + $id = $password->getID(); + + echo "Migrating VCS password {$id}...\n"; + + $input_hash = $password->getPasswordHash(); + $input_envelope = new PhutilOpaqueEnvelope($input_hash); + + $storage_hash = $best_hasher->getPasswordHashForStorage($input_envelope); + + queryfx( + $conn_w, + 'UPDATE %T SET passwordHash = %s WHERE id = %d', + $table->getTableName(), + $storage_hash->openEnvelope(), + $id); +} + +echo "Done.\n"; diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -426,6 +426,18 @@ return null; } + // If the user's password is stored using a less-than-optimal hash, upgrade + // them to the strongest available hash. + + $hash_envelope = new PhutilOpaqueEnvelope( + $password_entry->getPasswordHash()); + if (PhabricatorPasswordHasher::canUpgradeHash($hash_envelope)) { + $password_entry->setPassword($password, $user); + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $password_entry->save(); + unset($unguarded); + } + return $user; } diff --git a/src/applications/diffusion/panel/DiffusionSetPasswordPanel.php b/src/applications/diffusion/panel/DiffusionSetPasswordPanel.php --- a/src/applications/diffusion/panel/DiffusionSetPasswordPanel.php +++ b/src/applications/diffusion/panel/DiffusionSetPasswordPanel.php @@ -165,6 +165,26 @@ } } + $hash_envelope = new PhutilOpaqueEnvelope($vcspassword->getPasswordHash()); + + $form->appendChild( + id(new AphrontFormStaticControl()) + ->setLabel(pht('Current Algorithm')) + ->setValue( + PhabricatorPasswordHasher::getCurrentAlgorithmName($hash_envelope))); + + $form->appendChild( + id(new AphrontFormStaticControl()) + ->setLabel(pht('Best Available Algorithm')) + ->setValue(PhabricatorPasswordHasher::getBestAlgorithmName())); + + if (PhabricatorPasswordHasher::canUpgradeHash($hash_envelope)) { + $errors[] = pht( + 'The strength of your stored VCS password hash can be upgraded. '. + 'To upgrade, either: use the password to authenticate with a '. + 'repository; or change your password.'); + } + $object_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setForm($form) diff --git a/src/applications/repository/storage/PhabricatorRepositoryVCSPassword.php b/src/applications/repository/storage/PhabricatorRepositoryVCSPassword.php --- a/src/applications/repository/storage/PhabricatorRepositoryVCSPassword.php +++ b/src/applications/repository/storage/PhabricatorRepositoryVCSPassword.php @@ -9,26 +9,38 @@ public function setPassword( PhutilOpaqueEnvelope $password, PhabricatorUser $user) { - return $this->setPasswordHash($this->hashPassword($password, $user)); + $hash_envelope = $this->hashPassword($password, $user); + return $this->setPasswordHash($hash_envelope->openEnvelope()); } public function comparePassword( PhutilOpaqueEnvelope $password, PhabricatorUser $user) { - $hash = $this->hashPassword($password, $user); - return ($hash == $this->getPasswordHash()); + return PhabricatorPasswordHasher::comparePassword( + $this->getPasswordHashInput($password, $user), + new PhutilOpaqueEnvelope($this->getPasswordHash())); } - private function hashPassword( + private function getPasswordHashInput( PhutilOpaqueEnvelope $password, PhabricatorUser $user) { - if ($user->getPHID() != $this->getUserPHID()) { throw new Exception("User does not match password user PHID!"); } - return PhabricatorHash::digestPassword($password, $user->getPHID()); + $raw_input = PhabricatorHash::digestPassword($password, $user->getPHID()); + return new PhutilOpaqueEnvelope($raw_input); + } + + private function hashPassword( + PhutilOpaqueEnvelope $password, + PhabricatorUser $user) { + + $input_envelope = $this->getPasswordHashInput($password, $user); + + $best_hasher = PhabricatorPasswordHasher::getBestHasher(); + return $best_hasher->getPasswordHashForStorage($input_envelope); } } diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelPassword.php b/src/applications/settings/panel/PhabricatorSettingsPanelPassword.php --- a/src/applications/settings/panel/PhabricatorSettingsPanelPassword.php +++ b/src/applications/settings/panel/PhabricatorSettingsPanelPassword.php @@ -114,7 +114,6 @@ $hash_envelope = new PhutilOpaqueEnvelope($user->getPasswordHash()); if (PhabricatorPasswordHasher::canUpgradeHash($hash_envelope)) { - $best_hash = PhabricatorPasswordHasher::getBestHasher(); $errors[] = pht( 'The strength of your stored password hash can be upgraded. '. 'To upgrade, either: log out and log in using your password; or '. @@ -157,34 +156,16 @@ id(new AphrontFormSubmitControl()) ->setValue(pht('Change Password'))); - if (!strlen($user->getPasswordHash())) { - $current_name = pht('None'); - } else { - try { - $current_hasher = PhabricatorPasswordHasher::getHasherForHash( - new PhutilOpaqueEnvelope($user->getPasswordHash())); - $current_name = $current_hasher->getHumanReadableName(); - } catch (Exception $ex) { - $current_name = pht('Unknown'); - } - } - $form->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Current Algorithm')) - ->setValue($current_name)); - - try { - $best_hasher = PhabricatorPasswordHasher::getBestHasher(); - $best_name = $best_hasher->getHumanReadableName(); - } catch (Exception $ex) { - $best_name = pht('Unknown'); - } + ->setValue(PhabricatorPasswordHasher::getCurrentAlgorithmName( + new PhutilOpaqueEnvelope($user->getPasswordHash())))); $form->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Best Available Algorithm')) - ->setValue($best_name)); + ->setValue(PhabricatorPasswordHasher::getBestAlgorithmName())); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Change Password')) diff --git a/src/infrastructure/util/password/PhabricatorPasswordHasher.php b/src/infrastructure/util/password/PhabricatorPasswordHasher.php --- a/src/infrastructure/util/password/PhabricatorPasswordHasher.php +++ b/src/infrastructure/util/password/PhabricatorPasswordHasher.php @@ -385,4 +385,40 @@ return $hasher->verifyPassword($password, $parts['hash']); } + + /** + * Get the human-readable algorithm name for a given hash. + * + * @param PhutilOpaqueEnvelope Storage hash. + * @return string Human-readable algorithm name. + */ + public static function getCurrentAlgorithmName(PhutilOpaqueEnvelope $hash) { + $raw_hash = $hash->openEnvelope(); + if (!strlen($raw_hash)) { + return pht('None'); + } + + try { + $current_hasher = PhabricatorPasswordHasher::getHasherForHash($hash); + return $current_hasher->getHumanReadableName(); + } catch (Exception $ex) { + return pht('Unknown'); + } + } + + + /** + * Get the human-readable algorithm name for the best available hash. + * + * @return string Human-readable name for best hash. + */ + public static function getBestAlgorithmName() { + try { + $best_hasher = PhabricatorPasswordHasher::getBestHasher(); + return $best_hasher->getHumanReadableName(); + } catch (Exception $ex) { + return pht('Unknown'); + } + } + }