gerrit-server: use hashed passwords for HTTP.

Consequences:
* Removes the GET endpoint for the HTTP password
* Removes digest authentication
* Removes auth.gitBasicAuth config option.

With the move to NoteDB, the per-account data (including the HTTP
password) will be stored in a branch in the All-Users repo, where
it is subject to Gerrit ACLs.  Since these are notoriously hard to
setup correctly, we want to avoid storing the password in plaintext.

With this change, we support hashed passwords, and a schema upgrade
populates the existing 'password' field using previous passwords.

Tested migration manually:

  * ran schema upgrade
  * verified that schema upgrade inserts hashed passwords with gsql.
  * verified that the password still works with the new code.

Tested passwords manually:
  * verified that correct passwords get accepted when using curl --user.
  * verified that wrong passwords get rejected when using curl --user.

Change-Id: I26f5bcd7848040107e3721eeabf75baeb79c1724
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index a0e00a5..f82566b 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -141,6 +141,14 @@
 directory using either an anonymous request, or the configured
 <<ldap.username,ldap.username>> identity. Gerrit can also use kerberos if
 <<ldap.authentication,ldap.authentication>> is set to `GSSAPI`.
++
+If link:#auth.gitBasicAuthPolicy[`auth.gitBasicAuthPolicy`] is set to `HTTP`,
+the randomly generated HTTP password is used for authentication. On the other hand,
+if link:#auth.gitBasicAuthPolicy[`auth.gitBasicAuthPolicy`] is set to `HTTP_LDAP`,
+the password in the request is first checked against the HTTP password and, if
+it does not match, it is then validated against the LDAP password.
+Service users that only exist in the Gerrit database are authenticated by their
+HTTP passwords.
 
 * `LDAP_BIND`
 +
@@ -164,6 +172,12 @@
 Site owners have to register their application before getting started. Note
 that provider specific plugins must be used with this authentication scheme.
 +
+Git clients may send OAuth 2 access tokens instead of passwords in the Basic
+authentication header. Note that provider specific plugins must be installed to
+facilitate this authentication scheme. If multiple OAuth 2 provider plugins are
+installed one of them must be selected as default with the
+`auth.gitOAuthProvider` option.
++
 * `DEVELOPMENT_BECOME_ANY_ACCOUNT`
 +
 *DO NOT USE*.  Only for use in a development environment.
@@ -279,7 +293,7 @@
 [[auth.httpHeader]]auth.httpHeader::
 +
 HTTP header to trust the username from, or unset to select HTTP basic
-or digest authentication.  Only used if `auth.type` is set to `HTTP`.
+authentication.  Only used if `auth.type` is set to `HTTP`.
 
 [[auth.httpDisplaynameHeader]]auth.httpDisplaynameHeader::
 +
@@ -445,45 +459,16 @@
 the container.
 +
 This parameter only affects git over http traffic. If set to false
-then Gerrit will do the authentication (using DIGEST authentication).
+then Gerrit will do the authentication (using Basic authentication).
 +
 By default this is set to false.
 
-[[auth.gitBasicAuth]]auth.gitBasicAuth::
-+
-If true then Git over HTTP and HTTP/S traffic is authenticated using
-standard BasicAuth. Depending on the configured `auth.type`, credentials
-are validated against the randomly generated HTTP password, against LDAP
-(`auth.type = LDAP`) or against an OAuth 2 provider (`auth.type = OAUTH`).
-+
-This parameter affects git over HTTP traffic and access to the REST
-API. If set to false then Gerrit will authenticate through DIGEST
-authentication and the randomly generated HTTP password in the Gerrit
-database.
-+
-When `auth.type` is `LDAP`, users should authenticate using their LDAP passwords.
-However, if link:#auth.gitBasicAuthPolicy[`auth.gitBasicAuthPolicy`] is set to `HTTP`,
-the randomly generated HTTP password is used exclusively. In the other hand,
-if link:#auth.gitBasicAuthPolicy[`auth.gitBasicAuthPolicy`] is set to `HTTP_LDAP`,
-the password in the request is first checked against the HTTP password and, if
-it does not match, it is then validated against the LDAP password.
-Service users that only exist in the Gerrit database are authenticated by their
-HTTP passwords.
-+
-When `auth.type` is `OAUTH`, Git clients may send OAuth 2 access tokens
-instead of passwords in the Basic authentication header. Note that provider
-specific plugins must be installed to facilitate this authentication scheme.
-If multiple OAuth 2 provider plugins are installed one of them must be
-selected as default with the `auth.gitOAuthProvider` option.
-+
-By default this is set to false.
 
 [[auth.gitBasicAuthPolicy]]auth.gitBasicAuthPolicy::
 +
-When `auth.type` is `LDAP` and BasicAuth (i.e., link:#auth.gitBasicAuth[`auth.gitBasicAuth`]
-is set to true), it allows using either the generated HTTP password, the LDAP
-password or both to authenticate Git over HTTP and REST API requests. The
-supported values are:
+When `auth.type` is `LDAP`, it allows using either the generated HTTP password,
+the LDAP password, or both, to authenticate Git over HTTP and REST API
+requests. The supported values are:
 +
 *`HTTP`
 +
diff --git a/Documentation/config-sso.txt b/Documentation/config-sso.txt
index 5f35d73..04949bf 100644
--- a/Documentation/config-sso.txt
+++ b/Documentation/config-sso.txt
@@ -88,7 +88,7 @@
 Login using the other identity can only be performed after the linking is
 successful.
 
-== HTTP Basic/Digest Authentication
+== HTTP Basic Authentication
 
 When using HTTP authentication, Gerrit assumes that the servlet
 container or the frontend web server has performed all user
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index bb9c5c7..9a289e1 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -1443,7 +1443,7 @@
 ----
   curl -X POST -H "Content-Type: application/json" \
     -d '{message: "François", french: true}' \
-    --digest --user joe:secret \
+    --user joe:secret \
     http://host:port/a/changes/1/revisions/1/cookbook~say-hello
   "Bonjour François from change 1, patch set 1!"
 ----
@@ -2451,18 +2451,18 @@
 shows the error dialog. This means currently plugins cannot do any
 error handling and e.g. ignore expected errors.
 
-In the following example the REST endpoint would return '404 Not Found'
-if there is no HTTP password and the Gerrit core UI would display an
-error dialog for this. However having no HTTP password is not an error
-and the plugin may like to handle this case.
+In the following example the REST endpoint would return '404 Not
+Found' if the user has no username and the Gerrit core UI would
+display an error dialog for this. However having no username is
+not an error and the plugin may like to handle this case.
 
 [source,java]
 ----
-new RestApi("accounts").id("self").view("password.http")
+new RestApi("accounts").id("self").view("username")
     .get(new AsyncCallback<NativeString>() {
 
   @Override
-  public void onSuccess(NativeString httpPassword) {
+  public void onSuccess(NativeString username) {
     // TODO
   }
 
diff --git a/Documentation/dev-rest-api.txt b/Documentation/dev-rest-api.txt
index 308d4bd..fec9c97 100644
--- a/Documentation/dev-rest-api.txt
+++ b/Documentation/dev-rest-api.txt
@@ -47,7 +47,7 @@
 Example to set a Gerrit project's link:rest-api-projects.html#set-project-description[description]:
 
 ----
- curl -X PUT --digest --user john:2LlAB3K9B0PF --data-binary @project-desc.txt --header "Content-Type: application/json; charset=UTF-8" http://localhost:8080/a/projects/myproject/description
+ curl -X PUT --user john:2LlAB3K9B0PF --data-binary @project-desc.txt --header "Content-Type: application/json; charset=UTF-8" http://localhost:8080/a/projects/myproject/description
 ----
 
 === Authentication
@@ -56,7 +56,7 @@
 the command line:
 
 ----
- curl --digest --user username:password http://localhost:8080/a/path/to/api/
+ curl --user username:password http://localhost:8080/a/path/to/api/
 ----
 
 This makes it easy to switch users for testing of permissions.
@@ -65,7 +65,7 @@
 file (on Windows, `_netrc`):
 
 ----
- curl --digest -n http://localhost:8080/a/path/to/api/
+ curl -n http://localhost:8080/a/path/to/api/
 ----
 
 In both cases, the password should be the user's link:user-upload.html#http[HTTP password].
@@ -75,7 +75,7 @@
 To verify the headers returned from a REST API call, use `curl` in verbose mode:
 
 ----
-  curl -v -n --digest -X DELETE http://localhost:8080/a/path/to/api/
+  curl -v -n -X DELETE http://localhost:8080/a/path/to/api/
 ----
 
 The headers on both the request and the response will be printed.
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index f1b4abf..d4fa912 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -458,31 +458,6 @@
 
 If the account was already inactive the response is "`409 Conflict`".
 
-[[get-http-password]]
-=== Get HTTP Password
---
-'GET /accounts/link:#account-id[\{account-id\}]/password.http'
---
-
-Retrieves the HTTP password of an account.
-
-.Request
-----
-  GET /accounts/john.doe@example.com/password.http HTTP/1.0
-----
-
-.Response
-----
-  HTTP/1.1 200 OK
-  Content-Disposition: attachment
-  Content-Type: application/json; charset=UTF-8
-
-  )]}'
-  "Qmxlc21ydCB1YmVyIGFsbGVzIGluIGRlciBXZWx0IQ"
-----
-
-If the account does not have an HTTP password the response is "`404 Not Found`".
-
 [[set-http-password]]
 === Set/Generate HTTP Password
 --
@@ -1028,12 +1003,12 @@
   }
 ----
 
-Administrator that has authenticated with digest authentication:
+Administrator that has authenticated with basic authentication:
 
 .Request
 ----
   GET /a/accounts/self/capabilities HTTP/1.0
-  Authorization: Digest username="admin", realm="Gerrit Code Review", nonce="...
+  Authorization: Basic ABCDECF..
 ----
 
 .Response
@@ -1075,7 +1050,7 @@
 .Request
 ----
   GET /a/accounts/self/capabilities?q=createAccount&q=createGroup HTTP/1.0
-  Authorization: Digest username="admin", realm="Gerrit Code Review", nonce="...
+  Authorization: Basic ABCDEF...
 ----
 
 .Response
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index cd4f745..fd35353 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -470,9 +470,9 @@
 E.g. this could be used to flush all caches:
 +
 ----
-  for c in $(curl --digest --user jdoe:TNAuLkXsIV7w http://gerrit/a/config/server/caches/?format=TEXT_LIST | base64 -D)
+  for c in $(curl --user jdoe:TNAuLkXsIV7w http://gerrit/a/config/server/caches/?format=TEXT_LIST | base64 -D)
   do
-    curl --digest --user jdoe:TNAuLkXsIV7w -X POST http://gerrit/a/config/server/caches/$c/flush
+    curl --user jdoe:TNAuLkXsIV7w -X POST http://gerrit/a/config/server/caches/$c/flush
   done
 ----
 
@@ -1270,11 +1270,6 @@
 The link:config-gerrit.html#auth.httpPasswordUrl[URL to obtain an HTTP
 password]. Only set if link:config-gerrit.html#auth.type[authentication
 type] is `CUSTOM_EXTENSION`.
-|`is_git_basic_auth`          |optional, not set if `false`|
-Whether link:config-gerrit.html#auth.gitBasicAuth[basic authentication
-is used for Git over HTTP/HTTPS]. Only set if
-link:config-gerrit.html#auth.type[authentication type] is is `LDAP` or
-`LDAP_BIND`.
 |`git_basic_auth_policy`      |optional|
 The link:config-gerrit.html#auth.gitBasicAuthPolicy[policy] to authenticate
 Git over HTTP and REST API requests when
diff --git a/Documentation/rest-api-plugins.txt b/Documentation/rest-api-plugins.txt
index dfe9f0e..ce0fdb7 100644
--- a/Documentation/rest-api-plugins.txt
+++ b/Documentation/rest-api-plugins.txt
@@ -87,7 +87,7 @@
 following curl command can be used:
 
 ----
-  curl --digest --user admin:TNNuLkWsIV8w -X PUT --data-binary @delete-project-2.8.jar 'http://gerrit:8080/a/plugins/delete-project'
+  curl --user admin:TNNuLkWsIV8w -X PUT --data-binary @delete-project-2.8.jar 'http://gerrit:8080/a/plugins/delete-project'
 ----
 
 As response a link:#plugin-info[PluginInfo] entity is returned that
diff --git a/Documentation/rest-api.txt b/Documentation/rest-api.txt
index 7f7e62e..7928512 100644
--- a/Documentation/rest-api.txt
+++ b/Documentation/rest-api.txt
@@ -36,10 +36,8 @@
 `/a/`. For example to authenticate to `/projects/`, request the URL
 `/a/projects/`.
 
-By default Gerrit uses HTTP digest authentication with the HTTP password
-from the user's account settings page. HTTP basic authentication is used
-if link:config-gerrit.html#auth.gitBasicAuth[`auth.gitBasicAuth`] is set
-to true in the Gerrit configuration.
+Gerrit uses HTTP basic authentication with the HTTP password from the
+user's account settings page.
 
 [[preconditions]]
 === Preconditions
diff --git a/Documentation/user-upload.txt b/Documentation/user-upload.txt
index 6d0c47a..9efbb21 100644
--- a/Documentation/user-upload.txt
+++ b/Documentation/user-upload.txt
@@ -18,10 +18,9 @@
 On Gerrit installations that do not support SSH authentication, the
 user must authenticate via HTTP/HTTPS.
 
-When link:config-gerrit.html#auth.gitBasicAuth[gitBasicAuth] is enabled,
-the user is authenticated using standard BasicAuth. Depending on the value of
-link:#auth.gitBasicAuthPolicy[auth.gitBasicAuthPolicy], credentials are
-validated using:
+The user is authenticated using standard BasicAuth. Depending on the
+value of link:#auth.gitBasicAuthPolicy[auth.gitBasicAuthPolicy],
+credentials are validated using:
 
 * The randomly generated HTTP password on the `HTTP Password` tab
   in the user settings page if `gitBasicAuthPolicy` is `HTTP`.
@@ -29,9 +28,10 @@
 * Both, the HTTP and the LDAP passwords (in this order) if `gitBasicAuthPolicy`
   is `HTTP_LDAP`.
 
-When gitBasicAuthPolicy is not `LDAP`, the user's HTTP credentials can be
-accessed within Gerrit by going to `Settings`, and then accessing the `HTTP
-Password` tab.
+When gitBasicAuthPolicy is not `LDAP`, the user's HTTP credentials can
+be regenerated by going to `Settings`, and then accessing the `HTTP
+Password` tab. Revocation can effectively be done by regenerating the
+password and then forgetting it.
 
 For Gerrit installations where an link:config-gerrit.html#auth.httpPasswordUrl[HTTP password URL]
 is configured, the password can be obtained by clicking on `Obtain Password`
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AccountCreator.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
index de0c430..a154e15 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
@@ -27,6 +27,7 @@
 import com.google.gerrit.server.account.AccountByEmailCache;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.HashedPassword;
 import com.google.gerrit.server.account.VersionedAuthorizedKeys;
 import com.google.gerrit.server.index.account.AccountIndexer;
 import com.google.gerrit.server.ssh.SshKeyCache;
@@ -87,7 +88,8 @@
           new AccountExternalId(
               id, new AccountExternalId.Key(AccountExternalId.SCHEME_USERNAME, username));
       String httpPass = "http-pass";
-      extUser.setPassword(httpPass);
+      extUser.setPassword(HashedPassword.fromPassword(httpPass).encode());
+
       db.accountExternalIds().insert(Collections.singleton(extUser));
 
       if (email != null) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
index a4e4437..f51bbf5 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
@@ -88,7 +88,6 @@
     assertThat(i.auth.registerText).isNull();
     assertThat(i.auth.editFullNameUrl).isNull();
     assertThat(i.auth.httpPasswordUrl).isNull();
-    assertThat(i.auth.isGitBasicAuth).isNull();
 
     // change
     assertThat(i.change.allowDrafts).isNull();
@@ -163,7 +162,6 @@
     assertThat(i.auth.registerText).isNull();
     assertThat(i.auth.editFullNameUrl).isNull();
     assertThat(i.auth.httpPasswordUrl).isNull();
-    assertThat(i.auth.isGitBasicAuth).isNull();
 
     // change
     assertThat(i.change.allowDrafts).isTrue();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AuthInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AuthInfo.java
index 9780dd7..79c2250 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AuthInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AuthInfo.java
@@ -31,6 +31,5 @@
   public String registerText;
   public String editFullNameUrl;
   public String httpPasswordUrl;
-  public Boolean isGitBasicAuth;
   public GitBasicAuthPolicy gitBasicAuthPolicy;
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java
index 4f97783..3be9a12 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java
@@ -42,14 +42,10 @@
     Class<? extends Filter> authFilter;
     if (authConfig.isTrustContainerAuth()) {
       authFilter = ContainerAuthFilter.class;
-    } else if (authConfig.isGitBasicAuth()) {
-      if (authConfig.getAuthType() == OAUTH) {
-        authFilter = ProjectOAuthFilter.class;
-      } else {
-        authFilter = ProjectBasicAuthFilter.class;
-      }
+    } else if (authConfig.getAuthType() == OAUTH) {
+      authFilter = ProjectOAuthFilter.class;
     } else {
-      authFilter = ProjectDigestFilter.class;
+      authFilter = ProjectBasicAuthFilter.class;
     }
 
     if (isHttpEnabled()) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
index 88f9b4c..57ec9c5 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
@@ -140,7 +140,7 @@
     GitBasicAuthPolicy gitBasicAuthPolicy = authConfig.getGitBasicAuthPolicy();
     if (gitBasicAuthPolicy == GitBasicAuthPolicy.HTTP
         || gitBasicAuthPolicy == GitBasicAuthPolicy.HTTP_LDAP) {
-      if (passwordMatchesTheUserGeneratedOne(who, username, password)) {
+      if (who.checkPassword(password, username)) {
         return succeedAuthentication(who);
       }
     }
@@ -157,7 +157,7 @@
       setUserIdentified(whoAuthResult.getAccountId());
       return true;
     } catch (NoSuchUserException e) {
-      if (password.equals(who.getPassword(who.getUserName()))) {
+      if (who.checkPassword(password, who.getUserName())) {
         return succeedAuthentication(who);
       }
       log.warn("Authentication failed for " + username, e);
@@ -193,12 +193,6 @@
     ws.setAccessPathOk(AccessPath.REST_API, true);
   }
 
-  private boolean passwordMatchesTheUserGeneratedOne(
-      AccountState who, String username, String password) {
-    String accountPassword = who.getPassword(username);
-    return accountPassword != null && password != null && accountPassword.equals(password);
-  }
-
   private String encoding(HttpServletRequest req) {
     return MoreObjects.firstNonNull(req.getCharacterEncoding(), UTF_8.name());
   }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
deleted file mode 100644
index 7da8cda..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
+++ /dev/null
@@ -1,337 +0,0 @@
-// Copyright (C) 2010 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.concurrent.TimeUnit.HOURS;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
-import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
-import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
-
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.gerrit.server.AccessPath;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.config.CanonicalWebUrl;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gwtjsonrpc.server.SignedToken;
-import com.google.gwtjsonrpc.server.XsrfException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import java.io.IOException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletResponseWrapper;
-import org.eclipse.jgit.lib.Config;
-
-/**
- * Authenticates the current user by HTTP digest authentication.
- *
- * <p>The current HTTP request is authenticated by looking up the username from the Authorization
- * header and checking the digest response against the stored password. This filter is intended only
- * to protect the {@link GitOverHttpServlet} and its handled URLs, which provide remote repository
- * access over HTTP.
- *
- * @see <a href="http://www.ietf.org/rfc/rfc2617.txt">RFC 2617</a>
- */
-@Singleton
-class ProjectDigestFilter implements Filter {
-  public static final String REALM_NAME = "Gerrit Code Review";
-  private static final String AUTHORIZATION = "Authorization";
-
-  private final Provider<String> urlProvider;
-  private final DynamicItem<WebSession> session;
-  private final AccountCache accountCache;
-  private final Config config;
-  private final SignedToken tokens;
-  private ServletContext context;
-
-  @Inject
-  ProjectDigestFilter(
-      @CanonicalWebUrl @Nullable Provider<String> urlProvider,
-      DynamicItem<WebSession> session,
-      AccountCache accountCache,
-      @GerritServerConfig Config config)
-      throws XsrfException {
-    this.urlProvider = urlProvider;
-    this.session = session;
-    this.accountCache = accountCache;
-    this.config = config;
-    this.tokens = new SignedToken((int) SECONDS.convert(1, HOURS));
-  }
-
-  @Override
-  public void init(FilterConfig config) {
-    context = config.getServletContext();
-  }
-
-  @Override
-  public void destroy() {}
-
-  @Override
-  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
-      throws IOException, ServletException {
-    HttpServletRequest req = (HttpServletRequest) request;
-    Response rsp = new Response(req, (HttpServletResponse) response);
-
-    if (verify(req, rsp)) {
-      chain.doFilter(req, rsp);
-    }
-  }
-
-  private boolean verify(HttpServletRequest req, Response rsp) throws IOException {
-    final String hdr = req.getHeader(AUTHORIZATION);
-    if (hdr == null || !hdr.startsWith("Digest ")) {
-      // Allow an anonymous connection through, or it might be using a
-      // session cookie instead of digest authentication.
-      return true;
-    }
-
-    final Map<String, String> p = parseAuthorization(hdr);
-    final String user = p.get("username");
-    final String realm = p.get("realm");
-    final String nonce = p.get("nonce");
-    final String uri = p.get("uri");
-    final String response = p.get("response");
-    final String qop = p.get("qop");
-    final String nc = p.get("nc");
-    final String cnonce = p.get("cnonce");
-    final String method = req.getMethod();
-
-    if (user == null //
-        || realm == null //
-        || nonce == null //
-        || uri == null //
-        || response == null //
-        || !"auth".equals(qop) //
-        || !REALM_NAME.equals(realm)) {
-      context.log("Invalid header: " + AUTHORIZATION + ": " + hdr);
-      rsp.sendError(SC_FORBIDDEN);
-      return false;
-    }
-
-    String username = user;
-    if (config.getBoolean("auth", "userNameToLowerCase", false)) {
-      username = username.toLowerCase(Locale.US);
-    }
-
-    final AccountState who = accountCache.getByUsername(username);
-    if (who == null || !who.getAccount().isActive()) {
-      rsp.sendError(SC_UNAUTHORIZED);
-      return false;
-    }
-
-    final String passwd = who.getPassword(username);
-    if (passwd == null) {
-      rsp.sendError(SC_UNAUTHORIZED);
-      return false;
-    }
-
-    final String A1 = user + ":" + realm + ":" + passwd;
-    final String A2 = method + ":" + uri;
-    final String expect = KD(H(A1), nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + H(A2));
-
-    if (expect.equals(response)) {
-      try {
-        if (tokens.checkToken(nonce, "") != null) {
-          WebSession ws = session.get();
-          ws.setUserAccountId(who.getAccount().getId());
-          ws.setAccessPathOk(AccessPath.GIT, true);
-          ws.setAccessPathOk(AccessPath.REST_API, true);
-          return true;
-        }
-        rsp.stale = true;
-        rsp.sendError(SC_UNAUTHORIZED);
-        return false;
-      } catch (XsrfException e) {
-        context.log("Error validating nonce for digest authentication", e);
-        rsp.sendError(SC_INTERNAL_SERVER_ERROR);
-        return false;
-      }
-    }
-    rsp.sendError(SC_UNAUTHORIZED);
-    return false;
-  }
-
-  private static String H(String data) {
-    MessageDigest md = newMD5();
-    md.update(data.getBytes(UTF_8));
-    return LHEX(md.digest());
-  }
-
-  private static String KD(String secret, String data) {
-    MessageDigest md = newMD5();
-    md.update(secret.getBytes(UTF_8));
-    md.update((byte) ':');
-    md.update(data.getBytes(UTF_8));
-    return LHEX(md.digest());
-  }
-
-  private static MessageDigest newMD5() {
-    try {
-      return MessageDigest.getInstance("MD5");
-    } catch (NoSuchAlgorithmException e) {
-      throw new RuntimeException("No MD5 available", e);
-    }
-  }
-
-  private static final char[] LHEX = {
-    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //
-    'a', 'b', 'c', 'd', 'e', 'f',
-  };
-
-  private static String LHEX(byte[] bin) {
-    StringBuilder r = new StringBuilder(bin.length * 2);
-    for (byte b : bin) {
-      r.append(LHEX[(b >>> 4) & 0x0f]);
-      r.append(LHEX[b & 0x0f]);
-    }
-    return r.toString();
-  }
-
-  private Map<String, String> parseAuthorization(String auth) {
-    Map<String, String> p = new HashMap<>();
-    int next = "Digest ".length();
-    while (next < auth.length()) {
-      if (next < auth.length() && auth.charAt(next) == ',') {
-        next++;
-      }
-      while (next < auth.length() && Character.isWhitespace(auth.charAt(next))) {
-        next++;
-      }
-
-      int eq = auth.indexOf('=', next);
-      if (eq < 0 || eq + 1 == auth.length()) {
-        return Collections.emptyMap();
-      }
-
-      final String name = auth.substring(next, eq);
-      final String value;
-      if (auth.charAt(eq + 1) == '"') {
-        int dq = auth.indexOf('"', eq + 2);
-        if (dq < 0) {
-          return Collections.emptyMap();
-        }
-        value = auth.substring(eq + 2, dq);
-        next = dq + 1;
-
-      } else {
-        int space = auth.indexOf(' ', eq + 1);
-        int comma = auth.indexOf(',', eq + 1);
-        if (space < 0) {
-          space = auth.length();
-        }
-        if (comma < 0) {
-          comma = auth.length();
-        }
-
-        final int e = Math.min(space, comma);
-        value = auth.substring(eq + 1, e);
-        next = e + 1;
-      }
-      p.put(name, value);
-    }
-    return p;
-  }
-
-  private String newNonce() {
-    try {
-      return tokens.newToken("");
-    } catch (XsrfException e) {
-      throw new RuntimeException("Cannot generate new nonce", e);
-    }
-  }
-
-  class Response extends HttpServletResponseWrapper {
-    private static final String WWW_AUTHENTICATE = "WWW-Authenticate";
-    private final HttpServletRequest req;
-    Boolean stale;
-
-    Response(HttpServletRequest req, HttpServletResponse rsp) {
-      super(rsp);
-      this.req = req;
-    }
-
-    private void status(int sc) {
-      if (sc == SC_UNAUTHORIZED) {
-        StringBuilder v = new StringBuilder();
-        v.append("Digest");
-        v.append(" realm=\"").append(REALM_NAME).append("\"");
-
-        String url = urlProvider.get();
-        if (url == null) {
-          url = req.getContextPath();
-          if (url != null && !url.isEmpty() && !url.endsWith("/")) {
-            url += "/";
-          }
-        }
-        if (url != null && !url.isEmpty()) {
-          v.append(", domain=\"").append(url).append("\"");
-        }
-
-        v.append(", qop=\"auth\"");
-        if (stale != null) {
-          v.append(", stale=").append(stale);
-        }
-        v.append(", nonce=\"").append(newNonce()).append("\"");
-        setHeader(WWW_AUTHENTICATE, v.toString());
-
-      } else if (containsHeader(WWW_AUTHENTICATE)) {
-        setHeader(WWW_AUTHENTICATE, null);
-      }
-    }
-
-    @Override
-    public void sendError(int sc, String msg) throws IOException {
-      status(sc);
-      super.sendError(sc, msg);
-    }
-
-    @Override
-    public void sendError(int sc) throws IOException {
-      status(sc);
-      super.sendError(sc);
-    }
-
-    @Override
-    @Deprecated
-    public void setStatus(int sc, String sm) {
-      status(sc);
-      super.setStatus(sc, sm);
-    }
-
-    @Override
-    public void setStatus(int sc) {
-      status(sc);
-      super.setStatus(sc);
-    }
-  }
-}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java
index 2fe4ec3..168ab1e 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java
@@ -30,6 +30,7 @@
 import com.google.gerrit.reviewdb.client.AccountSshKey;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.HashedPassword;
 import com.google.gerrit.server.index.account.AccountIndex;
 import com.google.gerrit.server.index.account.AccountIndexCollection;
 import com.google.gwtorm.server.SchemaFactory;
@@ -95,7 +96,7 @@
               new AccountExternalId(
                   id, new AccountExternalId.Key(AccountExternalId.SCHEME_USERNAME, username));
           if (!Strings.isNullOrEmpty(httpPassword)) {
-            extUser.setPassword(httpPassword);
+            extUser.setPassword(HashedPassword.fromPassword(httpPassword).encode());
           }
           extIds.add(extUser);
           db.accountExternalIds().insert(Collections.singleton(extUser));
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java
index a789580..ac2b799 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java
@@ -87,6 +87,8 @@
   @Column(id = 3, notNull = false)
   protected String emailAddress;
 
+  // Encoded version of the hashed and salted password, to be interpreted by the
+  // {@link HashedPassword} class.
   @Column(id = 4, notNull = false)
   protected String password;
 
@@ -140,12 +142,12 @@
     return null != scheme ? getExternalId().substring(scheme.length() + 1) : null;
   }
 
-  public String getPassword() {
-    return password;
+  public void setPassword(String hashed) {
+    password = hashed;
   }
 
-  public void setPassword(String p) {
-    password = p;
+  public String getPassword() {
+    return password;
   }
 
   public boolean isTrusted() {
diff --git a/gerrit-server/BUILD b/gerrit-server/BUILD
index 578c2e1..e81b0ec 100644
--- a/gerrit-server/BUILD
+++ b/gerrit-server/BUILD
@@ -230,9 +230,11 @@
         "//lib:guava",
         "//lib:guava-retrying",
         "//lib:protobuf",
+        "//lib/bouncycastle:bcprov",
         "//lib/dropwizard:dropwizard-core",
         "//lib/guice:guice-assistedinject",
         "//lib/prolog:runtime",
+        "//lib/commons:codec",
     ],
 )
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
index b811c84..5a391a4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
@@ -18,6 +18,7 @@
 import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_USERNAME;
 
 import com.google.common.base.Function;
+import com.google.common.base.Strings;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
 import com.google.gerrit.common.Nullable;
@@ -32,8 +33,13 @@
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
+import org.apache.commons.codec.DecoderException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class AccountState {
+  private static final Logger logger = LoggerFactory.getLogger(AccountState.class);
+
   public static final Function<AccountState, Account.Id> ACCOUNT_ID_FUNCTION =
       a -> a.getAccount().getId();
 
@@ -70,14 +76,28 @@
     return account.getUserName();
   }
 
-  /** @return the password matching the requested username; or null. */
-  public String getPassword(String username) {
+  public boolean checkPassword(String password, String username) {
+    if (password == null) {
+      return false;
+    }
     for (AccountExternalId id : getExternalIds()) {
-      if (id.isScheme(AccountExternalId.SCHEME_USERNAME) && username.equals(id.getSchemeRest())) {
-        return id.getPassword();
+      // Only process the "username:$USER" entry, which is unique.
+      if (!id.isScheme(AccountExternalId.SCHEME_USERNAME) || !username.equals(id.getSchemeRest())) {
+        continue;
+      }
+
+      String hashedStr = id.getPassword();
+      if (!Strings.isNullOrEmpty(hashedStr)) {
+        try {
+          return HashedPassword.decode(hashedStr).checkPassword(password);
+        } catch (DecoderException e) {
+          logger.error(
+              String.format("DecoderException for user %s: %s ", username, e.getMessage()));
+          return false;
+        }
       }
     }
-    return null;
+    return false;
   }
 
   /** The external identities that identify the account holder. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
index 45f9183..7f8c4ec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
@@ -125,7 +125,7 @@
             id, new AccountExternalId.Key(AccountExternalId.SCHEME_USERNAME, username));
 
     if (input.httpPassword != null) {
-      extUser.setPassword(input.httpPassword);
+      extUser.setPassword(HashedPassword.fromPassword(input.httpPassword).encode());
     }
 
     if (db.accountExternalIds().get(extUser.getKey()) != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetHttpPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetHttpPassword.java
deleted file mode 100644
index 135cdf6..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetHttpPassword.java
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.account;
-
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.CurrentUser;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-
-@Singleton
-public class GetHttpPassword implements RestReadView<AccountResource> {
-
-  private final Provider<CurrentUser> self;
-
-  @Inject
-  GetHttpPassword(Provider<CurrentUser> self) {
-    this.self = self;
-  }
-
-  @Override
-  public String apply(AccountResource rsrc) throws AuthException, ResourceNotFoundException {
-    if (self.get() != rsrc.getUser() && !self.get().getCapabilities().canAdministrateServer()) {
-      throw new AuthException("not allowed to get http password");
-    }
-    AccountState s = rsrc.getUser().state();
-    if (s.getUserName() == null) {
-      throw new ResourceNotFoundException();
-    }
-    String p = s.getPassword(s.getUserName());
-    if (p == null) {
-      throw new ResourceNotFoundException();
-    }
-    return p;
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/HashedPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/HashedPassword.java
new file mode 100644
index 0000000..0323f4e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/HashedPassword.java
@@ -0,0 +1,116 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import com.google.common.base.Preconditions;
+import com.google.common.io.BaseEncoding;
+import com.google.common.primitives.Ints;
+import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
+import org.apache.commons.codec.DecoderException;
+import org.bouncycastle.crypto.generators.BCrypt;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * Holds logic for salted, hashed passwords. It uses BCrypt from BouncyCastle, which truncates
+ * passwords at 72 bytes.
+ */
+public class HashedPassword {
+  private static final String ALGORITHM_PREFIX = "bcrypt:";
+  private static final SecureRandom secureRandom = new SecureRandom();
+  private static final BaseEncoding codec = BaseEncoding.base64();
+
+  // bcrypt uses 2^cost rounds. Since we use a generated random password, no need
+  // for a high cost.
+  private static final int DEFAULT_COST = 4;
+
+  /**
+   * decodes a hashed password encoded with {@link #encode}.
+   *
+   * @throws DecoderException if input is malformed.
+   */
+  public static HashedPassword decode(String encoded) throws DecoderException {
+    if (!encoded.startsWith(ALGORITHM_PREFIX)) {
+      throw new DecoderException("unrecognized algorithm");
+    }
+
+    String[] fields = encoded.split(":");
+    if (fields.length != 4) {
+      throw new DecoderException("want 4 fields");
+    }
+
+    Integer cost = Ints.tryParse(fields[1]);
+    if (cost == null) {
+      throw new DecoderException("cost parse failed");
+    }
+
+    if (!(cost >= 4 && cost < 32)) {
+      throw new DecoderException("cost should be 4..31 inclusive, got " + cost);
+    }
+
+    byte[] salt = codec.decode(fields[2]);
+    if (salt.length != 16) {
+      throw new DecoderException("salt should be 16 bytes, got " + salt.length);
+    }
+    return new HashedPassword(codec.decode(fields[3]), salt, cost);
+  }
+
+  private static byte[] hashPassword(String password, byte[] salt, int cost) {
+    byte[] pwBytes = password.getBytes(StandardCharsets.UTF_8);
+
+    return BCrypt.generate(pwBytes, salt, cost);
+  }
+
+  public static HashedPassword fromPassword(String password) {
+    byte[] salt = newSalt();
+
+    return new HashedPassword(hashPassword(password, salt, DEFAULT_COST), salt, DEFAULT_COST);
+  }
+
+  private static byte[] newSalt() {
+    byte[] bytes = new byte[16];
+    secureRandom.nextBytes(bytes);
+    return bytes;
+  }
+
+  private byte[] salt;
+  private byte[] hashed;
+  private int cost;
+
+  private HashedPassword(byte[] hashed, byte[] salt, int cost) {
+    this.salt = salt;
+    this.hashed = hashed;
+    this.cost = cost;
+
+    Preconditions.checkState(cost >= 4 && cost < 32);
+
+    // salt must be 128 bit.
+    Preconditions.checkState(salt.length == 16);
+  }
+
+  /**
+   * Serialize the hashed password and its parameters for persistent storage.
+   *
+   * @return one-line string encoding the hash and salt.
+   */
+  public String encode() {
+    return ALGORITHM_PREFIX + cost + ":" + codec.encode(salt) + ":" + codec.encode(hashed);
+  }
+
+  public boolean checkPassword(String password) {
+    // Constant-time comparison, because we're paranoid.
+    return Arrays.areEqual(hashPassword(password, salt, cost), hashed);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
index 0080e34..775ce6d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
@@ -56,7 +56,6 @@
     put(EMAIL_KIND).to(PutEmail.class);
     delete(EMAIL_KIND).to(DeleteEmail.class);
     put(EMAIL_KIND, "preferred").to(PutPreferred.class);
-    get(ACCOUNT_KIND, "password.http").to(GetHttpPassword.class);
     put(ACCOUNT_KIND, "password.http").to(PutHttpPassword.class);
     delete(ACCOUNT_KIND, "password.http").to(PutHttpPassword.class);
     child(ACCOUNT_KIND, "sshkeys").to(SshKeys.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
index 311c12b..e4c0624 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
@@ -113,7 +113,8 @@
     if (id == null) {
       throw new ResourceNotFoundException();
     }
-    id.setPassword(newPassword);
+    id.setPassword(HashedPassword.fromPassword(newPassword).encode());
+
     dbProvider.get().accountExternalIds().update(Collections.singleton(id));
     accountCache.evict(user.getAccountId());
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthRequest.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthRequest.java
index c933190..71c5d26 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthRequest.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthRequest.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.auth;
 
 import com.google.gerrit.common.Nullable;
-import java.util.Objects;
 
 /** Defines an abstract request for user authentication to Gerrit. */
 public abstract class AuthRequest {
@@ -46,10 +45,4 @@
   public final String getPassword() {
     return password;
   }
-
-  public void checkPassword(String pwd) throws AuthException {
-    if (!Objects.equals(getPassword(), pwd)) {
-      throw new InvalidCredentialsException();
-    }
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/InternalAuthBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/InternalAuthBackend.java
index 3f2938f..508bf31 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/InternalAuthBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/InternalAuthBackend.java
@@ -38,6 +38,7 @@
     return "gerrit";
   }
 
+  // TODO(gerritcodereview-team): This function has no coverage.
   @Override
   public AuthUser authenticate(AuthRequest req)
       throws MissingCredentialsException, InvalidCredentialsException, UnknownUserException,
@@ -63,7 +64,9 @@
               + ": account inactive or not provisioned in Gerrit");
     }
 
-    req.checkPassword(who.getPassword(username));
+    if (!who.checkPassword(req.getPassword(), username)) {
+      throw new InvalidCredentialsException();
+    }
     return new AuthUser(AuthUser.UUID.create(username), username);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
index db7d567..cfc6cdc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
@@ -44,7 +44,6 @@
   private final boolean trustContainerAuth;
   private final boolean enableRunAs;
   private final boolean userNameToLowerCase;
-  private final boolean gitBasicAuth;
   private final boolean useContributorAgreements;
   private final String loginUrl;
   private final String loginText;
@@ -88,7 +87,6 @@
     cookieSecure = cfg.getBoolean("auth", "cookiesecure", false);
     trustContainerAuth = cfg.getBoolean("auth", "trustContainerAuth", false);
     enableRunAs = cfg.getBoolean("auth", null, "enableRunAs", true);
-    gitBasicAuth = cfg.getBoolean("auth", "gitBasicAuth", false);
     gitBasicAuthPolicy = getBasicAuthPolicy(cfg);
     useContributorAgreements = cfg.getBoolean("auth", "contributoragreements", false);
     userNameToLowerCase = cfg.getBoolean("auth", "userNameToLowerCase", false);
@@ -223,11 +221,6 @@
     return userNameToLowerCase;
   }
 
-  /** Whether git-over-http should use Gerrit basic authentication scheme. */
-  public boolean isGitBasicAuth() {
-    return gitBasicAuth;
-  }
-
   public GitBasicAuthPolicy getGitBasicAuthPolicy() {
     return gitBasicAuthPolicy;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
index 2ecbb54..2f68140 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
@@ -156,7 +156,6 @@
     info.useContributorAgreements = toBoolean(cfg.isUseContributorAgreements());
     info.editableAccountFields = new ArrayList<>(realm.getEditableFields());
     info.switchAccountUrl = cfg.getSwitchAccountUrl();
-    info.isGitBasicAuth = toBoolean(cfg.isGitBasicAuth());
     info.gitBasicAuthPolicy = cfg.getGitBasicAuthPolicy();
 
     if (info.useContributorAgreements != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index e2d16d8..a67a8a9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -35,7 +35,7 @@
 /** A version of the database schema. */
 public abstract class SchemaVersion {
   /** The current schema version. */
-  public static final Class<Schema_141> C = Schema_141.class;
+  public static final Class<Schema_142> C = Schema_142.class;
 
   public static int getBinaryVersion() {
     return guessVersion(C);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_142.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_142.java
new file mode 100644
index 0000000..df808df
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_142.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.gerrit.reviewdb.client.AccountExternalId;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.account.HashedPassword;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import java.sql.SQLException;
+import java.util.List;
+
+public class Schema_142 extends SchemaVersion {
+  @Inject
+  Schema_142(Provider<Schema_141> prior) {
+    super(prior);
+  }
+
+  @Override
+  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
+    List<AccountExternalId> newIds = db.accountExternalIds().all().toList();
+    for (AccountExternalId id : newIds) {
+      if (!id.isScheme(AccountExternalId.SCHEME_USERNAME)) {
+        continue;
+      }
+
+      String password = id.getPassword();
+      if (password != null) {
+        HashedPassword hashed = HashedPassword.fromPassword(password);
+        id.setPassword(hashed.encode());
+      }
+    }
+
+    db.accountExternalIds().upsert(newIds);
+  }
+}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/scripts/preview_submit_test.sh b/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/scripts/preview_submit_test.sh
index d76c239..0d8b97f 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/scripts/preview_submit_test.sh
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/scripts/preview_submit_test.sh
@@ -36,7 +36,7 @@
         exit 1
 fi
 
-curl --digest -u $gerrituser -w '%{http_code}' -o preview \
+curl -u $gerrituser -w '%{http_code}' -o preview \
     $server/a/changes/$changeId/revisions/current/preview_submit?format=tgz >http_code
 if ! grep 200 http_code >/dev/null
 then
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/account/HashedPasswordTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/account/HashedPasswordTest.java
new file mode 100644
index 0000000..4955c06
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/account/HashedPasswordTest.java
@@ -0,0 +1,64 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Strings;
+import org.apache.commons.codec.DecoderException;
+import org.junit.Test;
+
+public class HashedPasswordTest {
+
+  @Test
+  public void encodeOneLine() throws Exception {
+    String password = "secret";
+    HashedPassword hashed = HashedPassword.fromPassword(password);
+    assertThat(hashed.encode()).doesNotContain("\n");
+    assertThat(hashed.encode()).doesNotContain("\r");
+  }
+
+  @Test
+  public void encodeDecode() throws Exception {
+    String password = "secret";
+    HashedPassword hashed = HashedPassword.fromPassword(password);
+    HashedPassword roundtrip = HashedPassword.decode(hashed.encode());
+    assertThat(hashed.encode()).isEqualTo(roundtrip.encode());
+    assertThat(roundtrip.checkPassword(password)).isTrue();
+    assertThat(roundtrip.checkPassword("not the password")).isFalse();
+  }
+
+  @Test(expected = DecoderException.class)
+  public void invalidDecode() throws Exception {
+    HashedPassword.decode("invalid");
+  }
+
+  @Test
+  public void lengthLimit() throws Exception {
+    String password = Strings.repeat("1", 72);
+
+    // make sure it fits in varchar(255).
+    assertThat(HashedPassword.fromPassword(password).encode().length()).isLessThan(255);
+  }
+
+  @Test
+  public void basicFunctionality() throws Exception {
+    String password = "secret";
+    HashedPassword hashed = HashedPassword.fromPassword(password);
+
+    assertThat(hashed.checkPassword("false")).isFalse();
+    assertThat(hashed.checkPassword(password)).isTrue();
+  }
+}