android icon indicating copy to clipboard operation
android copied to clipboard

Support for client certificates

Open mlsxlist opened this issue 8 years ago • 44 comments

Actual behaviour

In order to secure Nextcloud on TLS level, it would be good if the app could support client certificates. If the client certificate is not sent on handshake, the server prevents access to Nextcloud logon page. This would provide a second line of defense.

Expected behaviour

Nextcloud app should support client certificates as other apps like caldav sync and carddav sync already do.

mlsxlist avatar Jan 29 '17 21:01 mlsxlist

Hello mlsxlist,

if this is of any use to the case, there is an open issue about this over at the owncloud github site:

https://github.com/owncloud/android/issues/163

Best wishes riesnerd

ghost avatar Jan 30 '17 21:01 ghost

I would vote this "medium" to get this done when we (@mario or me) have time. Everything that enhances the security of the app/nextcloud should have priority. cc @mario ?

tobiasKaminsky avatar Jan 31 '17 20:01 tobiasKaminsky

Okay thanks! In some way this also enhances security. ;)

ghost avatar Feb 01 '17 20:02 ghost

@tobiasKaminsky let me think about it, and I'll come back to you - we need to focus on fixing current issues first to make the app (more) usable than it is now - thought definitely this is something we want to do in the future.

mario avatar Feb 01 '17 20:02 mario

I would also very much like to see this, because it protects from potential issues with the login page as well as weak passwords and password guessing attacks.

seanlynch avatar Sep 10 '17 15:09 seanlynch

Any news on this ? This is a very important enhancement !

lucacarangelo avatar Feb 14 '18 18:02 lucacarangelo

@mario let me know if you still need a test setup with client certificates enabled. In the meantime I will try to look into this (I haven't worked on android apps yet, but I can find my way around in php/java/python so I will give it a try)

AndreasMettlen avatar Feb 25 '18 14:02 AndreasMettlen

@AndreasMettlen yes I do. Send me the required cert + url, server and pass to [email protected] :)

mario avatar Feb 25 '18 16:02 mario

@mario Did you have a chance yet to look into this ?

AndreasMettlen avatar Mar 15 '18 21:03 AndreasMettlen

@mario Did the second certificate and the talk app help you in making progress on this issue ? Anything I can do to support ?

AndreasMettlen avatar Apr 24 '18 21:04 AndreasMettlen

@AndreasMettlen I implemented the initial support for client cert in Talk app. Now I need to validate it works which is why I asked for the second one. I'll try to do that on Friday.

If it works, I can see how easy/hard it is to put it into the Files (this) app.

mario avatar Apr 24 '18 21:04 mario

Hi, I really look forward for this enhancement. In the meantime I've tried to patch the code on Android and it kinda works... even if it's ugly as hell 😈 I've used the Android KeyChain features to allow the user to select a PKCS12 certificate and then stored the cert alias into the app preferences:

AuthenticatorActivity

private void checkOcServer() {
        if (mHostUrlInput != null) {
            uri = mHostUrlInput.getText().toString().trim();
            mOkButton.setEnabled(false);
            showRefreshButton(false);
        } else {
            uri = mServerInfo.mBaseUrl;
        }

        mServerIsValid = false;
        mServerIsChecked = false;
        mServerInfo = new GetServerInfoOperation.ServerInfo();

        if (uri.length() != 0) {
            if (mHostUrlInput != null) {
                uri = AuthenticatorUrlUtils.stripIndexPhpOrAppsFiles(uri);
                mHostUrlInput.setText(uri);
            }

            // Handle internationalized domain names
            try {
                uri = DisplayUtils.convertIdn(uri, true);
            } catch (IllegalArgumentException ex) {
                // Let Owncloud library check the error of the malformed URI
                Log_OC.e(TAG, "Error converting internationalized domain name " + uri, ex);
            }

            if (mHostUrlInput != null) {
                mServerStatusText = getResources().getString(R.string.auth_testing_connection);
                mServerStatusIcon = R.drawable.progress_small;
                showServerStatus();
            }

            if (NetworkUtils.alias == null) {
                KeyChain.choosePrivateKeyAlias(this, (KeyChainAliasCallback) this, null, null, uri, -1, null);
            } else {
                startServerInfoIntent();
            }

        } else {
            mServerStatusText = "";
            mServerStatusIcon = 0;
            if (!webViewLoginMethod) {
                showServerStatus();
            }
        }
    }
public void alias(final String alias) {
        System.out.println("THREAD: " + Thread.currentThread().getName());
        System.out.println("Selected alias: " + alias);
        try {
            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext());
            SharedPreferences.Editor editor = sp.edit();
            editor.putString("TLS_ALIAS", alias);
            editor.commit();

            startServerInfoIntent();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Then it uses the cert to initialize the SslSocketFactory:

NetworkUtils (nextcloud-android-library)

public static AdvancedSslSocketFactory getAdvancedSslSocketFactory(Context context) 
    		throws GeneralSecurityException, IOException {

        if (mAdvancedSslSocketFactory  == null) {

            if(chain == null || pk == null || alias == null) {
                // try to recover from preferences
                SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
                alias = sp.getString("TLS_ALIAS", "");
                try {
                    chain = KeyChain.getCertificateChain(context, alias);
                    pk = KeyChain.getPrivateKey(context, alias);
                } catch (KeyChainException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (NullPointerException e) {
                    e.printStackTrace();
                }
            }

            if(chain != null) {
                KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());

                X509ExtendedKeyManager keyManager = new X509ExtendedKeyManager() {

                    @Override
                    public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
                        return alias;
                    }

                    @Override
                    public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
                        return alias;
                    }

                    @Override
                    public X509Certificate[] getCertificateChain(String s) {
                        return chain;
                    }

                    @Override
                    public String[] getClientAliases(String s, Principal[] principals) {
                        return new String[]{alias};
                    }

                    @Override
                    public String[] getServerAliases(String s, Principal[] principals) {
                        return new String[]{alias};
                    }

                    @Override
                    public PrivateKey getPrivateKey(String s) {
                        return pk;
                    }
                };

                TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                trustFactory.init(trustStore);
                TrustManager[] trustManagers = trustFactory.getTrustManagers();

                X509TrustManager[] tm = new X509TrustManager[] { new AdvancedX509TrustManager(trustStore) {

                    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    }

                    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    }

                    public X509Certificate[] getAcceptedIssuers() {
                        return chain;
                    }

                    public boolean isClientTrusted(X509Certificate[] arg0) {
                        return true;
                    }

                    public boolean isServerTrusted(X509Certificate[] arg0) {
                        return true;
                    }

                } };

                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(new KeyManager[] {keyManager}, tm, null);
                SSLContext.setDefault(sslContext);

                mHostnameVerifier = new BrowserCompatHostnameVerifier();
                mAdvancedSslSocketFactory = new AdvancedSslSocketFactory(sslContext, new AdvancedX509TrustManager(trustStore), mHostnameVerifier);

            } else {
                // can't recover the chain
                ...
            }
        }
        return mAdvancedSslSocketFactory;
    }

That's a big paste-up of various articles and posts that I've read to try to solve the problem... unfortunately I can't remember all of them: owncloud/android#163 kbremner

Hope it helps. Best wishes

mbrescia avatar May 15 '18 09:05 mbrescia

@mbrescia feel free to start a patch, and I can help? In the mean time, maybe you can try Nextcloud Talk v1.2.0beta? Same for you @AndreasMettlen ^_^

mario avatar May 15 '18 09:05 mario

Hello @mario i just updated to 2.0.0 from play store I ve got this error as soon as I enter the server address attempt to invoke virtual method java.lang.String com.nextcloud.talk.models.database.UserEntity.getClientCertificate() on a null object reference

I have android 8.1.0

ClCfe avatar Jun 07 '18 13:06 ClCfe

@ClCfe can you file a bug here, preferably with stacktrace?

https://github.com/nextcloud/talk-android

mario avatar Jun 07 '18 13:06 mario

@mbrescia is there any chance that you upload your whole example, because i tried your workaround but without success. best regards

proton2b avatar Jul 05 '18 08:07 proton2b

@proton2b Sure, I'll start a branch as soon as I can

mbrescia avatar Jul 26 '18 10:07 mbrescia

@mbrescia ok great, thanks in advance!

proton2b avatar Aug 21 '18 10:08 proton2b

Has there been any update on this? It would be an excellent addition to security

carlos-sarmiento avatar Apr 15 '19 07:04 carlos-sarmiento

Has there been any update on this? It would be an excellent addition to security

Unfortunately not.

proton2b avatar Apr 16 '19 05:04 proton2b

I'm really interested as well. Actually I already setup the server and my desktop client that way and was kind of puzzled the Android app refused to work with client certificates.

mirko avatar May 27 '19 23:05 mirko

+1

steffenfritz avatar Sep 02 '19 11:09 steffenfritz

I would also like to see client certificates supported in the nextcloud app.

ben423423n32j14e avatar Sep 03 '19 02:09 ben423423n32j14e

+1

TJB-99 avatar Sep 03 '19 17:09 TJB-99

Instead of adding +1 comments to this ticket please use the thumb up emoji at the top. Thanks.

mlsxlist avatar Sep 03 '19 17:09 mlsxlist

I know its not directly related but can someone share an Apache config for Collabora to communicate with Nextcloud when SSLVerifyClient require is set in Apache?

I've got it set on both the NextCloud and Collabora sub domains, I can authenticate fine via the certificate to both subdomains but Collabora won't work.

It's almost like the Collabora sub domain needs a copy of the client cert to communicate with the nextcloud sub domain. Am I on the right path?

ben423423n32j14e avatar Sep 03 '19 19:09 ben423423n32j14e

@ben423423n32j14e please don't hijack issues like that, this is not a support forum. I recommend you first have a look at https://github.com/nextcloud/richdocuments if you can find any info.

hostingnuggets avatar Sep 03 '19 20:09 hostingnuggets

I've found an alternative to certificate authentication for the Android app (but still keeping certificate authentication for the website and Collabora).

Basically I compiled my own version of the NextCloud Android app and replaced the http user agent with a really long random string.

If certificate authentication is not successful, an Apache re-write rule activates and if it can see the custom user agent from the app (being transmitted over https), it will allow bypassing the certificate authentication.

Note that the app must already be logged into Nextcloud before activating the Apache rewrite rule (not sure why).

ben423423n32j14e avatar Sep 17 '19 18:09 ben423423n32j14e

@stephanritscher seems to have made a working patch. Why hasn't it been integrated yet ?

razaborg avatar Apr 25 '20 11:04 razaborg

+1 vote for client cert auth from me! please!

rob2g2 avatar Apr 29 '20 07:04 rob2g2