From 5443f6e419bee6b634fa0d214ee0836ebcb0a52d Mon Sep 17 00:00:00 2001 From: Kai Zimmermann Date: Tue, 10 May 2016 12:01:55 +0200 Subject: [PATCH 1/4] Fixed auth problem in device simulator. Fixed equals ignore case in sha1 check. Signed-off-by: Kai Zimmermann --- .../simulator/DeviceSimulatorUpdater.java | 81 ++++++++++++++++--- .../artifact/repository/ArtifactStore.java | 3 +- 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java index 6f640ca53..4545cf759 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java @@ -41,6 +41,7 @@ import org.eclipse.hawkbit.simulator.event.ProgressUpdate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import com.google.common.eventbus.EventBus; @@ -193,7 +194,8 @@ public class DeviceSimulatorUpdater { switch (entry.getKey()) { case HTTP: case HTTPS: - status.add(downloadUrl(entry.getValue(), targetToken, artifact.getHashes().getSha1())); + status.add(downloadUrl(entry.getValue(), targetToken, artifact.getHashes().getSha1(), + artifact.getSize())); break; default: // not supported yet @@ -202,37 +204,54 @@ public class DeviceSimulatorUpdater { }); } - private static UpdateStatus downloadUrl(final String url, final String targetToken, final String sha1Hash) { - LOGGER.debug("Downloading {}", url); + private static UpdateStatus downloadUrl(final String url, final String targetToken, final String sha1Hash, + final long size) { + LOGGER.debug("Downloading {} with token {}, expected sha1 hash {} and size {}", url, + hideTokenDetails(targetToken), sha1Hash, size); long overallread = 0; try { final CloseableHttpClient httpclient = createHttpClientThatAcceptsAllServerCerts(); final HttpGet request = new HttpGet(url); - request.addHeader("TargetToken", targetToken); + request.addHeader("Authorization", "TargetToken " + targetToken); final String sha1HashResult; try (final CloseableHttpResponse response = httpclient.execute(request)) { + + if (response.getStatusLine().getStatusCode() != HttpStatus.OK.value()) { + final String message = wrongStatusCode(url, response); + return new UpdateStatus(ResponseStatus.ERROR, message); + } + + if (response.getEntity().getContentLength() != size) { + final String message = wrongContentLength(url, size, response); + return new UpdateStatus(ResponseStatus.ERROR, message); + } + final File tempFile = File.createTempFile("uploadFile", null); final MessageDigest md = MessageDigest.getInstance("SHA-1"); try (final DigestOutputStream dos = new DigestOutputStream(new FileOutputStream(tempFile), md)) { overallread = ByteStreams.copy(response.getEntity().getContent(), dos); - sha1HashResult = BaseEncoding.base16().lowerCase().encode(md.digest()); } finally { tempFile.delete(); } + + if (overallread != size) { + final String message = incompleteRead(url, size, overallread); + return new UpdateStatus(ResponseStatus.ERROR, message); + } + + sha1HashResult = BaseEncoding.base16().lowerCase().encode(md.digest()); } - if (!sha1Hash.equals(sha1HashResult)) { - final String message = "Download " + url + " failed with SHA1 hash missmatch (Expected: " + sha1Hash - + " but got: " + sha1HashResult + ")"; - LOGGER.debug(message); + if (!sha1Hash.equalsIgnoreCase(sha1HashResult)) { + final String message = wrongHash(url, sha1Hash, overallread, sha1HashResult); return new UpdateStatus(ResponseStatus.ERROR, message); } } catch (IOException | KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) { - LOGGER.error("Failed to download {}", url, e); + LOGGER.error("Failed to download {} with {}", url, e.getMessage()); return new UpdateStatus(ResponseStatus.ERROR, "Failed to download " + url + ": " + e.getMessage()); } @@ -241,6 +260,48 @@ public class DeviceSimulatorUpdater { return new UpdateStatus(ResponseStatus.SUCCESSFUL, message); } + private static String hideTokenDetails(final String targetToken) { + if (targetToken.isEmpty()) { + return ""; + } + + if (targetToken.length() <= 3) { + return "***"; + } + + return targetToken.substring(0, 3) + "***" + + targetToken.substring(targetToken.length() - 3, targetToken.length()); + } + + private static String wrongHash(final String url, final String sha1Hash, final long overallread, + final String sha1HashResult) { + final String message = "Download " + url + " failed with SHA1 hash missmatch (Expected: " + sha1Hash + + " but got: " + sha1HashResult + ") (" + overallread + " bytes)"; + LOGGER.error(message); + return message; + } + + private static String incompleteRead(final String url, final long size, final long overallread) { + final String message = "Download " + url + " is incomplete (Expected: " + size + " but got: " + overallread + + ")"; + LOGGER.error(message); + return message; + } + + private static String wrongContentLength(final String url, final long size, + final CloseableHttpResponse response) { + final String message = "Download " + url + " has wrong content length (Expected: " + size + " but got: " + + response.getEntity().getContentLength() + ")"; + LOGGER.error(message); + return message; + } + + private static String wrongStatusCode(final String url, final CloseableHttpResponse response) { + final String message = "Download " + url + " failed (" + response.getStatusLine().getStatusCode() + ")"; + LOGGER.error(message); + return message; + } + private static CloseableHttpClient createHttpClientThatAcceptsAllServerCerts() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { final SSLContextBuilder builder = new SSLContextBuilder(); diff --git a/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactStore.java b/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactStore.java index 94b093cb3..d863f60cf 100644 --- a/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactStore.java +++ b/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactStore.java @@ -185,7 +185,8 @@ public class ArtifactStore implements ArtifactRepository { throw new ArtifactStoreException(e.getMessage(), e); } - if (hash != null && hash.getMd5() != null && !storedArtifact.getHashes().getMd5().equals(hash.getMd5())) { + if (hash != null && hash.getMd5() != null + && !storedArtifact.getHashes().getMd5().equalsIgnoreCase(hash.getMd5())) { throw new HashNotMatchException("The given md5 hash " + hash.getMd5() + " not matching the calculated md5 hash " + storedArtifact.getHashes().getMd5(), HashNotMatchException.MD5); From bc37ea9b23313b941c97c6a4cfe0277440046ae3 Mon Sep 17 00:00:00 2001 From: Kai Zimmermann Date: Tue, 10 May 2016 12:08:29 +0200 Subject: [PATCH 2/4] Improved hiding for small tokens. Signed-off-by: Kai Zimmermann --- .../eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java index 4545cf759..7e03c0f07 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java @@ -265,12 +265,12 @@ public class DeviceSimulatorUpdater { return ""; } - if (targetToken.length() <= 3) { + if (targetToken.length() <= 6) { return "***"; } - return targetToken.substring(0, 3) + "***" - + targetToken.substring(targetToken.length() - 3, targetToken.length()); + return targetToken.substring(0, 2) + "***" + + targetToken.substring(targetToken.length() - 2, targetToken.length()); } private static String wrongHash(final String url, final String sha1Hash, final long overallread, From 1236f65d9da85cbd8cacd99492c61622f26ce764 Mon Sep 17 00:00:00 2001 From: Kai Zimmermann Date: Tue, 10 May 2016 13:12:19 +0200 Subject: [PATCH 3/4] Improved sha1 gen performance by using buffered streams. Signed-off-by: Kai Zimmermann --- .../hawkbit/simulator/DeviceSimulatorUpdater.java | 12 ++++++++++-- .../artifact/repository/ArtifactStore.java | 15 +++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java index 7e03c0f07..197fcd7da 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java @@ -8,6 +8,8 @@ */ package org.eclipse.hawkbit.simulator; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -232,9 +234,15 @@ public class DeviceSimulatorUpdater { final MessageDigest md = MessageDigest.getInstance("SHA-1"); try (final DigestOutputStream dos = new DigestOutputStream(new FileOutputStream(tempFile), md)) { - overallread = ByteStreams.copy(response.getEntity().getContent(), dos); + try (final BufferedOutputStream bdos = new BufferedOutputStream(dos)) { + try (BufferedInputStream bis = new BufferedInputStream(response.getEntity().getContent())) { + overallread = ByteStreams.copy(bis, bdos); + } + } } finally { - tempFile.delete(); + if (tempFile != null && !tempFile.delete()) { + LOGGER.error("Could not delete temporary file: {}", tempFile); + } } if (overallread != size) { diff --git a/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactStore.java b/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactStore.java index d863f60cf..5f12498ff 100644 --- a/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactStore.java +++ b/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/ArtifactStore.java @@ -8,11 +8,14 @@ */ package org.eclipse.hawkbit.artifact.repository; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.security.DigestOutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -122,7 +125,11 @@ public class ArtifactStore implements ArtifactRepository { LOGGER.debug("storing file {} of content {}", filename, contentType); tempFile = File.createTempFile("uploadFile", null); try (final FileOutputStream os = new FileOutputStream(tempFile)) { - return store(content, contentType, os, tempFile, hash); + try (BufferedOutputStream bos = new BufferedOutputStream(os)) { + try (BufferedInputStream bis = new BufferedInputStream(content)) { + return store(content, contentType, bos, tempFile, hash); + } + } } } catch (final IOException | MongoException e1) { throw new ArtifactStoreException(e1.getMessage(), e1); @@ -162,7 +169,7 @@ public class ArtifactStore implements ArtifactRepository { } - private DbArtifact store(final InputStream content, final String contentType, final FileOutputStream os, + private DbArtifact store(final InputStream content, final String contentType, final OutputStream os, final File tempFile, final DbArtifactHash hash) { final GridFsArtifact storedArtifact; try { @@ -196,8 +203,8 @@ public class ArtifactStore implements ArtifactRepository { } - private static String computeSHA1Hash(final InputStream stream, final FileOutputStream os, - final String providedSHA1Sum) throws NoSuchAlgorithmException, IOException { + private static String computeSHA1Hash(final InputStream stream, final OutputStream os, final String providedSHA1Sum) + throws NoSuchAlgorithmException, IOException { String sha1Hash; // compute digest final MessageDigest md = MessageDigest.getInstance("SHA-1"); From a9283b402ab020d4103da229a96dd8bfa6d488dd Mon Sep 17 00:00:00 2001 From: Kai Zimmermann Date: Tue, 10 May 2016 13:21:53 +0200 Subject: [PATCH 4/4] Improved code readability. Signed-off-by: Kai Zimmermann --- .../hawkbit/simulator/DeviceSimulatorUpdater.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java index 197fcd7da..3704b9854 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java @@ -43,6 +43,7 @@ import org.eclipse.hawkbit.simulator.event.ProgressUpdate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; @@ -117,6 +118,11 @@ public class DeviceSimulatorUpdater { } private static final class DeviceSimulatorUpdateThread implements Runnable { + /** + * + */ + private static final int MINIMUM_TOKENLENGTH_FOR_HINT = 6; + private static final Random rndSleep = new SecureRandom(); private final AbstractSimulatedDevice device; @@ -215,7 +221,7 @@ public class DeviceSimulatorUpdater { try { final CloseableHttpClient httpclient = createHttpClientThatAcceptsAllServerCerts(); final HttpGet request = new HttpGet(url); - request.addHeader("Authorization", "TargetToken " + targetToken); + request.addHeader(HttpHeaders.AUTHORIZATION, "TargetToken " + targetToken); final String sha1HashResult; try (final CloseableHttpResponse response = httpclient.execute(request)) { @@ -273,7 +279,7 @@ public class DeviceSimulatorUpdater { return ""; } - if (targetToken.length() <= 6) { + if (targetToken.length() <= MINIMUM_TOKENLENGTH_FOR_HINT) { return "***"; }