Skip to content

Commit

Permalink
Port hash verification from old branch to new project structure
Browse files Browse the repository at this point in the history
  • Loading branch information
booky10 committed Jul 29, 2023
1 parent 4e0309b commit e0743f9
Show file tree
Hide file tree
Showing 9 changed files with 367 additions and 154 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package dev.booky.stackdeobf.http;
// Created by booky10 in StackDeobfuscator (02:09 10.06.23)

public class FailedUrlVerificationException extends RuntimeException {

public FailedUrlVerificationException() {
}

public FailedUrlVerificationException(String message) {
super(message);
}

public FailedUrlVerificationException(String message, Throwable cause) {
super(message, cause);
}

public FailedUrlVerificationException(Throwable cause) {
super(cause);
}
}
84 changes: 56 additions & 28 deletions common/src/main/java/dev/booky/stackdeobf/http/HttpUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import java.util.WeakHashMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;

public final class HttpUtil {

Expand All @@ -23,53 +22,82 @@ public final class HttpUtil {
private HttpUtil() {
}

static boolean isSuccess(int code) {
return code >= 200 && code <= 299;
}

private static HttpClient getHttpClient(Executor executor) {
synchronized (HTTP) {
return HTTP.computeIfAbsent(executor, $ -> HttpClient.newBuilder().executor(executor).build());
}
}

public static byte[] getSync(URI uri) {
return getSync(uri, ForkJoinPool.commonPool());
public static CompletableFuture<byte[]> getAsync(URI url, Executor executor) {
HttpRequest request = HttpRequest.newBuilder(url).build();
return getAsync(request, executor);
}

public static byte[] getSync(URI uri, Executor executor) {
return getAsync(uri, executor).join();
}
public static CompletableFuture<byte[]> getAsync(HttpRequest request, Executor executor) {
return getAsyncRaw(request, executor).thenApply(resp -> {
byte[] bodyBytes = resp.getRespBody();

public static CompletableFuture<byte[]> getAsync(URI uri) {
return getAsync(uri, ForkJoinPool.commonPool());
}
String message = "Received {} bytes ({}) with status {} from {} {} in {}ms";
Object[] args = {bodyBytes.length, FileUtils.byteCountToDisplaySize(bodyBytes.length),
resp.getRespCode(), request.method(), request.uri(), resp.getDurationMs()};

public static CompletableFuture<byte[]> getAsync(URI uri, Executor executor) {
return getAsync(HttpRequest.newBuilder(uri).build(), executor);
if (!isSuccess(resp.getRespCode())) {
LOGGER.error(message, args);
throw new FailedHttpRequestException(resp.getResponse());
}
LOGGER.info(message, args);
return bodyBytes;
});
}

public static CompletableFuture<byte[]> getAsync(HttpRequest request, Executor executor) {
static CompletableFuture<RawHttpResponse> getAsyncRaw(HttpRequest request, Executor executor) {
HttpResponse.BodyHandler<byte[]> handler = HttpResponse.BodyHandlers.ofByteArray();

LOGGER.info("Requesting {}...", request.uri());
long start = System.currentTimeMillis();

return getHttpClient(executor).sendAsync(request, handler).thenApplyAsync(resp -> {
long timeDiff = System.currentTimeMillis() - start;
byte[] bodyBytes = resp.body();
return getHttpClient(executor)
.sendAsync(request, handler)
.thenApplyAsync(resp -> {
long timeDiff = System.currentTimeMillis() - start;
return new RawHttpResponse(request, resp, timeDiff);
}, executor);
}

String message = "Received {} bytes ({}) with status {} from {} in {}ms";
Object[] args = {bodyBytes.length, FileUtils.byteCountToDisplaySize(bodyBytes.length),
resp.statusCode(), request.uri(), timeDiff};
static final class RawHttpResponse {

if (!isSuccess(resp.statusCode())) {
LOGGER.error(message, args);
throw new FailedHttpRequestException(resp);
}
private final HttpRequest request;
private final HttpResponse<byte[]> response;
private final long durationMs;

LOGGER.info(message, args);
return bodyBytes;
}, executor);
}
public RawHttpResponse(HttpRequest request, HttpResponse<byte[]> response, long durationMs) {
this.request = request;
this.response = response;
this.durationMs = durationMs;
}

private static boolean isSuccess(int code) {
return code >= 200 && code <= 299;
public byte[] getRespBody() {
return this.response.body();
}

public int getRespCode() {
return this.response.statusCode();
}

public HttpRequest getRequest() {
return this.request;
}

public HttpResponse<byte[]> getResponse() {
return this.response;
}

public long getDurationMs() {
return this.durationMs;
}
}
}
125 changes: 125 additions & 0 deletions common/src/main/java/dev/booky/stackdeobf/http/VerifiableUrl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package dev.booky.stackdeobf.http;
// Created by booky10 in StackDeobfuscator (01:57 10.06.23)

import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.net.URI;
import java.net.http.HttpRequest;
import java.util.Base64;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;

public final class VerifiableUrl {

private static final Logger LOGGER = LogManager.getLogger("StackDeobfuscator");

private final URI uri;
private final HashType hashType;
private final HashCode hashCode;

public VerifiableUrl(URI uri, HashType hashType, String hash) {
this(uri, hashType, HashCode.fromString(hash));
}

public VerifiableUrl(URI uri, HashType hashType, byte[] hash) {
this(uri, hashType, HashCode.fromBytes(hash));
}

public VerifiableUrl(URI uri, HashType hashType, HashCode hashCode) {
this.uri = uri;
this.hashType = hashType;
this.hashCode = hashCode;
}

public static CompletableFuture<VerifiableUrl> resolve(URI url, HashType hashType, Executor executor) {
LOGGER.info("Downloading {} hash for {}...", hashType, url);
URI hashUrl = URI.create(url + hashType.getExtension());
return HttpUtil.getAsync(hashUrl, executor)
.thenApply(hashStrBytes -> {
// the hash file contains the hash bytes as hex, so this has to be
// converted to a string and then parsed to bytes again
String hashStr = new String(hashStrBytes);
return new VerifiableUrl(url, hashType, hashStr);
});
}

public static CompletableFuture<VerifiableUrl> resolveByMd5Header(URI url, Executor executor) {
HttpRequest request = HttpRequest.newBuilder(url)
.method("HEAD", HttpRequest.BodyPublishers.noBody())
.build();
HashType hashType = HashType.MD5;

LOGGER.info("Downloading {} hash for {}...", hashType, url);
return HttpUtil.getAsyncRaw(request, executor).thenApply(resp -> {
byte[] bodyBytes = resp.getRespBody();

String message = "Received {} bytes ({}) with status {} from {} {} in {}ms";
Object[] args = {bodyBytes.length, FileUtils.byteCountToDisplaySize(bodyBytes.length),
resp.getRespCode(), request.method(), url, resp.getDurationMs()};

if (!HttpUtil.isSuccess(resp.getRespCode())) {
LOGGER.error(message, args);
throw new FailedHttpRequestException(resp.getResponse());
}
LOGGER.info(message, args);

// sent by piston-meta server, base64-encoded md5 hash bytes of the response body
Optional<String> optMd5Hash = resp.getResponse().headers().firstValue("content-md5");
optMd5Hash.orElseThrow(() -> new FailedUrlVerificationException("No expected 'content-md5'"
+ " header found for " + url));

byte[] md5Bytes = Base64.getDecoder().decode(optMd5Hash.get());
return new VerifiableUrl(url, hashType, md5Bytes);
});
}

public CompletableFuture<byte[]> get(Executor executor) {
return HttpUtil.getAsync(this.uri, executor).thenApply(bytes -> {
HashCode hashCode = this.hashType.hash(bytes);
LOGGER.info("Verifying {} hash {} for {}...",
this.hashType, hashCode, this.uri);

if (!hashCode.equals(this.hashCode)) {
throw new FailedUrlVerificationException(this.hashType + " hash " + hashCode + " doesn't match "
+ this.hashCode + " for " + this.uri);
}
return bytes;
});
}

public URI getUrl() {
return this.uri;
}

public enum HashType {

@SuppressWarnings("deprecation")
MD5(".md5", Hashing.md5()),
@SuppressWarnings("deprecation")
SHA1(".sha1", Hashing.sha1()),
SHA256(".sha256", Hashing.sha256()),
SHA512(".sha512", Hashing.sha512());

private final String extension;
private final HashFunction hashFunction;

HashType(String extension, HashFunction hashFunction) {
this.extension = extension;
this.hashFunction = hashFunction;
}

public HashCode hash(byte[] bytes) {
return this.hashFunction.hashBytes(bytes);
}

public String getExtension() {
return this.extension;
}
}
}
Loading

0 comments on commit e0743f9

Please sign in to comment.