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="deleting_repo">Deleting current repo…</string>
|
||||||
<string name="adding_apks_format">Adding %s to 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_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="linking_apks">Linking APKs into the repo…</string>
|
||||||
<string name="copying_icons">Copying app icons 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>
|
<string name="updated_local_repo">Finished updating local repo</string>
|
||||||
|
@ -211,30 +211,6 @@ public class LocalRepoKeyStore {
|
|||||||
return null;
|
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() {
|
public Certificate getCertificate() {
|
||||||
try {
|
try {
|
||||||
Key key = keyStore.getKey(INDEX_CERT_ALIAS, "".toCharArray());
|
Key key = keyStore.getKey(INDEX_CERT_ALIAS, "".toCharArray());
|
||||||
|
@ -16,9 +16,9 @@ import android.preference.PreferenceManager;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.Hasher;
|
||||||
import org.fdroid.fdroid.Preferences;
|
import org.fdroid.fdroid.Preferences;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.data.Apk;
|
|
||||||
import org.fdroid.fdroid.data.App;
|
import org.fdroid.fdroid.data.App;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
@ -28,6 +28,8 @@ import java.security.cert.CertificateEncodingException;
|
|||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarOutputStream;
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
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
|
// For ref, official F-droid repo presently uses a maxage of 14 days
|
||||||
private static final String DEFAULT_REPO_MAX_AGE_DAYS = "14";
|
private static final String DEFAULT_REPO_MAX_AGE_DAYS = "14";
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
private final PackageManager pm;
|
private final PackageManager pm;
|
||||||
private final AssetManager assetManager;
|
private final AssetManager assetManager;
|
||||||
private final SharedPreferences prefs;
|
private final SharedPreferences prefs;
|
||||||
@ -53,12 +56,15 @@ public class LocalRepoManager {
|
|||||||
private Map<String, App> apps = new HashMap<String, App>();
|
private Map<String, App> apps = new HashMap<String, App>();
|
||||||
|
|
||||||
public final File xmlIndex;
|
public final File xmlIndex;
|
||||||
|
private File xmlIndexJar = null;
|
||||||
|
private File xmlIndexJarUnsigned = null;
|
||||||
public final File webRoot;
|
public final File webRoot;
|
||||||
public final File fdroidDir;
|
public final File fdroidDir;
|
||||||
public final File repoDir;
|
public final File repoDir;
|
||||||
public final File iconsDir;
|
public final File iconsDir;
|
||||||
|
|
||||||
public LocalRepoManager(Context c) {
|
public LocalRepoManager(Context c) {
|
||||||
|
context = c;
|
||||||
pm = c.getPackageManager();
|
pm = c.getPackageManager();
|
||||||
assetManager = c.getAssets();
|
assetManager = c.getAssets();
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(c);
|
prefs = PreferenceManager.getDefaultSharedPreferences(c);
|
||||||
@ -70,6 +76,8 @@ public class LocalRepoManager {
|
|||||||
repoDir = new File(fdroidDir, "repo");
|
repoDir = new File(fdroidDir, "repo");
|
||||||
iconsDir = new File(repoDir, "icons");
|
iconsDir = new File(repoDir, "icons");
|
||||||
xmlIndex = new File(repoDir, "index.xml");
|
xmlIndex = new File(repoDir, "index.xml");
|
||||||
|
xmlIndexJar = new File(repoDir, "index.jar");
|
||||||
|
xmlIndexJarUnsigned = new File(repoDir, "index.unsigned.jar");
|
||||||
|
|
||||||
if (!fdroidDir.exists())
|
if (!fdroidDir.exists())
|
||||||
if (!fdroidDir.mkdir())
|
if (!fdroidDir.mkdir())
|
||||||
@ -278,6 +286,7 @@ public class LocalRepoManager {
|
|||||||
repo.setAttribute("icon", "blah.png");
|
repo.setAttribute("icon", "blah.png");
|
||||||
repo.setAttribute("maxage", String.valueOf(repoMaxAge));
|
repo.setAttribute("maxage", String.valueOf(repoMaxAge));
|
||||||
repo.setAttribute("name", repoName + " on " + ipAddressString);
|
repo.setAttribute("name", repoName + " on " + ipAddressString);
|
||||||
|
repo.setAttribute("pubkey", Hasher.hex(LocalRepoKeyStore.get(context).getCertificate()));
|
||||||
long timestamp = System.currentTimeMillis() / 1000L;
|
long timestamp = System.currentTimeMillis() / 1000L;
|
||||||
repo.setAttribute("timestamp", String.valueOf(timestamp));
|
repo.setAttribute("timestamp", String.valueOf(timestamp));
|
||||||
repo.setAttribute("url", uriString);
|
repo.setAttribute("url", uriString);
|
||||||
@ -417,4 +426,30 @@ public class LocalRepoManager {
|
|||||||
|
|
||||||
transformer.transform(domSource, result);
|
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("path", "/fdroid/repo");
|
||||||
values.put("name", repoName);
|
values.put("name", repoName);
|
||||||
// TODO set type based on "use HTTPS" pref
|
// TODO set type based on "use HTTPS" pref
|
||||||
// values.put("fingerprint", FDroidApp.repo.fingerprint);
|
values.put("fingerprint", FDroidApp.repo.fingerprint);
|
||||||
values.put("type", "fdroidrepo");
|
values.put("type", "fdroidrepo");
|
||||||
pairService = ServiceInfo.create("_http._tcp.local.",
|
pairService = ServiceInfo.create("_http._tcp.local.",
|
||||||
repoName, FDroidApp.port, 0, 0, values);
|
repoName, FDroidApp.port, 0, 0, values);
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
package org.fdroid.fdroid.net;
|
package org.fdroid.fdroid.net;
|
||||||
|
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.net.wifi.WifiInfo;
|
import android.net.wifi.WifiInfo;
|
||||||
@ -16,7 +15,9 @@ import android.util.Log;
|
|||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.Preferences;
|
import org.fdroid.fdroid.Preferences;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
|
import org.fdroid.fdroid.localrepo.LocalRepoKeyStore;
|
||||||
|
|
||||||
|
import java.security.cert.Certificate;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public class WifiStateChangeService extends Service {
|
public class WifiStateChangeService extends Service {
|
||||||
@ -67,6 +68,8 @@ public class WifiStateChangeService extends Service {
|
|||||||
FDroidApp.repo.name = Preferences.get().getLocalRepoName();
|
FDroidApp.repo.name = Preferences.get().getLocalRepoName();
|
||||||
FDroidApp.repo.address = String.format(Locale.ENGLISH, "%s://%s:%d/fdroid/repo",
|
FDroidApp.repo.address = String.format(Locale.ENGLISH, "%s://%s:%d/fdroid/repo",
|
||||||
scheme, FDroidApp.ipAddressString, FDroidApp.port);
|
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.setUriString(FDroidApp.repo.address);
|
||||||
FDroidApp.localRepo.writeIndexPage(
|
FDroidApp.localRepo.writeIndexPage(
|
||||||
Utils.getSharingUri(WifiStateChangeService.this, FDroidApp.repo).toString());
|
Utils.getSharingUri(WifiStateChangeService.this, FDroidApp.repo).toString());
|
||||||
|
@ -22,9 +22,17 @@ import android.view.*;
|
|||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
|
||||||
import org.fdroid.fdroid.*;
|
import org.fdroid.fdroid.*;
|
||||||
|
import org.fdroid.fdroid.localrepo.LocalRepoKeyStore;
|
||||||
import org.fdroid.fdroid.localrepo.LocalRepoService;
|
import org.fdroid.fdroid.localrepo.LocalRepoService;
|
||||||
import org.fdroid.fdroid.net.WifiStateChangeService;
|
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.Locale;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
@ -252,6 +260,28 @@ public class LocalRepoActivity extends Activity {
|
|||||||
fingerprintTextView.setVisibility(View.GONE);
|
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
|
// the required NFC API was added in 4.0 aka Ice Cream Sandwich
|
||||||
if (Build.VERSION.SDK_INT >= 14) {
|
if (Build.VERSION.SDK_INT >= 14) {
|
||||||
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
||||||
@ -300,6 +330,8 @@ public class LocalRepoActivity extends Activity {
|
|||||||
FDroidApp.localRepo.writeIndexPage(sharingUri.toString());
|
FDroidApp.localRepo.writeIndexPage(sharingUri.toString());
|
||||||
publishProgress(getString(R.string.writing_index_xml));
|
publishProgress(getString(R.string.writing_index_xml));
|
||||||
FDroidApp.localRepo.writeIndexXML();
|
FDroidApp.localRepo.writeIndexXML();
|
||||||
|
publishProgress(getString(R.string.writing_index_jar));
|
||||||
|
FDroidApp.localRepo.writeIndexJar();
|
||||||
publishProgress(getString(R.string.linking_apks));
|
publishProgress(getString(R.string.linking_apks));
|
||||||
FDroidApp.localRepo.copyApksToRepo();
|
FDroidApp.localRepo.copyApksToRepo();
|
||||||
publishProgress(getString(R.string.copying_icons));
|
publishProgress(getString(R.string.copying_icons));
|
||||||
|
@ -200,8 +200,7 @@ public class RepoListFragment extends ListFragment
|
|||||||
MenuItemCompat.SHOW_AS_ACTION_ALWAYS |
|
MenuItemCompat.SHOW_AS_ACTION_ALWAYS |
|
||||||
MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT);
|
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(
|
menu.add(Menu.NONE, SCAN_FOR_REPOS, 1, R.string.menu_scan_repo).setIcon(
|
||||||
android.R.drawable.ic_menu_search);
|
android.R.drawable.ic_menu_search);
|
||||||
}
|
}
|
||||||
@ -271,8 +270,7 @@ public class RepoListFragment extends ListFragment
|
|||||||
path = "/fdroid/repo";
|
path = "/fdroid/repo";
|
||||||
String serviceUrl = protocol + serviceInfo.getInetAddresses()[0]
|
String serviceUrl = protocol + serviceInfo.getInetAddresses()[0]
|
||||||
+ ":" + serviceInfo.getPort() + path;
|
+ ":" + serviceInfo.getPort() + path;
|
||||||
// TODO get fingerprint from TXT record
|
showAddRepo(serviceUrl, serviceInfo.getPropertyString("fingerprint"));
|
||||||
showAddRepo(serviceUrl, "");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user