Adding support for SPKI pins, trust-on-first-use of TLS certs.

In order to support F-droid repositories hosted with HTTPS using
a self-signed certificate the f-droid client should prompt the user to
trust or 'memorize' the certificate presented by a repository. The
MemorizingTrustManager[0] project enables easy integration of
a prompting activity and corresponding trust manager implementation.
This behaviour is useful to projects such as Kerplapp[1] that boostrap
an F-droid repository on a user's device where it isn't possible to
acquire a long lived CA vetted TLS certificate.

In addition to Trust-on-First-Use (TOFU) behaviour, this patch
integrates the PinningTrustManager [2] project by Moxie Marlinspike to
allow the FDroid client to ship a hardcoded set of Subject Public Key
Identifier pins [3] for the official FDroid repository TLS certificate,
and the Guardian Project TLS certificate. Additional pins can be added
to the FDroidPins.java class.

The upstream release of AndroidPinning by moxie0 uses a minsdk value of
8. The Fdroid client has a minsdk of 5, presenting compatibility issues
using the AndroidPinning lib as a submodule. Fortunately it seems there
is no technical reason preventing using a minSDK of 5 with
AndroidPinning. I have created a fork with this change and submitted
a pull req upstream. Until this pull is merged we can use my fork of
AndroidPinning as the submodule.

The new 'flow' for deciding if a repositories presented TLS certificate
should be trusted is as follows:

1) If the certificate was previously trusted by a TOFU action, then the
   certificate is accepted as trusted

2) If the certificate wasn't previously trusted by a TOFU action but
   there is a matching SPKI pin then the certificate is accepted as
   trusted

3) If the certificate wasn't previously trusted by a TOFU action and
   there is no SPKI pin but the certificate is signed by a trusted
   Certificate Authority it is accepted as trusted (This is the
   behaviour of the FDroid client prior to this patch with all other
   conditions being a hard-fail).

4) If the certificate wasn't previously trusted by a TOFU action and
   there is no SPKI pin and the certificate is not signed by a trusted
   CA (i.e. self signed or otherwise) then the user is prompted to TOFU
   the certificate. The user may choose to trust the certificate for the
   current connection or forever. If the user chooses an option other
   than "deny" the certificate is accepted as trusted for the specified
   duration.

Users currently using a TLS protected repository will see *no
difference* in user experience after this patch is merged as the only
TLS protected repositories that would function prior to this patch were
providing certificates that match condition #3.

[0] https://github.com/ge0rg/MemorizingTrustManager/wiki/Integration
[1] https://github.com/guardianproject/kerplapp
[2] https://github.com/moxie0/AndroidPinning
[3] https://www.imperialviolet.org/2011/05/04/pinning.html
This commit is contained in:
Daniel McCarney 2013-12-11 11:46:08 -05:00
parent 5f22877e1c
commit 254327f9a7
8 changed files with 134 additions and 6 deletions

6
.gitmodules vendored
View File

@ -2,3 +2,9 @@
path = extern/Universal-Image-Loader path = extern/Universal-Image-Loader
url = https://github.com/nostra13/Android-Universal-Image-Loader url = https://github.com/nostra13/Android-Universal-Image-Loader
ignore = dirty ignore = dirty
[submodule "extern/MemorizingTrustManager"]
path = extern/MemorizingTrustManager
url = https://github.com/ge0rg/MemorizingTrustManager.git
[submodule "extern/AndroidPinning"]
path = extern/AndroidPinning
url = https://github.com/binaryparadox/AndroidPinning.git

View File

@ -201,6 +201,9 @@
android:resource="@xml/searchable" /> android:resource="@xml/searchable" />
</activity> </activity>
<!--Used for SSL TOFU, supported by extern/MemorizingTrustManager lib -->
<activity android:name="de.duenndns.ssl.MemorizingActivity" />
<receiver android:name=".StartupReceiver" > <receiver android:name=".StartupReceiver" >
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />

View File

@ -12,8 +12,10 @@ The only required tools are the Android SDK and Apache Ant.
``` ```
git submodule update --init git submodule update --init
android update project -p . android update project -p . --name F-droid
android update project -p extern/Universal-Image-Loader/library android update lib-project -p extern/Universal-Image-Loader/library
android update lib-project -p extern/AndroidPinning
android update lib-project -p extern/MemorizingTrustManager
ant clean release ant clean release
``` ```

1
extern/AndroidPinning vendored Submodule

@ -0,0 +1 @@
Subproject commit 526654e1b9997b32e513d58d9094d4c1102a6cb3

1
extern/MemorizingTrustManager vendored Submodule

@ -0,0 +1 @@
Subproject commit 49452f67a760dfef77ddaa7e0b7d88c713c4a195

View File

@ -3,3 +3,5 @@ proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.
target=android-19 target=android-19
android.library.reference.1=extern/Universal-Image-Loader/library android.library.reference.1=extern/Universal-Image-Loader/library
android.library.reference.2=extern/MemorizingTrustManager
android.library.reference.3=extern/AndroidPinning

View File

@ -19,26 +19,45 @@
package org.fdroid.fdroid; package org.fdroid.fdroid;
import java.io.File; import java.io.File;
import java.lang.Runtime; import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import android.app.Application; import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import android.app.Activity; import android.app.Activity;
import android.preference.PreferenceManager; import android.app.Application;
import android.util.Log;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.Utils;
import android.graphics.Bitmap;
import android.preference.PreferenceManager;
import android.util.Log;
import com.nostra13.universalimageloader.cache.disc.impl.LimitedAgeDiscCache; import com.nostra13.universalimageloader.cache.disc.impl.LimitedAgeDiscCache;
import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiscCache;
import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator; import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
import com.nostra13.universalimageloader.utils.StorageUtils; import com.nostra13.universalimageloader.utils.StorageUtils;
import de.duenndns.ssl.MemorizingTrustManager;
import org.thoughtcrime.ssl.pinning.PinningTrustManager;
import org.thoughtcrime.ssl.pinning.SystemKeyStore;
public class FDroidApp extends Application { public class FDroidApp extends Application {
private static enum Theme { private static enum Theme {
@ -117,6 +136,44 @@ public class FDroidApp extends Application {
.threadPoolSize(Runtime.getRuntime().availableProcessors() * 2) .threadPoolSize(Runtime.getRuntime().availableProcessors() * 2)
.build(); .build();
ImageLoader.getInstance().init(config); ImageLoader.getInstance().init(config);
try {
SSLContext sc = SSLContext.getInstance("TLS");
X509TrustManager defaultTrustManager = null;
/*
* init a trust manager factory with a null keystore to access the system trust managers
*/
TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = null;
tmf.init(ks);
TrustManager[] mgrs = tmf.getTrustManagers();
if(mgrs.length > 0 && mgrs[0] instanceof X509TrustManager)
defaultTrustManager = (X509TrustManager) mgrs[0];
/*
* compose a chain of trust managers as follows:
* MemorizingTrustManager -> Pinning Trust Manager -> System Trust Manager
*/
PinningTrustManager pinMgr = new PinningTrustManager(SystemKeyStore.getInstance(ctx),FDroidCertPins.getPinList(), 0);
MemorizingTrustManager memMgr = new MemorizingTrustManager(ctx, pinMgr, defaultTrustManager);
/*
* initialize a SSLContext with the outermost trust manager, use this
* context to set the default SSL socket factory for the HTTPSURLConnection
* class.
*/
sc.init(null, new TrustManager[] {memMgr}, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (KeyManagementException e) {
Log.e("FDroid", "Unable to set up trust manager chain. KeyManagementException");
} catch (NoSuchAlgorithmException e) {
Log.e("FDroid", "Unable to set up trust manager chain. NoSuchAlgorithmException");
} catch (KeyStoreException e) {
Log.e("FDroid", "Unable to set up trust manager chain. KeyStoreException");
}
} }
private Context ctx; private Context ctx;

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.fdroid.fdroid;
import java.util.ArrayList;
import java.util.Arrays;
public class FDroidCertPins {
public static final String[] DEFAULT_PINS =
{
/*
* SubjectDN: CN=f-droid.org, OU=PositiveSSL, OU=Domain Control Validated
* IssuerDN: CN=PositiveSSL CA 2, O=COMODO CA Limited, L=Salford, ST=Greater Manchester, C=GB
* Fingerprint: 84B91CDF2312CB9BA7F3BE803783302F8D8C299F
* SPKI Pin: 638F93856E1F5EDFCBD40C46D4160CFF21B0713A
*/
"638F93856E1F5EDFCBD40C46D4160CFF21B0713A",
/*
* SubjectDN: CN=guardianproject.info, OU=Gandi Standard SSL, OU=Domain Control Validated
* IssuerDN: CN=Gandi Standard SSL CA, O=GANDI SAS, C=FR
* Fingerprint: 187C2573E924DFCBFF2A781A2F99D71C6E031828
* SPKI Pin: EB6BBC6C6BAEEA20CB0F3357720D86E0F3A526F4
*/
"EB6BBC6C6BAEEA20CB0F3357720D86E0F3A526F4",
};
public static ArrayList<String> PINLIST = null;
public static String[] getPinList()
{
if(PINLIST == null)
{
PINLIST = new ArrayList<String>();
PINLIST.addAll(Arrays.asList(DEFAULT_PINS));
}
return PINLIST.toArray(new String[PINLIST.size()]);
}
}