sign local repo based on key generated using spongycastle

This makes it so the local repo is always signed by a locally generated and
stored key.  That key will become the unique ID that represents a given
local repo.  It should seamlessly upgrade any existing unsigned local repo
next time that the user makes any changes to their local repo.

fixes #3380 https://dev.guardianproject.info/issues/3380
This commit is contained in:
Hans-Christoph Steiner 2014-05-22 19:02:25 -04:00
parent 22fb0337b9
commit 5f2efbb72a
7 changed files with 76 additions and 31 deletions

View File

@ -175,6 +175,7 @@
<string name="deleting_repo">Deleting current repo&#8230;</string>
<string name="adding_apks_format">Adding %s to repo&#8230;</string>
<string name="writing_index_xml">Writing raw index file (index.xml)&#8230;</string>
<string name="writing_index_jar">Writing signed index file (index.jar)&#8230;</string>
<string name="linking_apks">Linking APKs into the repo&#8230;</string>
<string name="copying_icons">Copying app icons into the repo&#8230;</string>
<string name="updated_local_repo">Finished updating local repo</string>

View File

@ -211,30 +211,6 @@ public class LocalRepoKeyStore {
return null;
}
// This is take from FDroid: org.fdroid.fdroid.DB.calcFingerprint()
// TODO once this code is part of FDroid, replace this with DB.calcFingerprint()
public String getFingerprint() {
String ret = null;
try {
Certificate cert = getCertificate();
if (cert != null) {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(cert.getEncoded());
byte[] fingerprint = digest.digest();
Formatter formatter = new Formatter(new StringBuilder());
for (int i = 0; i < fingerprint.length; i++) {
formatter.format("%02X", fingerprint[i]);
}
ret = formatter.toString();
formatter.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
public Certificate getCertificate() {
try {
Key key = keyStore.getKey(INDEX_CERT_ALIAS, "".toCharArray());

View File

@ -16,9 +16,9 @@ import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.Hasher;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.App;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@ -28,6 +28,8 @@ import java.security.cert.CertificateEncodingException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@ -42,6 +44,7 @@ public class LocalRepoManager {
// For ref, official F-droid repo presently uses a maxage of 14 days
private static final String DEFAULT_REPO_MAX_AGE_DAYS = "14";
private final Context context;
private final PackageManager pm;
private final AssetManager assetManager;
private final SharedPreferences prefs;
@ -53,12 +56,15 @@ public class LocalRepoManager {
private Map<String, App> apps = new HashMap<String, App>();
public final File xmlIndex;
private File xmlIndexJar = null;
private File xmlIndexJarUnsigned = null;
public final File webRoot;
public final File fdroidDir;
public final File repoDir;
public final File iconsDir;
public LocalRepoManager(Context c) {
context = c;
pm = c.getPackageManager();
assetManager = c.getAssets();
prefs = PreferenceManager.getDefaultSharedPreferences(c);
@ -70,6 +76,8 @@ public class LocalRepoManager {
repoDir = new File(fdroidDir, "repo");
iconsDir = new File(repoDir, "icons");
xmlIndex = new File(repoDir, "index.xml");
xmlIndexJar = new File(repoDir, "index.jar");
xmlIndexJarUnsigned = new File(repoDir, "index.unsigned.jar");
if (!fdroidDir.exists())
if (!fdroidDir.mkdir())
@ -278,6 +286,7 @@ public class LocalRepoManager {
repo.setAttribute("icon", "blah.png");
repo.setAttribute("maxage", String.valueOf(repoMaxAge));
repo.setAttribute("name", repoName + " on " + ipAddressString);
repo.setAttribute("pubkey", Hasher.hex(LocalRepoKeyStore.get(context).getCertificate()));
long timestamp = System.currentTimeMillis() / 1000L;
repo.setAttribute("timestamp", String.valueOf(timestamp));
repo.setAttribute("url", uriString);
@ -417,4 +426,30 @@ public class LocalRepoManager {
transformer.transform(domSource, result);
}
public void writeIndexJar() throws IOException {
BufferedOutputStream bo = new BufferedOutputStream(
new FileOutputStream(xmlIndexJarUnsigned));
JarOutputStream jo = new JarOutputStream(bo);
BufferedInputStream bi = new BufferedInputStream(new FileInputStream(xmlIndex));
JarEntry je = new JarEntry("index.xml");
jo.putNextEntry(je);
byte[] buf = new byte[1024];
int bytesRead;
while ((bytesRead = bi.read(buf)) != -1) {
jo.write(buf, 0, bytesRead);
}
bi.close();
jo.close();
bo.close();
LocalRepoKeyStore.get(context).signZip(xmlIndexJarUnsigned, xmlIndexJar);
xmlIndexJarUnsigned.delete();
}
}

View File

@ -201,7 +201,7 @@ public class LocalRepoService extends Service {
values.put("path", "/fdroid/repo");
values.put("name", repoName);
// TODO set type based on "use HTTPS" pref
// values.put("fingerprint", FDroidApp.repo.fingerprint);
values.put("fingerprint", FDroidApp.repo.fingerprint);
values.put("type", "fdroidrepo");
pairService = ServiceInfo.create("_http._tcp.local.",
repoName, FDroidApp.port, 0, 0, values);

View File

@ -2,7 +2,6 @@
package org.fdroid.fdroid.net;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.wifi.WifiInfo;
@ -16,7 +15,9 @@ import android.util.Log;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.localrepo.LocalRepoKeyStore;
import java.security.cert.Certificate;
import java.util.Locale;
public class WifiStateChangeService extends Service {
@ -67,6 +68,8 @@ public class WifiStateChangeService extends Service {
FDroidApp.repo.name = Preferences.get().getLocalRepoName();
FDroidApp.repo.address = String.format(Locale.ENGLISH, "%s://%s:%d/fdroid/repo",
scheme, FDroidApp.ipAddressString, FDroidApp.port);
Certificate localCert = LocalRepoKeyStore.get(getApplication()).getCertificate();
FDroidApp.repo.fingerprint = Utils.calcFingerprint(localCert);
FDroidApp.localRepo.setUriString(FDroidApp.repo.address);
FDroidApp.localRepo.writeIndexPage(
Utils.getSharingUri(WifiStateChangeService.this, FDroidApp.repo).toString());

View File

@ -22,9 +22,17 @@ import android.view.*;
import android.widget.*;
import org.fdroid.fdroid.*;
import org.fdroid.fdroid.localrepo.LocalRepoKeyStore;
import org.fdroid.fdroid.localrepo.LocalRepoService;
import org.fdroid.fdroid.net.WifiStateChangeService;
import org.spongycastle.operator.OperatorCreationException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
@ -252,6 +260,28 @@ public class LocalRepoActivity extends Activity {
fingerprintTextView.setVisibility(View.GONE);
}
// Once the IP address is known we need to generate a self signed
// certificate to use for HTTPS that has a CN field set to the
// ipAddressString. We'll generate it even if useHttps is false
// to simplify having to detect when that preference changes.
try {
LocalRepoKeyStore.get(this).setupHTTPSCertificate();
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (OperatorCreationException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// the required NFC API was added in 4.0 aka Ice Cream Sandwich
if (Build.VERSION.SDK_INT >= 14) {
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
@ -300,6 +330,8 @@ public class LocalRepoActivity extends Activity {
FDroidApp.localRepo.writeIndexPage(sharingUri.toString());
publishProgress(getString(R.string.writing_index_xml));
FDroidApp.localRepo.writeIndexXML();
publishProgress(getString(R.string.writing_index_jar));
FDroidApp.localRepo.writeIndexJar();
publishProgress(getString(R.string.linking_apks));
FDroidApp.localRepo.copyApksToRepo();
publishProgress(getString(R.string.copying_icons));

View File

@ -200,8 +200,7 @@ public class RepoListFragment extends ListFragment
MenuItemCompat.SHOW_AS_ACTION_ALWAYS |
MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT);
if (Build.VERSION.SDK_INT >= 16)
{
if (Build.VERSION.SDK_INT >= 16) {
menu.add(Menu.NONE, SCAN_FOR_REPOS, 1, R.string.menu_scan_repo).setIcon(
android.R.drawable.ic_menu_search);
}
@ -271,8 +270,7 @@ public class RepoListFragment extends ListFragment
path = "/fdroid/repo";
String serviceUrl = protocol + serviceInfo.getInetAddresses()[0]
+ ":" + serviceInfo.getPort() + path;
// TODO get fingerprint from TXT record
showAddRepo(serviceUrl, "");
showAddRepo(serviceUrl, serviceInfo.getPropertyString("fingerprint"));
}
});