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:
parent
22fb0337b9
commit
5f2efbb72a
@ -175,6 +175,7 @@
|
||||
<string name="deleting_repo">Deleting current repo…</string>
|
||||
<string name="adding_apks_format">Adding %s to repo…</string>
|
||||
<string name="writing_index_xml">Writing raw index file (index.xml)…</string>
|
||||
<string name="writing_index_jar">Writing signed index file (index.jar)…</string>
|
||||
<string name="linking_apks">Linking APKs into the repo…</string>
|
||||
<string name="copying_icons">Copying app icons into the repo…</string>
|
||||
<string name="updated_local_repo">Finished updating local repo</string>
|
||||
|
@ -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());
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
@ -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));
|
||||
|
@ -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"));
|
||||
}
|
||||
});
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user