Support SHA-256 in addition to MD5
The client will now check the SHA-256 hash of an APK if it's provided in the index but otherwise falls back to the MD5 hash.
This commit is contained in:
parent
e225731db4
commit
6950085b56
@ -22,9 +22,7 @@ import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URL;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -238,16 +236,8 @@ public class AppDetails extends ListActivity {
|
||||
PackageInfo pi = pm.getPackageInfo(appid,
|
||||
PackageManager.GET_SIGNATURES);
|
||||
mInstalledSignature = pi.signatures[0];
|
||||
MessageDigest md;
|
||||
md = MessageDigest.getInstance("MD5");
|
||||
byte[] md5sum = new byte[32];
|
||||
md.update(mInstalledSignature.toCharsString().getBytes());
|
||||
md5sum = md.digest();
|
||||
BigInteger bigInt = new BigInteger(1, md5sum);
|
||||
String md5hash = bigInt.toString(16);
|
||||
while (md5hash.length() < 32)
|
||||
md5hash = "0" + md5hash;
|
||||
mInstalledSigID = md5hash;
|
||||
Hasher hash = new Hasher("MD5", mInstalledSignature);
|
||||
mInstalledSigID = hash.getHash();
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.d("FDroid", "Failed to get installed signature");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
@ -443,9 +433,8 @@ public class AppDetails extends ListActivity {
|
||||
f = new File(localfile);
|
||||
if (f.exists()) {
|
||||
// We do - if its hash matches, we'll use it...
|
||||
Md5Handler hash = new Md5Handler();
|
||||
String calcedhash = hash.md5Calc(f);
|
||||
if (curapk.hash.equalsIgnoreCase(calcedhash)) {
|
||||
Hasher hash = new Hasher(curapk.hashType, f);
|
||||
if (hash.match(curapk.hash)) {
|
||||
apk_file = localfile;
|
||||
Log.d("FDroid", "Using cached apk at " + localfile);
|
||||
Message msg = new Message();
|
||||
@ -515,16 +504,15 @@ public class AppDetails extends ListActivity {
|
||||
msg.sendToTarget();
|
||||
return;
|
||||
}
|
||||
Md5Handler hash = new Md5Handler();
|
||||
String calcedhash = hash.md5Calc(f);
|
||||
if (curapk.hash.equalsIgnoreCase(calcedhash)) {
|
||||
Hasher hash = new Hasher(curapk.hashType, f);
|
||||
if (hash.match(curapk.hash)) {
|
||||
apk_file = localfile;
|
||||
} else {
|
||||
msg = new Message();
|
||||
msg.obj = getString(R.string.corrupt_download);
|
||||
download_error_handler.sendMessage(msg);
|
||||
Log.d("FDroid", "Downloaded file hash of "
|
||||
+ calcedhash + " did not match repo's "
|
||||
+ hash.getHash() + " did not match repo's "
|
||||
+ curapk.hash);
|
||||
// No point keeping a bad file, whether we're
|
||||
// caching or
|
||||
|
@ -171,6 +171,7 @@ public class DB {
|
||||
public int size; // Size in bytes - 0 means we don't know!
|
||||
public String server;
|
||||
public String hash;
|
||||
public String hashType;
|
||||
public int minSdkVersion; // 0 if unknown
|
||||
public CommaSeparatedList permissions; // null if empty or unknown
|
||||
public CommaSeparatedList features; // null if empty or unknown
|
||||
@ -281,9 +282,9 @@ public class DB {
|
||||
//
|
||||
private static final String[][] DB_UPGRADES = {
|
||||
|
||||
// Version 2...
|
||||
// Version 2...
|
||||
{ "alter table " + TABLE_APP + " add marketVersion text",
|
||||
"alter table " + TABLE_APP + " add marketVercode integer" },
|
||||
"alter table " + TABLE_APP + " add marketVercode integer" },
|
||||
|
||||
// Version 3...
|
||||
{ "alter table " + TABLE_APK + " add apkSource text" },
|
||||
@ -312,7 +313,11 @@ public class DB {
|
||||
"alter table " + TABLE_APK + " add features string" },
|
||||
|
||||
// Version 11...
|
||||
{ "alter table " + TABLE_APP + " add requirements string" }};
|
||||
{ "alter table " + TABLE_APP + " add requirements string" },
|
||||
|
||||
// Version 12...
|
||||
{ "alter table " + TABLE_APK + " add hashType string",
|
||||
"update " + TABLE_APK + " set hashType = 'MD5'" }};
|
||||
|
||||
private class DBHelper extends SQLiteOpenHelper {
|
||||
|
||||
@ -504,6 +509,8 @@ public class DB {
|
||||
apk.vercode = c2.getInt(c2.getColumnIndex("vercode"));
|
||||
apk.server = c2.getString(c2.getColumnIndex("server"));
|
||||
apk.hash = c2.getString(c2.getColumnIndex("hash"));
|
||||
apk.hashType = c2.getString(c2
|
||||
.getColumnIndex("hashType"));
|
||||
apk.sig = c2.getString(c2.getColumnIndex("sig"));
|
||||
apk.srcname = c2.getString(c2.getColumnIndex("srcname"));
|
||||
apk.size = c2.getInt(c2.getColumnIndex("size"));
|
||||
@ -795,6 +802,7 @@ public class DB {
|
||||
values.put("vercode", upapk.vercode);
|
||||
values.put("server", upapk.server);
|
||||
values.put("hash", upapk.hash);
|
||||
values.put("hashType", upapk.hashType);
|
||||
values.put("sig", upapk.sig);
|
||||
values.put("srcname", upapk.srcname);
|
||||
values.put("size", upapk.size);
|
||||
|
115
src/org/fdroid/fdroid/Hasher.java
Normal file
115
src/org/fdroid/fdroid/Hasher.java
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2011 Ciaran Gultnieks <ciaran@ciarang.com>
|
||||
* Copyright (C) 2011 Henrik Tunedal <tunedal@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import android.content.pm.Signature;
|
||||
|
||||
public class Hasher {
|
||||
|
||||
private MessageDigest digest;
|
||||
private File f;
|
||||
private Signature s;
|
||||
private String hashCache;
|
||||
|
||||
public Hasher(String type, File f) throws NoSuchAlgorithmException {
|
||||
init(type);
|
||||
this.f = f;
|
||||
}
|
||||
|
||||
public Hasher(String type, Signature s) throws NoSuchAlgorithmException {
|
||||
init(type);
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
private void init(String type) throws NoSuchAlgorithmException {
|
||||
try {
|
||||
digest = MessageDigest.getInstance(type);
|
||||
} catch (Exception e) {
|
||||
throw new NoSuchAlgorithmException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate hash (as lowercase hexadecimal string) for the file
|
||||
// specified in the constructor. This will return a cached value
|
||||
// on subsequent invocations, unless reset() in called. Returns
|
||||
// the empty string on failure.
|
||||
public String getHash() {
|
||||
if (hashCache != null)
|
||||
return hashCache;
|
||||
|
||||
String hash = null;
|
||||
byte[] buffer = new byte[1024];
|
||||
int read = 0;
|
||||
|
||||
try {
|
||||
InputStream is;
|
||||
if (s == null)
|
||||
is = new FileInputStream(f);
|
||||
else
|
||||
is = new ByteArrayInputStream(s.toCharsString().getBytes());
|
||||
while ((read = is.read(buffer)) > 0) {
|
||||
digest.update(buffer, 0, read);
|
||||
}
|
||||
byte[] checksum = digest.digest();
|
||||
BigInteger bigInt = new BigInteger(1, checksum);
|
||||
hash = bigInt.toString(16).toLowerCase();
|
||||
|
||||
// Add leading zeros
|
||||
int targetLength = digest.getDigestLength() * 2;
|
||||
if (hash.length() < targetLength) {
|
||||
StringBuilder sb = new StringBuilder(targetLength);
|
||||
for (int i = hash.length(); i < targetLength; i++) {
|
||||
sb.append('0');
|
||||
}
|
||||
sb.append(hash);
|
||||
hash = sb.toString();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
return hashCache = "";
|
||||
}
|
||||
|
||||
return hashCache = hash;
|
||||
}
|
||||
|
||||
// Compare the calculated hash to another string, ignoring case,
|
||||
// returning true if they are equal. The empty string and null are
|
||||
// considered non-matching.
|
||||
public boolean match(String otherHash) {
|
||||
if (hashCache == null) getHash();
|
||||
if (otherHash == null || hashCache.equals(""))
|
||||
return false;
|
||||
return hashCache.equals(otherHash.toLowerCase());
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
hashCache = null;
|
||||
digest.reset();
|
||||
}
|
||||
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class Md5Handler {
|
||||
|
||||
private MessageDigest digest;
|
||||
|
||||
public Md5Handler() {
|
||||
try {
|
||||
digest = MessageDigest.getInstance("MD5");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public String md5Calc(File f) {
|
||||
String md5hash = null;
|
||||
byte[] buffer = new byte[1024];
|
||||
int read = 0;
|
||||
|
||||
try {
|
||||
InputStream is = new FileInputStream(f);
|
||||
while ((read = is.read(buffer)) > 0) {
|
||||
digest.update(buffer, 0, read);
|
||||
}
|
||||
byte[] md5sum = digest.digest();
|
||||
BigInteger bigInt = new BigInteger(1, md5sum);
|
||||
md5hash = bigInt.toString(16);
|
||||
|
||||
// We need 32 hex digits - add leading zeros in an inefficient and
|
||||
// brute-force manner...
|
||||
while (md5hash.length() < 32)
|
||||
md5hash = "0" + md5hash;
|
||||
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
return md5hash;
|
||||
}
|
||||
|
||||
}
|
@ -56,7 +56,8 @@ public class RepoXMLHandler extends DefaultHandler {
|
||||
private DB.Apk curapk = null;
|
||||
private String curchars = null;
|
||||
|
||||
public String pubkey;
|
||||
private String pubkey;
|
||||
private String hashType;
|
||||
|
||||
public RepoXMLHandler(String srv, DB db) {
|
||||
mserver = srv;
|
||||
@ -110,7 +111,15 @@ public class RepoXMLHandler extends DefaultHandler {
|
||||
curapk.size = 0;
|
||||
}
|
||||
} else if (curel.equals("hash")) {
|
||||
curapk.hash = str;
|
||||
if (hashType == null || hashType.equals("md5")) {
|
||||
if (curapk.hash == null) {
|
||||
curapk.hash = str;
|
||||
curapk.hashType = "MD5";
|
||||
}
|
||||
} else if (hashType.equals("sha256")) {
|
||||
curapk.hash = str;
|
||||
curapk.hashType = "SHA-256";
|
||||
}
|
||||
} else if (curel.equals("sig")) {
|
||||
curapk.sig = str;
|
||||
} else if (curel.equals("srcname")) {
|
||||
@ -186,6 +195,9 @@ public class RepoXMLHandler extends DefaultHandler {
|
||||
curapk = new DB.Apk();
|
||||
curapk.id = curapp.id;
|
||||
curapk.server = mserver;
|
||||
hashType = null;
|
||||
} else if (localName == "hash" && curapk != null) {
|
||||
hashType = attributes.getValue("", "type");
|
||||
}
|
||||
curchars = null;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user