Skip to content

Commit

Permalink
Check fingerprints of downloaded extensions before installing
Browse files Browse the repository at this point in the history
  • Loading branch information
Mishiranu committed Dec 20, 2020
1 parent 11d6361 commit 73c214d
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 55 deletions.
51 changes: 50 additions & 1 deletion src/chan/content/ChanManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.DisplayMetrics;
import android.util.Log;
import androidx.annotation.NonNull;
Expand Down Expand Up @@ -103,7 +105,7 @@ public static ChanManager getInstance() {
return INSTANCE;
}

public static final class Fingerprints {
public static final class Fingerprints implements Parcelable {
public final Set<String> fingerprints;

public Fingerprints(Set<String> fingerprints) {
Expand Down Expand Up @@ -133,6 +135,37 @@ public String toString() {
}
return builder.toString();
}

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(fingerprints.size());
for (String fingerprint : fingerprints) {
dest.writeString(fingerprint);
}
}

public static final Creator<Fingerprints> CREATOR = new Creator<Fingerprints>() {
@Override
public Fingerprints createFromParcel(Parcel source) {
int fingerprintsSize = source.readInt();
HashSet<String> fingerprints = new HashSet<>();
for (int i = 0; i < fingerprintsSize; i++) {
String fingerprint = source.readString();
fingerprints.add(fingerprint);
}
return new Fingerprints(Collections.unmodifiableSet(fingerprints));
}

@Override
public Fingerprints[] newArray(int size) {
return new Fingerprints[size];
}
};
}

public static class ExtensionItem {
Expand Down Expand Up @@ -870,6 +903,22 @@ public Fingerprints getApplicationFingerprints() {
return applicationFingerprints;
}

public Fingerprints getFingerprints(File file) {
PackageManager packageManager = MainApplication.getInstance().getPackageManager();
PackageInfo packageInfo;
try {
packageInfo = packageManager.getPackageArchiveInfo(file.getPath(), PACKAGE_MANAGER_SIGNATURE_FLAGS);
} catch (Exception e) {
e.printStackTrace();
return null;
}
if (packageInfo == null) {
Log.e("ChanManager", "Invalid package file: " + file.getName());
return null;
}
return extractFingerprints(packageInfo);
}

Chan getFallbackChan() {
return fallbackChan;
}
Expand Down
7 changes: 5 additions & 2 deletions src/com/mishiranu/dashchan/content/UpdaterActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,15 @@ public static class Request {
public final String versionName;
public final Uri uri;
public final byte[] sha256sum;
public final ChanManager.Fingerprints checkFingerprints;

public Request(String extensionName, String versionName, Uri uri, byte[] sha256sum) {
public Request(String extensionName, String versionName, Uri uri,
byte[] sha256sum, ChanManager.Fingerprints checkFingerprints) {
this.extensionName = extensionName;
this.versionName = versionName;
this.uri = uri;
this.sha256sum = sha256sum;
this.checkFingerprints = checkFingerprints;
}
}

Expand All @@ -205,7 +208,7 @@ public static void startUpdater(List<Request> requests) {
for (Request request : requests) {
String name = request.extensionName + "-" + request.versionName + ".apk";
DownloadService.DownloadItem downloadItem = new DownloadService.DownloadItem(null,
request.uri, name, request.sha256sum);
request.uri, name, request.sha256sum, request.checkFingerprints);
if (ChanManager.EXTENSION_NAME_CLIENT.equals(request.extensionName)) {
clientDownloadItem = downloadItem;
} else {
Expand Down
40 changes: 34 additions & 6 deletions src/com/mishiranu/dashchan/content/async/ReadFileTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import android.content.Context;
import android.net.Uri;
import android.util.Log;
import chan.content.Chan;
import chan.content.ChanConfiguration;
import chan.content.ChanManager;
import chan.content.ChanPerformer;
import chan.content.ExtensionException;
import chan.content.InvalidResponseException;
Expand Down Expand Up @@ -54,6 +56,7 @@ default void onFinishDownloading(boolean success, Uri uri, DataFile file, ErrorI
private final File cachedMediaFile;
private final boolean overwrite;
private final byte[] checkSha256;
private final ChanManager.Fingerprints checkFingerprints;

private ErrorItem errorItem;

Expand All @@ -69,21 +72,22 @@ public void onProgressChange(long progress, long progressMax) {
public static ReadFileTask createCachedMediaFile(Context context, FileCallback callback, Chan chan,
Uri fromUri, File cachedMediaFile) {
DataFile toFile = DataFile.obtain(context, DataFile.Target.CACHE, cachedMediaFile.getName());
return new ReadFileTask(callback, chan, fromUri, toFile, null, true, null);
return new ReadFileTask(callback, chan, fromUri, toFile, null, true, null, null);
}

public static ReadFileTask createShared(Callback callback, Chan chan,
Uri fromUri, DataFile toFile, boolean overwrite, byte[] checkSha256) {
public static ReadFileTask createShared(Callback callback, Chan chan, Uri fromUri, DataFile toFile,
boolean overwrite, byte[] checkSha256, ChanManager.Fingerprints checkFingerprints) {
File cachedMediaFile = CacheManager.getInstance().getMediaFile(fromUri, true);
if (cachedMediaFile == null || !cachedMediaFile.exists() ||
CacheManager.getInstance().cancelCachedMediaBusy(cachedMediaFile)) {
cachedMediaFile = null;
}
return new ReadFileTask(callback, chan, fromUri, toFile, cachedMediaFile, overwrite, checkSha256);
return new ReadFileTask(callback, chan, fromUri, toFile, cachedMediaFile,
overwrite, checkSha256, checkFingerprints);
}

private ReadFileTask(Callback callback, Chan chan, Uri fromUri, DataFile toFile,
File cachedMediaFile, boolean overwrite, byte[] checkSha256) {
private ReadFileTask(Callback callback, Chan chan, Uri fromUri, DataFile toFile, File cachedMediaFile,
boolean overwrite, byte[] checkSha256, ChanManager.Fingerprints checkFingerprints) {
super(chan);
this.callback = callback;
this.chan = chan;
Expand All @@ -92,6 +96,7 @@ private ReadFileTask(Callback callback, Chan chan, Uri fromUri, DataFile toFile,
this.cachedMediaFile = cachedMediaFile;
this.overwrite = overwrite;
this.checkSha256 = checkSha256;
this.checkFingerprints = checkFingerprints;
}

@Override
Expand Down Expand Up @@ -178,6 +183,29 @@ protected Boolean run(HttpHolder holder) {
if (digest != null) {
byte[] sha256 = digest.digest();
if (!Arrays.equals(sha256, checkSha256)) {
Log.e("ReadFileTask", "SHA-256 validation failed: requested " +
Arrays.toString(checkSha256) + ", got " + Arrays.toString(sha256));
errorItem = new ErrorItem(ErrorItem.Type.INVALID_RESPONSE);
return false;
}
}
if (checkFingerprints != null) {
String errorReason = null;
File file = toFile.getFileOrUri().first;
if (file == null) {
errorReason = "not a regular file";
} else {
ChanManager.Fingerprints fingerprints = ChanManager.getInstance().getFingerprints(file);
if (fingerprints == null) {
errorReason = "invalid file";
} else {
if (!checkFingerprints.equals(fingerprints)) {
errorReason = "fingerprints do not match";
}
}
}
if (errorReason != null) {
Log.e("ReadFileTask", "Fingerprint validation failed: " + errorReason);
errorItem = new ErrorItem(ErrorItem.Type.INVALID_RESPONSE);
return false;
}
Expand Down
55 changes: 30 additions & 25 deletions src/com/mishiranu/dashchan/content/async/ReadUpdateTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,11 @@ public static class PackageItem implements Parcelable {
public final long length;
public final Uri source;
public final byte[] sha256sum;
public final ChanManager.Fingerprints fingerprints;

public PackageItem(String repository, String title, String versionName, long versionCode,
int minApiVersion, int maxApiVersion, int apiVersion, long length, Uri source, byte[] sha256sum) {
int minApiVersion, int maxApiVersion, int apiVersion, long length, Uri source,
byte[] sha256sum, ChanManager.Fingerprints fingerprints) {
this.repository = repository;
this.title = title;
this.versionName = versionName;
Expand All @@ -194,6 +196,7 @@ public PackageItem(String repository, String title, String versionName, long ver
this.length = length;
this.source = source;
this.sha256sum = sha256sum;
this.fingerprints = fingerprints;
}

@Override
Expand All @@ -213,6 +216,10 @@ public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(length);
dest.writeString(source != null ? source.toString() : null);
dest.writeByteArray(sha256sum);
dest.writeByte((byte) (fingerprints != null ? 1 : 0));
if (fingerprints != null) {
fingerprints.writeToParcel(dest, flags);
}
}

public static final Creator<PackageItem> CREATOR = new Creator<PackageItem>() {
Expand All @@ -229,8 +236,10 @@ public PackageItem createFromParcel(Parcel source) {
String sourceString = source.readString();
Uri sourceUri = sourceString != null ? Uri.parse(sourceString) : null;
byte[] sha256sum = source.createByteArray();
ChanManager.Fingerprints fingerprints = source.readByte() != 0
? ChanManager.Fingerprints.CREATOR.createFromParcel(source) : null;
return new PackageItem(repository, title, versionName, versionCode,
minVersion, maxVersion, version, length, sourceUri, sha256sum);
minVersion, maxVersion, version, length, sourceUri, sha256sum, fingerprints);
}

@Override
Expand Down Expand Up @@ -279,7 +288,7 @@ public static Uri normalizeRelativeUri(Uri base, String uriOrPath) {

private static PackageItem extractPackageItem(DataVersion dataVersion, JSONObject chanObject,
String extensionName, String repository, Uri uri, Long installedCode,
ChanManager.Fingerprints fingerprints) throws JSONException {
ChanManager.Fingerprints requireFingerprints) throws JSONException {
String title;
String versionName;
long versionCode;
Expand Down Expand Up @@ -342,28 +351,24 @@ private static PackageItem extractPackageItem(DataVersion dataVersion, JSONObjec
for (int j = 0; j < fingerprintsArray.length(); j++) {
rawFingerprints.add(fingerprintsArray.optString(j));
}
} else {
} else if (!StringUtils.isEmpty(fingerprint)) {
rawFingerprints.add(fingerprint);
}
if (requireFingerprintChecksum && rawFingerprints.isEmpty()) {
return null;
}
if (fingerprints != null) {
HashSet<String> fingerprintsSet = new HashSet<>();
for (String rawFingerprint : rawFingerprints) {
if (!StringUtils.isEmpty(rawFingerprint)) {
rawFingerprint = rawFingerprint.replaceAll("[^a-fA-F0-9]", "")
.toLowerCase(Locale.US);
if (rawFingerprint.length() == 64) {
fingerprintsSet.add(rawFingerprint);
}
HashSet<String> fingerprintsSet = new HashSet<>();
for (String rawFingerprint : rawFingerprints) {
if (!StringUtils.isEmpty(rawFingerprint)) {
rawFingerprint = rawFingerprint.replaceAll("[^a-fA-F0-9]", "").toLowerCase(Locale.US);
if (rawFingerprint.length() == 64) {
fingerprintsSet.add(rawFingerprint);
}
}
ChanManager.Fingerprints chanFingerprints = new ChanManager
.Fingerprints(fingerprintsSet);
if (!chanFingerprints.equals(fingerprints)) {
return null;
}
}
if (requireFingerprintChecksum && fingerprintsSet.isEmpty()) {
return null;
}
ChanManager.Fingerprints fingerprints = new ChanManager.Fingerprints(fingerprintsSet);
if (requireFingerprints != null && !requireFingerprints.equals(fingerprints)) {
return null;
}
if (sha256sumString != null) {
sha256sumString = sha256sumString.replaceAll("[^a-fA-F0-9]", "").toLowerCase(Locale.US);
Expand All @@ -390,10 +395,10 @@ private static PackageItem extractPackageItem(DataVersion dataVersion, JSONObjec
return null;
}
return new PackageItem(repository, title, versionName, versionCode,
minApiVersion, maxApiVersion, 0, length, sourceUri, sha256sum);
minApiVersion, maxApiVersion, 0, length, sourceUri, sha256sum, fingerprints);
} else {
return new PackageItem(repository, title, versionName, versionCode,
0, 0, apiVersion, length, sourceUri, sha256sum);
0, 0, apiVersion, length, sourceUri, sha256sum, fingerprints);
}
}

Expand Down Expand Up @@ -674,14 +679,14 @@ protected Pair<ErrorItem, UpdateDataMap> run(HttpHolder holder) {
ChanManager.EXTENSION_NAME_CLIENT, applicationTitle, new ArrayList<>());
applicationItem.packageItems.add(new PackageItem(null, null,
applicationVersionName, applicationVersionCode,
ChanManager.MIN_VERSION, ChanManager.MAX_VERSION, 0, -1, null, null));
ChanManager.MIN_VERSION, ChanManager.MAX_VERSION, 0, -1, null, null, null));
updateDataMap.put(ChanManager.EXTENSION_NAME_CLIENT, applicationItem);
for (ChanManager.ExtensionItem extensionItem : extensionItems) {
applicationItem = new ApplicationItem(extensionItem.type == ChanManager.ExtensionItem.Type.LIBRARY
? ApplicationItem.Type.LIBRARY : ApplicationItem.Type.CHAN,
extensionItem.name, extensionItem.title, new ArrayList<>());
applicationItem.packageItems.add(new PackageItem(null, null, extensionItem.versionName,
extensionItem.versionCode, 0, 0, extensionItem.apiVersion, -1, null, null));
extensionItem.versionCode, 0, 0, extensionItem.apiVersion, -1, null, null, null));
updateDataMap.put(extensionItem.name, applicationItem);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,8 @@ protected Result run() {
String iconPath = archiveName + "/" + DIRECTORY_THUMBNAILS + "/" + iconName;
htmlBuilder.addIcon(iconPath, icon.title);
if (downloadIcon && saveThumbnails) {
thumbnailsToDownload.add(new DownloadService.DownloadItem(chan.name, iconUri, iconName, null));
thumbnailsToDownload.add(new DownloadService.DownloadItem(chan.name,
iconUri, iconName, null, null));
}
}
}
Expand Down Expand Up @@ -265,11 +266,11 @@ protected Result run() {
file.width, file.height);
if (saveFiles) {
filesToDownload.add(new DownloadService.DownloadItem(chan.name,
fileUri, fileName, null));
fileUri, fileName, null, null));
}
if (saveThumbnails && thumbnailUri != null) {
thumbnailsToDownload.add(new DownloadService.DownloadItem(chan.name,
thumbnailUri, thumbnailName, null));
thumbnailUri, thumbnailName, null, null));
}
}
}
Expand Down
Loading

0 comments on commit 73c214d

Please sign in to comment.