protected function getPasswordHash(PhutilOpaqueEnvelope $envelope) { $raw_input = $envelope->openEnvelope(); $hash = $raw_input; for ($ii = 0; $ii < 1000; $ii++) { $hash = md5($hash); } return new PhutilOpaqueEnvelope($hash); }
/** * Digest a string into a password hash. This is similar to @{method:digest}, * but requires a salt and iterates the hash to increase cost. */ public static function digestPassword(PhutilOpaqueEnvelope $envelope, $salt) { $result = $envelope->openEnvelope(); if (!$result) { throw new Exception(pht('Trying to digest empty password!')); } for ($ii = 0; $ii < 1000; $ii++) { $result = self::digest($result, $salt); } return $result; }
protected function canUpgradeInternalHash(PhutilOpaqueEnvelope $hash) { $info = password_get_info($hash->openEnvelope()); // NOTE: If the costs don't match -- even if the new cost is lower than // the old cost -- count this as an upgrade. This allows costs to be // adjusted down and hashing to be migrated toward the new cost if costs // are ever configured too high for some reason. $cost = idx($info['options'], 'cost'); if ($cost != $this->getBcryptCost()) { return true; } return false; }
private function formatStorageProperties(PhutilOpaqueEnvelope $key_envelope, PhutilOpaqueEnvelope $iv_envelope) { // Encode the raw binary data with base64 so we can wrap it in JSON. $data = array('iv.base64' => base64_encode($iv_envelope->openEnvelope()), 'key.base64' => base64_encode($key_envelope->openEnvelope())); // Encode the base64 data with JSON. $data_clear = phutil_json_encode($data); // Encrypt the block key with the master key, using a unique IV. $data_iv = self::newAES256IV(); $key_name = $this->getMasterKeyName(); $master_key = $this->getMasterKeyMaterial($key_name); $data_cipher = $this->encryptData($data_clear, $master_key, $data_iv); return array('key.name' => $key_name, 'iv.base64' => base64_encode($data_iv->openEnvelope()), 'payload.base64' => base64_encode($data_cipher)); }
public function decryptSecret(PhutilOpaqueEnvelope $secret, PhutilOpaqueEnvelope $password) { $tmp = new TempFile(); Filesystem::writeFile($tmp, $secret->openEnvelope()); if (!Filesystem::binaryExists('ssh-keygen')) { throw new Exception(pht('Decrypting SSH keys requires the `ssh-keygen` binary, but it ' . 'is not available in PATH. Either make it available or strip the ' . 'password fromt his SSH key manually before uploading it.')); } list($err, $stdout, $stderr) = exec_manual('ssh-keygen -p -P %P -N %s -f %s', $password, '', (string) $tmp); if ($err) { return null; } else { return new PhutilOpaqueEnvelope(Filesystem::readFile($tmp)); } }
private function authenticateHTTPRepositoryUser($username, PhutilOpaqueEnvelope $password) { if (!PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth')) { // No HTTP auth permitted. return null; } if (!strlen($username)) { // No username. return null; } if (!strlen($password->openEnvelope())) { // No password. return null; } $user = id(new PhabricatorPeopleQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withUsernames(array($username))->executeOne(); if (!$user) { // Username doesn't match anything. return null; } if (!$user->isUserActivated()) { // User is not activated. return null; } $password_entry = id(new PhabricatorRepositoryVCSPassword())->loadOneWhere('userPHID = %s', $user->getPHID()); if (!$password_entry) { // User doesn't have a password set. return null; } if (!$password_entry->comparePassword($password, $user)) { // Password doesn't match. 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; }
public static function getTOTPCode(PhutilOpaqueEnvelope $key, $timestamp) { $binary_timestamp = pack('N*', 0) . pack('N*', $timestamp); $binary_key = self::base32Decode($key->openEnvelope()); $hash = hash_hmac('sha1', $binary_timestamp, $binary_key, true); // See RFC 4226. $offset = ord($hash[19]) & 0xf; $code = (ord($hash[$offset + 0]) & 0x7f) << 24 | (ord($hash[$offset + 1]) & 0xff) << 16 | (ord($hash[$offset + 2]) & 0xff) << 8 | ord($hash[$offset + 3]); $code = $code % 1000000; $code = str_pad($code, 6, '0', STR_PAD_LEFT); return $code; }
public function auth($username, PhutilOpaqueEnvelope $password) { if (strlen(trim($username)) == 0) { throw new Exception('Username can not be empty'); } if (PhabricatorEnv::getEnvConfig('ldap.search-first')) { // To protect against people phishing for accounts we catch the // exception and present the default exception that would be presented // in the case of a failed bind. try { $user = $this->getUser($this->getUsernameAttribute(), $username); $username = $user[$this->getSearchAttribute()][0]; } catch (PhabricatorLDAPUnknownUserException $e) { throw new Exception($this->invalidLDAPUserErrorMessage(self::LDAP_INVALID_CREDENTIALS, ldap_err2str(self::LDAP_INVALID_CREDENTIALS))); } } $conn = $this->getConnection(); $activeDirectoryDomain = PhabricatorEnv::getEnvConfig('ldap.activedirectory_domain'); if ($activeDirectoryDomain) { $dn = $username . '@' . $activeDirectoryDomain; } else { $dn = ldap_sprintf('%Q=%s,%Q', $this->getSearchAttribute(), $username, $this->getBaseDN()); } // NOTE: It is very important we suppress any messages that occur here, // because it logs passwords if it reaches an error log of any sort. DarkConsoleErrorLogPluginAPI::enableDiscardMode(); $result = @ldap_bind($conn, $dn, $password->openEnvelope()); DarkConsoleErrorLogPluginAPI::disableDiscardMode(); if (!$result) { throw new Exception($this->invalidLDAPUserErrorMessage(ldap_errno($conn), ldap_error($conn))); } $this->userData = $this->getUser($this->getSearchAttribute(), $username); return $this->userData; }
private function getPasswordHashInput(PhutilOpaqueEnvelope $password) { $input = $this->getUsername() . $password->openEnvelope() . $this->getPHID() . $this->getPasswordSalt(); return new PhutilOpaqueEnvelope($input); }
/** * 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 = self::getHasherForHash($hash); return $current_hasher->getHumanReadableName(); } catch (Exception $ex) { $info = self::parseHashFromStorage($hash); $name = $info['name']; return pht('Unknown ("%s")', $name); } }
private function bindLDAP($conn, $user, PhutilOpaqueEnvelope $pass) { $profiler = PhutilServiceProfiler::getInstance(); $call_id = $profiler->beginServiceCall(array('type' => 'ldap', 'call' => 'bind', 'user' => $user)); // NOTE: ldap_bind() dumps cleartext passwords into logs by default. Keep // it quiet. if (strlen($user)) { $ok = @ldap_bind($conn, $user, $pass->openEnvelope()); } else { $ok = @ldap_bind($conn); } $profiler->endServiceCall($call_id, array()); if (!$ok) { if (strlen($user)) { $this->raiseConnectionException($conn, pht('Failed to bind to LDAP server (as user "%s").', $user)); } else { $this->raiseConnectionException($conn, pht('Failed to bind to LDAP server (without username).')); } } }
public function processRequest(AphrontRequest $request) { $user = $request->getUser(); $token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession($user, $request, '/settings/'); $min_len = PhabricatorEnv::getEnvConfig('account.minimum-password-length'); $min_len = (int) $min_len; // NOTE: To change your password, you need to prove you own the account, // either by providing the old password or by carrying a token to // the workflow from a password reset email. $key = $request->getStr('key'); $token = null; if ($key) { $token = id(new PhabricatorAuthTemporaryTokenQuery())->setViewer($user)->withObjectPHIDs(array($user->getPHID()))->withTokenTypes(array(PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE))->withTokenCodes(array(PhabricatorHash::digest($key)))->withExpired(false)->executeOne(); } $e_old = true; $e_new = true; $e_conf = true; $errors = array(); if ($request->isFormPost()) { if (!$token) { $envelope = new PhutilOpaqueEnvelope($request->getStr('old_pw')); if (!$user->comparePassword($envelope)) { $errors[] = pht('The old password you entered is incorrect.'); $e_old = pht('Invalid'); } } $pass = $request->getStr('new_pw'); $conf = $request->getStr('conf_pw'); if (strlen($pass) < $min_len) { $errors[] = pht('Your new password is too short.'); $e_new = pht('Too Short'); } else { if ($pass !== $conf) { $errors[] = pht('New password and confirmation do not match.'); $e_conf = pht('Invalid'); } else { if (PhabricatorCommonPasswords::isCommonPassword($pass)) { $e_new = pht('Very Weak'); $e_conf = pht('Very Weak'); $errors[] = pht('Your new password is very weak: it is one of the most common ' . 'passwords in use. Choose a stronger password.'); } } } if (!$errors) { // This write is unguarded because the CSRF token has already // been checked in the call to $request->isFormPost() and // the CSRF token depends on the password hash, so when it // is changed here the CSRF token check will fail. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $envelope = new PhutilOpaqueEnvelope($pass); id(new PhabricatorUserEditor())->setActor($user)->changePassword($user, $envelope); unset($unguarded); if ($token) { // Destroy the token. $token->delete(); // If this is a password set/reset, kick the user to the home page // after we update their account. $next = '/'; } else { $next = $this->getPanelURI('?saved=true'); } id(new PhabricatorAuthSessionEngine())->terminateLoginSessions($user, $request->getCookie(PhabricatorCookies::COOKIE_SESSION)); return id(new AphrontRedirectResponse())->setURI($next); } } $hash_envelope = new PhutilOpaqueEnvelope($user->getPasswordHash()); if (strlen($hash_envelope->openEnvelope())) { try { $can_upgrade = PhabricatorPasswordHasher::canUpgradeHash($hash_envelope); } catch (PhabricatorPasswordHasherUnavailableException $ex) { $can_upgrade = false; // Only show this stuff if we aren't on the reset workflow. We can // do resets regardless of the old hasher's availability. if (!$token) { $errors[] = pht('Your password is currently hashed using an algorithm which is ' . 'no longer available on this install.'); $errors[] = pht('Because the algorithm implementation is missing, your password ' . 'can not be used or updated.'); $errors[] = pht('To set a new password, request a password reset link from the ' . 'login screen and then follow the instructions.'); } } if ($can_upgrade) { $errors[] = pht('The strength of your stored password hash can be upgraded. ' . 'To upgrade, either: log out and log in using your password; or ' . 'change your password.'); } } $len_caption = null; if ($min_len) { $len_caption = pht('Minimum password length: %d characters.', $min_len); } $form = new AphrontFormView(); $form->setUser($user)->addHiddenInput('key', $key); if (!$token) { $form->appendChild(id(new AphrontFormPasswordControl())->setLabel(pht('Old Password'))->setError($e_old)->setName('old_pw')); } $form->appendChild(id(new AphrontFormPasswordControl())->setDisableAutocomplete(true)->setLabel(pht('New Password'))->setError($e_new)->setName('new_pw')); $form->appendChild(id(new AphrontFormPasswordControl())->setDisableAutocomplete(true)->setLabel(pht('Confirm Password'))->setCaption($len_caption)->setError($e_conf)->setName('conf_pw')); $form->appendChild(id(new AphrontFormSubmitControl())->setValue(pht('Change Password'))); $form->appendChild(id(new AphrontFormStaticControl())->setLabel(pht('Current Algorithm'))->setValue(PhabricatorPasswordHasher::getCurrentAlgorithmName(new PhutilOpaqueEnvelope($user->getPasswordHash())))); $form->appendChild(id(new AphrontFormStaticControl())->setLabel(pht('Best Available Algorithm'))->setValue(PhabricatorPasswordHasher::getBestAlgorithmName())); $form->appendRemarkupInstructions(pht('NOTE: Changing your password will terminate any other outstanding ' . 'login sessions.')); $form_box = id(new PHUIObjectBoxView())->setHeaderText(pht('Change Password'))->setFormSaved($request->getStr('saved'))->setFormErrors($errors)->setForm($form); return array($form_box); }
/** * Add a HTTP basic authentication header to the request. * * @param string Username to authenticate with. * @param PhutilOpaqueEnvelope Password to authenticate with. * @return this * @task config */ public function setHTTPBasicAuthCredentials($username, PhutilOpaqueEnvelope $password) { $password_plaintext = $password->openEnvelope(); $credentials = base64_encode($username . ':' . $password_plaintext); return $this->addHeader('Authorization', 'Basic ' . $credentials); }
private function hashPassword(PhutilOpaqueEnvelope $envelope) { $hash = $this->getUsername() . $envelope->openEnvelope() . $this->getPHID() . $this->getPasswordSalt(); for ($ii = 0; $ii < 1000; $ii++) { $hash = md5($hash); } return $hash; }