From cfa4646161e10e6537926e454e2ec1040fc9706a Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 25 Oct 2017 07:53:33 -0400 Subject: [PATCH] Adjust SHA-512 supported format on plugin install This commit adjusts the format of the SHA-512 checksum files supported by the plugin installer. In particular, we now require that the SHA-512 format be a single-line file containing the checksum followed by two spaces followed by the filename. We continue to support the legacy format for SHA-1. Relates #27093 --- .../plugins/InstallPluginCommand.java | 39 ++++++++-- .../plugins/InstallPluginCommandTests.java | 72 +++++++++++++++---- 2 files changed, 94 insertions(+), 17 deletions(-) diff --git a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java index ec017cfffb5..7029ba048d0 100644 --- a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java +++ b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java @@ -43,6 +43,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; +import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.net.URLDecoder; @@ -385,10 +386,40 @@ class InstallPluginCommand extends EnvironmentAwareCommand { } final String expectedChecksum; try (InputStream in = checksumUrl.openStream()) { - BufferedReader checksumReader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); - expectedChecksum = checksumReader.readLine(); - if (checksumReader.readLine() != null) { - throw new UserException(ExitCodes.IO_ERROR, "Invalid checksum file at " + checksumUrl); + /* + * The supported format of the SHA-1 files is a single-line file containing the SHA-1. The supported format of the SHA-512 files + * is a single-line file containing the SHA-512 and the filename, separated by two spaces. For SHA-1, we verify that the hash + * matches, and that the file contains a single line. For SHA-512, we verify that the hash and the filename match, and that the + * file contains a single line. + */ + if (digestAlgo.equals("SHA-1")) { + final BufferedReader checksumReader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); + expectedChecksum = checksumReader.readLine(); + if (checksumReader.readLine() != null) { + throw new UserException(ExitCodes.IO_ERROR, "Invalid checksum file at " + checksumUrl); + } + } else { + final BufferedReader checksumReader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); + final String checksumLine = checksumReader.readLine(); + final String[] fields = checksumLine.split(" {2}"); + if (fields.length != 2) { + throw new UserException(ExitCodes.IO_ERROR, "Invalid checksum file at " + checksumUrl); + } + expectedChecksum = fields[0]; + final String[] segments = URI.create(urlString).getPath().split("/"); + final String expectedFile = segments[segments.length - 1]; + if (fields[1].equals(expectedFile) == false) { + final String message = String.format( + Locale.ROOT, + "checksum file at [%s] is not for this plugin, expected [%s] but was [%s]", + checksumUrl, + expectedFile, + fields[1]); + throw new UserException(ExitCodes.IO_ERROR, message); + } + if (checksumReader.readLine() != null) { + throw new UserException(ExitCodes.IO_ERROR, "Invalid checksum file at " + checksumUrl); + } } } diff --git a/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java b/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java index 103baccd2d4..f5f8cdb32ef 100644 --- a/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java +++ b/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java @@ -47,6 +47,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; @@ -77,6 +78,7 @@ import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; +import static org.elasticsearch.test.hamcrest.RegexMatcher.matches; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; @@ -799,8 +801,7 @@ public class InstallPluginCommandTests extends ESTestCase { public void assertInstallPluginFromUrl(String pluginId, String name, String url, String stagingHash) throws Exception { MessageDigest digest = MessageDigest.getInstance("SHA-512"); - assertInstallPluginFromUrl(pluginId, name, url, stagingHash, ".sha512", - bytes -> MessageDigests.toHexString(digest.digest(bytes))); + assertInstallPluginFromUrl(pluginId, name, url, stagingHash, ".sha512", checksumAndFilename(digest, url)); } public void testOfficalPlugin() throws Exception { @@ -839,8 +840,7 @@ public class InstallPluginCommandTests extends ESTestCase { public void testMavenSha1Backcompat() throws Exception { String url = "https://repo1.maven.org/maven2/mygroup/myplugin/1.0.0/myplugin-1.0.0.zip"; MessageDigest digest = MessageDigest.getInstance("SHA-1"); - MockTerminal terminal = assertInstallPluginFromUrl("mygroup:myplugin:1.0.0", "myplugin", url, null, - ".sha1", bytes -> MessageDigests.toHexString(digest.digest(bytes))); + MockTerminal terminal = assertInstallPluginFromUrl("mygroup:myplugin:1.0.0", "myplugin", url, null, ".sha1", checksum(digest)); assertTrue(terminal.getOutput(), terminal.getOutput().contains("sha512 not found, falling back to sha1")); } @@ -848,8 +848,7 @@ public class InstallPluginCommandTests extends ESTestCase { String url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Version.CURRENT + ".zip"; MessageDigest digest = MessageDigest.getInstance("SHA-1"); UserException e = expectThrows(UserException.class, () -> - assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, null, ".sha1", - bytes -> MessageDigests.toHexString(digest.digest(bytes)))); + assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, null, ".sha1", checksum(digest))); assertEquals(ExitCodes.IO_ERROR, e.exitCode); assertEquals("Plugin checksum missing: " + url + ".sha512", e.getMessage()); } @@ -862,12 +861,41 @@ public class InstallPluginCommandTests extends ESTestCase { assertEquals("Plugin checksum missing: " + url + ".sha1", e.getMessage()); } - public void testInvalidShaFile() throws Exception { + public void testInvalidShaFileMissingFilename() throws Exception { String url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Version.CURRENT + ".zip"; MessageDigest digest = MessageDigest.getInstance("SHA-512"); UserException e = expectThrows(UserException.class, () -> - assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, null, ".sha512", - bytes -> MessageDigests.toHexString(digest.digest(bytes)) + "\nfoobar")); + assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, null, ".sha512", checksum(digest))); + assertEquals(ExitCodes.IO_ERROR, e.exitCode); + assertTrue(e.getMessage(), e.getMessage().startsWith("Invalid checksum file")); + } + + public void testInvalidShaFileMismatchFilename() throws Exception { + String url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Version.CURRENT + ".zip"; + MessageDigest digest = MessageDigest.getInstance("SHA-512"); + UserException e = expectThrows(UserException.class, () -> + assertInstallPluginFromUrl( + "analysis-icu", + "analysis-icu", + url, + null, + ".sha512", + checksumAndString(digest, " repository-s3-" + Version.CURRENT + ".zip"))); + assertEquals(ExitCodes.IO_ERROR, e.exitCode); + assertThat(e, hasToString(matches("checksum file at \\[.*\\] is not for this plugin"))); + } + + public void testInvalidShaFileContainingExtraLine() throws Exception { + String url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Version.CURRENT + ".zip"; + MessageDigest digest = MessageDigest.getInstance("SHA-512"); + UserException e = expectThrows(UserException.class, () -> + assertInstallPluginFromUrl( + "analysis-icu", + "analysis-icu", + url, + null, + ".sha512", + checksumAndString(digest, " analysis-icu-" + Version.CURRENT + ".zip\nfoobar"))); assertEquals(ExitCodes.IO_ERROR, e.exitCode); assertTrue(e.getMessage(), e.getMessage().startsWith("Invalid checksum file")); } @@ -875,8 +903,13 @@ public class InstallPluginCommandTests extends ESTestCase { public void testSha512Mismatch() throws Exception { String url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Version.CURRENT + ".zip"; UserException e = expectThrows(UserException.class, () -> - assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, null, ".sha512", - bytes -> "foobar")); + assertInstallPluginFromUrl( + "analysis-icu", + "analysis-icu", + url, + null, + ".sha512", + bytes -> "foobar analysis-icu-" + Version.CURRENT + ".zip")); assertEquals(ExitCodes.IO_ERROR, e.exitCode); assertTrue(e.getMessage(), e.getMessage().contains("SHA-512 mismatch, expected foobar")); } @@ -884,8 +917,7 @@ public class InstallPluginCommandTests extends ESTestCase { public void testSha1Mismatch() throws Exception { String url = "https://repo1.maven.org/maven2/mygroup/myplugin/1.0.0/myplugin-1.0.0.zip"; UserException e = expectThrows(UserException.class, () -> - assertInstallPluginFromUrl("mygroup:myplugin:1.0.0", "myplugin", url, null, - ".sha1", bytes -> "foobar")); + assertInstallPluginFromUrl("mygroup:myplugin:1.0.0", "myplugin", url, null, ".sha1", bytes -> "foobar")); assertEquals(ExitCodes.IO_ERROR, e.exitCode); assertTrue(e.getMessage(), e.getMessage().contains("SHA-1 mismatch, expected foobar")); } @@ -917,4 +949,18 @@ public class InstallPluginCommandTests extends ESTestCase { MockTerminal terminal = installPlugin(pluginZip, env.v1()); assertTrue(Files.exists(KeyStoreWrapper.keystorePath(env.v2().configFile()))); } + + private Function checksum(final MessageDigest digest) { + return checksumAndString(digest, ""); + } + + private Function checksumAndFilename(final MessageDigest digest, final String url) throws MalformedURLException { + final String[] segments = URI.create(url).getPath().split("/"); + return checksumAndString(digest, " " + segments[segments.length - 1]); + } + + private Function checksumAndString(final MessageDigest digest, final String s) { + return bytes -> MessageDigests.toHexString(digest.digest(bytes)) + s; + } + }