Extract SSH logic from AccountOperationsImpl and TestAccount

Creation of SSH keys is only needed for a few tests that test SSH (tests
annotated with @UseSsh). It’s cleaner to have the logic for creating and
managing SSH keys in a separate class.

Change-Id: Ia82d43080dd8e8abcfb23f938f65f10c57525bde
Signed-off-by: Edwin Kempin <ekempin@google.com>
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 6ff5320..d0e13c0 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -36,6 +36,7 @@
 import com.google.common.jimfs.Jimfs;
 import com.google.common.primitives.Chars;
 import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context;
+import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.ContributorAgreement;
@@ -129,7 +130,7 @@
 import com.google.inject.Inject;
 import com.google.inject.Module;
 import com.google.inject.Provider;
-import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.KeyPair;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -251,6 +252,7 @@
   @Inject protected MutableNotesMigration notesMigration;
   @Inject protected ChangeNotes.Factory notesFactory;
   @Inject protected BatchAbandon batchAbandon;
+  @Inject protected TestSshKeys sshKeys;
 
   protected EventRecorder eventRecorder;
   protected GerritServer server;
@@ -450,12 +452,13 @@
     return null;
   }
 
-  protected void initSsh() throws JSchException {
+  protected void initSsh() throws Exception {
     if (testRequiresSsh
         && SshMode.useSsh()
         && (adminSshSession == null || userSshSession == null)) {
       // Create Ssh sessions
-      GitUtil.initSsh(admin);
+      KeyPair adminKeyPair = sshKeys.getKeyPair(admin);
+      GitUtil.initSsh(adminKeyPair);
       Context ctx = newRequestContext(user);
       atrScope.set(ctx);
       userSshSession = ctx.getSession();
@@ -829,7 +832,7 @@
   private Context newRequestContext(TestAccount account) {
     return atrScope.newContext(
         reviewDbProvider,
-        new SshSession(server, account),
+        new SshSession(sshKeys, server, account),
         identifiedUserFactory.create(account.getId()));
   }
 
diff --git a/java/com/google/gerrit/acceptance/AccountCreator.java b/java/com/google/gerrit/acceptance/AccountCreator.java
index 08375b0..1416797 100644
--- a/java/com/google/gerrit/acceptance/AccountCreator.java
+++ b/java/com/google/gerrit/acceptance/AccountCreator.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.acceptance;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static java.nio.charset.StandardCharsets.US_ASCII;
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
@@ -27,22 +26,15 @@
 import com.google.gerrit.server.ServerInitiated;
 import com.google.gerrit.server.account.AccountsUpdate;
 import com.google.gerrit.server.account.GroupCache;
-import com.google.gerrit.server.account.VersionedAuthorizedKeys;
 import com.google.gerrit.server.account.externalids.ExternalId;
 import com.google.gerrit.server.group.InternalGroup;
 import com.google.gerrit.server.group.db.GroupsUpdate;
 import com.google.gerrit.server.group.db.InternalGroupUpdate;
-import com.google.gerrit.server.ssh.SshKeyCache;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
-import com.jcraft.jsch.JSch;
-import com.jcraft.jsch.JSchException;
-import com.jcraft.jsch.KeyPair;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -57,29 +49,20 @@
 
   private final Sequences sequences;
   private final Provider<AccountsUpdate> accountsUpdateProvider;
-  private final VersionedAuthorizedKeys.Accessor authorizedKeys;
   private final GroupCache groupCache;
   private final Provider<GroupsUpdate> groupsUpdateProvider;
-  private final SshKeyCache sshKeyCache;
-  private final boolean sshEnabled;
 
   @Inject
   AccountCreator(
       Sequences sequences,
       @ServerInitiated Provider<AccountsUpdate> accountsUpdateProvider,
-      VersionedAuthorizedKeys.Accessor authorizedKeys,
       GroupCache groupCache,
-      @ServerInitiated Provider<GroupsUpdate> groupsUpdateProvider,
-      SshKeyCache sshKeyCache,
-      @SshEnabled boolean sshEnabled) {
+      @ServerInitiated Provider<GroupsUpdate> groupsUpdateProvider) {
     accounts = new HashMap<>();
     this.sequences = sequences;
     this.accountsUpdateProvider = accountsUpdateProvider;
-    this.authorizedKeys = authorizedKeys;
     this.groupCache = groupCache;
     this.groupsUpdateProvider = groupsUpdateProvider;
-    this.sshKeyCache = sshKeyCache;
-    this.sshEnabled = sshEnabled;
   }
 
   public synchronized TestAccount create(
@@ -124,14 +107,7 @@
       }
     }
 
-    KeyPair sshKey = null;
-    if (sshEnabled && username != null) {
-      sshKey = genSshKey();
-      authorizedKeys.addKey(id, publicKey(sshKey, email));
-      sshKeyCache.evict(username);
-    }
-
-    account = new TestAccount(id, username, email, fullName, sshKey, httpPass);
+    account = new TestAccount(id, username, email, fullName, httpPass);
     if (username != null) {
       accounts.put(username, account);
     }
@@ -174,18 +150,6 @@
     accounts.values().removeIf(a -> ids.contains(a.id));
   }
 
-  public static KeyPair genSshKey() throws JSchException {
-    JSch jsch = new JSch();
-    return KeyPair.genKeyPair(jsch, KeyPair.ECDSA, 256);
-  }
-
-  public static String publicKey(KeyPair sshKey, String comment)
-      throws UnsupportedEncodingException {
-    ByteArrayOutputStream out = new ByteArrayOutputStream();
-    sshKey.writePublicKey(out, comment);
-    return out.toString(US_ASCII.name()).trim();
-  }
-
   private void addGroupMember(AccountGroup.UUID groupUuid, Account.Id accountId)
       throws OrmException, IOException, NoSuchGroupException, ConfigInvalidException {
     InternalGroupUpdate groupUpdate =
diff --git a/java/com/google/gerrit/acceptance/GitUtil.java b/java/com/google/gerrit/acceptance/GitUtil.java
index e11651f..cdfdae7 100644
--- a/java/com/google/gerrit/acceptance/GitUtil.java
+++ b/java/com/google/gerrit/acceptance/GitUtil.java
@@ -19,10 +19,12 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.primitives.Ints;
+import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
 import com.google.gerrit.common.FooterConstants;
 import com.google.gerrit.reviewdb.client.Project;
 import com.jcraft.jsch.JSch;
 import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.KeyPair;
 import com.jcraft.jsch.Session;
 import java.io.IOException;
 import java.util.List;
@@ -56,7 +58,7 @@
   private static final AtomicInteger testRepoCount = new AtomicInteger();
   private static final int TEST_REPO_WINDOW_DAYS = 2;
 
-  public static void initSsh(TestAccount a) {
+  public static void initSsh(KeyPair keyPair) {
     final Properties config = new Properties();
     config.put("StrictHostKeyChecking", "no");
     JSch.setConfig(config);
@@ -70,7 +72,8 @@
           protected void configure(Host hc, Session session) {
             try {
               final JSch jsch = getJSch(hc, FS.DETECTED);
-              jsch.addIdentity("KeyPair", a.privateKey(), a.sshKey.getPublicKeyBlob(), null);
+              jsch.addIdentity(
+                  "KeyPair", TestSshKeys.privateKey(keyPair), keyPair.getPublicKeyBlob(), null);
             } catch (JSchException e) {
               throw new RuntimeException(e);
             }
diff --git a/java/com/google/gerrit/acceptance/SshSession.java b/java/com/google/gerrit/acceptance/SshSession.java
index f7369d7..9e515ca 100644
--- a/java/com/google/gerrit/acceptance/SshSession.java
+++ b/java/com/google/gerrit/acceptance/SshSession.java
@@ -16,32 +16,34 @@
 
 import static com.google.common.base.Preconditions.checkState;
 
+import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
 import com.jcraft.jsch.ChannelExec;
 import com.jcraft.jsch.JSch;
-import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.KeyPair;
 import com.jcraft.jsch.Session;
-import java.io.IOException;
 import java.io.InputStream;
 import java.net.InetSocketAddress;
 import java.util.Scanner;
 
 public class SshSession {
+  private final TestSshKeys sshKeys;
   private final InetSocketAddress addr;
   private final TestAccount account;
   private Session session;
   private String error;
 
-  public SshSession(GerritServer server, TestAccount account) {
+  public SshSession(TestSshKeys sshKeys, GerritServer server, TestAccount account) {
+    this.sshKeys = sshKeys;
     this.addr = server.getSshdAddress();
     this.account = account;
   }
 
-  public void open() throws JSchException {
+  public void open() throws Exception {
     getSession();
   }
 
   @SuppressWarnings("resource")
-  public String exec(String command, InputStream opt) throws JSchException, IOException {
+  public String exec(String command, InputStream opt) throws Exception {
     ChannelExec channel = (ChannelExec) getSession().openChannel("exec");
     try {
       channel.setCommand(command);
@@ -60,7 +62,7 @@
     }
   }
 
-  public InputStream exec2(String command, InputStream opt) throws JSchException, IOException {
+  public InputStream exec2(String command, InputStream opt) throws Exception {
     ChannelExec channel = (ChannelExec) getSession().openChannel("exec");
     channel.setCommand(command);
     channel.setInputStream(opt);
@@ -69,7 +71,7 @@
     return in;
   }
 
-  public String exec(String command) throws JSchException, IOException {
+  public String exec(String command) throws Exception {
     return exec(command, null);
   }
 
@@ -88,10 +90,12 @@
     }
   }
 
-  private Session getSession() throws JSchException {
+  private Session getSession() throws Exception {
     if (session == null) {
+      KeyPair keyPair = sshKeys.getKeyPair(account);
       JSch jsch = new JSch();
-      jsch.addIdentity("KeyPair", account.privateKey(), account.sshKey.getPublicKeyBlob(), null);
+      jsch.addIdentity(
+          "KeyPair", TestSshKeys.privateKey(keyPair), keyPair.getPublicKeyBlob(), null);
       session =
           jsch.getSession(account.username, addr.getAddress().getHostAddress(), addr.getPort());
       session.setConfig("StrictHostKeyChecking", "no");
diff --git a/java/com/google/gerrit/acceptance/TestAccount.java b/java/com/google/gerrit/acceptance/TestAccount.java
index 3563ca1..094e8b0 100644
--- a/java/com/google/gerrit/acceptance/TestAccount.java
+++ b/java/com/google/gerrit/acceptance/TestAccount.java
@@ -19,8 +19,6 @@
 import com.google.common.net.InetAddresses;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.mail.Address;
-import com.jcraft.jsch.KeyPair;
-import java.io.ByteArrayOutputStream;
 import java.net.InetSocketAddress;
 import java.util.Arrays;
 import java.util.List;
@@ -45,31 +43,17 @@
   public final String email;
   public final Address emailAddress;
   public final String fullName;
-  public final KeyPair sshKey;
   public final String httpPassword;
 
-  TestAccount(
-      Account.Id id,
-      String username,
-      String email,
-      String fullName,
-      KeyPair sshKey,
-      String httpPassword) {
+  TestAccount(Account.Id id, String username, String email, String fullName, String httpPassword) {
     this.id = id;
     this.username = username;
     this.email = email;
     this.emailAddress = new Address(fullName, email);
     this.fullName = fullName;
-    this.sshKey = sshKey;
     this.httpPassword = httpPassword;
   }
 
-  public byte[] privateKey() {
-    ByteArrayOutputStream out = new ByteArrayOutputStream();
-    sshKey.writePrivateKey(out);
-    return out.toByteArray();
-  }
-
   public PersonIdent getIdent() {
     return new PersonIdent(fullName, email);
   }
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
index 7278feb..befc04c 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
@@ -15,9 +15,7 @@
 package com.google.gerrit.acceptance.testsuite.account;
 
 import static com.google.common.base.Preconditions.checkState;
-import static java.nio.charset.StandardCharsets.US_ASCII;
 
-import com.google.gerrit.acceptance.SshEnabled;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.Sequences;
 import com.google.gerrit.server.ServerInitiated;
@@ -25,17 +23,10 @@
 import com.google.gerrit.server.account.Accounts;
 import com.google.gerrit.server.account.AccountsUpdate;
 import com.google.gerrit.server.account.InternalAccountUpdate;
-import com.google.gerrit.server.account.VersionedAuthorizedKeys;
 import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.ssh.SshKeyCache;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.jcraft.jsch.JSch;
-import com.jcraft.jsch.JSchException;
-import com.jcraft.jsch.KeyPair;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
 import java.util.Optional;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 
@@ -49,26 +40,13 @@
   private final Accounts accounts;
   private final AccountsUpdate accountsUpdate;
   private final Sequences seq;
-  private final SshKeyCache sshKeyCache;
-  private final VersionedAuthorizedKeys.Accessor authorizedKeys;
-  private final boolean sshEnabled;
 
   @Inject
   public AccountOperationsImpl(
-      Accounts accounts,
-      @ServerInitiated AccountsUpdate accountsUpdate,
-      Sequences seq,
-      SshKeyCache sshKeyCache,
-      VersionedAuthorizedKeys.Accessor authorizedKeys,
-      // TODO(ekempin,aliceks): Find a way not to use this config parameter here. Ideally,
-      // completely factor out SSH from this class.
-      @SshEnabled boolean sshEnabled) {
+      Accounts accounts, @ServerInitiated AccountsUpdate accountsUpdate, Sequences seq) {
     this.accounts = accounts;
     this.accountsUpdate = accountsUpdate;
     this.seq = seq;
-    this.sshKeyCache = sshKeyCache;
-    this.authorizedKeys = authorizedKeys;
-    this.sshEnabled = sshEnabled;
   }
 
   @Override
@@ -86,13 +64,7 @@
         (account, updateBuilder) ->
             fillBuilder(updateBuilder, accountCreation, account.getAccount().getId());
     AccountState createdAccount = createAccount(accountUpdater);
-
-    TestAccount.Builder builder = toTestAccount(createdAccount);
-    Optional<String> userName = createdAccount.getUserName();
-    if (sshEnabled && userName.isPresent()) {
-      addSshKeyPair(builder, createdAccount.getAccount(), userName.get());
-    }
-    return builder.build();
+    return toTestAccount(createdAccount);
   }
 
   private AccountState createAccount(AccountsUpdate.AccountUpdater accountUpdater)
@@ -112,33 +84,14 @@
     accountCreation.status().ifPresent(builder::setStatus);
   }
 
-  private void addSshKeyPair(TestAccount.Builder builder, Account account, String username)
-      throws Exception {
-    KeyPair sshKey = genSshKey();
-    authorizedKeys.addKey(account.getId(), publicKey(sshKey, account.getPreferredEmail()));
-    sshKeyCache.evict(username);
-    builder.sshKeyPair(sshKey);
-  }
-
-  private static KeyPair genSshKey() throws JSchException {
-    JSch jsch = new JSch();
-    return KeyPair.genKeyPair(jsch, KeyPair.RSA);
-  }
-
-  private static String publicKey(KeyPair sshKey, String comment)
-      throws UnsupportedEncodingException {
-    ByteArrayOutputStream out = new ByteArrayOutputStream();
-    sshKey.writePublicKey(out, comment);
-    return out.toString(US_ASCII.name()).trim();
-  }
-
-  private static TestAccount.Builder toTestAccount(AccountState accountState) {
+  private static TestAccount toTestAccount(AccountState accountState) {
     Account createdAccount = accountState.getAccount();
     return TestAccount.builder()
         .accountId(createdAccount.getId())
         .preferredEmail(Optional.ofNullable(createdAccount.getPreferredEmail()))
         .fullname(Optional.ofNullable(createdAccount.getFullName()))
-        .username(accountState.getUserName());
+        .username(accountState.getUserName())
+        .build();
   }
 
   private static InternalAccountUpdate.Builder setPreferredEmail(
@@ -175,7 +128,7 @@
               .get(accountId)
               .orElseThrow(
                   () -> new IllegalStateException("Tried to get non-existing test account"));
-      return toTestAccount(account).build();
+      return toTestAccount(account);
     }
 
     @Override
@@ -189,7 +142,7 @@
           (account, updateBuilder) -> fillBuilder(updateBuilder, accountUpdate, accountId);
       Optional<AccountState> updatedAccount = updateAccount(accountUpdater);
       checkState(updatedAccount.isPresent(), "Tried to update non-existing test account");
-      return toTestAccount(updatedAccount.get()).build();
+      return toTestAccount(updatedAccount.get());
     }
 
     private Optional<AccountState> updateAccount(AccountsUpdate.AccountUpdater accountUpdater)
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/TestAccount.java b/java/com/google/gerrit/acceptance/testsuite/account/TestAccount.java
index 83abf7a..0189847 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/TestAccount.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/TestAccount.java
@@ -16,7 +16,6 @@
 
 import com.google.auto.value.AutoValue;
 import com.google.gerrit.reviewdb.client.Account;
-import com.jcraft.jsch.KeyPair;
 import java.util.Optional;
 
 @AutoValue
@@ -29,9 +28,6 @@
 
   public abstract Optional<String> username();
 
-  // TODO(ekempin,aliceks): Factor out SSH key handling from the class.
-  public abstract Optional<KeyPair> sshKeyPair();
-
   static Builder builder() {
     return new AutoValue_TestAccount.Builder();
   }
@@ -46,8 +42,6 @@
 
     abstract Builder username(Optional<String> username);
 
-    abstract Builder sshKeyPair(KeyPair keyPair);
-
     abstract TestAccount build();
   }
 }
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/TestSshKeys.java b/java/com/google/gerrit/acceptance/testsuite/account/TestSshKeys.java
new file mode 100644
index 0000000..0cb5cf3
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/testsuite/account/TestSshKeys.java
@@ -0,0 +1,112 @@
+// Copyright (C) 2018 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.acceptance.testsuite.account;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
+import com.google.gerrit.acceptance.SshEnabled;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.account.VersionedAuthorizedKeys;
+import com.google.gerrit.server.ssh.SshKeyCache;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.KeyPair;
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Map;
+
+@Singleton
+public class TestSshKeys {
+  private final Map<String, KeyPair> sshKeyPairs;
+
+  private final VersionedAuthorizedKeys.Accessor authorizedKeys;
+  private final SshKeyCache sshKeyCache;
+  private final boolean sshEnabled;
+
+  @Inject
+  TestSshKeys(
+      VersionedAuthorizedKeys.Accessor authorizedKeys,
+      SshKeyCache sshKeyCache,
+      @SshEnabled boolean sshEnabled) {
+    this.authorizedKeys = authorizedKeys;
+    this.sshKeyCache = sshKeyCache;
+    this.sshEnabled = sshEnabled;
+    this.sshKeyPairs = new HashMap<>();
+  }
+
+  // TODO(ekempin): Remove this method when com.google.gerrit.acceptance.TestAccount is gone
+  public KeyPair getKeyPair(com.google.gerrit.acceptance.TestAccount account) throws Exception {
+    checkState(sshEnabled, "Requested SSH key pair, but SSH is disabled");
+    checkState(
+        account.username != null,
+        "Requested SSH key pair for account %s, but username is not set",
+        account.id);
+
+    String username = account.username;
+    KeyPair keyPair = sshKeyPairs.get(username);
+    if (keyPair == null) {
+      keyPair = createKeyPair(account.id, username, account.email);
+      sshKeyPairs.put(username, keyPair);
+    }
+    return keyPair;
+  }
+
+  public KeyPair getKeyPair(TestAccount account) throws Exception {
+    checkState(sshEnabled, "Requested SSH key pair, but SSH is disabled");
+    checkState(
+        account.username().isPresent(),
+        "Requested SSH key pair for account %s, but username is not set",
+        account.accountId());
+
+    String username = account.username().get();
+    KeyPair keyPair = sshKeyPairs.get(username);
+    if (keyPair == null) {
+      keyPair = createKeyPair(account.accountId(), username, account.preferredEmail().orElse(null));
+      sshKeyPairs.put(username, keyPair);
+    }
+    return keyPair;
+  }
+
+  private KeyPair createKeyPair(Account.Id accountId, String username, @Nullable String email)
+      throws Exception {
+    KeyPair keyPair = genSshKey();
+    authorizedKeys.addKey(accountId, publicKey(keyPair, email));
+    sshKeyCache.evict(username);
+    return keyPair;
+  }
+
+  public static KeyPair genSshKey() throws JSchException {
+    JSch jsch = new JSch();
+    return KeyPair.genKeyPair(jsch, KeyPair.ECDSA, 256);
+  }
+
+  public static String publicKey(KeyPair sshKey, @Nullable String comment)
+      throws UnsupportedEncodingException {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    sshKey.writePublicKey(out, comment);
+    return out.toString(US_ASCII.name()).trim();
+  }
+
+  public static byte[] privateKey(KeyPair keyPair) {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    keyPair.writePrivateKey(out);
+    return out.toByteArray();
+  }
+}
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 85e045e..e88e662 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -40,7 +40,6 @@
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSetMultimap;
 import com.google.common.collect.Iterables;
@@ -49,12 +48,12 @@
 import com.google.common.util.concurrent.AtomicLongMap;
 import com.google.common.util.concurrent.Runnables;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
 import com.google.gerrit.acceptance.GerritConfig;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.UseSsh;
 import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
+import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.AccessSection;
@@ -125,6 +124,7 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.name.Named;
+import com.jcraft.jsch.KeyPair;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -302,22 +302,6 @@
         RefUpdateCounter.projectRef(allUsers, RefNames.REFS_SEQUENCES + Sequences.NAME_ACCOUNTS));
   }
 
-  @Test
-  @UseSsh
-  public void createWithSshKeysByAccountCreator() throws Exception {
-    Account.Id accountId =
-        createByAccountCreator(3); // account creation + external ID creation + adding SSH keys
-    refUpdateCounter.assertRefUpdateFor(
-        ImmutableMap.of(
-            RefUpdateCounter.projectRef(allUsers, RefNames.refsUsers(accountId)),
-            2,
-            RefUpdateCounter.projectRef(allUsers, RefNames.REFS_EXTERNAL_IDS),
-            1,
-            RefUpdateCounter.projectRef(
-                allUsers, RefNames.REFS_SEQUENCES + Sequences.NAME_ACCOUNTS),
-            1));
-  }
-
   private Account.Id createByAccountCreator(int expectedAccountReindexCalls) throws Exception {
     String name = "foo";
     TestAccount foo = accountCreator.create(name);
@@ -1863,12 +1847,13 @@
     assertThat(info).hasSize(1);
     assertSequenceNumbers(info);
     SshKeyInfo key = info.get(0);
-    String inital = AccountCreator.publicKey(admin.sshKey, admin.email);
+    KeyPair keyPair = sshKeys.getKeyPair(admin);
+    String inital = TestSshKeys.publicKey(keyPair, admin.email);
     assertThat(key.sshPublicKey).isEqualTo(inital);
     accountIndexedCounter.assertNoReindex();
 
     // Add a new key
-    String newKey = AccountCreator.publicKey(AccountCreator.genSshKey(), admin.email);
+    String newKey = TestSshKeys.publicKey(TestSshKeys.genSshKey(), admin.email);
     gApi.accounts().self().addSshKey(newKey);
     info = gApi.accounts().self().listSshKeys();
     assertThat(info).hasSize(2);
@@ -1883,7 +1868,7 @@
     accountIndexedCounter.assertNoReindex();
 
     // Add another new key
-    String newKey2 = AccountCreator.publicKey(AccountCreator.genSshKey(), admin.email);
+    String newKey2 = TestSshKeys.publicKey(TestSshKeys.genSshKey(), admin.email);
     gApi.accounts().self().addSshKey(newKey2);
     info = gApi.accounts().self().listSshKeys();
     assertThat(info).hasSize(3);
diff --git a/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java b/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
index c4c9e87..9a5a899 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
@@ -26,8 +26,6 @@
 import com.google.gerrit.acceptance.UseSsh;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.sshd.Commands;
-import com.jcraft.jsch.JSchException;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -123,8 +121,7 @@
     testCommandExecution(SLAVE_COMMANDS);
   }
 
-  private void testCommandExecution(Map<String, List<String>> commands)
-      throws JSchException, IOException {
+  private void testCommandExecution(Map<String, List<String>> commands) throws Exception {
     for (String root : commands.keySet()) {
       for (String command : commands.get(root)) {
         // We can't assert that adminSshSession.hasError() is false, because using the --help