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:
Henrik Tunedal 2011-03-16 20:45:25 +01:00
parent e225731db4
commit 6950085b56
5 changed files with 147 additions and 71 deletions

View File

@ -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

View File

@ -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
@ -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);

View 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();
}
}

View File

@ -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;
}
}

View File

@ -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")) {
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;
}