Tt-rss-android: add ability to use SSL client certificate

Hi,

I’d like to send a PR to add the ability to use client certificates to the Android app. The patch itself is here[1]; you could just pull it, or if you prefer a PR via Gogs, my username is angelsl.

[1]: github DOT com/angelsl/tt-rss-android/commit/5694abfa7896e0f89fd36ccd118990d7c6304f20 (I can’t include links yet :()

Thank you!

this PR is a whole lot of changes for a very obscure feature an absolute minority of people would ever use.

don’t want to disappoint you but i can’t really see myself merging this.

I think it is relatively minor, but that’s fair enough.

In any case, this patch will be here for anyone who needs it. Presumably anyone who uses this will be able to build it themselves too.

Thanks.

1 Like

no plugins on android.

My bad. I should not post before I’ve had coffee!

Hi, sorry for bothering. I’m also interested in this feature, but https://github.com/angelsl/tt-rss-android is gone. Do you still keep your changes around?

I figured out a proof-of-concept :slight_smile:

diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiCommon.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiCommon.java
index ce5089f8..7aba2f83 100644
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiCommon.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiCommon.java
@@ -7,6 +7,7 @@ import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.preference.PreferenceManager;
+import android.security.KeyChain;
 import android.util.Log;
 
 import com.google.gson.Gson;
@@ -15,8 +16,17 @@ import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 
 import java.io.IOException;
+import java.net.Socket;
+import java.security.cert.X509Certificate;
+import java.security.KeyStore;
+import java.security.Principal;
+import java.security.PrivateKey;
 import java.util.HashMap;
 import java.util.Locale;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509ExtendedKeyManager;
+import javax.net.ssl.X509TrustManager;
 
 import androidx.annotation.NonNull;
 import okhttp3.Credentials;
@@ -133,7 +143,65 @@ public class ApiCommon {
 
             Request request = requestBuilder.build();
 
-            Response response = new OkHttpClient()
+            final String alias = m_prefs.getString("certAlias", "");
+            // get provider certificate and private key
+            // borrowed from https://github.com/bitfireAT/davx5-ose/blob/v4.3-ose/app/src/main/java/at/bitfire/davdroid/HttpClient.kt#L223
+            final X509Certificate[] certs = KeyChain.getCertificateChain(context, alias);
+            final PrivateKey privateKey = KeyChain.getPrivateKey(context, alias);
+            Log.d(TAG, "Using provider certificate " + alias);
+
+            X509ExtendedKeyManager keyManager = new X509ExtendedKeyManager() {
+                @Override
+                public String[] getServerAliases(String keyType, Principal[] issuers) {
+                    return null;
+                }
+
+                @Override
+                public String chooseServerAlias(String keyTypes, Principal[] issuers, Socket socket) {
+                    return null;
+                }
+
+                @Override
+                public String[] getClientAliases(String p0, Principal[] issuers) {
+                    return new String[] { alias };
+                }
+
+                @Override
+                public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
+                    return alias;
+                }
+
+                @Override
+                public X509Certificate[] getCertificateChain(String forAlias) {
+                    if (forAlias == alias) {
+                        return certs;
+                    }
+                    return null;
+                }
+
+                @Override
+                public PrivateKey getPrivateKey(String forAlias) {
+                    if (forAlias == alias) {
+                        return privateKey;
+                    }
+                    return null;
+                }
+            };
+
+            TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+            factory.init((KeyStore) null);
+            X509TrustManager trustManager = (X509TrustManager) factory.getTrustManagers()[0];
+
+            SSLContext sslContext = SSLContext.getInstance("TLS");
+            sslContext.init(
+                new X509ExtendedKeyManager[] { keyManager },
+                new X509TrustManager[] { trustManager },
+                null);
+
+            OkHttpClient client = new OkHttpClient.Builder()
+                .sslSocketFactory(sslContext.getSocketFactory(), trustManager)
+                .build();
+            Response response = client
                     .newCall(request)
                     .execute();
 
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/PreferencesFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/PreferencesFragment.java
index 1fb663d1..7dbb2a50 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/PreferencesFragment.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/PreferencesFragment.java
@@ -1,11 +1,15 @@
 package org.fox.ttrss;
 
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.preference.Preference;
 import android.preference.PreferenceFragment;
+import android.preference.PreferenceManager;
+import android.security.KeyChain;
+import android.security.KeyChainAliasCallback;
 
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -41,6 +45,27 @@ public class PreferencesFragment extends PreferenceFragment {
             }
         });
 
+        findPreference("choose_certificate").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+            @Override
+            public boolean onPreferenceClick(Preference preference) {
+                KeyChain.choosePrivateKeyAlias (getActivity(),
+                    new KeyChainAliasCallback() {
+                        public void alias (String alias) {
+                            SharedPreferences m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
+                            SharedPreferences.Editor editor = m_prefs.edit();
+                            editor.putString("certAlias", alias);
+                            editor.apply();
+                        }
+                    },
+                    null, // keyTypes
+                    null, // issuers
+                    null, // uri
+                    null // alias
+                );
+                return true;
+            }
+        });
+
         CommonActivity activity = (CommonActivity) getActivity();
 
         findPreference("force_phone_layout").setEnabled(activity.isTablet());
diff --git a/org.fox.ttrss/src/main/res/xml/preferences.xml b/org.fox.ttrss/src/main/res/xml/preferences.xml
index 5555c28f..af6c6769 100755
--- a/org.fox.ttrss/src/main/res/xml/preferences.xml
+++ b/org.fox.ttrss/src/main/res/xml/preferences.xml
@@ -27,6 +27,11 @@
             android:key="network_settings"
             android:title="@string/prefs_network_settings" />
 
+        <Preference
+            android:title="Choose certificate..."
+            android:key="choose_certificate"
+        />
+
     </PreferenceCategory>
     <PreferenceCategory
         android:key="category_look_and_feel"

this seems clean enough. why proof of concept? does it work? :slight_smile:

It works. It’s a proof-of-concept as the code is messy - many things are placed together, and there is basically no error handling. Also, most of codes are translated from another project. I don’t really understand how it works. :joy:

business as usual then. :slight_smile:

i don’t really feel like screwing around with android right now but i’ll keep this in mind in case i’m particularly bored. thanks for investigating this. :+1:

1 Like

could you file a PR against master branch of tt-rss-android on dev.tt-rss.org?

Thanks for considering, but I may not open a pull request recently. I haven’t had time to study codes I copied, and I prefer not to put security-related codes without first understanding it.