Allow additional SSH-key formats

So far only RSA-type SSH-keys were allowed to be used for service users.
Since Gerrit and older versions of this plugin allowed other formats
as well, this could break existing installations.

This change adds a validator-class for SSH-keys that is able to validate
a wider range of SSH-key formats. The validation is far from perfect and
only meant to minimize the risk of creating unusable accounts, because
of an invalid SSH-key.

Change-Id: I3396ee449f6f88708514583a055a45c69abb4dd9
diff --git a/BUILD b/BUILD
index 36d967f..fcad0e5 100644
--- a/BUILD
+++ b/BUILD
@@ -1,8 +1,9 @@
-load("//tools/bzl:plugin.bzl", "gerrit_plugin")
+load("//tools/bzl:junit.bzl", "junit_tests")
+load("//tools/bzl:plugin.bzl", "PLUGIN_DEPS", "PLUGIN_TEST_DEPS", "gerrit_plugin")
 
 gerrit_plugin(
     name = "serviceuser",
-    srcs = glob(["src/main/java/com/googlesource/gerrit/plugins/serviceuser/**/*.java"]),
+    srcs = glob(["src/main/java/**/*.java"]),
     manifest_entries = [
         "Gerrit-PluginName: serviceuser",
         "Gerrit-Module: com.googlesource.gerrit.plugins.serviceuser.Module",
@@ -11,3 +12,15 @@
     ],
     resources = glob(["src/main/resources/**/*"]),
 )
+
+junit_tests(
+    name = "serviceuser_tests",
+    testonly = 1,
+    srcs = glob([
+        "src/test/java/**/*Test.java",
+    ]),
+    tags = ["serviceuser"],
+    deps = PLUGIN_TEST_DEPS + PLUGIN_DEPS + [
+        ":serviceuser__plugin",
+    ],
+)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUser.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUser.java
index 713ab41..80ddf1b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUser.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUser.java
@@ -47,9 +47,7 @@
 import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.serviceuser.CreateServiceUser.Input;
 import com.googlesource.gerrit.plugins.serviceuser.GetServiceUser.ServiceUserInfo;
-import java.io.BufferedReader;
 import java.io.IOException;
-import java.io.StringReader;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
@@ -142,10 +140,7 @@
       throw new BadRequestException("sshKey not set");
     }
 
-    final BufferedReader br = new BufferedReader(new StringReader(input.sshKey));
-    String line = br.readLine();
-    if (line == null
-        || !(line.equals("---- BEGIN SSH2 PUBLIC KEY ----") || line.startsWith("ssh-rsa"))) {
+    if (!SshKeyValidator.validateFormat(input.sshKey)) {
       throw new BadRequestException("sshKey invalid.");
     }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/SshKeyValidator.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/SshKeyValidator.java
new file mode 100644
index 0000000..9a95db1
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/SshKeyValidator.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2019 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.googlesource.gerrit.plugins.serviceuser;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+class SshKeyValidator {
+
+  private static final String OPENSSH_KEY_PREFIXES[] = {
+    "ssh-ed25519", "ssh-rsa", "ssh-dss", "ecdsa-sha2-"
+  };
+  private static final Pattern RFC_KEY_FORMAT_PATTERN =
+      Pattern.compile(
+          "(?s)^-{4,5}\\s?BEGIN.* PUBLIC KEY\\s?-{4,5}.+-{4,5}\\s?END.* PUBLIC KEY\\s?-{4,5}$");
+
+  static boolean validateFormat(String sshKey) {
+    if (validateRfcFormat(sshKey)) {
+      return true;
+    }
+
+    return validateOpenSshFormat(sshKey);
+  }
+
+  private static boolean validateOpenSshFormat(String sshKey) {
+    for (String prefix : OPENSSH_KEY_PREFIXES) {
+      if (sshKey.startsWith(prefix)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  private static boolean validateRfcFormat(String sshKey) {
+    Matcher matcher = RFC_KEY_FORMAT_PATTERN.matcher(sshKey);
+    return matcher.find();
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/serviceuser/SshKeyValidatorTest.java b/src/test/java/com/googlesource/gerrit/plugins/serviceuser/SshKeyValidatorTest.java
new file mode 100644
index 0000000..619ab98
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/serviceuser/SshKeyValidatorTest.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2019 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.googlesource.gerrit.plugins.serviceuser;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class SshKeyValidatorTest {
+
+  private final String[] VALID_PUBLIC_KEYS = {
+    "---- BEGIN SSH2 PUBLIC KEY ----\n"
+        + "   Comment: comment\n"
+        + "   AAAAB3NzaC1\n"
+        + "   ---- END SSH2 PUBLIC KEY ----",
+    "---- BEGIN PUBLIC KEY ----\n"
+        + "   Comment: comment\n"
+        + "   AAAAB3NzaC1\n"
+        + "   ---- END PUBLIC KEY ----",
+    "-----BEGIN RSA PUBLIC KEY-----\nMIIBC\n-----END RSA PUBLIC KEY-----",
+    "ssh-rsa AAAAB3NzaC1",
+    "ssh-dss AAAAB3NzaC1",
+    "ssh-ed25519 AAAAB3NzaC1",
+    "ecdsa-sha2-nistp256 AAAAB3NzaC1"
+  };
+
+  private final String[] INVALID_PUBLIC_KEYS = {
+    "---- BEGIN SSH2 PUBLIC KEY ----\n   Comment: comment\n   AAAAB3NzaC1\n",
+    "-----BEGIN PRIVATE KEY-----\nMIIBC\n-----END PRIVATE KEY-----",
+    "AAAAB3NzaC1\n   ---- END SSH2 PUBLIC KEY ----",
+    "",
+    "invalid key"
+  };
+
+  @Test
+  public void testValidateSshKeyFormat_Valid() {
+    for (String keyToTest : VALID_PUBLIC_KEYS) {
+      assertThat(SshKeyValidator.validateFormat(keyToTest)).isTrue();
+    }
+  }
+
+  @Test
+  public void testValidateSshKeyFormat_Invalid() {
+    for (String keyToTest : INVALID_PUBLIC_KEYS) {
+      assertThat(SshKeyValidator.validateFormat(keyToTest)).isFalse();
+    }
+  }
+}
diff --git a/tools/bzl/junit.bzl b/tools/bzl/junit.bzl
new file mode 100644
index 0000000..240c448
--- /dev/null
+++ b/tools/bzl/junit.bzl
@@ -0,0 +1,5 @@
+load(
+    "@com_googlesource_gerrit_bazlets//tools:junit.bzl",
+    _junit_tests = "junit_tests",
+)
+junit_tests = _junit_tests
diff --git a/tools/bzl/plugin.bzl b/tools/bzl/plugin.bzl
index 89a1643..4d2dbdd 100644
--- a/tools/bzl/plugin.bzl
+++ b/tools/bzl/plugin.bzl
@@ -2,7 +2,9 @@
     "@com_googlesource_gerrit_bazlets//:gerrit_plugin.bzl",
     _gerrit_plugin = "gerrit_plugin",
     _plugin_deps = "PLUGIN_DEPS",
+    _plugin_test_deps = "PLUGIN_TEST_DEPS",
 )
 
 gerrit_plugin = _gerrit_plugin
 PLUGIN_DEPS = _plugin_deps
+PLUGIN_TEST_DEPS = _plugin_test_deps