More robust failure conditions for local repo keystore.

Was experiencing some problems with local repo keystore on Android 2.3.4.
Previously, the local repo keystore could fail to create, but would still
return a LocalRepoKeyStore instance to be used. Now it throws a checked
exception, and is dealt with in the relveant places.

Also cleaned up some calls to "e.printStackTrace()" and replaced with
more informative logging messages.
This commit is contained in:
Peter Serwylo 2014-11-29 03:58:43 +11:00
parent 0a4725b962
commit 6d0207c32e
5 changed files with 119 additions and 57 deletions

View File

@ -34,7 +34,9 @@ import kellinwood.security.zipsigner.ZipSigner;
// TODO Address exception handling in a uniform way throughout // TODO Address exception handling in a uniform way throughout
public class LocalRepoKeyStore { public class LocalRepoKeyStore {
private static final String TAG = "KerplappKeyStore";
private static final String TAG = "org.fdroid.fdroid.localrepo.LocalRepoKeyStore";
public static final String INDEX_CERT_ALIAS = "fdroid"; public static final String INDEX_CERT_ALIAS = "fdroid";
public static final String HTTP_CERT_ALIAS = "https"; public static final String HTTP_CERT_ALIAS = "https";
@ -49,26 +51,49 @@ public class LocalRepoKeyStore {
private KeyManager[] keyManagers; private KeyManager[] keyManagers;
private File keyStoreFile; private File keyStoreFile;
public static LocalRepoKeyStore get(Context context) { public static LocalRepoKeyStore get(Context context) throws InitException {
if (localRepoKeyStore == null) if (localRepoKeyStore == null)
localRepoKeyStore = new LocalRepoKeyStore(context); localRepoKeyStore = new LocalRepoKeyStore(context);
return localRepoKeyStore; return localRepoKeyStore;
} }
private LocalRepoKeyStore(Context context) { public static class InitException extends Exception {
public InitException(String detailMessage) {
super(detailMessage);
}
}
private LocalRepoKeyStore(Context context) throws InitException {
try { try {
Log.d(TAG, "generating LocalRepoKeyStore instance");
File appKeyStoreDir = context.getDir("keystore", Context.MODE_PRIVATE); File appKeyStoreDir = context.getDir("keystore", Context.MODE_PRIVATE);
Log.d(TAG, "Generating LocalRepoKeyStore instance: " + appKeyStoreDir.getAbsolutePath());
this.keyStoreFile = new File(appKeyStoreDir, "kerplapp.bks"); this.keyStoreFile = new File(appKeyStoreDir, "kerplapp.bks");
Log.d(TAG, "Using default KeyStore type: " + KeyStore.getDefaultType());
this.keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); this.keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
if (keyStoreFile.exists()) {
try {
Log.d(TAG, "Keystore already exists, loading...");
keyStore.load(new FileInputStream(keyStoreFile), "".toCharArray());
} catch (IOException e) {
Log.e(TAG, "Error while loading existing keystore. Will delete and create a new one.");
// NOTE: Could opt to delete and then re-create the keystore here, but that may
// be undesirable. For example - if you were to re-connect to an existing device
// that you have swapped apps with in the past, then you would really want the
// signature to be the same as last time.
throw new InitException("Could not initialize local repo keystore: " + e);
}
}
if (!keyStoreFile.exists()) {
// If there isn't a persisted BKS keystore on disk we need to // If there isn't a persisted BKS keystore on disk we need to
// create a new empty keystore // create a new empty keystore
if (!keyStoreFile.exists()) {
// Init a new keystore with a blank passphrase // Init a new keystore with a blank passphrase
Log.d(TAG, "Keystore doesn't exist, creating...");
keyStore.load(null, "".toCharArray()); keyStore.load(null, "".toCharArray());
} else {
keyStore.load(new FileInputStream(keyStoreFile), "".toCharArray());
} }
/* /*
@ -113,17 +138,23 @@ public class LocalRepoKeyStore {
wrappedKeyManager wrappedKeyManager
}; };
} catch (UnrecoverableKeyException e) { } catch (UnrecoverableKeyException e) {
e.printStackTrace(); Log.e(TAG, "Error loading keystore: " + e.getMessage());
Log.e(TAG, Log.getStackTraceString(e));
} catch (KeyStoreException e) { } catch (KeyStoreException e) {
e.printStackTrace(); Log.e(TAG, "Error loading keystore: " + e.getMessage());
Log.e(TAG, Log.getStackTraceString(e));
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
e.printStackTrace(); Log.e(TAG, "Error loading keystore: " + e.getMessage());
Log.e(TAG, Log.getStackTraceString(e));
} catch (CertificateException e) { } catch (CertificateException e) {
e.printStackTrace(); Log.e(TAG, "Error loading keystore: " + e.getMessage());
Log.e(TAG, Log.getStackTraceString(e));
} catch (OperatorCreationException e) { } catch (OperatorCreationException e) {
e.printStackTrace(); Log.e(TAG, "Error loading keystore: " + e.getMessage());
Log.e(TAG, Log.getStackTraceString(e));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); Log.e(TAG, "Error loading keystore: " + e.getMessage());
Log.e(TAG, Log.getStackTraceString(e));
} }
} }
@ -144,7 +175,8 @@ public class LocalRepoKeyStore {
FDroidApp.ipAddressString); FDroidApp.ipAddressString);
addToStore(HTTP_CERT_ALIAS, kerplappKeypair, indexCert); addToStore(HTTP_CERT_ALIAS, kerplappKeypair, indexCert);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); Log.e(TAG, "Failed to setup HTTPS certificate: " + e);
Log.e(TAG, Log.getStackTraceString(e));
} }
} }
@ -174,21 +206,29 @@ public class LocalRepoKeyStore {
zipSigner.signZip(input.getAbsolutePath(), output.getAbsolutePath()); zipSigner.signZip(input.getAbsolutePath(), output.getAbsolutePath());
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
e.printStackTrace(); Log.e(TAG, "Unable to sign local repo index: " + e);
Log.e(TAG, Log.getStackTraceString(e));
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
e.printStackTrace(); Log.e(TAG, "Unable to sign local repo index: " + e);
Log.e(TAG, Log.getStackTraceString(e));
} catch (InstantiationException e) { } catch (InstantiationException e) {
e.printStackTrace(); Log.e(TAG, "Unable to sign local repo index: " + e);
Log.e(TAG, Log.getStackTraceString(e));
} catch (KeyStoreException e) { } catch (KeyStoreException e) {
e.printStackTrace(); Log.e(TAG, "Unable to sign local repo index: " + e);
Log.e(TAG, Log.getStackTraceString(e));
} catch (UnrecoverableKeyException e) { } catch (UnrecoverableKeyException e) {
e.printStackTrace(); Log.e(TAG, "Unable to sign local repo index: " + e);
Log.e(TAG, Log.getStackTraceString(e));
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
e.printStackTrace(); Log.e(TAG, "Unable to sign local repo index: " + e);
Log.e(TAG, Log.getStackTraceString(e));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); Log.e(TAG, "Unable to sign local repo index: " + e);
Log.e(TAG, Log.getStackTraceString(e));
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
e.printStackTrace(); Log.e(TAG, "Unable to sign local repo index: " + e);
Log.e(TAG, Log.getStackTraceString(e));
} }
} }
@ -217,11 +257,14 @@ public class LocalRepoKeyStore {
if (key instanceof PrivateKey) if (key instanceof PrivateKey)
return keyStore.getCertificate(INDEX_CERT_ALIAS); return keyStore.getCertificate(INDEX_CERT_ALIAS);
} catch (KeyStoreException e) { } catch (KeyStoreException e) {
e.printStackTrace(); Log.e(TAG, "Unable to get certificate for local repo: " + e);
Log.e(TAG, Log.getStackTraceString(e));
} catch (UnrecoverableKeyException e) { } catch (UnrecoverableKeyException e) {
e.printStackTrace(); Log.e(TAG, "Unable to get certificate for local repo: " + e);
Log.e(TAG, Log.getStackTraceString(e));
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
e.printStackTrace(); Log.e(TAG, "Unable to get certificate for local repo: " + e);
Log.e(TAG, Log.getStackTraceString(e));
} }
return null; return null;
} }

View File

@ -185,7 +185,8 @@ public class LocalRepoManager {
symlinkIndexPageElsewhere("../../", repoCAPS); symlinkIndexPageElsewhere("../../", repoCAPS);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); Log.e(TAG, "Error writing local repo index: " + e.getMessage());
Log.e(TAG, Log.getStackTraceString(e));
} }
} }
@ -249,13 +250,16 @@ public class LocalRepoManager {
PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_META_DATA); PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_META_DATA);
app.icon = getIconFile(packageName, packageInfo.versionCode).getName(); app.icon = getIconFile(packageName, packageInfo.versionCode).getName();
} catch (NameNotFoundException e) { } catch (NameNotFoundException e) {
e.printStackTrace(); Log.e(TAG, "Error adding app to local repo: " + e.getMessage());
Log.e(TAG, Log.getStackTraceString(e));
return; return;
} catch (CertificateEncodingException e) { } catch (CertificateEncodingException e) {
e.printStackTrace(); Log.e(TAG, "Error adding app to local repo: " + e.getMessage());
Log.e(TAG, Log.getStackTraceString(e));
return; return;
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); Log.e(TAG, "Error adding app to local repo: " + e.getMessage());
Log.e(TAG, Log.getStackTraceString(e));
return; return;
} }
Log.i(TAG, "apps.put: " + packageName); Log.i(TAG, "apps.put: " + packageName);
@ -317,7 +321,7 @@ public class LocalRepoManager {
// TODO this needs to be ported to < android-8 // TODO this needs to be ported to < android-8
@TargetApi(8) @TargetApi(8)
private void writeIndexXML() throws TransformerException, ParserConfigurationException { private void writeIndexXML() throws TransformerException, ParserConfigurationException, LocalRepoKeyStore.InitException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder(); DocumentBuilder builder = factory.newDocumentBuilder();
@ -480,12 +484,11 @@ public class LocalRepoManager {
writeIndexXML(); writeIndexXML();
} catch (Exception e) { } catch (Exception e) {
Toast.makeText(context, R.string.failed_to_create_index, Toast.LENGTH_LONG).show(); Toast.makeText(context, R.string.failed_to_create_index, Toast.LENGTH_LONG).show();
e.printStackTrace(); Log.e(TAG, Log.getStackTraceString(e));
return; return;
} }
BufferedOutputStream bo = new BufferedOutputStream( BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream(xmlIndexJarUnsigned));
new FileOutputStream(xmlIndexJarUnsigned));
JarOutputStream jo = new JarOutputStream(bo); JarOutputStream jo = new JarOutputStream(bo);
BufferedInputStream bi = new BufferedInputStream(new FileInputStream(xmlIndex)); BufferedInputStream bi = new BufferedInputStream(new FileInputStream(xmlIndex));
@ -504,8 +507,13 @@ public class LocalRepoManager {
jo.close(); jo.close();
bo.close(); bo.close();
try {
LocalRepoKeyStore.get(context).signZip(xmlIndexJarUnsigned, xmlIndexJar); LocalRepoKeyStore.get(context).signZip(xmlIndexJarUnsigned, xmlIndexJar);
} catch (LocalRepoKeyStore.InitException e) {
throw new IOException("Could not sign index - keystore failed to initialize");
} finally {
xmlIndexJarUnsigned.delete(); xmlIndexJarUnsigned.delete();
} }
}
} }

View File

@ -23,6 +23,7 @@ import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Preferences.ChangeListener; import org.fdroid.fdroid.Preferences.ChangeListener;
import org.fdroid.fdroid.R; import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.net.LocalHTTPD; import org.fdroid.fdroid.net.LocalHTTPD;
import org.fdroid.fdroid.net.WifiStateChangeService; import org.fdroid.fdroid.net.WifiStateChangeService;
import org.fdroid.fdroid.views.swap.SwapActivity; import org.fdroid.fdroid.views.swap.SwapActivity;
@ -226,7 +227,8 @@ public class LocalRepoService extends Service {
Log.w(TAG, "port " + prev + " occupied, trying on " + FDroidApp.port + "!"); Log.w(TAG, "port " + prev + " occupied, trying on " + FDroidApp.port + "!");
startService(new Intent(LocalRepoService.this, WifiStateChangeService.class)); startService(new Intent(LocalRepoService.this, WifiStateChangeService.class));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); Log.e(TAG, "Could not start local repo HTTP server: " + e);
Log.e(TAG, Log.getStackTraceString(e));
} }
Looper.loop(); // start the message receiving loop Looper.loop(); // start the message receiving loop
} }
@ -279,7 +281,8 @@ public class LocalRepoService extends Service {
jmdns = JmDNS.create(); jmdns = JmDNS.create();
jmdns.registerService(pairService); jmdns.registerService(pairService);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); Log.e(TAG, "Error while registering jmdns service: " + e);
Log.e(TAG, Log.getStackTraceString(e));
} }
} }
}).start(); }).start();
@ -300,11 +303,7 @@ public class LocalRepoService extends Service {
pairService = null; pairService = null;
} }
jmdns.unregisterAllServices(); jmdns.unregisterAllServices();
try { Utils.closeQuietly(jmdns);
jmdns.close();
} catch (IOException e) {
e.printStackTrace();
}
jmdns = null; jmdns = null;
} }
} }

View File

@ -92,7 +92,11 @@ public class LocalHTTPD extends NanoHTTPD {
localRepoKeyStore.getKeyManagers()); localRepoKeyStore.getKeyManagers());
makeSecure(factory); makeSecure(factory);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); Log.e(TAG, "Could not enable HTTPS: " + e.getMessage());
Log.e(TAG, Log.getStackTraceString(e));
} catch (LocalRepoKeyStore.InitException e) {
Log.e(TAG, "Could not enable HTTPS: " + e.getMessage());
Log.e(TAG, Log.getStackTraceString(e));
} }
} }

View File

@ -82,9 +82,6 @@ public class WifiStateChangeService extends Service {
return null; return null;
Context context = WifiStateChangeService.this.getApplicationContext(); Context context = WifiStateChangeService.this.getApplicationContext();
LocalRepoKeyStore localRepoKeyStore = LocalRepoKeyStore.get(context);
Certificate localCert = localRepoKeyStore.getCertificate();
FDroidApp.repo.fingerprint = Utils.calcFingerprint(localCert);
LocalRepoManager lrm = LocalRepoManager.get(context); LocalRepoManager lrm = LocalRepoManager.get(context);
lrm.setUriString(FDroidApp.repo.address); lrm.setUriString(FDroidApp.repo.address);
lrm.writeIndexPage(Utils.getSharingUri(context, FDroidApp.repo).toString()); lrm.writeIndexPage(Utils.getSharingUri(context, FDroidApp.repo).toString());
@ -92,6 +89,11 @@ public class WifiStateChangeService extends Service {
if (isCancelled()) if (isCancelled())
return null; return null;
try {
LocalRepoKeyStore localRepoKeyStore = LocalRepoKeyStore.get(context);
Certificate localCert = localRepoKeyStore.getCertificate();
FDroidApp.repo.fingerprint = Utils.calcFingerprint(localCert);
/* /*
* Once the IP address is known we need to generate a self * Once the IP address is known we need to generate a self
* signed certificate to use for HTTPS that has a CN field set * signed certificate to use for HTTPS that has a CN field set
@ -101,8 +103,14 @@ public class WifiStateChangeService extends Service {
*/ */
if (Preferences.get().isLocalRepoHttpsEnabled()) if (Preferences.get().isLocalRepoHttpsEnabled())
localRepoKeyStore.setupHTTPSCertificate(); localRepoKeyStore.setupHTTPSCertificate();
} catch (LocalRepoKeyStore.InitException e) {
Log.e(TAG, "Unable to configure a fingerprint or HTTPS for the local repo: " + e.getMessage());
Log.e(TAG, Log.getStackTraceString(e));
}
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); Log.e(TAG, Log.getStackTraceString(e));
} }
return null; return null;
} }