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..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 @@ -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; @@ -41,6 +43,8 @@ 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; import com.google.common.eventbus.EventBus; @@ -114,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; @@ -193,7 +202,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 +212,60 @@ 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(HttpHeaders.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()); + 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) { + 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 +274,48 @@ public class DeviceSimulatorUpdater { return new UpdateStatus(ResponseStatus.SUCCESSFUL, message); } + private static String hideTokenDetails(final String targetToken) { + if (targetToken.isEmpty()) { + return ""; + } + + if (targetToken.length() <= MINIMUM_TOKENLENGTH_FOR_HINT) { + return "***"; + } + + 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, + 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..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 { @@ -185,7 +192,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); @@ -195,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");