diff --git a/res/values/strings.xml b/res/values/strings.xml index 179619796..c5fc6105e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -175,6 +175,7 @@ Deleting current repo… Adding %s to repo… Writing raw index file (index.xml)… + Writing signed index file (index.jar)… Linking APKs into the repo… Copying app icons into the repo… Finished updating local repo diff --git a/src/org/fdroid/fdroid/localrepo/LocalRepoKeyStore.java b/src/org/fdroid/fdroid/localrepo/LocalRepoKeyStore.java index 27778c2fb..c81d79a40 100644 --- a/src/org/fdroid/fdroid/localrepo/LocalRepoKeyStore.java +++ b/src/org/fdroid/fdroid/localrepo/LocalRepoKeyStore.java @@ -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()); diff --git a/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java b/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java index 21108edd1..29b35fc71 100644 --- a/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java +++ b/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java @@ -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 apps = new HashMap(); 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(); + } } diff --git a/src/org/fdroid/fdroid/localrepo/LocalRepoService.java b/src/org/fdroid/fdroid/localrepo/LocalRepoService.java index 690105fdc..3e783493b 100644 --- a/src/org/fdroid/fdroid/localrepo/LocalRepoService.java +++ b/src/org/fdroid/fdroid/localrepo/LocalRepoService.java @@ -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); diff --git a/src/org/fdroid/fdroid/net/WifiStateChangeService.java b/src/org/fdroid/fdroid/net/WifiStateChangeService.java index 78b35abe6..f7cd2e111 100644 --- a/src/org/fdroid/fdroid/net/WifiStateChangeService.java +++ b/src/org/fdroid/fdroid/net/WifiStateChangeService.java @@ -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()); diff --git a/src/org/fdroid/fdroid/views/LocalRepoActivity.java b/src/org/fdroid/fdroid/views/LocalRepoActivity.java index b7d4e3f21..411caed54 100644 --- a/src/org/fdroid/fdroid/views/LocalRepoActivity.java +++ b/src/org/fdroid/fdroid/views/LocalRepoActivity.java @@ -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)); diff --git a/src/org/fdroid/fdroid/views/fragments/RepoListFragment.java b/src/org/fdroid/fdroid/views/fragments/RepoListFragment.java index d5b23c5ac..afd69cddf 100644 --- a/src/org/fdroid/fdroid/views/fragments/RepoListFragment.java +++ b/src/org/fdroid/fdroid/views/fragments/RepoListFragment.java @@ -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")); } });